Skip to content

Commit c367836

Browse files
author
Julien Couvreur
committed
Extensions: address some diagnostic quality issues
1 parent 3b5303b commit c367836

23 files changed

+1012
-255
lines changed

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

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8688,9 +8688,9 @@ static bool tryResolveExtensionInScope(
86888688
{
86898689
if (propertyResult != null)
86908690
{
8691+
firstResult = makeErrorResult(methodResult, propertyResult, expression, left, memberName, arity, lookupResult, analyzedArguments, ref actualMethodArguments, binder, diagnostics);
86918692
methodResult.Free(keepArguments: true);
86928693
propertyResult.Free();
8693-
firstResult = makeErrorResult(left.Type, memberName, arity, lookupResult, expression, diagnostics);
86948694
}
86958695
else
86968696
{
@@ -8714,28 +8714,28 @@ static bool tryResolveExtensionInScope(
87148714
}
87158715

87168716
// ambiguous between methods and properties
8717+
result = makeErrorResult(methodResult, propertyResult, expression, left, memberName, arity, lookupResult, analyzedArguments, ref actualMethodArguments, binder, diagnostics);
87178718
methodResult.Free(keepArguments: true);
87188719
propertyResult?.Free();
8719-
result = makeErrorResult(left.Type, memberName, arity, lookupResult, expression, diagnostics);
87208720
return true;
87218721
}
87228722

87238723
// If the search in the current scope resulted in any applicable property (regardless of whether a best
87248724
// applicable property could be determined) then our search is complete.
87258725
Debug.Assert(propertyResult?.HasAnyApplicableMember == true);
8726-
methodResult.Free(keepArguments: true);
87278726
if (propertyResult.Succeeded && propertyResult.BestResult.Member is { } bestProperty)
87288727
{
87298728
// property wins
8729+
methodResult.Free(keepArguments: true);
87308730
propertyResult.Free();
87318731
result = new MethodGroupResolution(bestProperty, LookupResultKind.Viable, diagnostics.ToReadOnly());
87328732
return true;
87338733
}
87348734

87358735
// ambiguous between multiple applicable properties
8736+
result = makeErrorResult(methodResult, propertyResult, expression, left, memberName, arity, lookupResult, analyzedArguments, ref actualMethodArguments, binder, diagnostics);
8737+
methodResult.Free(keepArguments: true);
87368738
propertyResult.Free();
8737-
// Tracked by https://github.com/dotnet/roslyn/issues/78830 : diagnostic quality, consider using the property overload resolution result in the result to improve reported diagnostics
8738-
result = makeErrorResult(left.Type, memberName, arity, lookupResult, expression, diagnostics);
87398739
return true;
87408740
}
87418741

@@ -8796,12 +8796,7 @@ static MethodGroupResolution resolveMethods(
87968796
return default;
87978797
}
87988798

8799-
if (actualMethodArguments == null)
8800-
{
8801-
// Create a set of arguments for overload resolution including the receiver.
8802-
actualMethodArguments = AnalyzedArguments.GetInstance();
8803-
CombineExtensionMethodArguments(left, analyzedArguments, actualMethodArguments);
8804-
}
8799+
initActualArguments(left, analyzedArguments, ref actualMethodArguments);
88058800

88068801
var overloadResolutionResult = OverloadResolutionResult<MethodSymbol>.GetInstance();
88078802
CompoundUseSiteInfo<AssemblySymbol> overloadResolutionUseSiteInfo = binder.GetNewCompoundUseSiteInfo(diagnostics);
@@ -8862,14 +8857,54 @@ static MethodGroupResolution resolveMethods(
88628857
return overloadResolutionResult;
88638858
}
88648859

8865-
static MethodGroupResolution makeErrorResult(TypeSymbol receiverType, string memberName, int arity, LookupResult lookupResult, SyntaxNode expression, BindingDiagnosticBag diagnostics)
8860+
static MethodGroupResolution makeErrorResult(
8861+
MethodGroupResolution methodResult,
8862+
OverloadResolutionResult<PropertySymbol> propertyResult,
8863+
SyntaxNode expression,
8864+
BoundExpression left,
8865+
string memberName,
8866+
int arity,
8867+
LookupResult lookupResult,
8868+
AnalyzedArguments? analyzedArguments,
8869+
ref AnalyzedArguments? actualMethodArguments,
8870+
Binder binder,
8871+
BindingDiagnosticBag diagnostics)
88668872
{
8867-
// Tracked by https://github.com/dotnet/roslyn/issues/78830 : diagnostic quality, we'll want to describe what went wrong in a useful way (see OverloadResolutionResult.ReportDiagnostics)
8868-
var errorInfo = new CSDiagnosticInfo(ErrorCode.ERR_ExtensionResolutionFailed, receiverType, memberName);
8869-
diagnostics.Add(errorInfo, expression.Location);
8870-
var resultSymbol = new ExtendedErrorTypeSymbol(containingSymbol: null, lookupResult.Symbols.ToImmutable(), LookupResultKind.OverloadResolutionFailure, errorInfo, arity);
8873+
Debug.Assert(propertyResult is not null);
8874+
ImmutableArray<Symbol> symbols = lookupResult.Symbols.ToImmutable();
8875+
8876+
DiagnosticInfo errorInfo;
8877+
if (methodResult.HasAnyApplicableMethod && propertyResult.HasAnyApplicableMember)
8878+
{
8879+
var firstMethod = methodResult.OverloadResolutionResult?.BestResult.Member ?? methodResult.MethodGroup.Methods[0];
8880+
var firstProperty = propertyResult.BestResult.Member;
8881+
errorInfo = OverloadResolutionResult<Symbol>.CreateAmbiguousCallDiagnosticInfo(binder.Compilation, firstMethod, firstProperty, symbols, isExtension: true);
8882+
8883+
diagnostics.Add(errorInfo, expression.Location);
8884+
}
8885+
else
8886+
{
8887+
initActualArguments(left, analyzedArguments, ref actualMethodArguments);
8888+
8889+
propertyResult.ReportDiagnostics(binder, expression.Location, expression, diagnostics, memberName, left, left.Syntax, actualMethodArguments, symbols,
8890+
typeContainingConstructor: null, delegateTypeBeingInvoked: null, isMethodGroupConversion: false, isExtension: true);
8891+
8892+
errorInfo = new CSDiagnosticInfo(ErrorCode.ERR_ExtensionResolutionFailed, left.Type, memberName);
8893+
}
8894+
8895+
ExtendedErrorTypeSymbol resultSymbol = new ExtendedErrorTypeSymbol(containingSymbol: null, symbols, LookupResultKind.OverloadResolutionFailure, errorInfo, arity);
88718896
return new MethodGroupResolution(resultSymbol, LookupResultKind.Viable, diagnostics.ToReadOnly());
88728897
}
8898+
8899+
static void initActualArguments(BoundExpression left, AnalyzedArguments? analyzedArguments, [NotNull] ref AnalyzedArguments? actualMethodArguments)
8900+
{
8901+
if (actualMethodArguments == null)
8902+
{
8903+
// Create a set of arguments for overload resolution including the receiver.
8904+
actualMethodArguments = AnalyzedArguments.GetInstance();
8905+
CombineExtensionMethodArguments(left, analyzedArguments, actualMethodArguments);
8906+
}
8907+
}
88738908
}
88748909

88758910
private bool AllowRefOmittedArguments(BoundExpression receiver)

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

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,8 @@ internal void ReportDiagnostics<T>(
201201
bool isMethodGroupConversion = false,
202202
RefKind? returnRefKind = null,
203203
TypeSymbol delegateOrFunctionPointerType = null,
204-
bool isParamsModifierValidation = false) where T : Symbol
204+
bool isParamsModifierValidation = false,
205+
bool isExtension = false) where T : Symbol
205206
{
206207
Debug.Assert(!this.Succeeded, "Don't ask for diagnostic info on a successful overload resolution result.");
207208

@@ -220,7 +221,7 @@ internal void ReportDiagnostics<T>(
220221
// however, be more than one. We'll check for that first, since applicable candidates are
221222
// always better than inapplicable candidates.
222223

223-
if (HadAmbiguousBestMethods(binder.Compilation, diagnostics, symbols, location))
224+
if (HadAmbiguousBestMethods(binder.Compilation, diagnostics, symbols, location, isExtension))
224225
{
225226
return;
226227
}
@@ -243,7 +244,7 @@ internal void ReportDiagnostics<T>(
243244
// (Obviously, there can't be a LessDerived cycle, since we break type hierarchy cycles during
244245
// symbol table construction.)
245246

246-
if (HadAmbiguousWorseMethods(binder.Compilation, diagnostics, symbols, location, queryClause != null, receiver, name))
247+
if (HadAmbiguousWorseMethods(binder.Compilation, diagnostics, symbols, location, queryClause != null, receiver, name, isExtension))
247248
{
248249
return;
249250
}
@@ -774,10 +775,22 @@ private bool TypeInferenceFailed(
774775
}
775776
else
776777
{
777-
diagnostics.Add(new DiagnosticInfoWithSymbols(
778-
ErrorCode.ERR_NoSuchMemberOrExtension,
779-
new object[] { instanceArgument.Type, inferenceFailed.Member.Name },
780-
symbols), location);
778+
if (inferenceFailed.Member.Kind == SymbolKind.Method)
779+
{
780+
// error CS0411: The type arguments for method 'M<T>(T)' cannot be inferred
781+
// from the usage. Try specifying the type arguments explicitly.
782+
diagnostics.Add(new DiagnosticInfoWithSymbols(
783+
ErrorCode.ERR_CantInferMethTypeArgs,
784+
new object[] { inferenceFailed.Member },
785+
symbols), location);
786+
}
787+
else
788+
{
789+
diagnostics.Add(new DiagnosticInfoWithSymbols(
790+
ErrorCode.ERR_NoSuchMemberOrExtension,
791+
new object[] { instanceArgument.Type, inferenceFailed.Member.Name },
792+
symbols), location);
793+
}
781794
}
782795

783796
return true;
@@ -1388,7 +1401,7 @@ static Symbol unwrapIfParamsCollection(MemberResolutionResult<TMember> badArg, P
13881401
}
13891402
}
13901403

1391-
private bool HadAmbiguousWorseMethods(CSharpCompilation compilation, BindingDiagnosticBag diagnostics, ImmutableArray<Symbol> symbols, Location location, bool isQuery, BoundExpression receiver, string name)
1404+
private bool HadAmbiguousWorseMethods(CSharpCompilation compilation, BindingDiagnosticBag diagnostics, ImmutableArray<Symbol> symbols, Location location, bool isQuery, BoundExpression receiver, string name, bool isExtension)
13921405
{
13931406
MemberResolutionResult<TMember> worseResult1;
13941407
MemberResolutionResult<TMember> worseResult2;
@@ -1417,7 +1430,8 @@ private bool HadAmbiguousWorseMethods(CSharpCompilation compilation, BindingDiag
14171430
compilation,
14181431
worseResult1.LeastOverriddenMember.ConstructedFrom(),
14191432
worseResult2.LeastOverriddenMember.ConstructedFrom(),
1420-
symbols),
1433+
symbols,
1434+
isExtension),
14211435
location);
14221436
}
14231437

@@ -1453,7 +1467,7 @@ private int TryGetFirstTwoWorseResults(out MemberResolutionResult<TMember> first
14531467
return count;
14541468
}
14551469

1456-
private bool HadAmbiguousBestMethods(CSharpCompilation compilation, BindingDiagnosticBag diagnostics, ImmutableArray<Symbol> symbols, Location location)
1470+
private bool HadAmbiguousBestMethods(CSharpCompilation compilation, BindingDiagnosticBag diagnostics, ImmutableArray<Symbol> symbols, Location location, bool isExtension)
14571471
{
14581472
MemberResolutionResult<TMember> validResult1;
14591473
MemberResolutionResult<TMember> validResult2;
@@ -1473,7 +1487,8 @@ private bool HadAmbiguousBestMethods(CSharpCompilation compilation, BindingDiagn
14731487
compilation,
14741488
validResult1.LeastOverriddenMember.ConstructedFrom(),
14751489
validResult2.LeastOverriddenMember.ConstructedFrom(),
1476-
symbols),
1490+
symbols,
1491+
isExtension),
14771492
location);
14781493

14791494
return true;
@@ -1508,10 +1523,13 @@ private int TryGetFirstTwoValidResults(out MemberResolutionResult<TMember> first
15081523
return count;
15091524
}
15101525

1511-
private static DiagnosticInfoWithSymbols CreateAmbiguousCallDiagnosticInfo(CSharpCompilation compilation, Symbol first, Symbol second, ImmutableArray<Symbol> symbols)
1526+
internal static DiagnosticInfoWithSymbols CreateAmbiguousCallDiagnosticInfo(CSharpCompilation compilation, Symbol first, Symbol second, ImmutableArray<Symbol> symbols, bool isExtension)
15121527
{
1528+
// error: The extension resolution is ambiguous between the following members: 'first' and 'second'
1529+
// OR
1530+
// error: The call is ambiguous between the following methods or properties: 'first' and 'second'
15131531
var distinguisher = new SymbolDistinguisher(compilation, first, second);
1514-
return new DiagnosticInfoWithSymbols(ErrorCode.ERR_AmbigCall, [distinguisher.First, distinguisher.Second], symbols);
1532+
return new DiagnosticInfoWithSymbols(isExtension ? ErrorCode.ERR_AmbigExtension : ErrorCode.ERR_AmbigCall, [distinguisher.First, distinguisher.Second], symbols);
15151533
}
15161534

15171535
[Conditional("DEBUG")]

src/Compilers/CSharp/Portable/CSharpResources.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8168,4 +8168,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
81688168
<data name="ERR_BadVisBaseType" xml:space="preserve">
81698169
<value>Inconsistent accessibility: type '{1}' is less accessible than class '{0}' </value>
81708170
</data>
8171+
<data name="ERR_AmbigExtension" xml:space="preserve">
8172+
<value>The extension resolution is ambiguous between the following members: '{0}' and '{1}'</value>
8173+
</data>
81718174
</root>

src/Compilers/CSharp/Portable/Errors/ErrorCode.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2435,6 +2435,7 @@ internal enum ErrorCode
24352435
HDN_RedundantPatternStackGuard = 9337,
24362436

24372437
ERR_BadVisBaseType = 9338,
2438+
ERR_AmbigExtension = 9339,
24382439

24392440
// Note: you will need to do the following after adding errors:
24402441
// 1) Update ErrorFacts.IsBuildOnlyDiagnostic (src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs)

src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2543,6 +2543,7 @@ or ErrorCode.HDN_RedundantPattern
25432543
or ErrorCode.WRN_RedundantPattern
25442544
or ErrorCode.HDN_RedundantPatternStackGuard
25452545
or ErrorCode.ERR_BadVisBaseType
2546+
or ErrorCode.ERR_AmbigExtension
25462547
=> false,
25472548
};
25482549
#pragma warning restore CS8524 // The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value.

src/Compilers/CSharp/Portable/Parser/LanguageParser.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4697,7 +4697,6 @@ private SeparatedSyntaxList<ParameterSyntax> ParseParameterList(
46974697
allowTrailingSeparator: false,
46984698
requireOneElement: forExtension, // For extension declarations, we require at least one receiver parameter
46994699
allowSemicolonAsSeparator: false);
4700-
// Tracked by https://github.com/dotnet/roslyn/issues/78830 : diagnostic quality, consider suppressing parsing diagnostics on extension parameters beyond the first one
47014700

47024701
_termState = saveTerm;
47034702
close = this.EatToken(closeKind);

src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,8 @@ private void VisitNamedTypeWithoutNullability(INamedTypeSymbol symbol)
294294

295295
//visit the enclosing type if the style requires it
296296
if (Format.TypeQualificationStyle == SymbolDisplayTypeQualificationStyle.NameAndContainingTypes ||
297-
Format.TypeQualificationStyle == SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces)
297+
Format.TypeQualificationStyle == SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces ||
298+
(Format.TypeQualificationStyle == SymbolDisplayTypeQualificationStyle.NameOnly && symbol.IsExtension))
298299
{
299300
if (IncludeNamedType(symbol.ContainingType))
300301
{

src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)