|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable disable
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
#if DEBUG
using System.Text;
#endif
namespace Microsoft.CodeAnalysis.CSharp
{
/// <summary>
/// Summarizes the results of an overload resolution analysis, as described in section 7.5 of
/// the language specification. Describes whether overload resolution succeeded, and which
/// method was selected if overload resolution succeeded, as well as detailed information about
/// each method that was considered.
/// </summary>
internal class OverloadResolutionResult<TMember> where TMember : Symbol
{
private MemberResolutionResult<TMember> _bestResult;
private ThreeState _bestResultState;
internal readonly ArrayBuilder<MemberResolutionResult<TMember>> ResultsBuilder;
// Create an overload resolution result from a single result.
internal OverloadResolutionResult()
{
this.ResultsBuilder = new ArrayBuilder<MemberResolutionResult<TMember>>();
}
internal void Clear()
{
_bestResult = default(MemberResolutionResult<TMember>);
_bestResultState = ThreeState.Unknown;
this.ResultsBuilder.Clear();
}
/// <summary>
/// True if overload resolution successfully selected a single best method.
/// </summary>
public bool Succeeded
{
get
{
EnsureBestResultLoaded();
return _bestResultState == ThreeState.True && _bestResult.Result.IsValid;
}
}
/// <summary>
/// If overload resolution successfully selected a single best method, returns information
/// about that method. Otherwise returns null.
/// </summary>
public MemberResolutionResult<TMember> ValidResult
{
get
{
EnsureBestResultLoaded();
Debug.Assert(_bestResultState == ThreeState.True && _bestResult.Result.IsValid);
return _bestResult;
}
}
private void EnsureBestResultLoaded()
{
if (!_bestResultState.HasValue())
{
_bestResultState = TryGetBestResult(this.ResultsBuilder, out _bestResult);
}
}
/// <summary>
/// If there was a method that overload resolution considered better than all others,
/// returns information about that method. A method may be returned even if that method was
/// not considered a successful overload resolution, as long as it was better that any other
/// potential method considered.
/// </summary>
public MemberResolutionResult<TMember> BestResult
{
get
{
EnsureBestResultLoaded();
Debug.Assert(_bestResultState == ThreeState.True);
return _bestResult;
}
}
/// <summary>
/// Returns information about each method that was considered during overload resolution,
/// and what the results of overload resolution were for that method.
/// </summary>
public ImmutableArray<MemberResolutionResult<TMember>> Results
{
get
{
return this.ResultsBuilder.ToImmutable();
}
}
/// <summary>
/// Returns true if one or more of the members in the group are applicable. (Note that
/// Succeeded implies IsApplicable but IsApplicable does not imply Succeeded. It is possible
/// that no applicable member was better than all others.)
/// </summary>
internal bool HasAnyApplicableMember
{
get
{
foreach (var res in this.ResultsBuilder)
{
if (res.Result.IsApplicable)
{
return true;
}
}
return false;
}
}
/// <summary>
/// Returns all methods in the group that are applicable, <see cref="HasAnyApplicableMember"/>.
/// </summary>
internal ImmutableArray<TMember> GetAllApplicableMembers()
{
var result = ArrayBuilder<TMember>.GetInstance();
foreach (var res in this.ResultsBuilder)
{
if (res.Result.IsApplicable)
{
result.Add(res.Member);
}
}
return result.ToImmutableAndFree();
}
private static ThreeState TryGetBestResult(ArrayBuilder<MemberResolutionResult<TMember>> allResults, out MemberResolutionResult<TMember> best)
{
best = default(MemberResolutionResult<TMember>);
ThreeState haveBest = ThreeState.False;
foreach (var pair in allResults)
{
if (pair.Result.IsValid)
{
if (haveBest == ThreeState.True)
{
Debug.Assert(false, "How did we manage to get two methods in the overload resolution results that were both better than every other method?");
best = default(MemberResolutionResult<TMember>);
return ThreeState.False;
}
haveBest = ThreeState.True;
best = pair;
}
}
// TODO: There might be a situation in which there were no valid results but we still want to identify a "best of a bad lot" result for
// TODO: error reporting.
return haveBest;
}
/// <summary>
/// Called when overload resolution has failed. Figures out the best way to describe what went wrong.
/// </summary>
/// <remarks>
/// Overload resolution (effectively) starts out assuming that all candidates are valid and then
/// gradually disqualifies them. Therefore, our strategy will be to perform our checks in the
/// reverse order - the farther a candidate got through the process without being flagged, the
/// "better" it was.
///
/// Note that "final validation" is performed after overload resolution,
/// so final validation errors are not seen here. Final validation errors include
/// violations of constraints on method type parameters, static/instance mismatches,
/// and so on.
/// </remarks>
internal void ReportDiagnostics<T>(
Binder binder,
Location location,
SyntaxNode nodeOpt,
BindingDiagnosticBag diagnostics,
string name,
BoundExpression receiver,
SyntaxNode invokedExpression,
AnalyzedArguments arguments,
ImmutableArray<T> memberGroup, // the T is just a convenience for the caller
NamedTypeSymbol typeContainingConstructor,
NamedTypeSymbol delegateTypeBeingInvoked,
CSharpSyntaxNode queryClause = null,
bool isMethodGroupConversion = false,
RefKind? returnRefKind = null,
TypeSymbol delegateOrFunctionPointerType = null,
bool isParamsModifierValidation = false) where T : Symbol
{
Debug.Assert(!this.Succeeded, "Don't ask for diagnostic info on a successful overload resolution result.");
// Each argument must have non-null Display in case it is used in a diagnostic.
Debug.Assert(arguments.Arguments.All(a => a.Display != null));
// This kind is only used for default(MemberResolutionResult<T>), so we should never see it in
// the candidate list.
AssertNone(MemberResolutionKind.None);
var symbols = StaticCast<Symbol>.From(memberGroup);
//// PHASE 1: Valid candidates ////
// Since we're here, we know that there isn't exactly one applicable candidate. There may,
// however, be more than one. We'll check for that first, since applicable candidates are
// always better than inapplicable candidates.
if (HadAmbiguousBestMethods(diagnostics, symbols, location))
{
return;
}
// Since we didn't return, we know that there aren't two or more applicable candidates.
// From above, we know there isn't exactly one either. Therefore, there must not be any
// applicable candidates.
AssertNone(MemberResolutionKind.ApplicableInNormalForm);
AssertNone(MemberResolutionKind.ApplicableInExpandedForm);
// There are two ways that otherwise-applicable candidates can be ruled out by overload resolution:
// a) there is another applicable candidate that is strictly better, or
// b) there is another applicable candidate from a more derived type.
// There can't be exactly one such candidate, since that would the existence of some better
// applicable candidate, which would have either won or been detected above. It is possible,
// however, that there are multiple candidates that are worse than each other in a cycle.
// This might sound like a paradox, but it is in fact possible. Because there are
// intransitivities in convertibility (where A-->B, B-->C and C-->A but none of the
// opposite conversions are legal) there are also intransitivities in betterness.
// (Obviously, there can't be a LessDerived cycle, since we break type hierarchy cycles during
// symbol table construction.)
if (HadAmbiguousWorseMethods(diagnostics, symbols, location, queryClause != null, receiver, name))
{
return;
}
// Since we didn't return, we know that there aren't two or "worse" candidates. As above,
// there also can't be a single one. Therefore, there are none.
AssertNone(MemberResolutionKind.Worse);
//// PHASE 2: Applicability failures ////
// Overload resolution performed these checks just before weeding out less-derived and worse candidates.
// If we got as far as converting a lambda to a delegate type, and we failed to
// do so, then odds are extremely good that the failure is the ultimate cause
// of the overload resolution failing to find any applicable method. Report
// the errors out of each lambda argument, if there were any.
// NOTE: There isn't a MemberResolutionKind for this error condition.
if (HadLambdaConversionError(diagnostics, arguments))
{
return;
}
// If there is any instance(or alternatively static) method accessed through a
// type(or alternatively expression) then the first such method is the best bad method.
// To retain existing behavior, we use the location of the invoked expression for the error.
if (HadStaticInstanceMismatch(diagnostics, symbols, invokedExpression?.GetLocation() ?? location, binder, receiver, nodeOpt, delegateOrFunctionPointerType))
{
return;
}
// When overload resolution is being done to resolve a method group conversion (to a delegate type),
// if there is any method being converted to a delegate type, but the method's return
// ref kind does not match the delegate, then the first such method is the best bad method.
// Otherwise if there is any method whose return type does not match the delegate, then the
// first such method is the best bad method
if (isMethodGroupConversion && returnRefKind != null &&
HadReturnMismatch(location, diagnostics, delegateOrFunctionPointerType))
{
return;
}
// Otherwise, if there is any such method where type inference succeeded but inferred
// type arguments that violate the constraints on the method, then the first such method is
// the best bad method.
if (HadConstraintFailure(location, diagnostics))
{
return;
}
// Since we didn't return...
AssertNone(MemberResolutionKind.ConstraintFailure);
// If there's a less-derived candidate, it must be less derived than some applicable or
// "worse" candidate. Since there are none of those, there must not be any less-derived
// candidates either.
AssertNone(MemberResolutionKind.LessDerived);
// Otherwise, if there is any such method that has a bad argument conversion or out/ref mismatch
// then the first such method found is the best bad method.
if (HadBadArguments(diagnostics, binder, name, receiver, arguments, symbols, location, binder.Flags, isMethodGroupConversion))
{
return;
}
// Since we didn't return...
AssertNone(MemberResolutionKind.BadArgumentConversion);
// Otherwise, if there is any such method where type inference succeeded but inferred
// a parameter type that violates its own constraints then the first such method is
// the best bad method.
if (HadConstructedParameterFailedConstraintCheck(binder.Conversions, binder.Compilation, diagnostics, location))
{
return;
}
// Since we didn't return...
AssertNone(MemberResolutionKind.ConstructedParameterFailedConstraintCheck);
// Otherwise, if there is any such method where type inference succeeded but inferred
// an inaccessible type then the first such method found is the best bad method.
if (InaccessibleTypeArgument(diagnostics, symbols, location))
{
return;
}
// Since we didn't return...
AssertNone(MemberResolutionKind.InaccessibleTypeArgument);
// Otherwise, if there is any such method where type inference failed then the
// first such method is the best bad method.
if (TypeInferenceFailed(binder, diagnostics, symbols, receiver, arguments, location, queryClause))
{
return;
}
// Since we didn't return...
AssertNone(MemberResolutionKind.TypeInferenceFailed);
AssertNone(MemberResolutionKind.TypeInferenceExtensionInstanceArgument);
//// PHASE 3: Use site errors ////
// Overload resolution checks for use site errors between argument analysis and applicability testing.
// Otherwise, if there is any such method that cannot be used because it is
// in an unreferenced assembly then the first such method is the best bad method.
if (UseSiteError())
{
return;
}
// Since we didn't return...
AssertNone(MemberResolutionKind.UseSiteError);
//// PHASE 4: Argument analysis failures and unsupported metadata ////
// The first to checks in overload resolution are for unsupported metadata (Symbol.HasUnsupportedMetadata)
// and argument analysis. We don't want to report unsupported metadata unless nothing else went wrong -
// otherwise we'd report errors about losing candidates, effectively "pulling in" unnecessary assemblies.
bool supportedRequiredParameterMissingConflicts = false;
MemberResolutionResult<TMember> firstSupported = default(MemberResolutionResult<TMember>);
MemberResolutionResult<TMember> firstUnsupported = default(MemberResolutionResult<TMember>);
var supportedInPriorityOrder = new MemberResolutionResult<TMember>[7]; // from highest to lowest priority
const int duplicateNamedArgumentPriority = 0;
const int requiredParameterMissingPriority = 1;
const int nameUsedForPositionalPriority = 2;
const int noCorrespondingNamedParameterPriority = 3;
const int noCorrespondingParameterPriority = 4;
const int badNonTrailingNamedArgumentPriority = 5;
const int wrongCallingConventionPriority = 6;
foreach (MemberResolutionResult<TMember> result in this.ResultsBuilder)
{
switch (result.Result.Kind)
{
case MemberResolutionKind.UnsupportedMetadata:
if (firstSupported.IsNull)
{
firstUnsupported = result;
}
break;
case MemberResolutionKind.NoCorrespondingNamedParameter:
if (supportedInPriorityOrder[noCorrespondingNamedParameterPriority].IsNull ||
result.Result.FirstBadArgument > supportedInPriorityOrder[noCorrespondingNamedParameterPriority].Result.FirstBadArgument)
{
supportedInPriorityOrder[noCorrespondingNamedParameterPriority] = result;
}
break;
case MemberResolutionKind.NoCorrespondingParameter:
if (supportedInPriorityOrder[noCorrespondingParameterPriority].IsNull)
{
supportedInPriorityOrder[noCorrespondingParameterPriority] = result;
}
break;
case MemberResolutionKind.RequiredParameterMissing:
if (supportedInPriorityOrder[requiredParameterMissingPriority].IsNull)
{
Debug.Assert(!supportedRequiredParameterMissingConflicts);
supportedInPriorityOrder[requiredParameterMissingPriority] = result;
}
else
{
supportedRequiredParameterMissingConflicts = true;
}
break;
case MemberResolutionKind.NameUsedForPositional:
if (supportedInPriorityOrder[nameUsedForPositionalPriority].IsNull ||
result.Result.FirstBadArgument > supportedInPriorityOrder[nameUsedForPositionalPriority].Result.FirstBadArgument)
{
supportedInPriorityOrder[nameUsedForPositionalPriority] = result;
}
break;
case MemberResolutionKind.BadNonTrailingNamedArgument:
if (supportedInPriorityOrder[badNonTrailingNamedArgumentPriority].IsNull ||
result.Result.FirstBadArgument > supportedInPriorityOrder[badNonTrailingNamedArgumentPriority].Result.FirstBadArgument)
{
supportedInPriorityOrder[badNonTrailingNamedArgumentPriority] = result;
}
break;
case MemberResolutionKind.DuplicateNamedArgument:
{
if (supportedInPriorityOrder[duplicateNamedArgumentPriority].IsNull ||
result.Result.FirstBadArgument > supportedInPriorityOrder[duplicateNamedArgumentPriority].Result.FirstBadArgument)
{
supportedInPriorityOrder[duplicateNamedArgumentPriority] = result;
}
}
break;
case MemberResolutionKind.WrongCallingConvention:
{
if (supportedInPriorityOrder[wrongCallingConventionPriority].IsNull)
{
supportedInPriorityOrder[wrongCallingConventionPriority] = result;
}
}
break;
default:
// Based on the asserts above, we know that only the kinds above
// are possible at this point. This should only throw if a new
// kind is added without appropriate checking above.
throw ExceptionUtilities.UnexpectedValue(result.Result.Kind);
}
}
foreach (var supported in supportedInPriorityOrder)
{
if (supported.IsNotNull)
{
firstSupported = supported;
break;
}
}
// If there are any supported candidates, we don't care about unsupported candidates.
if (firstSupported.IsNotNull)
{
if (firstSupported.Member is FunctionPointerMethodSymbol
&& firstSupported.Result.Kind == MemberResolutionKind.NoCorrespondingNamedParameter)
{
int badArg = firstSupported.Result.FirstBadArgument;
Debug.Assert(arguments.Names[badArg].HasValue);
Location badName = arguments.Names[badArg].GetValueOrDefault().Location;
diagnostics.Add(ErrorCode.ERR_FunctionPointersCannotBeCalledWithNamedArguments, badName);
return;
}
// If there are multiple supported candidates, we don't have a good way to choose the best
// one so we report a general diagnostic (below).
else if (!(firstSupported.Result.Kind == MemberResolutionKind.RequiredParameterMissing && supportedRequiredParameterMissingConflicts)
&& !isMethodGroupConversion
// Function pointer type symbols don't have named parameters, so we just want to report a general mismatched parameter
// count instead of name errors.
&& (firstSupported.Member is not FunctionPointerMethodSymbol))
{
switch (firstSupported.Result.Kind)
{
// Otherwise, if there is any such method that has a named argument and a positional
// argument for the same parameter then the first such method is the best bad method.
case MemberResolutionKind.NameUsedForPositional:
ReportNameUsedForPositional(firstSupported, diagnostics, arguments, symbols);
return;
// Otherwise, if there is any such method that has a named argument that corresponds
// to no parameter then the first such method is the best bad method.
case MemberResolutionKind.NoCorrespondingNamedParameter:
ReportNoCorrespondingNamedParameter(firstSupported, name, diagnostics, arguments, delegateTypeBeingInvoked, symbols);
return;
// Otherwise, if there is any such method that has a required parameter
// but no argument was supplied for it then the first such method is
// the best bad method.
case MemberResolutionKind.RequiredParameterMissing:
if ((binder.Flags & BinderFlags.CollectionExpressionConversionValidation) != 0)
{
if (receiver is null)
{
Debug.Assert(firstSupported.Member is MethodSymbol { MethodKind: MethodKind.Constructor });
diagnostics.Add(
isParamsModifierValidation ?
ErrorCode.ERR_ParamsCollectionMissingConstructor :
ErrorCode.ERR_CollectionExpressionMissingConstructor,
location);
}
else
{
Debug.Assert(firstSupported.Member is MethodSymbol { Name: "Add" });
diagnostics.Add(ErrorCode.ERR_CollectionExpressionMissingAdd, location, receiver.Type);
}
}
else
{
// CONSIDER: for consistency with dev12, we would goto default except in omitted ref cases.
ReportMissingRequiredParameter(firstSupported, diagnostics, delegateTypeBeingInvoked, symbols, location);
}
return;
// NOTE: For some reason, there is no specific handling for this result kind.
case MemberResolutionKind.NoCorrespondingParameter:
break;
// Otherwise, if there is any such method that has a named argument was used out-of-position
// and followed by unnamed arguments.
case MemberResolutionKind.BadNonTrailingNamedArgument:
ReportBadNonTrailingNamedArgument(firstSupported, diagnostics, arguments, symbols);
return;
case MemberResolutionKind.DuplicateNamedArgument:
ReportDuplicateNamedArgument(firstSupported, diagnostics, arguments);
return;
}
}
else if (firstSupported.Result.Kind == MemberResolutionKind.WrongCallingConvention)
{
ReportWrongCallingConvention(location, diagnostics, symbols, firstSupported, ((FunctionPointerTypeSymbol)delegateOrFunctionPointerType).Signature);
return;
}
}
else if (firstUnsupported.IsNotNull)
{
// Otherwise, if there is any such method that cannot be used because it is
// unsupported by the language then the first such method is the best bad method.
// This is the first kind of problem overload resolution checks for, so it should
// be the last MemberResolutionKind we check for. Candidates with this kind
// failed the soonest.
// CONSIDER: report his on every unsupported candidate?
ReportUnsupportedMetadata(location, diagnostics, symbols, firstUnsupported);
return;
}
// If the user provided a number of arguments that works for no possible method in the method
// group then we give an error saying that. Every method will have an error of the form
// "missing required parameter" or "argument corresponds to no parameter", and therefore we
// have no way of choosing a "best bad method" to report the error on. We should simply
// say that no possible method can take the given number of arguments.
// CAVEAT: For method group conversions, the caller reports a different diagnostics.
if (!isMethodGroupConversion)
{
ReportBadParameterCount(diagnostics, name, arguments, symbols, location, typeContainingConstructor, delegateTypeBeingInvoked);
}
}
private static void ReportUnsupportedMetadata(Location location, BindingDiagnosticBag diagnostics, ImmutableArray<Symbol> symbols, MemberResolutionResult<TMember> firstUnsupported)
{
DiagnosticInfo diagInfo = firstUnsupported.Member.GetUseSiteInfo().DiagnosticInfo;
Debug.Assert(diagInfo != null);
Debug.Assert(diagInfo.Severity == DiagnosticSeverity.Error);
// Attach symbols to the diagnostic info.
diagInfo = new DiagnosticInfoWithSymbols(
(ErrorCode)diagInfo.Code,
diagInfo.Arguments,
symbols);
Symbol.ReportUseSiteDiagnostic(diagInfo, diagnostics, location);
}
private static void ReportWrongCallingConvention(Location location, BindingDiagnosticBag diagnostics, ImmutableArray<Symbol> symbols, MemberResolutionResult<TMember> firstSupported, MethodSymbol target)
{
Debug.Assert(firstSupported.Result.Kind == MemberResolutionKind.WrongCallingConvention);
diagnostics.Add(new DiagnosticInfoWithSymbols(
ErrorCode.ERR_WrongFuncPtrCallingConvention,
new object[] { firstSupported.Member, target.CallingConvention },
symbols), location);
}
private bool UseSiteError()
{
var bad = GetFirstMemberKind(MemberResolutionKind.UseSiteError);
if (bad.IsNull)
{
return false;
}
Debug.Assert(bad.Member.GetUseSiteInfo().DiagnosticInfo.Severity == DiagnosticSeverity.Error,
"Why did we use MemberResolutionKind.UseSiteError if we didn't have a use site error?");
// Use site errors are reported unconditionally in PerformMemberOverloadResolution/PerformObjectCreationOverloadResolution.
return true;
}
private bool InaccessibleTypeArgument(
BindingDiagnosticBag diagnostics,
ImmutableArray<Symbol> symbols,
Location location)
{
var inaccessible = GetFirstMemberKind(MemberResolutionKind.InaccessibleTypeArgument);
if (inaccessible.IsNull)
{
return false;
}
// error CS0122: 'M<X>(I<X>)' is inaccessible due to its protection level
diagnostics.Add(new DiagnosticInfoWithSymbols(
ErrorCode.ERR_BadAccess,
new object[] { inaccessible.Member },
symbols), location);
return true;
}
private bool HadStaticInstanceMismatch(
BindingDiagnosticBag diagnostics,
ImmutableArray<Symbol> symbols,
Location location,
Binder binder,
BoundExpression receiverOpt,
SyntaxNode nodeOpt,
TypeSymbol delegateOrFunctionPointerType)
{
var staticInstanceMismatch = GetFirstMemberKind(MemberResolutionKind.StaticInstanceMismatch);
if (staticInstanceMismatch.IsNull)
{
return false;
}
if (receiverOpt?.HasErrors != true)
{
Symbol symbol = staticInstanceMismatch.Member;
// Certain compiler-generated invocations produce custom diagnostics.
if (receiverOpt?.Kind == BoundKind.QueryClause)
{
// Could not find an implementation of the query pattern for source type '{0}'. '{1}' not found.
diagnostics.Add(ErrorCode.ERR_QueryNoProvider, location, receiverOpt.Type, symbol.Name);
}
else if (binder.Flags.Includes(BinderFlags.CollectionInitializerAddMethod))
{
diagnostics.Add(ErrorCode.ERR_InitializerAddHasWrongSignature, location, symbol);
}
else if (nodeOpt?.Kind() == SyntaxKind.AwaitExpression && symbol.Name == WellKnownMemberNames.GetAwaiter)
{
diagnostics.Add(ErrorCode.ERR_BadAwaitArg, location, receiverOpt.Type);
}
else if (delegateOrFunctionPointerType is FunctionPointerTypeSymbol)
{
diagnostics.Add(ErrorCode.ERR_FuncPtrMethMustBeStatic, location, symbol);
}
else
{
ErrorCode errorCode =
symbol.RequiresInstanceReceiver()
? Binder.WasImplicitReceiver(receiverOpt) && binder.InFieldInitializer && !binder.BindingTopLevelScriptCode
? ErrorCode.ERR_FieldInitRefNonstatic
: ErrorCode.ERR_ObjectRequired
: ErrorCode.ERR_ObjectProhibited;
// error CS0176: Member 'Program.M(B)' cannot be accessed with an instance reference; qualify it with a type name instead
// -or-
// error CS0120: An object reference is required for the non-static field, method, or property 'Program.M(B)'
diagnostics.Add(new DiagnosticInfoWithSymbols(
errorCode,
new object[] { symbol },
symbols), location);
}
}
return true;
}
private bool HadReturnMismatch(Location location, BindingDiagnosticBag diagnostics, TypeSymbol delegateOrFunctionPointerType)
{
var mismatch = GetFirstMemberKind(MemberResolutionKind.WrongRefKind);
if (!mismatch.IsNull)
{
diagnostics.Add(delegateOrFunctionPointerType.IsFunctionPointer() ? ErrorCode.ERR_FuncPtrRefMismatch : ErrorCode.ERR_DelegateRefMismatch,
location, mismatch.Member, delegateOrFunctionPointerType);
return true;
}
mismatch = GetFirstMemberKind(MemberResolutionKind.WrongReturnType);
if (!mismatch.IsNull)
{
var method = (MethodSymbol)(Symbol)mismatch.Member;
diagnostics.Add(ErrorCode.ERR_BadRetType, location, method, method.ReturnType);
return true;
}
return false;
}
private bool HadConstraintFailure(Location location, BindingDiagnosticBag diagnostics)
{
var constraintFailure = GetFirstMemberKind(MemberResolutionKind.ConstraintFailure);
if (constraintFailure.IsNull)
{
return false;
}
foreach (var pair in constraintFailure.Result.ConstraintFailureDiagnostics)
{
if (pair.UseSiteInfo.DiagnosticInfo is object)
{
diagnostics.Add(new CSDiagnostic(pair.UseSiteInfo.DiagnosticInfo, location));
}
}
return true;
}
private bool TypeInferenceFailed(
Binder binder,
BindingDiagnosticBag diagnostics,
ImmutableArray<Symbol> symbols,
BoundExpression receiver,
AnalyzedArguments arguments,
Location location,
CSharpSyntaxNode queryClause = null)
{
var inferenceFailed = GetFirstMemberKind(MemberResolutionKind.TypeInferenceFailed);
if (inferenceFailed.IsNotNull)
{
if (queryClause != null)
{
Binder.ReportQueryInferenceFailed(queryClause, inferenceFailed.Member.Name, receiver, arguments, symbols, diagnostics);
}
else
{
// error CS0411: The type arguments for method 'M<T>(T)' cannot be inferred
// from the usage. Try specifying the type arguments explicitly.
diagnostics.Add(new DiagnosticInfoWithSymbols(
ErrorCode.ERR_CantInferMethTypeArgs,
new object[] { inferenceFailed.Member },
symbols), location);
}
return true;
}
inferenceFailed = GetFirstMemberKind(MemberResolutionKind.TypeInferenceExtensionInstanceArgument);
if (inferenceFailed.IsNotNull)
{
Debug.Assert(arguments.Arguments.Count > 0);
var instanceArgument = arguments.Arguments[0];
if (queryClause != null)
{
binder.ReportQueryLookupFailed(queryClause, instanceArgument, inferenceFailed.Member.Name, symbols, diagnostics);
}
else
{
diagnostics.Add(new DiagnosticInfoWithSymbols(
ErrorCode.ERR_NoSuchMemberOrExtension,
new object[] { instanceArgument.Type, inferenceFailed.Member.Name },
symbols), location);
}
return true;
}
return false;
}
private static void ReportNameUsedForPositional(
MemberResolutionResult<TMember> bad,
BindingDiagnosticBag diagnostics,
AnalyzedArguments arguments,
ImmutableArray<Symbol> symbols)
{
int badArg = bad.Result.FirstBadArgument;
// We would not have gotten this error had there not been a named argument.
Debug.Assert(arguments.Names.Count > badArg);
Debug.Assert(arguments.Names[badArg].HasValue);
(string badName, Location location) = arguments.Names[badArg].GetValueOrDefault();
Debug.Assert(badName != null);
// Named argument 'x' specifies a parameter for which a positional argument has already been given
diagnostics.Add(new DiagnosticInfoWithSymbols(
ErrorCode.ERR_NamedArgumentUsedInPositional,
new object[] { badName },
symbols), location);
}
private static void ReportBadNonTrailingNamedArgument(
MemberResolutionResult<TMember> bad,
BindingDiagnosticBag diagnostics,
AnalyzedArguments arguments,
ImmutableArray<Symbol> symbols)
{
int badArg = bad.Result.FirstBadArgument;
// We would not have gotten this error had there not been a named argument.
Debug.Assert(arguments.Names.Count > badArg);
Debug.Assert(arguments.Names[badArg].HasValue);
(string badName, Location location) = arguments.Names[badArg].GetValueOrDefault();
Debug.Assert(badName != null);
// Named argument 'x' is used out-of-position but is followed by an unnamed argument.
diagnostics.Add(new DiagnosticInfoWithSymbols(
ErrorCode.ERR_BadNonTrailingNamedArgument,
new object[] { badName },
symbols), location);
}
private static void ReportDuplicateNamedArgument(MemberResolutionResult<TMember> result, BindingDiagnosticBag diagnostics, AnalyzedArguments arguments)
{
Debug.Assert(result.Result.BadArgumentsOpt.TrueBits().Count() == 1);
Debug.Assert(arguments.Names[result.Result.FirstBadArgument].HasValue);
(string name, Location location) = arguments.Names[result.Result.FirstBadArgument].GetValueOrDefault();
Debug.Assert(name != null);
// CS: Named argument '{0}' cannot be specified multiple times
diagnostics.Add(new CSDiagnosticInfo(ErrorCode.ERR_DuplicateNamedArgument, name), location);
}
private static void ReportNoCorrespondingNamedParameter(
MemberResolutionResult<TMember> bad,
string methodName,
BindingDiagnosticBag diagnostics,
AnalyzedArguments arguments,
NamedTypeSymbol delegateTypeBeingInvoked,
ImmutableArray<Symbol> symbols)
{
// We know that there is at least one method that had a number of arguments
// passed that was valid for *some* method in the candidate set. Given that
// fact, we seek the *best* method in the candidate set to report the error
// on. If we have a method that has a valid number of arguments, but the
// call was inapplicable because there was a bad name, that's a candidate
// for the "best" overload.
int badArg = bad.Result.FirstBadArgument;
// We would not have gotten this error had there not been a named argument.
Debug.Assert(arguments.Names.Count > badArg);
Debug.Assert(arguments.Names[badArg].HasValue);
(string badName, Location location) = arguments.Names[badArg].GetValueOrDefault();
Debug.Assert(badName != null);
// error CS1739: The best overload for 'M' does not have a parameter named 'x'
// Error CS1746: The delegate 'D' does not have a parameter named 'x'
ErrorCode code = (object)delegateTypeBeingInvoked != null ?
ErrorCode.ERR_BadNamedArgumentForDelegateInvoke :
ErrorCode.ERR_BadNamedArgument;
object obj = (object)delegateTypeBeingInvoked ?? methodName;
diagnostics.Add(new DiagnosticInfoWithSymbols(
code,
new object[] { obj, badName },
symbols), location);
}
private static void ReportMissingRequiredParameter(
MemberResolutionResult<TMember> bad,
BindingDiagnosticBag diagnostics,
NamedTypeSymbol delegateTypeBeingInvoked,
ImmutableArray<Symbol> symbols,
Location location)
{
// We know that there is at least one method that had a number of arguments
// passed that was valid for *some* method in the candidate set. Given that
// fact, we seek the *best* method in the candidate set to report the error
// on. If we have a method that has a valid number of arguments, but the
// call was inapplicable because a required parameter does not have a
// corresponding argument then that's a candidate for the "best" overload.
//
// For example, you might have M(int x, int y, int z = 3) and a call
// M(1, z:4) -- the error cannot be "no overload of M takes 2 arguments"
// because M does take two arguments; M(1, 2) would be legal. The
// error instead has to be that there was no argument corresponding
// to required formal parameter 'y'.
TMember badMember = bad.Member;
ImmutableArray<ParameterSymbol> parameters = badMember.GetParameters();
int badParamIndex = bad.Result.BadParameter;
string badParamName;
if (badParamIndex == parameters.Length)
{
Debug.Assert(badMember.Kind == SymbolKind.Method);
Debug.Assert(((MethodSymbol)(object)badMember).IsVararg);
badParamName = SyntaxFacts.GetText(SyntaxKind.ArgListKeyword);
}
else
{
badParamName = parameters[badParamIndex].Name;
}
// There is no argument given that corresponds to the required parameter '{0}' of '{1}'
object obj = (object)delegateTypeBeingInvoked ?? badMember;
diagnostics.Add(new DiagnosticInfoWithSymbols(
ErrorCode.ERR_NoCorrespondingArgument,
new object[] { badParamName, obj },
symbols), location);
}
private static void ReportBadParameterCount(
BindingDiagnosticBag diagnostics,
string name,
AnalyzedArguments arguments,
ImmutableArray<Symbol> symbols,
Location location,
NamedTypeSymbol typeContainingConstructor,
NamedTypeSymbol delegateTypeBeingInvoked)
{
// error CS1501: No overload for method 'M' takes n arguments
// error CS1729: 'M' does not contain a constructor that takes n arguments
// error CS1593: Delegate 'M' does not take n arguments
// error CS8757: Function pointer 'M' does not take n arguments
FunctionPointerMethodSymbol functionPointerMethodBeingInvoked = symbols.IsDefault || symbols.Length != 1
? null
: symbols[0] as FunctionPointerMethodSymbol;
(ErrorCode code, object target) = (typeContainingConstructor, delegateTypeBeingInvoked, functionPointerMethodBeingInvoked) switch
{
(object t, _, _) => (ErrorCode.ERR_BadCtorArgCount, t),
(_, object t, _) => (ErrorCode.ERR_BadDelArgCount, t),
(_, _, object t) => (ErrorCode.ERR_BadFuncPointerArgCount, t),
_ => (ErrorCode.ERR_BadArgCount, name)
};
int argCount = arguments.Arguments.Count;
if (arguments.IsExtensionMethodInvocation)
{
argCount--;
}
diagnostics.Add(new DiagnosticInfoWithSymbols(
code,
new object[] { target, argCount },
symbols), location);
return;
}
private bool HadConstructedParameterFailedConstraintCheck(
ConversionsBase conversions,
CSharpCompilation compilation,
BindingDiagnosticBag diagnostics,
Location location)
{
// We know that there is at least one method that had a number of arguments
// passed that was valid for *some* method in the candidate set. Given that
// fact, we seek the *best* method in the candidate set to report the error
// on. If we have a generic method that has a valid number of arguments, but the
// call was inapplicable because a formal parameter type failed to meet its
// constraints, give an error.
//
// That could happen like this:
//
// void Q<T>(T t1, Nullable<T> t2) where T : struct
//
// Q("", null);
//
// Every required parameter has a corresponding argument. Type inference succeeds and infers
// that T is string. Each argument is convertible to the corresponding formal parameter type.
// What makes this a not-applicable candidate is not that the constraint on T is violated, but
// rather that the constraint on *Nullable<T>* is violated; Nullable<string> is not a legal
// type, and so this is not an applicable candidate.
//
// In language versions before the feature 'ImprovedOverloadCandidates' was added to the language,
// checking whether constraints are violated *on T in Q<T>* occurs *after* overload resolution
// successfully chooses a unique best method; but with the addition of the
// feature 'ImprovedOverloadCandidates', constraint checks on the method's own type arguments
// occurs during candidate selection.
//
// Note that this failure need not involve type inference; Q<string>(null, null) would also be
// illegal for the same reason.
//
// The question then arises as to what error to report here. The native compiler reports that
// the constraint is violated on the method, even though the fact that precipitates the
// failure of overload resolution to classify this as an applicable candidate is the constraint
// violation on Nullable<T>. Most of the time this is actually a pretty sensible error message;
// if you say Q<string>(...) then it seems reasonable to give an error that says that string is
// bad for Q, not that it is bad for its formal parameters under construction. Since the compiler
// will not allow Q<T> to be declared without a constraint that ensures that Nullable<T>'s
// constraints are met, typically a failure to provide a type argument that works for the
// formal parameter type will also be a failure for the method type parameter.
//
// However, there could be error recovery scenarios. Suppose instead we had said
//
// void Q<T>(T t1, Nullable<T> t2)
//
// with no constraint on T. We will give an error at declaration time, but if later we
// are asked to provide an analysis of Q<string>("", null), the right thing to do is NOT
// to say "constraint is violated on T in Q<T>" because there is no constraint to be
// violated here. The error is (1) that the constraint is violated on Nullable<T> and
// (2) that there is a constraint missing on Q<T>.
//
// Another error-recovery scenario in which the method's constraint is not violated:
//
// struct C<U> where U : struct {}
// ...
// void Q<T>(Nullable<T> nt) where T : struct {}
// ...
// Q<C<string>>(null);
//
// C<string> is clearly an error, but equally clearly it does not violate the constraint
// on T because it is a struct. If we attempt overload resolution then overload resolution
// will say that Q<C<string>> is not an applicable candidate because N<C<string>> is not
// a valid type. N is not the problem; C<string> is a struct. C<string> is the problem.
//
// See test case CS0310ERR_NewConstraintNotSatisfied02 for an even more complex version
// of this flavor of error recovery.
var result = GetFirstMemberKind(MemberResolutionKind.ConstructedParameterFailedConstraintCheck);
if (result.IsNull)
{
return false;
}
// We would not have gotten as far as type inference succeeding if the argument count
// was invalid.
// Normally a failure to meet constraints on a formal parameter type is also a failure
// to meet constraints on the method's type argument. See if that's the case; if it
// is, then just report that error.
MethodSymbol method = (MethodSymbol)(Symbol)result.Member;
if (!method.CheckConstraints(new ConstraintsHelper.CheckConstraintsArgs(compilation, conversions, includeNullability: false, location, diagnostics)))
{
// The error is already reported into the diagnostics bag.
return true;
}
// We are in the unusual position that a constraint has been violated on a formal parameter type
// without being violated on the method. Report that the constraint is violated on the
// formal parameter type.
TypeSymbol formalParameterType = method.GetParameterType(result.Result.BadParameter);
var boxedArgs = ConstraintsHelper.CheckConstraintsArgsBoxed.Allocate(compilation, conversions, includeNullability: false, location, diagnostics);
formalParameterType.CheckAllConstraints(boxedArgs);
boxedArgs.Free();
return true;
}
private static bool HadLambdaConversionError(BindingDiagnosticBag diagnostics, AnalyzedArguments arguments)
{
bool hadError = false;
foreach (var argument in arguments.Arguments)
{
if (argument.Kind == BoundKind.UnboundLambda)
{
hadError |= ((UnboundLambda)argument).GenerateSummaryErrors(diagnostics);
}
}
return hadError;
}
private bool HadBadArguments(
BindingDiagnosticBag diagnostics,
Binder binder,
string name,
BoundExpression receiver,
AnalyzedArguments arguments,
ImmutableArray<Symbol> symbols,
Location location,
BinderFlags flags,
bool isMethodGroupConversion)
{
var badArg = GetFirstMemberKind(MemberResolutionKind.BadArgumentConversion);
if (badArg.IsNull)
{
return false;
}
if (isMethodGroupConversion)
{
return true;
}
var method = badArg.Member;
// The best overloaded method match for '{0}' has some invalid arguments
// Since we have bad arguments to report, there is no need to report an error on the invocation itself.
//var di = new DiagnosticInfoWithSymbols(
// ErrorCode.ERR_BadArgTypes,
// new object[] { badArg.Method },
// symbols);
//
if (flags.Includes(BinderFlags.CollectionInitializerAddMethod))
{
// However, if we are binding the collection initializer Add method, we do want to generate
// ErrorCode.ERR_BadArgTypesForCollectionAdd or ErrorCode.ERR_InitializerAddHasParamModifiers
// as there is no explicit call to Add method.
int argumentOffset = arguments.IsExtensionMethodInvocation ? 1 : 0;
var parameters = method.GetParameters();
for (int i = argumentOffset; i < parameters.Length; i++)
{
if (parameters[i].RefKind != RefKind.None)
{
// The best overloaded method match '{0}' for the collection initializer element cannot be used. Collection initializer 'Add' methods cannot have ref or out parameters.
diagnostics.Add(ErrorCode.ERR_InitializerAddHasParamModifiers, location, symbols, method);
return true;
}
}
if (flags.Includes(BinderFlags.CollectionExpressionConversionValidation))
{
diagnostics.Add(ErrorCode.ERR_CollectionExpressionMissingAdd, location, receiver.Type);
}
else
{
// The best overloaded Add method '{0}' for the collection initializer has some invalid arguments
diagnostics.Add(ErrorCode.ERR_BadArgTypesForCollectionAdd, location, symbols, method);
}
}
foreach (var arg in badArg.Result.BadArgumentsOpt.TrueBits())
{
ReportBadArgumentError(diagnostics, binder, name, arguments, symbols, badArg, method, arg);
}
return true;
}
private static void ReportBadArgumentError(
BindingDiagnosticBag diagnostics,
Binder binder,
string name,
AnalyzedArguments arguments,
ImmutableArray<Symbol> symbols,
MemberResolutionResult<TMember> badArg,
TMember method,
int arg)
{
BoundExpression argument = arguments.Argument(arg);
if (argument.HasAnyErrors)
{
// If the argument had an error reported then do not report further errors for
// overload resolution failure.
return;
}
int parm = badArg.Result.ParameterFromArgument(arg);
SourceLocation sourceLocation = new SourceLocation(argument.Syntax);
// Early out: if the bad argument is an __arglist parameter then simply report that:
if (method.GetIsVararg() && parm == method.GetParameterCount())
{
// NOTE: No SymbolDistinguisher required, since one of the arguments is "__arglist".
// CS1503: Argument {0}: cannot convert from '{1}' to '{2}'
diagnostics.Add(
ErrorCode.ERR_BadArgType,
sourceLocation,
symbols,
arg + 1,
argument.Display,
"__arglist");
return;
}
ParameterSymbol parameter = method.GetParameters()[parm];
bool isLastParameter = method.GetParameterCount() == parm + 1; // This is used to later decide if we need to try to unwrap a params collection
RefKind refArg = arguments.RefKind(arg);
RefKind refParameter = parameter.RefKind;
if (arguments.IsExtensionMethodThisArgument(arg))
{
Debug.Assert(refArg == RefKind.None);
if (refParameter == RefKind.Ref || refParameter == RefKind.In)
{
// For ref and ref-readonly extension methods, we omit the "ref" modifier on receiver arguments.
// Setting the correct RefKind for finding the correct diagnostics message.
// For other ref kinds, keeping it as it is to find mismatch errors.
refArg = refParameter;
}
}
// If the expression is untyped because it is a lambda, anonymous method, method group or null
// then we never want to report the error "you need a ref on that thing". Rather, we want to
// say that you can't convert "null" to "ref int".
if (!argument.HasExpressionType() &&
argument.Kind != BoundKind.OutDeconstructVarPendingInference &&
argument.Kind != BoundKind.OutVariablePendingInference &&
argument.Kind != BoundKind.DiscardExpression)
{
TypeSymbol parameterType = unwrapIfParamsCollection(badArg, parameter, isLastParameter) is TypeSymbol t ? t : parameter.Type;
// If the problem is that a lambda isn't convertible to the given type, also report why.
// The argument and parameter type might match, but may not have same in/out modifiers
if (argument.Kind == BoundKind.UnboundLambda && refArg == refParameter)
{
((UnboundLambda)argument).GenerateAnonymousFunctionConversionError(diagnostics, parameterType);
}
else if (argument.Kind == BoundKind.MethodGroup && parameterType.TypeKind == TypeKind.Delegate &&
Conversions.ReportDelegateOrFunctionPointerMethodGroupDiagnostics(binder, (BoundMethodGroup)argument, parameterType, diagnostics))
{
// a diagnostic has been reported by ReportDelegateOrFunctionPointerMethodGroupDiagnostics
}
else if (argument.Kind == BoundKind.MethodGroup && parameterType.TypeKind == TypeKind.FunctionPointer)
{
diagnostics.Add(ErrorCode.ERR_MissingAddressOf, sourceLocation);
}
else if (argument.Kind == BoundKind.UnconvertedAddressOfOperator &&
Conversions.ReportDelegateOrFunctionPointerMethodGroupDiagnostics(binder, ((BoundUnconvertedAddressOfOperator)argument).Operand, parameterType, diagnostics))
{
// a diagnostic has been reported by ReportDelegateOrFunctionPointerMethodGroupDiagnostics
}
else if (argument.Kind == BoundKind.UnconvertedCollectionExpression)
{
binder.GenerateImplicitConversionErrorForCollectionExpression((BoundUnconvertedCollectionExpression)argument, parameterType, diagnostics);
}
else
{
// There's no symbol for the argument, so we don't need a SymbolDistinguisher.
// Argument 1: cannot convert from '<null>' to 'ref int'
diagnostics.Add(
ErrorCode.ERR_BadArgType,
sourceLocation,
symbols,
arg + 1,
argument.Display, //'<null>' doesn't need refkind
new FormattedSymbol(unwrapIfParamsCollection(badArg, parameter, isLastParameter), SymbolDisplayFormat.CSharpErrorMessageNoParameterNamesFormat));
}
}
else if (refArg != refParameter &&
!(refArg == RefKind.None && refParameter == RefKind.In) &&
!(refArg == RefKind.Ref && refParameter == RefKind.In && binder.Compilation.IsFeatureEnabled(MessageID.IDS_FeatureRefReadonlyParameters)) &&
!(refParameter == RefKind.RefReadOnlyParameter && refArg is RefKind.None or RefKind.Ref or RefKind.In))
{
// Special case for 'string literal -> interpolated string handler' for better user experience
// Skip if parameter's ref kind is 'out' since it is invalid ref kind for passing interpolated string
if (isStringLiteralToInterpolatedStringHandlerArgumentConversion(argument, parameter) &&
refParameter != RefKind.Out)
{
// CS9205: Expected interpolated string
diagnostics.Add(ErrorCode.ERR_ExpectedInterpolatedString, sourceLocation);
}
else if (refArg == RefKind.Ref && refParameter == RefKind.In && !binder.Compilation.IsFeatureEnabled(MessageID.IDS_FeatureRefReadonlyParameters))
{
// Argument {0} may not be passed with the 'ref' keyword in language version {1}. To pass 'ref' arguments to 'in' parameters, upgrade to language version {2} or greater.
diagnostics.Add(
ErrorCode.ERR_BadArgExtraRefLangVersion,
sourceLocation,
symbols,
arg + 1,
binder.Compilation.LanguageVersion.ToDisplayString(),
new CSharpRequiredLanguageVersion(MessageID.IDS_FeatureRefReadonlyParameters.RequiredVersion()));
}
else if (refParameter is RefKind.None or RefKind.In or RefKind.RefReadOnlyParameter)
{
// Argument {0} may not be passed with the '{1}' keyword
diagnostics.Add(
ErrorCode.ERR_BadArgExtraRef,
sourceLocation,
symbols,
arg + 1,
refArg.ToArgumentDisplayString());
}
else
{
// Argument {0} must be passed with the '{1}' keyword
diagnostics.Add(
ErrorCode.ERR_BadArgRef,
sourceLocation,
symbols,
arg + 1,
refParameter.ToParameterDisplayString());
}
}
else
{
Debug.Assert(argument.Kind != BoundKind.OutDeconstructVarPendingInference);
Debug.Assert(argument.Kind != BoundKind.OutVariablePendingInference);
Debug.Assert(argument.Kind != BoundKind.DiscardExpression || argument.HasExpressionType());
Debug.Assert(argument.Display != null);
if (arguments.IsExtensionMethodThisArgument(arg))
{
Debug.Assert((arg == 0) && (parm == arg));
Debug.Assert(!badArg.Result.ConversionForArg(parm).IsImplicit);
// CS1929: '{0}' does not contain a definition for '{1}' and the best extension method overload '{2}' requires a receiver of type '{3}'
diagnostics.Add(
ErrorCode.ERR_BadInstanceArgType,
sourceLocation,
symbols,
argument.Display,
name,
method,
new FormattedSymbol(parameter, SymbolDisplayFormat.CSharpErrorMessageNoParameterNamesFormat));
Debug.Assert((object)parameter == unwrapIfParamsCollection(badArg, parameter, isLastParameter), "If they ever differ, just call the method when constructing the diagnostic.");
}
else
{
// There's only one slot in the error message for the refkind + arg type, but there isn't a single
// object that contains both values, so we have to construct our own.
// NOTE: since this is a symbol, it will use the SymbolDisplay options for parameters (i.e. will
// have the same format as the display value of the parameter).
if (argument.Display is TypeSymbol argType)
{
// Special case for 'string literal -> interpolated string handler' for better user experience
if (isStringLiteralToInterpolatedStringHandlerArgumentConversion(argument, parameter))
{
// CS9205: Expected interpolated string
diagnostics.Add(ErrorCode.ERR_ExpectedInterpolatedString, sourceLocation);
}
else
{
SignatureOnlyParameterSymbol displayArg = new SignatureOnlyParameterSymbol(
TypeWithAnnotations.Create(argType),
ImmutableArray<CustomModifier>.Empty,
isParamsArray: false,
isParamsCollection: false,
refKind: refArg);
SymbolDistinguisher distinguisher = new SymbolDistinguisher(binder.Compilation, displayArg, unwrapIfParamsCollection(badArg, parameter, isLastParameter));
// CS1503: Argument {0}: cannot convert from '{1}' to '{2}'
diagnostics.Add(
ErrorCode.ERR_BadArgType,
sourceLocation,
symbols,
arg + 1,
distinguisher.First,
distinguisher.Second);
}
}
else
{
diagnostics.Add(
ErrorCode.ERR_BadArgType,
sourceLocation,
symbols,
arg + 1,
argument.Display,
new FormattedSymbol(unwrapIfParamsCollection(badArg, parameter, isLastParameter), SymbolDisplayFormat.CSharpErrorMessageNoParameterNamesFormat));
}
}
}
static bool isStringLiteralToInterpolatedStringHandlerArgumentConversion(BoundExpression argument, ParameterSymbol parameter)
=> argument is BoundLiteral { Type.SpecialType: SpecialType.System_String } &&
parameter.Type is NamedTypeSymbol { IsInterpolatedStringHandlerType: true };
// <summary>
// If an argument fails to convert to the type of the corresponding parameter and that
// parameter is a params collection, then the error message should reflect the element type
// of the params collection - not the collection type.
// </summary>
static Symbol unwrapIfParamsCollection(MemberResolutionResult<TMember> badArg, ParameterSymbol parameter, bool isLastParameter)
{
// We only try to unwrap parameters if they are a parameter collection and are on the last position
if (isLastParameter && badArg.Result.ParamsElementTypeOpt.HasType)
{
Debug.Assert(badArg.Result.ParamsElementTypeOpt.Type != (object)ErrorTypeSymbol.EmptyParamsCollectionElementTypeSentinel);
return badArg.Result.ParamsElementTypeOpt.Type;
}
return parameter;
}
}
private bool HadAmbiguousWorseMethods(BindingDiagnosticBag diagnostics, ImmutableArray<Symbol> symbols, Location location, bool isQuery, BoundExpression receiver, string name)
{
MemberResolutionResult<TMember> worseResult1;
MemberResolutionResult<TMember> worseResult2;
// UNDONE: It is unfortunate that we simply choose the first two methods as the
// UNDONE: two to say that are ambiguous; they might not actually be ambiguous
// UNDONE: with each other. We might consider building a better heuristic here.
int nWorse = TryGetFirstTwoWorseResults(out worseResult1, out worseResult2);
if (nWorse <= 1)
{
Debug.Assert(nWorse == 0, "How is it that there is exactly one applicable but worse method, and exactly zero applicable best methods? What was better than this thing?");
return false;
}
if (isQuery)
{
// Multiple implementations of the query pattern were found for source type '{0}'. Ambiguous call to '{1}'.
diagnostics.Add(ErrorCode.ERR_QueryMultipleProviders, location, receiver.Type, name);
}
else
{
// error CS0121: The call is ambiguous between the following methods or properties: 'P.W(A)' and 'P.W(B)'
diagnostics.Add(
CreateAmbiguousCallDiagnosticInfo(
worseResult1.LeastOverriddenMember.OriginalDefinition,
worseResult2.LeastOverriddenMember.OriginalDefinition,
symbols),
location);
}
return true;
}
private int TryGetFirstTwoWorseResults(out MemberResolutionResult<TMember> first, out MemberResolutionResult<TMember> second)
{
int count = 0;
bool foundFirst = false;
bool foundSecond = false;
first = default(MemberResolutionResult<TMember>);
second = default(MemberResolutionResult<TMember>);
foreach (var res in this.ResultsBuilder)
{
if (res.Result.Kind == MemberResolutionKind.Worse)
{
count++;
if (!foundFirst)
{
first = res;
foundFirst = true;
}
else if (!foundSecond)
{
second = res;
foundSecond = true;
}
}
}
return count;
}
private bool HadAmbiguousBestMethods(BindingDiagnosticBag diagnostics, ImmutableArray<Symbol> symbols, Location location)
{
MemberResolutionResult<TMember> validResult1;
MemberResolutionResult<TMember> validResult2;
var nValid = TryGetFirstTwoValidResults(out validResult1, out validResult2);
if (nValid <= 1)
{
Debug.Assert(nValid == 0, "Why are we doing error reporting on an overload resolution problem that had one valid result?");
return false;
}
// error CS0121: The call is ambiguous between the following methods or properties:
// 'P.Ambiguous(object, string)' and 'P.Ambiguous(string, object)'
diagnostics.Add(
CreateAmbiguousCallDiagnosticInfo(
validResult1.LeastOverriddenMember.OriginalDefinition,
validResult2.LeastOverriddenMember.OriginalDefinition,
symbols),
location);
return true;
}
private int TryGetFirstTwoValidResults(out MemberResolutionResult<TMember> first, out MemberResolutionResult<TMember> second)
{
int count = 0;
bool foundFirst = false;
bool foundSecond = false;
first = default(MemberResolutionResult<TMember>);
second = default(MemberResolutionResult<TMember>);
foreach (var res in this.ResultsBuilder)
{
if (res.Result.IsValid)
{
count++;
if (!foundFirst)
{
first = res;
foundFirst = true;
}
else if (!foundSecond)
{
second = res;
foundSecond = true;
}
}
}
return count;
}
private static DiagnosticInfoWithSymbols CreateAmbiguousCallDiagnosticInfo(Symbol first, Symbol second, ImmutableArray<Symbol> symbols)
{
var arguments = (first.ContainingNamespace != second.ContainingNamespace) ?
new object[]
{
new FormattedSymbol(first, SymbolDisplayFormat.CSharpErrorMessageFormat),
new FormattedSymbol(second, SymbolDisplayFormat.CSharpErrorMessageFormat)
} :
new object[]
{
first,
second
};
return new DiagnosticInfoWithSymbols(ErrorCode.ERR_AmbigCall, arguments, symbols);
}
[Conditional("DEBUG")]
private void AssertNone(MemberResolutionKind kind)
{
foreach (var result in this.ResultsBuilder)
{
if (result.Result.Kind == kind)
{
throw ExceptionUtilities.UnexpectedValue(kind);
}
}
}
private MemberResolutionResult<TMember> GetFirstMemberKind(MemberResolutionKind kind)
{
foreach (var result in this.ResultsBuilder)
{
if (result.Result.Kind == kind)
{
return result;
}
}
return default(MemberResolutionResult<TMember>);
}
#if DEBUG
internal string Dump()
{
if (ResultsBuilder.Count == 0)
{
return "Overload resolution failed because the method group was empty.";
}
var sb = new StringBuilder();
if (this.Succeeded)
{
sb.AppendLine("Overload resolution succeeded and chose " + this.ValidResult.Member.ToString());
}
else if (System.Linq.Enumerable.Count(ResultsBuilder, x => x.Result.IsValid) > 1)
{
sb.AppendLine("Overload resolution failed because of ambiguous possible best methods.");
}
else if (System.Linq.Enumerable.Any(ResultsBuilder, x => (x.Result.Kind == MemberResolutionKind.TypeInferenceFailed) || (x.Result.Kind == MemberResolutionKind.TypeInferenceExtensionInstanceArgument)))
{
sb.AppendLine("Overload resolution failed (possibly) because type inference was unable to infer type parameters.");
}
sb.AppendLine("Detailed results:");
foreach (var result in ResultsBuilder)
{
sb.AppendFormat("method: {0} reason: {1}\n", result.Member.ToString(), result.Result.Kind.ToString());
}
return sb.ToString();
}
#endif
#region "Poolable"
internal static OverloadResolutionResult<TMember> GetInstance()
{
return s_pool.Allocate();
}
internal void Free()
{
this.Clear();
s_pool.Free(this);
}
//2) Expose the pool or the way to create a pool or the way to get an instance.
// for now we will expose both and figure which way works better
private static readonly ObjectPool<OverloadResolutionResult<TMember>> s_pool = CreatePool();
private static ObjectPool<OverloadResolutionResult<TMember>> CreatePool()
{
ObjectPool<OverloadResolutionResult<TMember>> pool = null;
pool = new ObjectPool<OverloadResolutionResult<TMember>>(() => new OverloadResolutionResult<TMember>(), 10);
return pool;
}
#endregion
}
}
|