-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Extensions: address or split remaining open issues directly associated with test plan #79452
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 26 commits
a7b8337
77c106a
a8a9da2
1fa8d49
5e37325
7a406cb
5324232
8d46bd8
27934c4
550f190
9d23ff2
4c91ee4
77a8add
c532cea
d928bbe
b934bf5
7a434de
08147a8
a0750b5
454eaef
e62a6e7
9aa80f4
6a4b7d5
c2419b4
0cb34ee
01dbe85
6e282c7
61b0198
a0cc39d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7970,7 +7970,7 @@ private BoundExpression MakeMemberAccessValue(BoundExpression expr, BindingDiagn | |
| { | ||
| var methodGroup = (BoundMethodGroup)expr; | ||
| CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); | ||
| var resolution = this.ResolveMethodGroup(methodGroup, analyzedArguments: null, useSiteInfo: ref useSiteInfo, options: OverloadResolution.Options.None); | ||
| var resolution = this.ResolveMethodGroup(methodGroup, analyzedArguments: null, useSiteInfo: ref useSiteInfo, options: OverloadResolution.Options.None, acceptOnlyMethods: true); | ||
| Debug.Assert(!resolution.IsNonMethodExtensionMember(out _)); | ||
| diagnostics.Add(expr.Syntax, useSiteInfo); | ||
| if (!expr.HasAnyErrors) | ||
|
|
@@ -8851,60 +8851,6 @@ private bool AllowRefOmittedArguments(BoundExpression receiver) | |
| } | ||
| #nullable disable | ||
|
|
||
| private void PopulateExtensionMethodsFromSingleBinder( | ||
| ExtensionScope scope, | ||
| MethodGroup methodGroup, | ||
| SyntaxNode node, | ||
| BoundExpression left, | ||
| string rightName, | ||
| ImmutableArray<TypeWithAnnotations> typeArgumentsWithAnnotations, | ||
| BindingDiagnosticBag diagnostics) | ||
| { | ||
| int arity; | ||
| if (typeArgumentsWithAnnotations.IsDefault) | ||
| { | ||
| arity = 0; | ||
| } | ||
| else | ||
| { | ||
| arity = typeArgumentsWithAnnotations.Length; | ||
| } | ||
|
|
||
| var lookupResult = LookupResult.GetInstance(); | ||
| CompoundUseSiteInfo<AssemblySymbol> useSiteInfo = GetNewCompoundUseSiteInfo(diagnostics); | ||
| this.LookupExtensionMethods(lookupResult, scope, rightName, arity, ref useSiteInfo); | ||
| diagnostics.Add(node, useSiteInfo); | ||
|
|
||
| if (lookupResult.IsMultiViable) | ||
| { | ||
| Debug.Assert(lookupResult.Symbols.Any()); | ||
| var members = ArrayBuilder<Symbol>.GetInstance(); | ||
| bool wasError; | ||
| Symbol symbol = GetSymbolOrMethodOrPropertyGroup(lookupResult, node, rightName, arity, members, diagnostics, out wasError, qualifierOpt: null); | ||
| Debug.Assert((object)symbol == null); | ||
| Debug.Assert(members.Count > 0); | ||
| methodGroup.PopulateWithExtensionMethods(left, members, typeArgumentsWithAnnotations, lookupResult.Kind); | ||
| members.Free(); | ||
| } | ||
|
|
||
| lookupResult.Free(); | ||
| } | ||
|
|
||
| private void LookupExtensionMethods(LookupResult lookupResult, ExtensionScope scope, string rightName, int arity, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo) | ||
| { | ||
| LookupOptions options; | ||
| if (arity == 0) | ||
| { | ||
| options = LookupOptions.AllMethodsOnArityZero; | ||
| } | ||
| else | ||
| { | ||
| options = LookupOptions.Default; | ||
| } | ||
|
|
||
| this.LookupExtensionMethodsInSingleBinder(scope, lookupResult, rightName, arity, options, ref useSiteInfo); | ||
| } | ||
|
|
||
| protected BoundExpression BindFieldAccess( | ||
| SyntaxNode node, | ||
| BoundExpression receiver, | ||
|
|
@@ -10520,7 +10466,8 @@ void makeCall(SyntaxNode syntax, BoundExpression receiver, MethodSymbol method, | |
| indexerOrSliceAccess = BindMethodGroupInvocation(syntax, syntax, method.Name, boundMethodGroup, analyzedArguments, | ||
| diagnostics, queryClause: null, ignoreNormalFormIfHasValidParamsParameter: true, anyApplicableCandidates: out bool _, | ||
| disallowExpandedNonArrayParams: false, | ||
| acceptOnlyMethods: true).MakeCompilerGenerated(); // Tracked by https://github.com/dotnet/roslyn/issues/76130 : Test effect of acceptOnlyMethods value | ||
| acceptOnlyMethods: true) // acceptOnlyMethods is not relevant since we won't search extensions | ||
| .MakeCompilerGenerated(); | ||
|
|
||
| analyzedArguments.Free(); | ||
| } | ||
|
|
@@ -10638,6 +10585,7 @@ internal MethodGroupResolution ResolveMethodGroup( | |
| AnalyzedArguments analyzedArguments, | ||
| ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo, | ||
| OverloadResolution.Options options, | ||
| bool acceptOnlyMethods, | ||
| RefKind returnRefKind = default, | ||
| TypeSymbol returnType = null, | ||
| in CallingConventionInfo callingConventionInfo = default) | ||
|
|
@@ -10649,7 +10597,7 @@ internal MethodGroupResolution ResolveMethodGroup( | |
| return ResolveMethodGroup( | ||
| node, node.Syntax, node.Name, analyzedArguments, ref useSiteInfo, | ||
| options, | ||
| 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 | ||
| acceptOnlyMethods: acceptOnlyMethods, | ||
| returnRefKind: returnRefKind, returnType: returnType, | ||
| callingConventionInfo: callingConventionInfo); | ||
| } | ||
|
|
@@ -10900,22 +10848,50 @@ private MethodGroupResolution ResolveDefaultMethodGroup( | |
| } | ||
| } | ||
|
|
||
| if (node.ReceiverOpt is not BoundTypeExpression && node.SearchExtensions) | ||
| if (node.SearchExtensions) | ||
| { | ||
| var receiver = node.ReceiverOpt!; | ||
| Debug.Assert(node.ReceiverOpt!.Type is not null); // extensions are only considered on member access | ||
|
|
||
| BoundExpression receiver = node.ReceiverOpt; | ||
| ImmutableArray<TypeWithAnnotations> typeArguments = node.TypeArgumentsOpt; | ||
| int arity = typeArguments.IsDefaultOrEmpty ? 0 : typeArguments.Length; | ||
| LookupOptions options = arity == 0 ? LookupOptions.AllMethodsOnArityZero : LookupOptions.Default; | ||
| var singleLookupResults = ArrayBuilder<SingleLookupResult>.GetInstance(); | ||
| CompoundUseSiteInfo<AssemblySymbol> discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded; | ||
|
|
||
| foreach (var scope in new ExtensionScopes(this)) | ||
| { | ||
| methods.Clear(); | ||
| var methodGroup = MethodGroup.GetInstance(); | ||
| PopulateExtensionMethodsFromSingleBinder(scope, methodGroup, node.Syntax, receiver, node.Name, node.TypeArgumentsOpt, BindingDiagnosticBag.Discarded); | ||
| foreach (var m in methodGroup.Methods) | ||
| singleLookupResults.Clear(); | ||
| scope.Binder.EnumerateAllExtensionMembersInSingleBinder(singleLookupResults, node.Name, arity, options, originalBinder: this, ref discardedUseSiteInfo, ref discardedUseSiteInfo); | ||
|
|
||
| foreach (SingleLookupResult singleLookupResult in singleLookupResults) | ||
| { | ||
| if (m.ReduceExtensionMethod(receiver.Type, Compilation) is { } reduced) | ||
| Symbol extensionMember = singleLookupResult.Symbol; | ||
| if (IsStaticInstanceMismatchForUniqueSignatureFromMethodGroup(receiver, extensionMember)) | ||
| { | ||
| methods.Add(reduced); | ||
| // Remove static/instance mismatches | ||
| continue; | ||
| } | ||
|
|
||
| // 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. | ||
| if (extensionMember is MethodSymbol m) | ||
| { | ||
| if (m.GetIsNewExtensionMember()) | ||
| { | ||
| // Note: new extension methods are subject to more stringent checks | ||
| var substituted = (MethodSymbol?)extensionMember.GetReducedAndFilteredSymbol(typeArguments, receiver.Type, Compilation, checkFullyInferred: true); | ||
| if (substituted is not null) | ||
| { | ||
| methods.Add(substituted); | ||
| } | ||
| } | ||
| else if (m.ReduceExtensionMethod(receiver.Type, Compilation) is { } reduced) | ||
| { | ||
| methods.Add(reduced); | ||
| } | ||
| } | ||
| } | ||
| methodGroup.Free(); | ||
|
|
||
| if (methods.Count == 0) | ||
| { | ||
|
|
@@ -10926,6 +10902,7 @@ private MethodGroupResolution ResolveDefaultMethodGroup( | |
| { | ||
| methods.Free(); | ||
| useParams = false; | ||
| singleLookupResults.Free(); | ||
| return null; | ||
| } | ||
|
|
||
|
|
@@ -10936,6 +10913,7 @@ private MethodGroupResolution ResolveDefaultMethodGroup( | |
| { | ||
| methods.Free(); | ||
| useParams = false; | ||
| singleLookupResults.Free(); | ||
| return null; | ||
| } | ||
|
|
||
|
|
@@ -10948,10 +10926,13 @@ private MethodGroupResolution ResolveDefaultMethodGroup( | |
| { | ||
| methods.Free(); | ||
| useParams = false; | ||
| singleLookupResults.Free(); | ||
| return null; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| singleLookupResults.Free(); | ||
| } | ||
|
|
||
| methods.Free(); | ||
|
|
@@ -10989,6 +10970,17 @@ static bool isCandidateUnique(ref MethodSymbol? method, MethodSymbol candidate) | |
| } | ||
| } | ||
|
|
||
| private static bool IsStaticInstanceMismatchForUniqueSignatureFromMethodGroup(BoundExpression receiver, Symbol extensionMember) | ||
| { | ||
| bool memberCountsAsStatic = extensionMember is MethodSymbol { IsExtensionMethod: true } ? false : extensionMember.IsStatic; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This logic is too special for a method with such "innocent" name. It looks like the method is meant to be used only under specific conditions. Consider renaming to avoid an accidental misuse of the method. #Closed |
||
| return receiver switch | ||
| { | ||
| BoundTypeOrValueExpression => false, | ||
| BoundTypeExpression => !memberCountsAsStatic, | ||
| _ => memberCountsAsStatic, | ||
| }; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// For C# 13 onwards, returns one of the methods from the method group if all instance methods, or extension methods | ||
| /// 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) | |
| var methods = ArrayBuilder<MethodSymbol>.GetInstance(capacity: singleLookupResults.Count); | ||
| foreach (SingleLookupResult singleLookupResult in singleLookupResults) | ||
| { | ||
| // Remove static/instance mismatches | ||
| Symbol extensionMember = singleLookupResult.Symbol; | ||
| bool memberCountsAsStatic = extensionMember is MethodSymbol { IsExtensionMethod: true } ? false : extensionMember.IsStatic; | ||
| switch (node.ReceiverOpt) | ||
| if (IsStaticInstanceMismatchForUniqueSignatureFromMethodGroup(receiver, extensionMember)) | ||
| { | ||
| case BoundTypeOrValueExpression: | ||
| break; | ||
| case BoundTypeExpression: | ||
| if (!memberCountsAsStatic) continue; | ||
| break; | ||
| default: | ||
| if (memberCountsAsStatic) continue; | ||
| break; | ||
| // Remove static/instance mismatches | ||
| continue; | ||
| } | ||
|
|
||
| // 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) | ||
| if (extensionMember is MethodSymbol method) | ||
| if (extensionMember is MethodSymbol) | ||
| { | ||
| var substituted = (MethodSymbol?)extensionMember.GetReducedAndFilteredSymbol(typeArguments, receiver.Type, Compilation, checkFullyInferred: true); | ||
| if (substituted is not null) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -676,7 +676,7 @@ private void ReduceFrom(FromClauseSyntax from, QueryTranslationState state, Bind | |
|
|
||
| private static BoundExpression? ExtractCastInvocation(BoundCall invocation) | ||
| { | ||
| int index = invocation.InvokedAsExtensionMethod ? 1 : 0; // Tracked by https://github.com/dotnet/roslyn/issues/76130: Add test coverage for his code path | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📝 |
||
| int index = invocation.InvokedAsExtensionMethod ? 1 : 0; | ||
| var c1 = invocation.Arguments[index] as BoundConversion; | ||
| var l1 = c1 != null ? c1.Operand as BoundLambda : null; | ||
| var r1 = l1 != null ? l1.Body.Statements[0] as BoundReturnStatement : null; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fact is not obvious. Does this happen because
boundMethodGroup.SearchExtensionsisfalse? Consider adding an assert then. #ClosedThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's because of
BoundMethodGroupFlags.Noneabove. The.SearchExtensionsflag isn't set