-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathGenericSymbolReference.cs
More file actions
209 lines (200 loc) · 9.05 KB
/
GenericSymbolReference.cs
File metadata and controls
209 lines (200 loc) · 9.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Operations;
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
namespace Monkeymoto.GeneratorUtils
{
/// <summary>
/// Represents a generic <see cref="ISymbol"/> and an associated <see cref="SyntaxNode"/>.
/// </summary>
public sealed class GenericSymbolReference : IEquatable<GenericSymbolReference>
{
private readonly int hashCode;
/// <summary>
/// Represents whether <see cref="Symbol">Symbol</see> is a closed generic type or closed generic method.
/// </summary>
/// <remarks>
/// <para>
/// This property only represents whether the <see cref="Symbol">Symbol</see> itself is a closed generic type
/// or closed generic method. This does not check the containing type or method. To check if the
/// <see cref="Node">Node</see> represents a source location with no open generic type parameters, see
/// <see cref="IsSyntaxReferenceClosedTypeOrMethod">IsSyntaxReferenceClosedTypeOrMethod</see> instead.
/// </para>
/// </remarks>
/// <returns>
/// <see langword="true"/> if <see cref="Symbol">Symbol</see> has no unsubstituted type arguments; otherwise,
/// <see langword="false"/>.
/// </returns>
public bool IsClosedTypeOrMethod { get; }
/// <summary>
/// Represents whether the <see cref="Node">Node</see> represents a source location with no open generic type
/// parameters.
/// </summary>
/// <remarks>
/// <para>
/// This property will check containing types and methods. If this <see cref="Node">Node</see> is inside of an
/// open generic type or method (at any level of nesting), then this property will return
/// <see langword="false"/>, regardless of whether <see cref="Symbol">Symbol</see> is a closed type or method.
/// To only check <see cref="Symbol">Symbol</see> see
/// <see cref="IsClosedTypeOrMethod">IsClosedTypeOrMethod</see> instead.
/// </para>
/// </remarks>
/// <returns>
/// <see langword="true"/> if <see cref="Node">Node</see> references a source location with no unsubstituted
/// type arguments; otherwise, <see langword="false"/>.
/// </returns>
public bool IsSyntaxReferenceClosedTypeOrMethod { get; }
/// <summary>
/// Represents the <see cref="SyntaxNode"/> associated with <see cref="Symbol">Symbol</see>.
/// </summary>
public SyntaxNode Node { get; }
/// <summary>
/// Represents the <see cref="SemanticModel"/> used to acquire <see cref="Symbol">Symbol</see>.
/// </summary>
public SemanticModel SemanticModel { get; }
/// <summary>
/// Represents the <see cref="ISymbol"/> associated with <see cref="Node">Node</see>.
/// </summary>
public ISymbol Symbol { get; }
/// <summary>
/// Represents the type arguments for this <see cref="Symbol">Symbol</see>.
/// </summary>
/// <remarks>
/// <para>
/// If <see cref="IsClosedTypeOrMethod">IsClosedTypeOrMethod</see> is <see langword="false"/>, then one or more
/// of these type arguments is an <see cref="ITypeParameterSymbol"/> or an open generic type; otherwise, these
/// values represent the closed type arguments.
/// </para>
/// </remarks>
public ImmutableArray<ITypeSymbol> TypeArguments { get; }
public static bool operator ==(GenericSymbolReference? left, GenericSymbolReference? right) =>
left?.Equals(right) ?? right is null;
public static bool operator !=(GenericSymbolReference? left, GenericSymbolReference? right) =>
!(left == right);
internal static GenericSymbolReference? FromSyntaxNodeInternal
(
SyntaxNode node,
SemanticModel semanticModel,
CancellationToken cancellationToken
)
{
ISymbol? symbol;
ImmutableArray<ITypeSymbol> typeArguments;
if (node is IdentifierNameSyntax)
{
node = node.Parent switch
{
InvocationExpressionSyntax => node.Parent,
MemberAccessExpressionSyntax => node.Parent.Parent!,
_ => node
};
IMethodSymbol? methodSymbol = semanticModel.GetOperation(node, cancellationToken) switch
{
IInvocationOperation { TargetMethod.IsGenericMethod: true } invocation =>
invocation.TargetMethod,
IMethodReferenceOperation { Method.IsGenericMethod: true } methodReference =>
methodReference.Method,
_ => null
};
if (methodSymbol is null)
{
return null;
}
symbol = methodSymbol;
typeArguments = methodSymbol.TypeArguments;
}
else
{
symbol = semanticModel.GetSymbolInfo(node, cancellationToken).Symbol;
switch (symbol)
{
case null or ISymbol { IsDefinition: true }:
return null;
case IMethodSymbol { IsGenericMethod: true } methodSymbol:
typeArguments = methodSymbol.TypeArguments;
break;
case INamedTypeSymbol { IsGenericType: true } namedTypeSymbol:
typeArguments = namedTypeSymbol.TypeArguments;
break;
default:
return null;
}
}
return new GenericSymbolReference(node, semanticModel, symbol, typeArguments, cancellationToken);
}
/// <summary>
/// Gets the hash code for this <see cref="GenericSymbolReference"/>.
/// </summary>
/// <remarks>
/// <para>
/// Borrowed from <see href="https://stackoverflow.com/a/1646913">Quick and Simple Hash Code Combinations -
/// Stack Overflow</see> answer by user <see href="https://stackoverflow.com/users/22656/jon-skeet">Jon
/// Skeet</see>, licensed under <see href="https://creativecommons.org/licenses/by-sa/2.5/">CC BY-SA 2.5</see>.
/// Changes have been made to match the fields of this class.
/// </para>
/// </remarks>
/// <returns>The hash value generated for this <see cref="GenericSymbolReference"/>.</returns>
private static int GetHashCode(SyntaxNode node, ISymbol symbol)
{
unchecked
{
int hash = 17;
hash = hash * 31 + node.GetHashCode();
hash = hash * 31 + SymbolEqualityComparer.Default.GetHashCode(symbol);
return hash;
}
}
internal static bool IsOpenTypeOrMethodSymbol(ISymbol symbol)
{
return symbol switch
{
ITypeParameterSymbol => true,
IMethodSymbol methodSymbol => methodSymbol.TypeArguments.Any(IsOpenTypeOrMethodSymbol),
INamedTypeSymbol namedTypeSymbol => namedTypeSymbol.TypeArguments.Any(IsOpenTypeOrMethodSymbol),
_ => false
};
}
private static bool IsSyntaxReferenceClosedTypeOrMethodSymbol(SyntaxNode node, SemanticModel semanticModel, CancellationToken cancellationToken)
{
var symbol = semanticModel.GetSymbolInfo(node, cancellationToken).Symbol;
if (symbol is null)
{
return false;
}
while (symbol is not null)
{
if (IsOpenTypeOrMethodSymbol(symbol))
{
return false;
}
symbol = symbol.ContainingSymbol;
}
return true;
}
internal GenericSymbolReference
(
SyntaxNode node,
SemanticModel semanticModel,
ISymbol symbol,
ImmutableArray<ITypeSymbol> typeArguments,
CancellationToken cancellationToken
)
{
IsClosedTypeOrMethod = !IsOpenTypeOrMethodSymbol(symbol);
IsSyntaxReferenceClosedTypeOrMethod = IsSyntaxReferenceClosedTypeOrMethodSymbol(node, semanticModel, cancellationToken);
Node = node;
SemanticModel = semanticModel;
Symbol = symbol;
TypeArguments = typeArguments;
hashCode = GetHashCode(node, symbol);
}
public override bool Equals(object? obj) => obj is GenericSymbolReference other && Equals(other);
public bool Equals(GenericSymbolReference? other) => (other is not null) && Node.IsEquivalentTo(other.Node) &&
SymbolEqualityComparer.Default.Equals(Symbol, other.Symbol);
public override int GetHashCode() => hashCode;
}
}