-
Notifications
You must be signed in to change notification settings - Fork 4k
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
Implement Lookup APIs on extensions #72948
Conversation
syntax, right: syntax, diagnostics, ref wasError); | ||
if (!typeArgumentsOpt.IsDefault) | ||
{ | ||
namedTypeSymbol = ConstructNamedTypeUnlessTypeArgumentOmitted(syntax, namedTypeSymbol, typeArgumentsSyntax, typeArgumentsOpt, diagnostics); |
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 fixes a bug with a generic type in an extension. It's illustrated by ExtensionMemberLookup_MatchingExtendedType_GenericType_GenericMember
amongst other tests.
b7f9f4b
to
8adbfc9
Compare
@@ -2368,6 +2370,7 @@ private BoundExpression BindNameofOperatorInternal(InvocationExpressionSyntax no | |||
CheckFeatureAvailability(node, MessageID.IDS_FeatureNameof, diagnostics); | |||
var argument = node.ArgumentList.Arguments[0].Expression; | |||
var boundArgument = BindExpression(argument, diagnostics); | |||
boundArgument = ResolveToExtensionMemberIfPossible(boundArgument, diagnostics); |
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.
📝 BindToNaturalType
(called below) also does this. I've been debating whether to remove the redundant call. We'll discuss
This problem goes away in my next PR. It will move ResolveToExtensionMemberIfPossible
into BindExpression
(for non-invocation cases) and remove it from BindToNaturalType
. #Resolved
syntax, right: syntax, diagnostics, ref wasError); | ||
if (!typeArgumentsOpt.IsDefault) | ||
{ | ||
namedTypeSymbol = ConstructNamedTypeUnlessTypeArgumentOmitted(syntax, namedTypeSymbol, typeArgumentsSyntax, typeArgumentsOpt, diagnostics); |
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.
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.
Yes (see tests with _OmittedTypeArgument
). Indeed, we produce a unbound generic symbol then.
syntax, right: syntax, diagnostics, ref wasError); | ||
if (!typeArgumentsOpt.IsDefault) | ||
{ | ||
namedTypeSymbol = ConstructNamedTypeUnlessTypeArgumentOmitted(syntax, namedTypeSymbol, typeArgumentsSyntax, typeArgumentsOpt, diagnostics); |
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.
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.
The constraints get checked a couple of layers into ConstructNamedTypeUnlessTypeArgumentOmitted
:
ConstructNamedTypeUnlessTypeArgumentOmitted
-> ConstructNamedType
-> CheckConstraintsForNamedType
.
Example test: InferredVariable_TypeReceiver_GenericType_BrokenConstraint
} | ||
|
||
[Fact] | ||
public void ExtensionMemberLookup_MatchingExtendedType_GenericType_GenericMember() |
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.
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.
Add ExtensionMemberLookup_MatchingExtendedType_GenericMember_TypeOnlyContext
internal override void GetCandidateExtensionMethods( | ||
ArrayBuilder<MethodSymbol> methods, | ||
string name, | ||
string? name, |
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.
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.
LookupSymbols
with includeReducedExtensionMethods = true
and name = null
.
/// </summary> | ||
IncludeExtensionMethods = 1 << 10, | ||
IncludeExtensionMethodsAndMembers = 1 << 10, |
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.
@@ -54,6 +54,9 @@ public SyntaxNode NameSyntax | |||
} | |||
} | |||
|
|||
public SeparatedSyntaxList<TypeSyntax> TypeArgumentsSyntax | |||
=> NameSyntax is GenericNameSyntax genericName ? genericName.TypeArgumentList.Arguments : default; |
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.
|
||
options |= LookupOptions.AllMethodsOnArityZero; | ||
options &= ~LookupOptions.MustBeInstance; |
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.
|
||
// We use a Lookup API (which uses a CheckViability check) instead of an AddLookup API (which uses a CanAddLookupSymbolInfo check), | ||
// but that is fine since we filter symbols that cannot be referenced by name below anyways. | ||
scope.Binder.LookupImplicitExtensionMembersInSingleBinder( |
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.
useSiteInfo.MergeAndClear(ref tempUseSiteInfo); | ||
if (tempUseSiteInfo.AccumulatesDiagnostics) | ||
{ | ||
useSiteInfo.MergeAndClear(ref tempUseSiteInfo); |
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.
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.
Filled #73188
Done with review pass (commit 1), tests are not looked at. |
/// <summary> | ||
/// Lookup extension methods by name and arity in the given binder and | ||
/// check viability in this binder. The lookup is performed on a single | ||
/// binder because extension method search stops at the first applicable | ||
/// method group from the nearest enclosing namespace. | ||
/// </summary> | ||
private void LookupExtensionMethodsInSingleBinder(ExtensionScope scope, LookupResult result, string name, int arity, LookupOptions options, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo) | ||
internal void LookupExtensionMethodsInSingleBinder(ExtensionScope scope, LookupResult result, string? name, int arity, LookupOptions options, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo) |
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.
@@ -925,14 +943,13 @@ internal virtual bool SupportsExtensions | |||
/// </summary> | |||
internal virtual void GetCandidateExtensionMethods( | |||
ArrayBuilder<MethodSymbol> methods, | |||
string name, | |||
string? name, |
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.
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 is annotating the method as currently implemented
// Lookup member in an extension type | ||
private void LookupMembersInExtension( | ||
LookupResult current, | ||
NamedTypeSymbol type, | ||
string name, | ||
string? name, |
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.AddMemberLookupSymbolsInfoInTypeParameter(result, (TypeParameterSymbol)type, options, originalBinder); | ||
break; | ||
|
||
case TypeKind.Interface: | ||
this.AddMemberLookupSymbolsInfoInInterface(result, type, options, originalBinder, type); | ||
accessThroughType ??= type; |
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.
case TypeKind.Class: | ||
case TypeKind.Struct: | ||
case TypeKind.Enum: | ||
case TypeKind.Delegate: | ||
case TypeKind.Array: | ||
case TypeKind.Dynamic: | ||
case TypeKind.Submission: | ||
this.AddMemberLookupSymbolsInfoInClass(result, type, options, originalBinder, type); | ||
accessThroughType ??= type; |
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.
|
||
if (type.ExtendedTypeNoUseSiteDiagnostics is { } extendedType) | ||
{ | ||
AddMemberLookupSymbolsInfoInType(result, extendedType, options, originalBinder, accessThroughType: type); |
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.AddMemberLookupSymbolsInfoInTypeParameter(result, (TypeParameterSymbol)type, options, originalBinder); | ||
break; | ||
|
||
case TypeKind.Interface: | ||
this.AddMemberLookupSymbolsInfoInInterface(result, type, options, originalBinder, type); | ||
accessThroughType ??= type; |
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.
/// <summary> | ||
/// Lookup extension methods by name and arity in the given binder and | ||
/// check viability in this binder. The lookup is performed on a single | ||
/// binder because extension method search stops at the first applicable | ||
/// method group from the nearest enclosing namespace. | ||
/// </summary> | ||
private void LookupExtensionMethodsInSingleBinder(ExtensionScope scope, LookupResult result, string name, int arity, LookupOptions options, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo) | ||
internal void LookupExtensionMethodsInSingleBinder(ExtensionScope scope, LookupResult result, string? name, int arity, LookupOptions options, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo) |
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.
internal void AddImplicitExtensionMemberLookupSymbolsInfoForType(LookupSymbolsInfo result, TypeSymbol type, LookupOptions options, Binder originalBinder) | ||
{ | ||
var accessThroughType = type; | ||
if (type.ExtendedTypeNoUseSiteDiagnostics is { } extendedType) |
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.
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.
Perhaps even a better question: "Why would we ever call this method with an extension type?"
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.
Extension types get the benefit of members from other extension types too.
class C { }
implicit extension E for C { }
implicit extension E2 for C { void Member() { } }
When trying to list all the extension members that are applicable to type E
, we should find E2.Member
.
Note: we also do this during binding (in LookupImplicitExtensionMembersInSingleBinder
).
options |= LookupOptions.AllMethodsOnArityZero; | ||
options &= ~LookupOptions.MustBeInstance; | ||
// PROTOTYPE(static) confirm that we want to exclude type parameters from extension member resolution or leave a comment | ||
if (container is TypeSymbol typeSymbol && !typeSymbol.IsTypeParameter()) |
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.
|
||
internal void AddImplicitExtensionMemberLookupSymbolsInfoForType(LookupSymbolsInfo result, TypeSymbol type, LookupOptions options, Binder originalBinder) | ||
{ | ||
var accessThroughType = type; |
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.
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.
No, I don't think this will be observable until we have inheritance in extensions
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.
No, I don't think this will be observable until we have inheritance in extensions
Are protected API declared in an extension accessible through extended type today? If they are not, then passing this accessThroughType
value might make a difference.
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.
They are not. Added a Lookup test for this and there was no observable impact
|
||
void addMemberLookupSymbolsInfoInExtension(LookupSymbolsInfo result, TypeSymbol type, LookupOptions options, Binder originalBinder) | ||
{ | ||
AddMemberLookupSymbolsInfoWithoutInheritance(result, type, options, originalBinder, accessThroughType: type); |
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.
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.
No, I don't think this will be observable until we have inheritance in extensions
|
||
if (type.ExtendedTypeNoUseSiteDiagnostics is { } extendedType) | ||
{ | ||
AddMemberLookupSymbolsInfoInType(result, extendedType, options, originalBinder, accessThroughType: type); |
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.
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.
Yes, Lookup_UnderlyingTypeMembers_Private
and Lookup_UnderlyingTypeMembers_Protected
Done with review pass (commit 8) |
The PR description also includes a fix for nested generic extension type scenario, which includes the change here and the handling of type arguments in In reply to: 2079830450 Refers to: src/Compilers/CSharp/Portable/Symbols/TypeUnification.cs:14 in 90e907a. [](commit_id = 90e907a, deletion_comment = False) |
|
||
foreach (NamedTypeSymbol extension in compatibleExtensions) | ||
{ | ||
AddMemberLookupSymbolsInfoWithoutInheritance(result, extension, options, scope.Binder, accessThroughType: null); |
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.
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.
Restored. Thanks
|
||
void addMemberLookupSymbolsInfoInExtension(LookupSymbolsInfo result, TypeSymbol type, LookupOptions options, Binder originalBinder) | ||
{ | ||
AddMemberLookupSymbolsInfoWithoutInheritance(result, type, options, originalBinder, accessThroughType: null); |
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.
Done with review pass (commit 10) |
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.
LGTM (commit 11)
... and fix nested generic extension type scenario
Note: only
LookupStaticMembers
andLookupNamespacesAndTypes
are implemented.LookupSymbols
will be implemented later (as we need to integrate the lookup for extension methods and extension type members before implementing it).Relates to test plan #66722