Skip to content

Commit cb8d294

Browse files
authored
Extensions: address or split remaining open issues directly associated with test plan (#79452)
1 parent 92afc3e commit cb8d294

37 files changed

+1896
-216
lines changed

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,9 @@ internal MethodInfo ReplaceWithExtensionImplementation(out bool wasError)
9292
var setMethod = replace(SetMethod);
9393
Symbol symbol = ReferenceEquals(Symbol, Method) && method is not null ? method : Symbol;
9494

95+
Debug.Assert(SetMethod?.GetIsNewExtensionMember() != true);
9596
wasError = (Method is not null && method is null) || (SetMethod is not null && setMethod is null);
9697

97-
// Tracked by https://github.com/dotnet/roslyn/issues/76130 : Test in compound assignment (ie. "setMethod")
9898
return new MethodInfo(symbol, method, setMethod);
9999

100100
static MethodSymbol? replace(MethodSymbol? method)
@@ -110,8 +110,7 @@ internal MethodInfo ReplaceWithExtensionImplementation(out bool wasError)
110110
ConstructIfGeneric(method.ContainingType.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics.Concat(method.TypeArgumentsWithAnnotations));
111111
}
112112

113-
// Tracked by https://github.com/dotnet/roslyn/issues/76130 : Test this code path
114-
return null;
113+
throw ExceptionUtilities.Unreachable(); // we don't bind to extension methods with unsupported metadata
115114
}
116115
}
117116

@@ -615,7 +614,7 @@ private BoundExpression CheckValue(BoundExpression expr, BindValueKind valueKind
615614
{
616615
var methodGroup = (BoundMethodGroup)expr;
617616
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
618-
var resolution = this.ResolveMethodGroup(methodGroup, analyzedArguments: null, useSiteInfo: ref useSiteInfo, options: OverloadResolution.Options.None);
617+
var resolution = this.ResolveMethodGroup(methodGroup, analyzedArguments: null, useSiteInfo: ref useSiteInfo, options: OverloadResolution.Options.None, acceptOnlyMethods: true);
619618
Debug.Assert(!resolution.IsNonMethodExtensionMember(out _));
620619
diagnostics.Add(expr.Syntax, useSiteInfo);
621620
Symbol otherSymbol = null;

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

Lines changed: 61 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -7970,7 +7970,7 @@ private BoundExpression MakeMemberAccessValue(BoundExpression expr, BindingDiagn
79707970
{
79717971
var methodGroup = (BoundMethodGroup)expr;
79727972
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
7973-
var resolution = this.ResolveMethodGroup(methodGroup, analyzedArguments: null, useSiteInfo: ref useSiteInfo, options: OverloadResolution.Options.None);
7973+
var resolution = this.ResolveMethodGroup(methodGroup, analyzedArguments: null, useSiteInfo: ref useSiteInfo, options: OverloadResolution.Options.None, acceptOnlyMethods: true);
79747974
Debug.Assert(!resolution.IsNonMethodExtensionMember(out _));
79757975
diagnostics.Add(expr.Syntax, useSiteInfo);
79767976
if (!expr.HasAnyErrors)
@@ -8851,60 +8851,6 @@ private bool AllowRefOmittedArguments(BoundExpression receiver)
88518851
}
88528852
#nullable disable
88538853

8854-
private void PopulateExtensionMethodsFromSingleBinder(
8855-
ExtensionScope scope,
8856-
MethodGroup methodGroup,
8857-
SyntaxNode node,
8858-
BoundExpression left,
8859-
string rightName,
8860-
ImmutableArray<TypeWithAnnotations> typeArgumentsWithAnnotations,
8861-
BindingDiagnosticBag diagnostics)
8862-
{
8863-
int arity;
8864-
if (typeArgumentsWithAnnotations.IsDefault)
8865-
{
8866-
arity = 0;
8867-
}
8868-
else
8869-
{
8870-
arity = typeArgumentsWithAnnotations.Length;
8871-
}
8872-
8873-
var lookupResult = LookupResult.GetInstance();
8874-
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
8875-
this.LookupExtensionMethods(lookupResult, scope, rightName, arity, ref useSiteInfo);
8876-
diagnostics.Add(node, useSiteInfo);
8877-
8878-
if (lookupResult.IsMultiViable)
8879-
{
8880-
Debug.Assert(lookupResult.Symbols.Any());
8881-
var members = ArrayBuilder<Symbol>.GetInstance();
8882-
bool wasError;
8883-
Symbol symbol = GetSymbolOrMethodOrPropertyGroup(lookupResult, node, rightName, arity, members, diagnostics, out wasError, qualifierOpt: null);
8884-
Debug.Assert((object)symbol == null);
8885-
Debug.Assert(members.Count > 0);
8886-
methodGroup.PopulateWithExtensionMethods(left, members, typeArgumentsWithAnnotations, lookupResult.Kind);
8887-
members.Free();
8888-
}
8889-
8890-
lookupResult.Free();
8891-
}
8892-
8893-
private void LookupExtensionMethods(LookupResult lookupResult, ExtensionScope scope, string rightName, int arity, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
8894-
{
8895-
LookupOptions options;
8896-
if (arity == 0)
8897-
{
8898-
options = LookupOptions.AllMethodsOnArityZero;
8899-
}
8900-
else
8901-
{
8902-
options = LookupOptions.Default;
8903-
}
8904-
8905-
this.LookupExtensionMethodsInSingleBinder(scope, lookupResult, rightName, arity, options, ref useSiteInfo);
8906-
}
8907-
89088854
protected BoundExpression BindFieldAccess(
89098855
SyntaxNode node,
89108856
BoundExpression receiver,
@@ -10520,7 +10466,8 @@ void makeCall(SyntaxNode syntax, BoundExpression receiver, MethodSymbol method,
1052010466
indexerOrSliceAccess = BindMethodGroupInvocation(syntax, syntax, method.Name, boundMethodGroup, analyzedArguments,
1052110467
diagnostics, queryClause: null, ignoreNormalFormIfHasValidParamsParameter: true, anyApplicableCandidates: out bool _,
1052210468
disallowExpandedNonArrayParams: false,
10523-
acceptOnlyMethods: true).MakeCompilerGenerated(); // Tracked by https://github.com/dotnet/roslyn/issues/76130 : Test effect of acceptOnlyMethods value
10469+
acceptOnlyMethods: true) // acceptOnlyMethods is not relevant since we won't search extensions
10470+
.MakeCompilerGenerated();
1052410471

1052510472
analyzedArguments.Free();
1052610473
}
@@ -10638,6 +10585,7 @@ internal MethodGroupResolution ResolveMethodGroup(
1063810585
AnalyzedArguments analyzedArguments,
1063910586
ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo,
1064010587
OverloadResolution.Options options,
10588+
bool acceptOnlyMethods,
1064110589
RefKind returnRefKind = default,
1064210590
TypeSymbol returnType = null,
1064310591
in CallingConventionInfo callingConventionInfo = default)
@@ -10649,7 +10597,7 @@ internal MethodGroupResolution ResolveMethodGroup(
1064910597
return ResolveMethodGroup(
1065010598
node, node.Syntax, node.Name, analyzedArguments, ref useSiteInfo,
1065110599
options,
10652-
acceptOnlyMethods: true, // Tracked by https://github.com/dotnet/roslyn/issues/76130 : Confirm this value is appropriate for all consumers of the enclosing method and test effect of this value for all of them
10600+
acceptOnlyMethods: acceptOnlyMethods,
1065310601
returnRefKind: returnRefKind, returnType: returnType,
1065410602
callingConventionInfo: callingConventionInfo);
1065510603
}
@@ -10900,22 +10848,50 @@ private MethodGroupResolution ResolveDefaultMethodGroup(
1090010848
}
1090110849
}
1090210850

10903-
if (node.ReceiverOpt is not BoundTypeExpression && node.SearchExtensions)
10851+
if (node.SearchExtensions)
1090410852
{
10905-
var receiver = node.ReceiverOpt!;
10853+
Debug.Assert(node.ReceiverOpt!.Type is not null); // extensions are only considered on member access
10854+
10855+
BoundExpression receiver = node.ReceiverOpt;
10856+
ImmutableArray<TypeWithAnnotations> typeArguments = node.TypeArgumentsOpt;
10857+
int arity = typeArguments.IsDefaultOrEmpty ? 0 : typeArguments.Length;
10858+
LookupOptions options = arity == 0 ? LookupOptions.AllMethodsOnArityZero : LookupOptions.Default;
10859+
var singleLookupResults = ArrayBuilder<SingleLookupResult>.GetInstance();
10860+
CompoundUseSiteInfo<AssemblySymbol> discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded;
10861+
1090610862
foreach (var scope in new ExtensionScopes(this))
1090710863
{
1090810864
methods.Clear();
10909-
var methodGroup = MethodGroup.GetInstance();
10910-
PopulateExtensionMethodsFromSingleBinder(scope, methodGroup, node.Syntax, receiver, node.Name, node.TypeArgumentsOpt, BindingDiagnosticBag.Discarded);
10911-
foreach (var m in methodGroup.Methods)
10865+
singleLookupResults.Clear();
10866+
scope.Binder.EnumerateAllExtensionMembersInSingleBinder(singleLookupResults, node.Name, arity, options, originalBinder: this, ref discardedUseSiteInfo, ref discardedUseSiteInfo);
10867+
10868+
foreach (SingleLookupResult singleLookupResult in singleLookupResults)
1091210869
{
10913-
if (m.ReduceExtensionMethod(receiver.Type, Compilation) is { } reduced)
10870+
Symbol extensionMember = singleLookupResult.Symbol;
10871+
if (IsStaticInstanceMismatchForUniqueSignatureFromMethodGroup(receiver, extensionMember))
1091410872
{
10915-
methods.Add(reduced);
10873+
// Remove static/instance mismatches
10874+
continue;
10875+
}
10876+
10877+
// Note: we only care about methods. If the expression resolved to a non-method extension member, we wouldn't get here to compute the function type for the expression.
10878+
if (extensionMember is MethodSymbol m)
10879+
{
10880+
if (m.GetIsNewExtensionMember())
10881+
{
10882+
// Note: new extension methods are subject to more stringent checks
10883+
var substituted = (MethodSymbol?)extensionMember.GetReducedAndFilteredSymbol(typeArguments, receiver.Type, Compilation, checkFullyInferred: true);
10884+
if (substituted is not null)
10885+
{
10886+
methods.Add(substituted);
10887+
}
10888+
}
10889+
else if (m.ReduceExtensionMethod(receiver.Type, Compilation) is { } reduced)
10890+
{
10891+
methods.Add(reduced);
10892+
}
1091610893
}
1091710894
}
10918-
methodGroup.Free();
1091910895

1092010896
if (methods.Count == 0)
1092110897
{
@@ -10926,6 +10902,7 @@ private MethodGroupResolution ResolveDefaultMethodGroup(
1092610902
{
1092710903
methods.Free();
1092810904
useParams = false;
10905+
singleLookupResults.Free();
1092910906
return null;
1093010907
}
1093110908

@@ -10936,6 +10913,7 @@ private MethodGroupResolution ResolveDefaultMethodGroup(
1093610913
{
1093710914
methods.Free();
1093810915
useParams = false;
10916+
singleLookupResults.Free();
1093910917
return null;
1094010918
}
1094110919

@@ -10948,10 +10926,13 @@ private MethodGroupResolution ResolveDefaultMethodGroup(
1094810926
{
1094910927
methods.Free();
1095010928
useParams = false;
10929+
singleLookupResults.Free();
1095110930
return null;
1095210931
}
1095310932
}
1095410933
}
10934+
10935+
singleLookupResults.Free();
1095510936
}
1095610937

1095710938
methods.Free();
@@ -10989,6 +10970,17 @@ static bool isCandidateUnique(ref MethodSymbol? method, MethodSymbol candidate)
1098910970
}
1099010971
}
1099110972

10973+
private static bool IsStaticInstanceMismatchForUniqueSignatureFromMethodGroup(BoundExpression receiver, Symbol extensionMember)
10974+
{
10975+
bool memberCountsAsStatic = extensionMember is MethodSymbol { IsExtensionMethod: true } ? false : extensionMember.IsStatic;
10976+
return receiver switch
10977+
{
10978+
BoundTypeOrValueExpression => false,
10979+
BoundTypeExpression => !memberCountsAsStatic,
10980+
_ => memberCountsAsStatic,
10981+
};
10982+
}
10983+
1099210984
/// <summary>
1099310985
/// For C# 13 onwards, returns one of the methods from the method group if all instance methods, or extension methods
1099410986
/// in the nearest scope, have the same signature ignoring parameter names and custom modifiers.
@@ -11088,23 +11080,15 @@ static bool isCandidateUnique(ref MethodSymbol? method, MethodSymbol candidate)
1108811080
var methods = ArrayBuilder<MethodSymbol>.GetInstance(capacity: singleLookupResults.Count);
1108911081
foreach (SingleLookupResult singleLookupResult in singleLookupResults)
1109011082
{
11091-
// Remove static/instance mismatches
1109211083
Symbol extensionMember = singleLookupResult.Symbol;
11093-
bool memberCountsAsStatic = extensionMember is MethodSymbol { IsExtensionMethod: true } ? false : extensionMember.IsStatic;
11094-
switch (node.ReceiverOpt)
11084+
if (IsStaticInstanceMismatchForUniqueSignatureFromMethodGroup(receiver, extensionMember))
1109511085
{
11096-
case BoundTypeOrValueExpression:
11097-
break;
11098-
case BoundTypeExpression:
11099-
if (!memberCountsAsStatic) continue;
11100-
break;
11101-
default:
11102-
if (memberCountsAsStatic) continue;
11103-
break;
11086+
// Remove static/instance mismatches
11087+
continue;
1110411088
}
1110511089

1110611090
// 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)
11107-
if (extensionMember is MethodSymbol method)
11091+
if (extensionMember is MethodSymbol)
1110811092
{
1110911093
var substituted = (MethodSymbol?)extensionMember.GetReducedAndFilteredSymbol(typeArguments, receiver.Type, Compilation, checkFullyInferred: true);
1111011094
if (substituted is not null)

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2069,7 +2069,7 @@ private ImmutableArray<BoundExpression> BuildArgumentsForErrorRecovery(AnalyzedA
20692069
var parameterListList = ArrayBuilder<ImmutableArray<ParameterSymbol>>.GetInstance();
20702070
foreach (var p in properties)
20712071
{
2072-
// Tracked by https://github.com/dotnet/roslyn/issues/76130: Revisit this with new extensions
2072+
Debug.Assert(!p.GetIsNewExtensionMember());
20732073
if (p.ParameterCount > 0)
20742074
{
20752075
parameterListList.Add(p.Parameters);
@@ -2353,7 +2353,7 @@ private void EnsureNameofExpressionSymbols(BoundMethodGroup methodGroup, Binding
23532353
{
23542354
// Check that the method group contains something applicable. Otherwise error.
23552355
CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics);
2356-
var resolution = ResolveMethodGroup(methodGroup, analyzedArguments: null, useSiteInfo: ref useSiteInfo, options: OverloadResolution.Options.None);
2356+
var resolution = ResolveMethodGroup(methodGroup, analyzedArguments: null, useSiteInfo: ref useSiteInfo, options: OverloadResolution.Options.None, acceptOnlyMethods: true);
23572357
Debug.Assert(!resolution.IsNonMethodExtensionMember(out _));
23582358

23592359
diagnostics.Add(methodGroup.Syntax, useSiteInfo);

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

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -525,28 +525,6 @@ private static void LookupMembersInNamespace(LookupResult result, NamespaceSymbo
525525
}
526526
}
527527

528-
// Tracked by https://github.com/dotnet/roslyn/issues/76130 : we should be able to remove this method once all the callers are updated to account for new extension members
529-
/// <summary>
530-
/// Lookup extension methods by name and arity in the given binder and
531-
/// check viability in this binder. The lookup is performed on a single
532-
/// binder because extension method search stops at the first applicable
533-
/// method group from the nearest enclosing namespace.
534-
/// </summary>
535-
private void LookupExtensionMethodsInSingleBinder(ExtensionScope scope, LookupResult result, string name, int arity, LookupOptions options, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
536-
{
537-
var methods = ArrayBuilder<MethodSymbol>.GetInstance();
538-
var binder = scope.Binder;
539-
binder.GetCandidateExtensionMethods(methods, name, arity, options, this);
540-
541-
foreach (var method in methods)
542-
{
543-
SingleLookupResult resultOfThisMember = this.CheckViability(method, arity, options, null, diagnose: true, useSiteInfo: ref useSiteInfo);
544-
result.MergeEqual(resultOfThisMember);
545-
}
546-
547-
methods.Free();
548-
}
549-
550528
#region "AttributeTypeLookup"
551529

552530
/// <summary>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -676,7 +676,7 @@ private void ReduceFrom(FromClauseSyntax from, QueryTranslationState state, Bind
676676

677677
private static BoundExpression? ExtractCastInvocation(BoundCall invocation)
678678
{
679-
int index = invocation.InvokedAsExtensionMethod ? 1 : 0; // Tracked by https://github.com/dotnet/roslyn/issues/76130: Add test coverage for his code path
679+
int index = invocation.InvokedAsExtensionMethod ? 1 : 0;
680680
var c1 = invocation.Arguments[index] as BoundConversion;
681681
var l1 = c1 != null ? c1.Operand as BoundLambda : null;
682682
var r1 = l1 != null ? l1.Body.Statements[0] as BoundReturnStatement : null;

src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/Conversions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,13 +258,13 @@ private static MethodGroupResolution ResolveDelegateOrFunctionPointerMethodGroup
258258
resolution = binder.ResolveMethodGroup(source, analyzedArguments, useSiteInfo: ref useSiteInfo,
259259
options: OverloadResolution.Options.InferWithDynamic | OverloadResolution.Options.IsMethodGroupConversion |
260260
(isFunctionPointer ? OverloadResolution.Options.IsFunctionPointerResolution : OverloadResolution.Options.None),
261-
returnRefKind: delegateInvokeMethodOpt.RefKind, returnType: delegateInvokeMethodOpt.ReturnType,
261+
acceptOnlyMethods: true, returnRefKind: delegateInvokeMethodOpt.RefKind, returnType: delegateInvokeMethodOpt.ReturnType,
262262
callingConventionInfo: callingConventionInfo);
263263
analyzedArguments.Free();
264264
}
265265
else
266266
{
267-
resolution = binder.ResolveMethodGroup(source, analyzedArguments: null, ref useSiteInfo, options: OverloadResolution.Options.IsMethodGroupConversion);
267+
resolution = binder.ResolveMethodGroup(source, analyzedArguments: null, useSiteInfo: ref useSiteInfo, options: OverloadResolution.Options.IsMethodGroupConversion, acceptOnlyMethods: true);
268268
}
269269

270270
Debug.Assert(!resolution.IsNonMethodExtensionMember(out _));

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1513,7 +1513,7 @@ private TypeWithAnnotations MethodGroupReturnType(
15131513
var resolution = binder.ResolveMethodGroup(source, analyzedArguments, useSiteInfo: ref useSiteInfo,
15141514
options: OverloadResolution.Options.IsMethodGroupConversion |
15151515
(isFunctionPointerResolution ? OverloadResolution.Options.IsFunctionPointerResolution : OverloadResolution.Options.None),
1516-
returnRefKind: delegateRefKind,
1516+
acceptOnlyMethods: true, returnRefKind: delegateRefKind,
15171517
// Since we are trying to infer the return type, it is not an input to resolving the method group
15181518
returnType: null,
15191519
callingConventionInfo: in callingConventionInfo);

0 commit comments

Comments
 (0)