Skip to content

Commit 2b5f28c

Browse files
authored
Extensions: update semantic model APIs (#77619)
1 parent babf51e commit 2b5f28c

File tree

26 files changed

+2116
-405
lines changed

26 files changed

+2116
-405
lines changed

src/Compilers/CSharp/CSharpAnalyzerDriver/CSharpDeclarationComputer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ private static void ComputeDeclarations(
102102
case SyntaxKind.StructDeclaration:
103103
case SyntaxKind.RecordDeclaration:
104104
case SyntaxKind.RecordStructDeclaration:
105-
// PROTOTYPE likely needs work for semantic model
105+
// PROTOTYPE likely needs work for analyzers
106106
{
107107
if (associatedSymbol is IMethodSymbol ctor)
108108
{

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1467,7 +1467,7 @@ static ImmutableArray<MethodSymbol> filterOutBadGenericMethods(
14671467

14681468
if (!typeParameters.IsEmpty)
14691469
{
1470-
if (resolution.IsExtensionMethodGroup)
1470+
if (resolution.IsExtensionMethodGroup) // PROTOTYPE we need to handle new extension methods
14711471
{
14721472
// We need to validate an ability to infer type arguments as well as check conversion to 'this' parameter.
14731473
// Overload resolution doesn't check the conversion when 'this' type refers to a type parameter

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

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8001,7 +8001,11 @@ private BoundExpression MakeMemberAccessValue(BoundExpression expr, BindingDiagn
80018001
if (resolution.IsNonMethodExtensionMember(out Symbol? extensionMember))
80028002
{
80038003
Debug.Assert(typeArgumentsOpt.IsDefault);
8004-
diagnostics.AddRange(resolution.Diagnostics); // PROTOTYPE test dependencies/diagnostics
8004+
if (!receiver.HasErrors)
8005+
{
8006+
diagnostics.AddRange(resolution.Diagnostics); // PROTOTYPE test dependencies/diagnostics
8007+
}
8008+
80058009
resolution.Free();
80068010

80078011
return GetExtensionMemberAccess(syntax, receiver, extensionMember, diagnostics);
@@ -8028,6 +8032,7 @@ private BoundExpression GetExtensionMemberAccess(SyntaxNode syntax, BoundExpress
80288032
return BindPropertyAccess(syntax, receiver, propertySymbol, diagnostics, LookupResultKind.Viable, hasErrors: false);
80298033

80308034
case ExtendedErrorTypeSymbol errorTypeSymbol:
8035+
// PROTOTYPE we should likely reduce (ie. do type inference and substitute) the candidates (like ToBadExpression)
80318036
return new BoundBadExpression(syntax, LookupResultKind.Viable, errorTypeSymbol.CandidateSymbols!, [receiver], CreateErrorType());
80328037

80338038
default:
@@ -8592,15 +8597,12 @@ static bool tryResolveExtensionInScope(
85928597
result = default;
85938598

85948599
// 1. gather candidates
8595-
scope.Binder.LookupExtensionMembersInSingleBinder(
8596-
lookupResult, left.Type, memberName, arity,
8597-
basesBeingResolved: null, lookupOptions, originalBinder: binder, ref useSiteInfo);
8598-
85998600
CompoundUseSiteInfo<AssemblySymbol> classicExtensionUseSiteInfo = binder.GetNewCompoundUseSiteInfo(diagnostics);
8600-
binder.LookupExtensionMethods(classicExtensionLookupResult, scope, memberName, arity, ref classicExtensionUseSiteInfo);
8601-
diagnostics.Add(expression, classicExtensionUseSiteInfo);
8601+
scope.Binder.LookupAllExtensionMembersInSingleBinder(
8602+
lookupResult, memberName, arity, lookupOptions,
8603+
originalBinder: binder, useSiteInfo: ref useSiteInfo, classicExtensionUseSiteInfo: ref classicExtensionUseSiteInfo);
86028604

8603-
lookupResult.MergeEqual(classicExtensionLookupResult);
8605+
diagnostics.Add(expression, classicExtensionUseSiteInfo);
86048606

86058607
if (!lookupResult.IsMultiViable)
86068608
{
@@ -8699,12 +8701,26 @@ static MethodGroupResolution resolveMethods(
86998701
// we can still prune the inapplicable extension methods using the receiver type
87008702
for (int i = methodGroup.Methods.Count - 1; i >= 0; i--)
87018703
{
8702-
if (methodGroup.Methods[i].IsExtensionMethod
8703-
&& (object)methodGroup.Methods[i].ReduceExtensionMethod(left.Type, binder.Compilation) == null)
8704+
MethodSymbol method = methodGroup.Methods[i];
8705+
TypeSymbol? receiverType = left.Type;
8706+
Debug.Assert(receiverType is not null);
8707+
8708+
bool inapplicable = false;
8709+
if (method.IsExtensionMethod
8710+
&& (object)method.ReduceExtensionMethod(receiverType, binder.Compilation) == null)
8711+
{
8712+
inapplicable = true;
8713+
}
8714+
else if (method.GetIsNewExtensionMember()
8715+
&& SourceNamedTypeSymbol.GetCompatibleSubstitutedMember(binder.Compilation, method, receiverType) == null)
8716+
{
8717+
inapplicable = true;
8718+
}
8719+
8720+
if (inapplicable)
87048721
{
87058722
methodGroup.Methods.RemoveAt(i);
87068723
}
8707-
// PROTOTYPE we'll want to do the same for new extension methods
87088724
}
87098725

87108726
if (methodGroup.Methods.Count != 0)
@@ -11025,7 +11041,7 @@ static bool isCandidateUnique(ref MethodSymbol? method, MethodSymbol candidate)
1102511041
foreach (var scope in new ExtensionScopes(this))
1102611042
{
1102711043
methodGroup.Clear();
11028-
PopulateExtensionMethodsFromSingleBinder(scope, methodGroup, node.Syntax, receiver, node.Name, typeArguments, BindingDiagnosticBag.Discarded);
11044+
PopulateExtensionMethodsFromSingleBinder(scope, methodGroup, node.Syntax, receiver, node.Name, typeArguments, BindingDiagnosticBag.Discarded); // PROTOTYPE account for new extension members
1102911045
var methods = ArrayBuilder<MethodSymbol>.GetInstance(capacity: methodGroup.Methods.Count);
1103011046
foreach (var extensionMethod in methodGroup.Methods)
1103111047
{

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

Lines changed: 58 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,19 @@ internal void LookupSymbolsSimpleName(
4444
}
4545
}
4646

47-
internal void LookupExtensionMethods(LookupResult result, string name, int arity, LookupOptions options, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
47+
#nullable enable
48+
internal void LookupAllExtensions(LookupResult result, string? name, LookupOptions options)
4849
{
50+
var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
51+
4952
foreach (var scope in new ExtensionScopes(this))
5053
{
51-
this.LookupExtensionMethodsInSingleBinder(scope, result, name, arity, options, ref useSiteInfo);
54+
scope.Binder.LookupAllExtensionMembersInSingleBinder(
55+
result, name, arity: 0, options: options,
56+
originalBinder: this, useSiteInfo: ref discardedUseSiteInfo, classicExtensionUseSiteInfo: ref discardedUseSiteInfo);
5257
}
5358
}
59+
#nullable disable
5460

5561
/// <summary>
5662
/// Look for any symbols in scope with the given name and arity.
@@ -177,34 +183,63 @@ protected void LookupMembersInternal(LookupResult result, NamespaceOrTypeSymbol
177183
}
178184

179185
#nullable enable
180-
private void LookupExtensionMembersInSingleBinder(LookupResult result, TypeSymbol receiverType,
181-
string name, int arity, ConsList<TypeSymbol>? basesBeingResolved, LookupOptions options,
182-
Binder originalBinder, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
186+
private void LookupAllExtensionMembersInSingleBinder(LookupResult result, string? name,
187+
int arity, LookupOptions options, Binder originalBinder, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo, ref CompoundUseSiteInfo<AssemblySymbol> classicExtensionUseSiteInfo)
183188
{
184-
Debug.Assert(name is not null);
189+
var singleLookupResults = ArrayBuilder<SingleLookupResult>.GetInstance();
190+
EnumerateAllExtensionMembersInSingleBinder(singleLookupResults, name, arity, options, originalBinder, ref useSiteInfo, ref classicExtensionUseSiteInfo);
191+
foreach (var singleLookupResult in singleLookupResults)
192+
{
193+
result.MergeEqual(singleLookupResult);
194+
}
185195

186-
var extensions = ArrayBuilder<NamedTypeSymbol>.GetInstance();
196+
singleLookupResults.Free();
197+
}
187198

188-
Debug.Assert(!receiverType.IsDynamic());
189-
if (!receiverType.IsErrorType())
199+
internal void EnumerateAllExtensionMembersInSingleBinder(ArrayBuilder<SingleLookupResult> result,
200+
string? name, int arity, LookupOptions options, Binder originalBinder, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo, ref CompoundUseSiteInfo<AssemblySymbol> classicExtensionUseSiteInfo)
201+
{
202+
// 1. Collect new extension members
203+
PooledHashSet<MethodSymbol>? implementationsToShadow = null;
204+
var extensionDeclarations = ArrayBuilder<NamedTypeSymbol>.GetInstance();
205+
this.GetExtensionDeclarations(extensionDeclarations, originalBinder);
206+
207+
foreach (NamedTypeSymbol extensionDeclaration in extensionDeclarations)
190208
{
191-
this.GetExtensionDeclarations(extensions, originalBinder);
209+
var candidates = name is null ? extensionDeclaration.GetMembers() : extensionDeclaration.GetMembers(name);
210+
211+
foreach (var candidate in candidates)
212+
{
213+
SingleLookupResult resultOfThisMember = originalBinder.CheckViability(candidate, arity, options, null, diagnose: true, useSiteInfo: ref useSiteInfo);
214+
result.Add(resultOfThisMember);
215+
216+
if (candidate is MethodSymbol { IsStatic: false } shadows &&
217+
shadows.OriginalDefinition.TryGetCorrespondingExtensionImplementationMethod() is { } toShadow)
218+
{
219+
implementationsToShadow ??= PooledHashSet<MethodSymbol>.GetInstance();
220+
implementationsToShadow.Add(toShadow);
221+
}
222+
}
192223
}
193224

194-
var tempResult = LookupResult.GetInstance();
195-
foreach (NamedTypeSymbol extension in extensions)
196-
{
197-
// No need for "diagnose" since we discard the lookup results (and associated diagnostic info)
198-
// unless the results are good.
199-
LookupMembersInClass(tempResult, extension, name, arity, basesBeingResolved,
200-
options, originalBinder, diagnose: false, ref useSiteInfo);
225+
extensionDeclarations.Free();
226+
227+
// 2. Collect classic extension methods
228+
var extensionMethods = ArrayBuilder<MethodSymbol>.GetInstance();
229+
this.GetCandidateExtensionMethods(extensionMethods, name, arity, options, originalBinder: originalBinder);
201230

202-
result.MergeEqual(tempResult);
203-
tempResult.Clear();
231+
foreach (var method in extensionMethods)
232+
{
233+
// Prefer instance extension declarations vs. their implementations
234+
if (implementationsToShadow is null || !implementationsToShadow.Remove(method.OriginalDefinition))
235+
{
236+
SingleLookupResult resultOfThisMember = originalBinder.CheckViability(method, arity, options, null, diagnose: true, useSiteInfo: ref classicExtensionUseSiteInfo);
237+
result.Add(resultOfThisMember);
238+
}
204239
}
205240

206-
tempResult.Free();
207-
extensions.Free();
241+
extensionMethods.Free();
242+
implementationsToShadow?.Free();
208243
}
209244
#nullable disable
210245

@@ -484,6 +519,7 @@ private static void LookupMembersInNamespace(LookupResult result, NamespaceSymbo
484519
}
485520
}
486521

522+
// PROTOTYPE we should be able to remove this method once all the callers are updated to account for new extension members
487523
/// <summary>
488524
/// Lookup extension methods by name and arity in the given binder and
489525
/// check viability in this binder. The lookup is performed on a single
@@ -1839,7 +1875,7 @@ private static bool WrongArity(Symbol symbol, int arity, bool diagnose, LookupOp
18391875
if (arity != 0 || (options & LookupOptions.AllMethodsOnArityZero) == 0)
18401876
{
18411877
MethodSymbol method = (MethodSymbol)symbol;
1842-
if (method.GetMemberTotalArity() != arity)
1878+
if (method.GetMemberArityIncludingExtension() != arity)
18431879
{
18441880
if (method.Arity == 0)
18451881
{

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1537,7 +1537,7 @@ private void CheckWhatCandidatesWeHave(
15371537
foreach (var scope in new ExtensionScopes(this))
15381538
{
15391539
lookupResult ??= LookupResult.GetInstance();
1540-
LookupExtensionMethods(lookupResult, scope, plainName, arity, ref useSiteInfo);
1540+
LookupExtensionMethods(lookupResult, scope, plainName, arity, ref useSiteInfo); // PROTOTYPE account for new extension members
15411541

15421542
if (lookupResult.IsMultiViable)
15431543
{

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,9 @@ internal enum LookupOptions
7575
UseBaseReferenceAccessibility = 1 << 9,
7676

7777
/// <summary>
78-
/// Include extension methods.
78+
/// Include extension members.
7979
/// </summary>
80-
IncludeExtensionMethods = 1 << 10,
80+
IncludeExtensionMembers = 1 << 10,
8181

8282
/// <summary>
8383
/// Consider only attribute types.

src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MethodGroup.cs

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -47,41 +47,15 @@ internal void PopulateWithExtensionMethods(
4747
this.PopulateHelper(receiverOpt, resultKind, error);
4848
this.IsExtensionMethodGroup = true;
4949

50-
PooledHashSet<MethodSymbol> implementationsToShadow = null;
51-
52-
if (members.Any(static m => m is MethodSymbol { IsExtensionMethod: true }) &&
53-
members.Any(static m => m is MethodSymbol { IsExtensionMethod: false, IsStatic: false }))
54-
{
55-
implementationsToShadow = PooledHashSet<MethodSymbol>.GetInstance();
56-
57-
foreach (var member in members)
58-
{
59-
if (member is MethodSymbol { IsExtensionMethod: false, IsStatic: false } shadows &&
60-
shadows.OriginalDefinition.TryGetCorrespondingExtensionImplementationMethod() is { } toShadow)
61-
{
62-
implementationsToShadow.Add(toShadow);
63-
}
64-
}
65-
}
66-
6750
foreach (var member in members)
6851
{
6952
if (member is MethodSymbol method)
7053
{
7154
Debug.Assert(method.IsExtensionMethod || method.GetIsNewExtensionMember());
72-
73-
// Prefer instance extension declarations vs. their implementations
74-
if (method.IsExtensionMethod && implementationsToShadow?.Remove(method.OriginalDefinition) == true)
75-
{
76-
continue;
77-
}
78-
7955
this.Methods.Add(method);
8056
}
8157
}
8258

83-
implementationsToShadow?.Free();
84-
8559
if (!typeArguments.IsDefault)
8660
{
8761
this.TypeArguments.AddRange(typeArguments);

0 commit comments

Comments
 (0)