Extensions: refine tracking of used imports#80485
Conversation
bc7a754 to
02445d9
Compare
02445d9 to
47a08ad
Compare
47a08ad to
6a8302b
Compare
|
/azp run |
|
Azure Pipelines successfully started running 2 pipeline(s). |
| /// An alternativeName should only provided if a name is provided. | ||
| /// </summary> | ||
| internal virtual void GetExtensionDeclarations(ArrayBuilder<NamedTypeSymbol> extensions, Binder originalBinder) | ||
| internal virtual void GetCandidateExtensionMembers( |
There was a problem hiding this comment.
Consider adding an "InSingleBinder" suffix to the name #Closed
| string nameTrue = OperatorFacts.UnaryOperatorNameFromOperatorKind(UnaryOperatorKind.True, isChecked: false); | ||
| signature.Method.OriginalDefinition.ContainingType.ContainingType.GetExtensionMembers(extensionCandidates, | ||
| nameTrue, alternativeName: null, arity: 0, | ||
| LookupOptions.AllMethodsOnArityZero | LookupOptions.MustBeInvocableIfMember); |
There was a problem hiding this comment.
Consider moving this common logic into unaryOperatorOverloadResolution #ByDesign
There was a problem hiding this comment.
I prefer leaving the call to GetCandidateExtensionMembers in the same code where the ArrayBuilder is declared. So we declared, populate, clear and free it in the same place
| /// Return the extension members from this specific binding scope | ||
| /// Since the lookup of extension members is iterative, proceeding one binding scope at a time, | ||
| /// GetExtensionDeclarations should not defer to the next binding scope. Instead, the caller is | ||
| /// GetCandiateExtensionMembers should not defer to the next binding scope. Instead, the caller is |
There was a problem hiding this comment.
| /// GetCandiateExtensionMembers should not defer to the next binding scope. Instead, the caller is | |
| /// GetCandidateExtensionMembers should not defer to the next binding scope. Instead, the caller is | |
| ``` #Resolved |
| /// GetCandiateExtensionMembers should not defer to the next binding scope. Instead, the caller is | ||
| /// responsible for walking the nested binding scopes from innermost to outermost. This method is overridden | ||
| /// to search the available members list in binding types that represent types, namespaces, and usings. | ||
| /// An alternativeName should only provided if a name is provided. |
There was a problem hiding this comment.
| /// An alternativeName should only provided if a name is provided. | |
| /// An alternativeName should only be provided if a name is provided. | |
| ``` #Resolved |
| } | ||
|
|
||
| #nullable enable | ||
| internal static bool IsInvalidExtensionReceiverParameter(ParameterSymbol thisParam) |
There was a problem hiding this comment.
Can this be private? Also consider inverting the logic and simplifying this to "is valid extension receiver parameter". #Resolved
| } | ||
|
|
||
| internal void GetExtensionContainers(ArrayBuilder<NamedTypeSymbol> extensions) | ||
| internal void GetExtensionMembers(ArrayBuilder<Symbol> members, string? name, string? alternativeName, int arity, LookupOptions options) |
There was a problem hiding this comment.
Should we assert that only LookupOptions.AllMethodsOnArityZero or LookupOptions.MustBeInvocableIfMember are specified? #Resolved
| } | ||
| """; | ||
|
|
||
| internal string ExtensionMarkerAttributeIL = """ |
There was a problem hiding this comment.
| internal string ExtensionMarkerAttributeIL = """ | |
| internal static readonly string ExtensionMarkerAttributeIL = """ | |
| ``` #Resolved |
| extensionCandidatesInSingleScope.Clear(); | ||
| scope.Binder.GetCandidateExtensionMembers(extensionCandidatesInSingleScope, | ||
| ordinaryInstanceOperatorName, checkedInstanceOperatorName, arity: 0, | ||
| LookupOptions.AllMethodsOnArityZero | LookupOptions.MustBeInvocableIfMember, this); |
| extensionDeclarationsInSingleScope.Clear(); | ||
| scope.Binder.GetExtensionDeclarations(extensionDeclarationsInSingleScope, this); | ||
| extensionCandidatesInSingleScope.Clear(); | ||
| scope.Binder.GetCandidateExtensionMembers(extensionCandidatesInSingleScope, |
| extensionCandidatesInSingleScope.Clear(); | ||
| scope.Binder.GetCandidateExtensionMembers(extensionCandidatesInSingleScope, | ||
| staticOperatorName1, staticOperatorName2Opt, arity: 0, | ||
| LookupOptions.AllMethodsOnArityZero | LookupOptions.MustBeInvocableIfMember, this); |
| } | ||
|
|
||
| internal static void AddOperators(ArrayBuilder<MethodSymbol> operators, ImmutableArray<Symbol> candidates) | ||
| internal static void AddOperators(ArrayBuilder<MethodSymbol> operators, IEnumerable<Symbol> candidates) |
| string nameTrue = OperatorFacts.UnaryOperatorNameFromOperatorKind(UnaryOperatorKind.True, isChecked: false); | ||
| signature.Method.OriginalDefinition.ContainingType.ContainingType.GetExtensionMembers(extensionCandidates, | ||
| nameTrue, alternativeName: null, arity: 0, | ||
| LookupOptions.AllMethodsOnArityZero | LookupOptions.MustBeInvocableIfMember); |
| extensionCandidates.Clear(); | ||
| signature.Method.OriginalDefinition.ContainingType.ContainingType.GetExtensionMembers(extensionCandidates, | ||
| nameFalse, alternativeName: null, arity: 0, | ||
| LookupOptions.AllMethodsOnArityZero | LookupOptions.MustBeInvocableIfMember); |
| extensionCandidatesInSingleScope.Clear(); | ||
| scope.Binder.GetCandidateExtensionMembers(extensionCandidatesInSingleScope, | ||
| name1, name2Opt, arity: 0, | ||
| LookupOptions.AllMethodsOnArityZero | LookupOptions.MustBeInvocableIfMember, this); |
| extensionCandidatesInSingleScope.Clear(); | ||
| scope.Binder.GetCandidateExtensionMembers(extensionCandidatesInSingleScope, | ||
| name1, name2Opt, arity: 0, | ||
| LookupOptions.AllMethodsOnArityZero | LookupOptions.MustBeInvocableIfMember, this); |
| extensionCandidatesInSingleScope.Clear(); | ||
| scope.Binder.GetCandidateExtensionMembers(extensionCandidatesInSingleScope, | ||
| staticOperatorName1, staticOperatorName2Opt, arity: 0, | ||
| LookupOptions.AllMethodsOnArityZero | LookupOptions.MustBeInvocableIfMember, this); |
| extensionCandidatesInSingleScope.Clear(); | ||
| scope.Binder.GetCandidateExtensionMembers(extensionCandidatesInSingleScope, | ||
| ordinaryInstanceOperatorName, checkedInstanceOperatorName, arity: 0, | ||
| LookupOptions.AllMethodsOnArityZero | LookupOptions.MustBeInvocableIfMember, this); |
| extensionDeclarationsInSingleScope.Clear(); | ||
| scope.Binder.GetExtensionDeclarations(extensionDeclarationsInSingleScope, this); | ||
| extensionCandidatesInSingleScope.Clear(); | ||
| scope.Binder.GetCandidateExtensionMembers(extensionCandidatesInSingleScope, |
| return; | ||
| } | ||
|
|
||
| if (extensionDeclaration.ExtensionParameter is not { } extensionParameter || |
There was a problem hiding this comment.
Thanks for catching that. I'll restore the type checks. The check for extension parameter being null is in GetExtensionMembers now
| { | ||
| Debug.Assert(extensionDeclaration.IsExtension); | ||
|
|
||
| if (extensionDeclaration.ExtensionParameter is null) |
There was a problem hiding this comment.
NamedTypeSymbol.GetExtensionMembers(...)
| { | ||
| Debug.Assert(extensionDeclaration.IsExtension); | ||
|
|
||
| if (extensionDeclaration.ExtensionParameter is null) |
|
Done with review pass (commit 3), tests aren't reviewed #Closed |
|
|
||
| internal void GetExtensionMembers(ArrayBuilder<Symbol> members, string? name, string? alternativeName, int arity, LookupOptions options) | ||
| { | ||
| Debug.Assert((options & ~(LookupOptions.IncludeExtensionMembers | LookupOptions.AllMethodsOnArityZero |
There was a problem hiding this comment.
This method does not seem to actually check LookupOptions.IncludeExtensionMembers #Closed
There was a problem hiding this comment.
It doesn't look at it, but may receive that option.
It didn't feel necessary to require this options, as many of the callers directly call GetCandidateExtensionMemberInSingleScope so I don't think they should have to further signify that they want extensions.
| string nameTrue = OperatorFacts.UnaryOperatorNameFromOperatorKind(UnaryOperatorKind.True, isChecked: false); | ||
| signature.Method.OriginalDefinition.ContainingType.ContainingType.GetExtensionMembers(extensionCandidates, | ||
| nameTrue, alternativeName: null, arity: 0, | ||
| LookupOptions.AllMethodsOnArityZero | LookupOptions.MustBeInvocableIfMember | LookupOptions.MustNotBeInstance); |
| extensionCandidates.Clear(); | ||
| signature.Method.OriginalDefinition.ContainingType.ContainingType.GetExtensionMembers(extensionCandidates, | ||
| nameFalse, alternativeName: null, arity: 0, | ||
| LookupOptions.AllMethodsOnArityZero | LookupOptions.MustBeInvocableIfMember | LookupOptions.MustNotBeInstance); |
|
|
||
| #nullable disable | ||
|
|
||
| using System; |
There was a problem hiding this comment.
Is the using used? #Closed
| } | ||
|
|
||
| if ((options & LookupOptions.MustBeInvocableIfMember) != 0 | ||
| && !Binder.IsInvocableMember(member, fieldsBeingBound: ConsList<FieldSymbol>.Empty)) |
There was a problem hiding this comment.
| return true; | ||
| } | ||
|
|
||
| internal void GetExtensionMembers(ArrayBuilder<Symbol> members, string? name, string? alternativeName, int arity, LookupOptions options) |
There was a problem hiding this comment.
|
|
||
| foreach (var candidate in candidates) | ||
| { | ||
| if (extensionMemberMatches(candidate, name, alternativeName, arity, options)) |
There was a problem hiding this comment.
I don't think we need an extra filter. There's no lookup option to exclude types. So it's up to the caller to either request an operator, an invocable member, or deal with various members we could find.
There was a problem hiding this comment.
Types are not supported in extensions, I suggest to filter them out always, not based on any option
|
Done with review pass (commit 5) #Closed |
|
Done with review pass (commit 6). It looks like an assert related to the changes in this PR is firing in CI #Closed |
Previously, all
usingdirectives that included extension blocks would have counted theusingas "used". Now, the lookup is based on specific name, arity and options we're looking for, and only extension members that match will result in theusingcounting as "used".The symbols reported from the semantic model APIs (
CandidateSymbolsandGetMemberGroup()) are also affected/trimmed.Addresses part of #79440 ("Consider refining GetExtensionDeclarations logic (shown below) to tracked used usings more finely")
Relates to test plan #76130