Skip to content

Commit b70f580

Browse files
committed
Extensions: account for new extensions in function type scenarios
1 parent f7ca15c commit b70f580

File tree

4 files changed

+796
-114
lines changed

4 files changed

+796
-114
lines changed

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

Lines changed: 45 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10983,13 +10983,18 @@ static bool isCandidateUnique(ref MethodSymbol? method, MethodSymbol candidate)
1098310983
useParams = false;
1098410984
MethodSymbol? foundMethod = null;
1098510985
var typeArguments = node.TypeArgumentsOpt;
10986+
int arity = typeArguments.IsDefaultOrEmpty ? 0 : typeArguments.Length;
10987+
10988+
// 1. instance methods
1098610989
if (node.ResultKind == LookupResultKind.Viable)
1098710990
{
1098810991
var methods = ArrayBuilder<MethodSymbol>.GetInstance(capacity: node.Methods.Length);
1098910992
foreach (var memberMethod in node.Methods)
1099010993
{
1099110994
switch (node.ReceiverOpt)
1099210995
{
10996+
case BoundTypeOrValueExpression:
10997+
break;
1099310998
case BoundTypeExpression:
1099410999
case null: // if `using static Class` is in effect, the receiver is missing
1099511000
if (!memberMethod.IsStatic) continue;
@@ -11001,7 +11006,6 @@ static bool isCandidateUnique(ref MethodSymbol? method, MethodSymbol candidate)
1100111006
break;
1100211007
}
1100311008

11004-
int arity = typeArguments.IsDefaultOrEmpty ? 0 : typeArguments.Length;
1100511009
if (memberMethod.Arity != arity)
1100611010
{
1100711011
// We have no way of inferring type arguments, so if the given type arguments
@@ -11042,52 +11046,63 @@ static bool isCandidateUnique(ref MethodSymbol? method, MethodSymbol candidate)
1104211046
}
1104311047
}
1104411048

11045-
if (node.ReceiverOpt is not BoundTypeExpression && node.SearchExtensions)
11049+
// 2. extensions
11050+
if (node.SearchExtensions)
1104611051
{
11047-
var receiver = node.ReceiverOpt!;
11048-
var methodGroup = MethodGroup.GetInstance();
11052+
Debug.Assert(node.ReceiverOpt!.Type is not null); // extensions are only considered on member access
11053+
11054+
BoundExpression receiver = node.ReceiverOpt;
11055+
LookupOptions options = arity == 0 ? LookupOptions.AllMethodsOnArityZero : LookupOptions.Default;
11056+
var singleLookupResults = ArrayBuilder<SingleLookupResult>.GetInstance();
11057+
CompoundUseSiteInfo<AssemblySymbol> discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
11058+
1104911059
foreach (var scope in new ExtensionScopes(this))
1105011060
{
11051-
methodGroup.Clear();
11052-
PopulateExtensionMethodsFromSingleBinder(scope, methodGroup, node.Syntax, receiver, node.Name, typeArguments, BindingDiagnosticBag.Discarded); // PROTOTYPE account for new extension members
11053-
var methods = ArrayBuilder<MethodSymbol>.GetInstance(capacity: methodGroup.Methods.Count);
11054-
foreach (var extensionMethod in methodGroup.Methods)
11055-
{
11056-
var substituted = typeArguments.IsDefaultOrEmpty ? extensionMethod : extensionMethod.Construct(typeArguments);
11057-
11058-
var reduced = substituted.ReduceExtensionMethod(receiver.Type, Compilation, out bool wasFullyInferred);
11059-
if (reduced is null)
11060-
{
11061-
// Extension method was not applicable
11062-
continue;
11063-
}
11061+
singleLookupResults.Clear();
11062+
scope.Binder.EnumerateAllExtensionMembersInSingleBinder(singleLookupResults, node.Name, arity, options, originalBinder: this, ref discardedUseSiteInfo, ref discardedUseSiteInfo);
1106411063

11065-
if (!wasFullyInferred)
11064+
var methods = ArrayBuilder<MethodSymbol>.GetInstance(capacity: singleLookupResults.Count);
11065+
foreach (SingleLookupResult singleLookupResult in singleLookupResults)
11066+
{
11067+
// Remove static/instance mismatches
11068+
Symbol extensionMember = singleLookupResult.Symbol;
11069+
bool memberCountsAsStatic = extensionMember is MethodSymbol { IsExtensionMethod: true } ? false : extensionMember.IsStatic;
11070+
switch (node.ReceiverOpt)
1106611071
{
11067-
continue;
11072+
case BoundTypeOrValueExpression:
11073+
break;
11074+
case BoundTypeExpression:
11075+
if (!memberCountsAsStatic) continue;
11076+
break;
11077+
default:
11078+
if (memberCountsAsStatic) continue;
11079+
break;
1106811080
}
1106911081

11070-
if (!satisfiesConstraintChecks(reduced))
11082+
// Note: we only care about methods since we're already decided this is a method group (ie. not resolving to some other kind of extension member)
11083+
if (extensionMember is MethodSymbol method)
1107111084
{
11072-
continue;
11085+
var substituted = (MethodSymbol?)extensionMember.GetReducedAndFilteredSymbol(typeArguments, receiver.Type, Compilation, checkFullyInferred: true);
11086+
if (substituted is not null)
11087+
{
11088+
methods.Add(substituted);
11089+
}
1107311090
}
11074-
11075-
methods.Add(reduced);
1107611091
}
1107711092

1107811093
if (!OverloadResolution.FilterMethodsForUniqueSignature(methods, out useParams))
1107911094
{
11095+
singleLookupResults.Free();
1108011096
methods.Free();
11081-
methodGroup.Free();
1108211097
return null;
1108311098
}
1108411099

11085-
foreach (var reduced in methods)
11100+
foreach (var method in methods)
1108611101
{
11087-
if (!isCandidateUnique(ref foundMethod, reduced))
11102+
if (!isCandidateUnique(ref foundMethod, method))
1108811103
{
11104+
singleLookupResults.Free();
1108911105
methods.Free();
11090-
methodGroup.Free();
1109111106
useParams = false;
1109211107
return null;
1109311108
}
@@ -11097,11 +11112,12 @@ static bool isCandidateUnique(ref MethodSymbol? method, MethodSymbol candidate)
1109711112

1109811113
if (foundMethod is not null)
1109911114
{
11100-
methodGroup.Free();
11115+
singleLookupResults.Free();
1110111116
return foundMethod;
1110211117
}
1110311118
}
11104-
methodGroup.Free();
11119+
11120+
singleLookupResults.Free();
1110511121
}
1110611122

1110711123
useParams = false;

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

Lines changed: 11 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -4683,92 +4683,23 @@ private static bool AddReducedAndFilteredSymbol(
46834683
TypeSymbol receiverType,
46844684
CSharpCompilation compilation)
46854685
{
4686-
if (member is MethodSymbol method)
4686+
Symbol? substitutedMember = member.GetReducedAndFilteredSymbol(typeArguments, receiverType, compilation, checkFullyInferred: false);
4687+
if (substitutedMember is null)
46874688
{
4688-
MethodSymbol constructedMethod;
4689-
if (!typeArguments.IsDefaultOrEmpty && method.GetMemberArityIncludingExtension() == typeArguments.Length)
4690-
{
4691-
constructedMethod = method.ConstructIncludingExtension(typeArguments);
4692-
Debug.Assert((object)constructedMethod != null);
4693-
4694-
if (!checkConstraintsIncludingExtension(constructedMethod, compilation, method.ContainingAssembly.CorLibrary.TypeConversions))
4695-
{
4696-
return false;
4697-
}
4698-
}
4699-
else
4700-
{
4701-
constructedMethod = method;
4702-
}
4703-
4704-
if ((object)receiverType != null)
4705-
{
4706-
if (method.IsExtensionMethod)
4707-
{
4708-
constructedMethod = constructedMethod.ReduceExtensionMethod(receiverType, compilation);
4709-
}
4710-
else
4711-
{
4712-
Debug.Assert(method.GetIsNewExtensionMember());
4713-
constructedMethod = (MethodSymbol)SourceNamedTypeSymbol.GetCompatibleSubstitutedMember(compilation, constructedMethod, receiverType)!;
4714-
}
4715-
4716-
if ((object)constructedMethod == null)
4717-
{
4718-
return false;
4719-
}
4720-
}
4721-
4722-
// Don't add exact duplicates.
4723-
if (filteredMembers.Contains(constructedMethod))
4724-
{
4725-
return false;
4726-
}
4727-
4728-
members.Add(member);
4729-
filteredMembers.Add(constructedMethod);
4730-
return true;
4731-
}
4732-
else if (member is PropertySymbol property)
4733-
{
4734-
Debug.Assert(receiverType is not null);
4735-
Debug.Assert(property.GetIsNewExtensionMember());
4736-
var constructedProperty = (PropertySymbol)SourceNamedTypeSymbol.GetCompatibleSubstitutedMember(compilation, property, receiverType)!;
4737-
4738-
if (constructedProperty is null)
4739-
{
4740-
return false;
4741-
}
4742-
4743-
members.Add(member);
4744-
filteredMembers.Add(constructedProperty);
4745-
return true;
4689+
return false;
47464690
}
47474691

4748-
throw ExceptionUtilities.UnexpectedValue(member.Kind);
4749-
4750-
static bool checkConstraintsIncludingExtension(MethodSymbol symbol, CSharpCompilation compilation, TypeConversions conversions)
4692+
// Don't add exact duplicates.
4693+
if (filteredMembers.Contains(substitutedMember))
47514694
{
4752-
var constraintArgs = new ConstraintsHelper.CheckConstraintsArgs(compilation, conversions, includeNullability: false,
4753-
NoLocation.Singleton, diagnostics: BindingDiagnosticBag.Discarded, template: CompoundUseSiteInfo<AssemblySymbol>.Discarded);
4754-
4755-
bool success = true;
4756-
4757-
if (symbol.GetIsNewExtensionMember())
4758-
{
4759-
NamedTypeSymbol extensionDeclaration = symbol.ContainingType;
4760-
success = extensionDeclaration.CheckConstraints(constraintArgs);
4761-
}
4762-
4763-
if (success)
4764-
{
4765-
success = symbol.CheckConstraints(constraintArgs);
4766-
}
4767-
4768-
return success;
4695+
return false;
47694696
}
4770-
#nullable disable
4697+
4698+
members.Add(member);
4699+
filteredMembers.Add(substitutedMember);
4700+
return true;
47714701
}
4702+
#nullable disable
47724703

47734704
private static void MergeReducedAndFilteredSymbol(
47744705
ArrayBuilder<Symbol> members,

src/Compilers/CSharp/Portable/Symbols/MemberSymbolExtensions.cs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using System.Linq;
1212
using Microsoft.CodeAnalysis.CSharp.Symbols;
1313
using Microsoft.CodeAnalysis.CSharp.Syntax;
14+
using Microsoft.CodeAnalysis.PooledObjects;
1415
using Microsoft.CodeAnalysis.Text;
1516
using Roslyn.Utilities;
1617

@@ -154,6 +155,99 @@ internal static TMember ConstructIncludingExtension<TMember>(this TMember member
154155
throw ExceptionUtilities.UnexpectedValue(member);
155156
}
156157

158+
// For lookup APIs in the semantic model, we can return symbols that aren't fully inferred.
159+
// But for function type inference, if the symbol isn't fully inferred with the information we have (the receiver and any explicit type arguments)
160+
// then we won't return it.
161+
internal static Symbol? GetReducedAndFilteredSymbol(this Symbol member, ImmutableArray<TypeWithAnnotations> typeArguments, TypeSymbol receiverType, CSharpCompilation compilation, bool checkFullyInferred)
162+
{
163+
if (member is MethodSymbol method)
164+
{
165+
// 1. construct with explicit type arguments if provided
166+
MethodSymbol constructed;
167+
if (!typeArguments.IsDefaultOrEmpty && method.GetMemberArityIncludingExtension() == typeArguments.Length)
168+
{
169+
constructed = method.ConstructIncludingExtension(typeArguments);
170+
Debug.Assert((object)constructed != null);
171+
172+
if (!checkConstraintsIncludingExtension(constructed, compilation, method.ContainingAssembly.CorLibrary.TypeConversions))
173+
{
174+
return null;
175+
}
176+
}
177+
else
178+
{
179+
constructed = method;
180+
}
181+
182+
// 2. infer type arguments based on the receiver type if needed, check applicability, reduce symbol (for classic extension methods), check whether fully inferred
183+
if ((object)receiverType != null)
184+
{
185+
if (method.IsExtensionMethod)
186+
{
187+
constructed = constructed.ReduceExtensionMethod(receiverType, compilation, out bool wasFullyInferred);
188+
189+
if (checkFullyInferred && !wasFullyInferred)
190+
{
191+
return null;
192+
}
193+
}
194+
else
195+
{
196+
Debug.Assert(method.GetIsNewExtensionMember());
197+
constructed = (MethodSymbol)SourceNamedTypeSymbol.GetCompatibleSubstitutedMember(compilation, constructed, receiverType)!;
198+
199+
if (checkFullyInferred && constructed.IsGenericMethod && typeArguments.IsDefaultOrEmpty)
200+
{
201+
return null;
202+
}
203+
}
204+
205+
if ((object)constructed == null)
206+
{
207+
return null;
208+
}
209+
}
210+
211+
return constructed;
212+
}
213+
else if (member is PropertySymbol property)
214+
{
215+
// infer type arguments based off the receiver type if needed, check applicability
216+
Debug.Assert(receiverType is not null);
217+
Debug.Assert(property.GetIsNewExtensionMember());
218+
var constructedProperty = (PropertySymbol)SourceNamedTypeSymbol.GetCompatibleSubstitutedMember(compilation, property, receiverType)!;
219+
220+
if (constructedProperty is null)
221+
{
222+
return null;
223+
}
224+
225+
return constructedProperty;
226+
}
227+
228+
throw ExceptionUtilities.UnexpectedValue(member.Kind);
229+
230+
static bool checkConstraintsIncludingExtension(MethodSymbol symbol, CSharpCompilation compilation, TypeConversions conversions)
231+
{
232+
var constraintArgs = new ConstraintsHelper.CheckConstraintsArgs(compilation, conversions, includeNullability: false,
233+
NoLocation.Singleton, diagnostics: BindingDiagnosticBag.Discarded, template: CompoundUseSiteInfo<AssemblySymbol>.Discarded);
234+
235+
bool success = true;
236+
237+
if (symbol.GetIsNewExtensionMember())
238+
{
239+
NamedTypeSymbol extensionDeclaration = symbol.ContainingType;
240+
success = extensionDeclaration.CheckConstraints(constraintArgs);
241+
}
242+
243+
if (success)
244+
{
245+
success = symbol.CheckConstraints(constraintArgs);
246+
}
247+
248+
return success;
249+
}
250+
}
157251
#nullable disable
158252

159253
/// <summary>

0 commit comments

Comments
 (0)