-
Couldn't load subscription status.
- Fork 4.2k
Extensions: address some diagnostic quality issues #80827
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
Conversation
c367836 to
b706ec5
Compare
b706ec5 to
06b2b5a
Compare
| allowTrailingSeparator: false, | ||
| requireOneElement: forExtension, // For extension declarations, we require at least one receiver parameter | ||
| allowSemicolonAsSeparator: false); | ||
| // Tracked by https://github.com/dotnet/roslyn/issues/78830 : diagnostic quality, consider suppressing parsing diagnostics on extension parameters beyond the first one |
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.
📝 I don't this will be that useful
| return new MethodGroupResolution(resultSymbol, LookupResultKind.Viable, diagnostics.ToReadOnly()); | ||
| } | ||
|
|
||
| static void initActualArguments(BoundExpression left, AnalyzedArguments? analyzedArguments, [NotNull] ref AnalyzedArguments? actualMethodArguments) |
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.
I realized that the "Actual" probably refers to the actualMethodArguments. Both feel confusing, but I guess this is a "pre-existing condition".
| if (methodResult.HasAnyApplicableMethod && propertyResult.HasAnyApplicableMember) | ||
| { | ||
| var firstMethod = methodResult.OverloadResolutionResult?.BestResult.Member ?? methodResult.MethodGroup.Methods[0]; | ||
| var firstProperty = propertyResult.BestResult.Member; |
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.
Good catch. This was broken. BestResult can only be accessed if the overload resolution has succeeded (ie. there's a single result that's still considered applicable, either in normal or expanded form, after overload resolution completes)
| } | ||
|
|
||
| ExtendedErrorTypeSymbol resultSymbol = new ExtendedErrorTypeSymbol(containingSymbol: null, symbols, LookupResultKind.OverloadResolutionFailure, errorInfo, arity); | ||
| return new MethodGroupResolution(resultSymbol, LookupResultKind.Viable, diagnostics.ToReadOnly()); |
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.
Made small adjustment to clarify.
It's meant to flow the kind from the LookupResult, but we can only get here when it's Viable (we have a check for IsMultiViable earlier in ResolveExtensions)
| if (Format.TypeQualificationStyle == SymbolDisplayTypeQualificationStyle.NameAndContainingTypes || | ||
| Format.TypeQualificationStyle == SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces) | ||
| Format.TypeQualificationStyle == SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces || | ||
| (Format.TypeQualificationStyle == SymbolDisplayTypeQualificationStyle.NameOnly && symbol.IsExtension)) |
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.
(Format.TypeQualificationStyle == SymbolDisplayTypeQualificationStyle.NameOnly && symbol.IsExtension)
I understand that this is probably gives us desired behavior for specific scenarios, but the condition itself is confusing. Especially for someone who doesn't know that we enumerated all possible SymbolDisplayTypeQualificationStyle values that are defined at the moment. I think the condition will have more sense and will be more robust to future changes to the SymbolDisplayTypeQualificationStyle enum if we simply use symbol.IsExtension as the condition. #Closed
| Diagnostic(ErrorCode.ERR_ExtensionResolutionFailed, "C.M").WithArguments("C", "M").WithLocation(1, 22)); | ||
| Diagnostic(ErrorCode.ERR_AmbigExtension, "C.M").WithArguments("E3.extension(C).M()", "E2.extension(C).M").WithLocation(1, 22)); | ||
|
|
||
| string expected(int index) |
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.
| DiagnosticInfo errorInfo; | ||
| if (methodResult.HasAnyApplicableMethod && propertyResult.HasAnyApplicableMember) | ||
| { | ||
| var firstMethod = methodResult.OverloadResolutionResult?.BestResult.Member ?? methodResult.MethodGroup.Methods[0]; |
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 thread was marked as resolved, but it is still not obvious to me why we assume that methodResult.MethodGroup.Methods[0] is an applicable method.
Is there a test that takes this code path?
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.
All the methods in that method group are as applicable as it gets (they meet lookup criteria and are compatible/substitutable given the receiver type).
The scenario where we don't have an overload resolution result of methods is when there are no arguments. For example var x = object.M; or System.Action a = object.M;.
This reporting path is tested by ReportDiagnostics_23, ReportDiagnostics_24 and ReportDiagnostics_25.
In such scenarios, we start with a lookup result (which ensures we match name and arity requirements), then resolveMethods prunes methods that are not compatible with the receiver (using ReduceExtensionMember), instead of running normal overload resolution. We then form a MethodGroupResolution that is considered to have applicable methods (ie. its HasAnyApplicableMethod is true), since there's not possible overload resolution we could run.
// MethodGroupResolution criteria that lets us get here
public bool HasAnyApplicableMethod
{
get
{
return (this.MethodGroup != null) &&
(this.ResultKind == LookupResultKind.Viable) &&
((this.OverloadResolutionResult == null) || this.OverloadResolutionResult.HasAnyApplicableMember);
}
}
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 1)
|
@dotnet/roslyn-compiler for second review. Thanks |
| return result.BestResult.Member; | ||
| } | ||
|
|
||
| return result.ResultsBuilder.First(r => r.Result.Kind == MemberResolutionKind.Worse).Member; |
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.
return result.ResultsBuilder.First(r => r.Result.Kind == MemberResolutionKind.Worse).Member;
This looks fragile because the logic doesn't look equivalent to OverloadResolutionResult<TMember>.HasAnyApplicableMember. I suggest adding an OverloadResolutionResult<TMember>.FirstApplicableMember API next to OverloadResolutionResult<TMember>.HasAnyApplicableMember with shared logic for picking an applicable member, and using it here. That should help with keeping consistent behavior now and in the future. #Closed
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 that API doesn't have to return the first applicable candidate. It might return the "best" applicable candidate. The main point is that there should be an obvious guarantee that we get something when HasAnyApplicableMember is true.
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.
I'm okay to move the logic closer to HasAnyApplicableMember, but the distinction that is left here is Worse vs. Worst and it is intentional.
Because the result had HasAnyApplicableMember but not Succeeded, we know there's at least one Worse in the set. Worst is worse than Worse
| { | ||
| initActualArguments(left, analyzedArguments, ref actualMethodArguments); | ||
|
|
||
| propertyResult.ReportDiagnostics(binder, expression.Location, expression, diagnostics, memberName, left, left.Syntax, actualMethodArguments, symbols, |
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 suspicious. It looks like we are using actualReceiverArguments in resolveProperties. Also, it looks like resolveProperties guarantees that actualReceiverArguments is initialized when propertyResult is not null. We can assert the fact after resolveProperties and on entry into this function. #Closed
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.
Thanks. I failed to noticed that we have separate actual arguments for methods vs. properties, and used the wrong one...
| } | ||
| else | ||
| { | ||
| initActualArguments(left, analyzedArguments, ref actualMethodArguments); |
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.
| return result.ResultsBuilder.First(r => r.Result.Kind == MemberResolutionKind.Worse).Member; | ||
| } | ||
|
|
||
| static void initActualArguments(BoundExpression left, AnalyzedArguments? analyzedArguments, [NotNull] ref AnalyzedArguments? actualMethodArguments) |
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 3) |
| LookupResult lookupResult, | ||
| AnalyzedArguments? analyzedArguments, | ||
| ref AnalyzedArguments? actualMethodArguments, | ||
| ref AnalyzedArguments? actualReceiverArguments, |
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.
| LookupResult lookupResult, | ||
| AnalyzedArguments? analyzedArguments, | ||
| ref AnalyzedArguments? actualMethodArguments, | ||
| ref AnalyzedArguments? actualReceiverArguments, |
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.
| string memberName, | ||
| int arity, | ||
| LookupResult lookupResult, | ||
| AnalyzedArguments? analyzedArguments, |
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.
| int arity, | ||
| LookupResult lookupResult, | ||
| AnalyzedArguments? analyzedArguments, | ||
| ref AnalyzedArguments? actualMethodArguments, |
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 prefer Worse over Worst candidates | ||
| return ResultsBuilder.First(r => r.Result.Kind == MemberResolutionKind.Worse).Member; |
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 4) |
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 6), assuming CI is passing
* upstream/main: (332 commits) Cache lambdas in analyzer driver (dotnet#80759) Add information for NuGet package version 4.14 (dotnet#80870) Add missing search keywords to VB Advanced options page Fix IDE0031 false positive when preprocessor directives are used in if statements (dotnet#80878) Use core compiler on netfx hosts with toolset package (dotnet#80631) Make string concat assert more precise (dotnet#80619) Extensions: address some diagnostic quality issues (dotnet#80827) Add note on traversal order for bound nodes (dotnet#80872) Ensure that locals at the top level of a constructor have the same safe-context as parameters (dotnet#80807) Fix handling of SymbolDisplayCompilerInternalOptions.UseArityForGenericTypes option for non-native symbol implementations (dotnet#80826) Update src/Analyzers/CSharp/Tests/UseCollectionInitializer/UseCollectionInitializerTests.cs Add IsValidContainingStatement check to prevent collection initializers in using declarations Add back old DocumentSpan constructor (dotnet#80864) Add tests verifying pointer types in type parameters require unsafe context (dotnet#80776) Add regression test for Interlocked.Exchange with nullable types (dotnet#80796) Add regression test for ParseAttributeArgumentList with invalid input (fixes dotnet#8699) (dotnet#80705) Add regression test for compiler crash with syntax error in indexer declaration (dotnet#80772) Add runtime NullReferenceException validation to foreach null iteration tests (dotnet#80839) Update MicrosoftBuildTasksCoreVersionForMetrics to 17.11.48 (dotnet#80812) Mark CS4009 error as a "build only" error. (dotnet#80698) ...
Addresses parts of #78830
extension(int)instead ofE.extension(int))Relates to test plan #76130