Skip to content

Commit 34b59c7

Browse files
committed
Extensions: allow cref references to extension members
1 parent 8628943 commit 34b59c7

File tree

16 files changed

+2587
-70
lines changed

16 files changed

+2587
-70
lines changed

src/Compilers/CSharp/Portable/Binder/Binder_Crefs.cs

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ private ImmutableArray<Symbol> BindCrefInternal(CrefSyntax syntax, out Symbol? a
3535
case SyntaxKind.IndexerMemberCref:
3636
case SyntaxKind.OperatorMemberCref:
3737
case SyntaxKind.ConversionOperatorMemberCref:
38+
case SyntaxKind.ExtensionMemberCref:
3839
return BindMemberCref((MemberCrefSyntax)syntax, containerOpt: null, ambiguityWinner: out ambiguityWinner, diagnostics: diagnostics);
3940
default:
4041
throw ExceptionUtilities.UnexpectedValue(syntax.Kind());
@@ -125,6 +126,9 @@ private ImmutableArray<Symbol> BindMemberCref(MemberCrefSyntax syntax, Namespace
125126
case SyntaxKind.ConversionOperatorMemberCref:
126127
result = BindConversionOperatorMemberCref((ConversionOperatorMemberCrefSyntax)syntax, containerOpt, out ambiguityWinner, diagnostics);
127128
break;
129+
case SyntaxKind.ExtensionMemberCref:
130+
result = BindExtensionMemberCref((ExtensionMemberCrefSyntax)syntax, containerOpt, out ambiguityWinner, diagnostics);
131+
break;
128132
default:
129133
throw ExceptionUtilities.UnexpectedValue(syntax.Kind());
130134
}
@@ -216,6 +220,137 @@ private ImmutableArray<Symbol> BindIndexerMemberCref(IndexerMemberCrefSyntax syn
216220
diagnostics: diagnostics);
217221
}
218222

223+
private ImmutableArray<Symbol> BindExtensionMemberCref(ExtensionMemberCrefSyntax syntax, NamespaceOrTypeSymbol? containerOpt, out Symbol? ambiguityWinner, BindingDiagnosticBag diagnostics)
224+
{
225+
if (containerOpt is not NamedTypeSymbol namedContainer)
226+
{
227+
ambiguityWinner = null;
228+
return ImmutableArray<Symbol>.Empty;
229+
}
230+
231+
ImmutableArray<Symbol> sortedSymbols = default;
232+
int arity = 0;
233+
TypeArgumentListSyntax? typeArgumentListSyntax = null;
234+
CrefParameterListSyntax? parameters = null;
235+
236+
if (syntax.Member is NameMemberCrefSyntax { Name: SimpleNameSyntax simpleName } nameMember)
237+
{
238+
arity = simpleName.Arity;
239+
typeArgumentListSyntax = simpleName is GenericNameSyntax genericName ? genericName.TypeArgumentList : null;
240+
parameters = nameMember.Parameters;
241+
242+
TypeArgumentListSyntax? extensionTypeArguments = syntax.TypeArgumentList;
243+
int extensionArity = extensionTypeArguments?.Arguments.Count ?? 0;
244+
sortedSymbols = computeSortedAndFilteredCrefExtensionMembers(namedContainer, simpleName.Identifier.ValueText, extensionArity, arity, extensionTypeArguments, diagnostics, syntax);
245+
}
246+
247+
if (sortedSymbols.IsDefaultOrEmpty)
248+
{
249+
ambiguityWinner = null;
250+
return [];
251+
}
252+
253+
Debug.Assert(sortedSymbols.All(s => s.GetIsNewExtensionMember()));
254+
255+
return ProcessCrefMemberLookupResults(sortedSymbols, arity, syntax, typeArgumentListSyntax, parameters, out ambiguityWinner, diagnostics);
256+
257+
ImmutableArray<Symbol> computeSortedAndFilteredCrefExtensionMembers(NamedTypeSymbol container, string name, int extensionArity, int arity, TypeArgumentListSyntax? extensionTypeArguments, BindingDiagnosticBag diagnostics, ExtensionMemberCrefSyntax syntax)
258+
{
259+
Debug.Assert(name is not null);
260+
261+
LookupOptions options = LookupOptions.AllMethodsOnArityZero | LookupOptions.MustNotBeParameter;
262+
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = this.GetNewCompoundUseSiteInfo(diagnostics);
263+
ArrayBuilder<Symbol>? sortedSymbolsBuilder = null;
264+
265+
foreach (var nested in container.GetTypeMembers())
266+
{
267+
if (!nested.IsExtension || nested.Arity != extensionArity || nested.ExtensionParameter is null)
268+
{
269+
continue;
270+
}
271+
272+
var constructedNested = (NamedTypeSymbol)ConstructWithCrefTypeParameters(extensionArity, extensionTypeArguments, nested);
273+
274+
var candidateExtensionSignature = new SignatureOnlyMethodSymbol(
275+
methodKind: MethodKind.Ordinary,
276+
typeParameters: IndexedTypeParameterSymbol.TakeSymbols(constructedNested.Arity),
277+
parameters: [constructedNested.ExtensionParameter],
278+
callingConvention: Cci.CallingConvention.Default,
279+
// These are ignored by this specific MemberSignatureComparer.
280+
containingType: null,
281+
name: null,
282+
refKind: RefKind.None,
283+
isInitOnly: false,
284+
isStatic: false,
285+
returnType: default,
286+
refCustomModifiers: [],
287+
explicitInterfaceImplementations: []);
288+
289+
ImmutableArray<ParameterSymbol> extensionParameterSymbols = syntax.Parameters is { } extensionParameterListSyntax ? BindCrefParameters(extensionParameterListSyntax, diagnostics) : default;
290+
291+
var providedExtensionSignature = new SignatureOnlyMethodSymbol(
292+
methodKind: MethodKind.Ordinary,
293+
typeParameters: IndexedTypeParameterSymbol.TakeSymbols(constructedNested.Arity),
294+
parameters: extensionParameterSymbols,
295+
callingConvention: Cci.CallingConvention.Default,
296+
// These are ignored by this specific MemberSignatureComparer.
297+
containingType: null,
298+
name: null,
299+
refKind: RefKind.None,
300+
isInitOnly: false,
301+
isStatic: false,
302+
returnType: default,
303+
refCustomModifiers: [],
304+
explicitInterfaceImplementations: []);
305+
306+
if (!MemberSignatureComparer.CrefComparer.Equals(candidateExtensionSignature, providedExtensionSignature))
307+
{
308+
continue;
309+
}
310+
311+
var candidates = constructedNested.GetMembers(name);
312+
313+
foreach (var candidate in candidates)
314+
{
315+
if (!SourceMemberContainerTypeSymbol.IsAllowedExtensionMember(candidate))
316+
{
317+
continue;
318+
}
319+
320+
if (arity != 0 && candidate.GetArity() != arity)
321+
{
322+
continue;
323+
}
324+
325+
// Note: we bypass the arity check here, as it would check for total arity (extension + member arity)
326+
SingleLookupResult result = this.CheckViability(candidate, arity: 0, options, accessThroughType: null, diagnose: true, useSiteInfo: ref useSiteInfo);
327+
328+
if (result.Kind == LookupResultKind.Viable)
329+
{
330+
sortedSymbolsBuilder ??= ArrayBuilder<Symbol>.GetInstance();
331+
sortedSymbolsBuilder.Add(result.Symbol);
332+
}
333+
}
334+
}
335+
336+
diagnostics.Add(syntax, useSiteInfo);
337+
338+
if (sortedSymbolsBuilder is null)
339+
{
340+
return ImmutableArray<Symbol>.Empty;
341+
}
342+
343+
// Since we resolve ambiguities by just picking the first symbol we encounter,
344+
// the order of the symbols matters for repeatability.
345+
if (sortedSymbolsBuilder.Count > 1)
346+
{
347+
sortedSymbolsBuilder.Sort(ConsistentSymbolOrder.Instance);
348+
}
349+
350+
return sortedSymbolsBuilder.ToImmutableAndFree();
351+
}
352+
}
353+
219354
// NOTE: not guaranteed to be a method (e.g. class op_Addition)
220355
// NOTE: constructor fallback logic applies
221356
private ImmutableArray<Symbol> BindOperatorMemberCref(OperatorMemberCrefSyntax syntax, NamespaceOrTypeSymbol? containerOpt, out Symbol? ambiguityWinner, BindingDiagnosticBag diagnostics)

src/Compilers/CSharp/Portable/Binder/WithCrefTypeParametersBinder.cs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ private void AddTypeParameters(TypeSyntax typeSyntax, MultiDictionary<string, Ty
9090
AddTypeParameters(qualifiedNameSyntax.Left, map);
9191
break;
9292
case SyntaxKind.GenericName:
93-
AddTypeParameters((GenericNameSyntax)typeSyntax, map);
93+
AddTypeParameters(((GenericNameSyntax)typeSyntax).TypeArgumentList.Arguments, map);
9494
break;
9595
case SyntaxKind.IdentifierName:
9696
case SyntaxKind.PredefinedType:
@@ -103,20 +103,28 @@ private void AddTypeParameters(TypeSyntax typeSyntax, MultiDictionary<string, Ty
103103
private void AddTypeParameters(MemberCrefSyntax memberSyntax, MultiDictionary<string, TypeParameterSymbol> map)
104104
{
105105
// Other members have arity 0.
106-
if (memberSyntax.Kind() == SyntaxKind.NameMemberCref)
106+
if (memberSyntax is NameMemberCrefSyntax nameMemberCref)
107107
{
108-
AddTypeParameters(((NameMemberCrefSyntax)memberSyntax).Name, map);
108+
AddTypeParameters(nameMemberCref.Name, map);
109+
}
110+
else if (memberSyntax is ExtensionMemberCrefSyntax extensionCref)
111+
{
112+
if (extensionCref.TypeArgumentList is { } extensionTypeArguments)
113+
{
114+
AddTypeParameters(extensionTypeArguments.Arguments, map);
115+
}
116+
117+
AddTypeParameters(extensionCref.Member, map);
109118
}
110119
}
111120

112-
private static void AddTypeParameters(GenericNameSyntax genericNameSyntax, MultiDictionary<string, TypeParameterSymbol> map)
121+
private static void AddTypeParameters(SeparatedSyntaxList<TypeSyntax> typeArguments, MultiDictionary<string, TypeParameterSymbol> map)
113122
{
114123
// NOTE: Dev11 does not warn about duplication, it just matches parameter types to the
115124
// *last* type parameter with the same name. That's why we're iterating backwards and
116125
// skipping subsequent symbols with the same name. This can result in some surprising
117126
// behavior. For example, both 'T's in "A<T>.B<T>" bind to the second implicitly
118127
// declared type parameter.
119-
SeparatedSyntaxList<TypeSyntax> typeArguments = genericNameSyntax.TypeArgumentList.Arguments;
120128
for (int i = typeArguments.Count - 1; i >= 0; i--)
121129
{
122130
// Other types (non-identifiers) are allowed in error scenarios, but they do not introduce new

src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,8 @@ internal static bool HasParameterList(CrefSyntax crefSyntax)
357357
return ((OperatorMemberCrefSyntax)crefSyntax).Parameters != null;
358358
case SyntaxKind.ConversionOperatorMemberCref:
359359
return ((ConversionOperatorMemberCrefSyntax)crefSyntax).Parameters != null;
360+
case SyntaxKind.ExtensionMemberCref:
361+
return HasParameterList(((ExtensionMemberCrefSyntax)crefSyntax).Member);
360362
}
361363

362364
return false;
@@ -379,7 +381,7 @@ private static SymbolInfo GetCrefSymbolInfo(OneOrMany<Symbol> symbols, SymbolInf
379381

380382
LookupResultKind resultKind = LookupResultKind.Ambiguous;
381383

382-
// The boundary between Ambiguous and OverloadResolutionFailure is let clear-cut for crefs.
384+
// The boundary between Ambiguous and OverloadResolutionFailure is less clear-cut for crefs.
383385
// We'll say that overload resolution failed if the syntax has a parameter list and if
384386
// all of the candidates have the same kind.
385387
SymbolKind firstCandidateKind = symbols[0].Kind;

src/Compilers/CSharp/Portable/DocumentationComments/DocumentationCommentIDVisitor.PartVisitor.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ public override object VisitNamedType(NamedTypeSymbol symbol, StringBuilder buil
181181
builder.Append('.');
182182
}
183183

184-
builder.Append(symbol.IsExtension ? symbol.ExtensionName : symbol.Name);
184+
builder.Append(symbol.IsExtension ? escape(symbol.ExtensionName) : symbol.Name);
185185

186186
if (symbol.Arity != 0)
187187
{
@@ -215,6 +215,12 @@ public override object VisitNamedType(NamedTypeSymbol symbol, StringBuilder buil
215215
}
216216

217217
return null;
218+
219+
static string escape(string s)
220+
{
221+
Debug.Assert(!s.Contains("&"));
222+
return s.Replace("<", "&lt;").Replace(">", "&gt;");
223+
}
218224
}
219225

220226
public override object VisitPointerType(PointerTypeSymbol symbol, StringBuilder builder)

src/Compilers/CSharp/Portable/Generated/CSharp.Generated.g4

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)