Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -761,7 +761,7 @@ private bool TypeInferenceFailed(
Location location,
CSharpSyntaxNode queryClause = null)
{
var inferenceFailed = GetFirstMemberKind(MemberResolutionKind.TypeInferenceFailed);
var inferenceFailed = GetFirstMemberKind(MemberResolutionKind.TypeInferenceFailed, arguments);
if (inferenceFailed.IsNotNull)
{
if (queryClause != null)
Expand All @@ -781,7 +781,7 @@ private bool TypeInferenceFailed(
return true;
}

inferenceFailed = GetFirstMemberKind(MemberResolutionKind.TypeInferenceExtensionInstanceArgument);
inferenceFailed = GetFirstMemberKind(MemberResolutionKind.TypeInferenceExtensionInstanceArgument, arguments);
if (inferenceFailed.IsNotNull)
{
Debug.Assert(arguments.Arguments.Count > 0);
Expand Down Expand Up @@ -1092,7 +1092,7 @@ private bool HadConstructedParameterFailedConstraintCheck(
return true;
}

private static bool HadLambdaConversionError(BindingDiagnosticBag diagnostics, AnalyzedArguments arguments)
private bool HadLambdaConversionError(BindingDiagnosticBag diagnostics, AnalyzedArguments arguments)
{
bool hadError = false;
foreach (var argument in arguments.Arguments)
Expand All @@ -1103,9 +1103,51 @@ private static bool HadLambdaConversionError(BindingDiagnosticBag diagnostics, A
}
}

// If we have lambda conversion errors, check if there are also bad argument conversions
// in non-lambda positions. If so, those errors might be more informative than the lambda
// errors, especially if there's a delegate-accepting overload that would work.
if (hadError && HasBadArgumentsInNonLambdaPositions(arguments))
{
// Check if there's a delegate-accepting overload with bad arguments in non-lambda positions
// If so, clear the lambda errors and let the bad argument errors be reported instead
foreach (var result in this.ResultsBuilder)
{
if (result.Result.Kind == MemberResolutionKind.BadArgumentConversion &&
HasDelegateParameterForLambdaArgument(result.Member, arguments))
{
// Clear the lambda errors - we'll report the bad arguments instead
diagnostics.Clear();
return false;
}
}
}

return hadError;
}

private bool HasBadArgumentsInNonLambdaPositions(AnalyzedArguments arguments)
{
// Check if any overload has bad arguments in non-lambda positions
foreach (var result in this.ResultsBuilder)
{
if (result.Result.Kind == MemberResolutionKind.BadArgumentConversion && !result.Result.BadArgumentsOpt.IsNull)
{
var parameters = result.Member.GetParametersIncludingExtensionParameter(skipExtensionIfStatic: false);

foreach (var badArgIndex in result.Result.BadArgumentsOpt.TrueBits())
{
// Check if this bad argument is a non-lambda argument
if (badArgIndex < arguments.Arguments.Count &&
arguments.Arguments[badArgIndex].Kind != BoundKind.UnboundLambda)
{
return true;
}
}
}
}
return false;
}

private bool HadBadArguments(
BindingDiagnosticBag diagnostics,
Binder binder,
Expand All @@ -1117,12 +1159,31 @@ private bool HadBadArguments(
BinderFlags flags,
bool isMethodGroupConversion)
{
var badArg = GetFirstMemberKind(MemberResolutionKind.BadArgumentConversion);
var badArg = GetFirstMemberKind(MemberResolutionKind.BadArgumentConversion, arguments);
if (badArg.IsNull)
{
return false;
}

// If we have a lambda/anonymous method argument converting to a non-delegate type,
// check if there's a type inference failure for a delegate-accepting overload.
// In that case, report the type inference error instead, as it's more informative.
if (!isMethodGroupConversion && HasLambdaArgumentConvertingToNonDelegate(badArg.Member, arguments))
{
var inferenceFailed = GetFirstMemberKind(MemberResolutionKind.TypeInferenceFailed, arguments);
if (inferenceFailed.IsNotNull)
{
// Report the type inference error instead
// 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;
}
}

if (isMethodGroupConversion)
{
return true;
Expand Down Expand Up @@ -1561,8 +1622,53 @@ private void AssertNone(MemberResolutionKind kind)
}
}

private MemberResolutionResult<TMember> GetFirstMemberKind(MemberResolutionKind kind)
private MemberResolutionResult<TMember> GetFirstMemberKind(MemberResolutionKind kind, AnalyzedArguments arguments = null)
{
// When looking for the best candidate to report errors, prefer members that accept delegate types
// in positions where lambda/anonymous method arguments are provided.
if (kind is MemberResolutionKind.BadArgumentConversion or
MemberResolutionKind.TypeInferenceFailed or
MemberResolutionKind.TypeInferenceExtensionInstanceArgument &&
arguments != null)
{
// First, check if any argument is a lambda or anonymous method
var hasLambdaOrAnonymousMethod = false;
foreach (var arg in arguments.Arguments)
{
if (arg.Kind == BoundKind.UnboundLambda)
{
hasLambdaOrAnonymousMethod = true;
break;
}
}

if (hasLambdaOrAnonymousMethod)
{
// Try to find a candidate that accepts a delegate in a position where we have a lambda
MemberResolutionResult<TMember> firstCandidate = default;
foreach (var result in this.ResultsBuilder)
{
if (result.Result.Kind == kind)
{
if (firstCandidate.IsNull)
{
firstCandidate = result;
}

// Check if this candidate has delegate-type parameters matching lambda positions
if (HasDelegateParameterForLambdaArgument(result.Member, arguments))
{
return result;
}
}
}

// If we didn't find a delegate-accepting overload, return the first one
return firstCandidate;
}
}

// Default behavior: return the first result with the specified kind
foreach (var result in this.ResultsBuilder)
{
if (result.Result.Kind == kind)
Expand All @@ -1574,6 +1680,52 @@ private MemberResolutionResult<TMember> GetFirstMemberKind(MemberResolutionKind
return default(MemberResolutionResult<TMember>);
}

private bool HasDelegateParameterForLambdaArgument(TMember member, AnalyzedArguments arguments)
{
var parameters = member.GetParametersIncludingExtensionParameter(skipExtensionIfStatic: false);

// Need to have matching number of arguments
if (parameters.Length == 0 || arguments.Arguments.Count == 0)
{
return false;
}

for (int i = 0; i < arguments.Arguments.Count && i < parameters.Length; i++)
{
var arg = arguments.Arguments[i];
if (arg.Kind == BoundKind.UnboundLambda)
{
var parameterType = parameters[i].Type;
if (parameterType.IsDelegateType())
{
return true;
}
}
}

return false;
}

private bool HasLambdaArgumentConvertingToNonDelegate(TMember member, AnalyzedArguments arguments)
{
var parameters = member.GetParametersIncludingExtensionParameter(skipExtensionIfStatic: false);

for (int i = 0; i < arguments.Arguments.Count && i < parameters.Length; i++)
{
var arg = arguments.Arguments[i];
if (arg.Kind == BoundKind.UnboundLambda)
{
var parameterType = parameters[i].Type;
if (!parameterType.IsDelegateType())
{
return true;
}
}
}

return false;
}

#if DEBUG
internal string Dump()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5863,9 +5863,9 @@ static void Main()
// (9,9): error CS0411: The type arguments for method 'Program.M<T>(object, T)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
// M(() => 1, () => 2);
Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "M").WithArguments("Program.M<T>(object, T)").WithLocation(9, 9),
// (10,14): error CS1660: Cannot convert lambda expression to type 'object' because it is not a delegate type
// (10,9): error CS0411: The type arguments for method 'Program.M<T, U>(T, U)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
// M(() => 1, f);
Diagnostic(ErrorCode.ERR_AnonMethToNonDel, "=>").WithArguments("lambda expression", "object").WithLocation(10, 14),
Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "M").WithArguments("Program.M<T, U>(T, U)").WithLocation(10, 9),
// (11,9): error CS0411: The type arguments for method 'Program.M<T>(object, T)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
// M(f, () => 2);
Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "M").WithArguments("Program.M<T>(object, T)").WithLocation(11, 9));
Expand Down Expand Up @@ -5935,12 +5935,12 @@ static void Main()

var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9);
comp.VerifyDiagnostics(
// (9,14): error CS1660: Cannot convert lambda expression to type 'Delegate' because it is not a delegate type
// (9,9): error CS0411: The type arguments for method 'Program.M<T, U>(T, U)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
// M(() => 1, () => 2);
Diagnostic(ErrorCode.ERR_AnonMethToNonDel, "=>").WithArguments("lambda expression", "System.Delegate").WithLocation(9, 14),
// (10,14): error CS1660: Cannot convert lambda expression to type 'Delegate' because it is not a delegate type
Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "M").WithArguments("Program.M<T, U>(T, U)").WithLocation(9, 9),
// (10,9): error CS0411: The type arguments for method 'Program.M<T, U>(T, U)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
// M(() => 1, f);
Diagnostic(ErrorCode.ERR_AnonMethToNonDel, "=>").WithArguments("lambda expression", "System.Delegate").WithLocation(10, 14));
Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "M").WithArguments("Program.M<T, U>(T, U)").WithLocation(10, 9));

var expectedOutput =
@"M<T, U>(T x, U y)
Expand Down Expand Up @@ -5972,9 +5972,9 @@ static void Main()
// (8,11): error CS1503: Argument 1: cannot convert from 'method group' to 'System.Delegate'
// F(Main);
Diagnostic(ErrorCode.ERR_BadArgType, "Main").WithArguments("1", "method group", "System.Delegate").WithLocation(8, 11),
// (9,14): error CS1660: Cannot convert lambda expression to type 'Delegate' because it is not a delegate type
// (9,9): error CS0411: The type arguments for method 'Program.F<T>(T)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
// F(() => 1);
Diagnostic(ErrorCode.ERR_AnonMethToNonDel, "=>").WithArguments("lambda expression", "System.Delegate").WithLocation(9, 14));
Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "F").WithArguments("Program.F<T>(T)").WithLocation(9, 9));

var expectedOutput =
@"F<Action>(Action t)
Expand Down Expand Up @@ -6036,9 +6036,6 @@ static void Main()

var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9);
comp.VerifyDiagnostics(
// (12,14): error CS1660: Cannot convert lambda expression to type 'Delegate' because it is not a delegate type
// F(() => 1, () => 2, string.Empty);
Diagnostic(ErrorCode.ERR_AnonMethToNonDel, "=>").WithArguments("lambda expression", "System.Delegate").WithLocation(12, 14),
// (12,23): error CS1660: Cannot convert lambda expression to type 'Delegate' because it is not a delegate type
// F(() => 1, () => 2, string.Empty);
Diagnostic(ErrorCode.ERR_AnonMethToNonDel, "=>").WithArguments("lambda expression", "System.Delegate").WithLocation(12, 23));
Expand Down
Loading