diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolutionResult.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolutionResult.cs index 7982f26bcba47..7498392584405 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolutionResult.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolutionResult.cs @@ -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) @@ -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); @@ -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) @@ -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, @@ -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)' 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; @@ -1561,8 +1622,53 @@ private void AssertNone(MemberResolutionKind kind) } } - private MemberResolutionResult GetFirstMemberKind(MemberResolutionKind kind) + private MemberResolutionResult 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 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) @@ -1574,6 +1680,52 @@ private MemberResolutionResult GetFirstMemberKind(MemberResolutionKind return default(MemberResolutionResult); } + 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() { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/DelegateTypeTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/DelegateTypeTests.cs index d4ad52de188ca..32a04657399bf 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/DelegateTypeTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/DelegateTypeTests.cs @@ -5863,9 +5863,9 @@ static void Main() // (9,9): error CS0411: The type arguments for method 'Program.M(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(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)' 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)").WithLocation(10, 9), // (11,9): error CS0411: The type arguments for method 'Program.M(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(object, T)").WithLocation(11, 9)); @@ -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)' 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)").WithLocation(9, 9), + // (10,9): error CS0411: The type arguments for method 'Program.M(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)").WithLocation(10, 9)); var expectedOutput = @"M(T x, U y) @@ -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)' 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)").WithLocation(9, 9)); var expectedOutput = @"F(Action t) @@ -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)); diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/OverloadResolutionTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/OverloadResolutionTests.cs index 041c8e9ce3c4f..1eb55c7a6d64f 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/OverloadResolutionTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/OverloadResolutionTests.cs @@ -11945,5 +11945,141 @@ readonly struct S """; CreateCompilation(source).VerifyDiagnostics(); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/21950")] + public void PreferDelegateOverloadForLambdaArgument() + { + var source = """ + using System; + + class Program + { + static void Main() + { + M(x => { }); + } + + static void M(string s) + { + } + + static void M(Action a) + { + } + } + """; + // The error should mention the generic delegate overload, not the string overload. + // Before the fix, this would report: CS1660: Cannot convert lambda expression to type 'string' + // After the fix, it should report: CS0411: The type arguments for method 'Program.M(Action)' cannot be inferred + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (7,9): error CS0411: The type arguments for method 'Program.M(Action)' cannot be inferred from the usage. Try specifying the type arguments explicitly. + // M(x => { }); + Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "M").WithArguments("Program.M(System.Action)").WithLocation(7, 9) + ); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/21950")] + public void PreferDelegateOverloadForLambdaArgument_MultipleNonDelegateOverloads() + { + var source = """ + using System; + + class Program + { + static void Main() + { + M(x => { }); + } + + static void M(string s) { } + static void M(int i) { } + static void M(Action a) { } + } + """; + // Even with multiple non-delegate overloads, should report the delegate one + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (7,9): error CS0411: The type arguments for method 'Program.M(Action)' cannot be inferred from the usage. Try specifying the type arguments explicitly. + // M(x => { }); + Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "M").WithArguments("Program.M(System.Action)").WithLocation(7, 9) + ); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/21950")] + public void PreferDelegateOverloadForLambdaArgument_WithAnonymousMethod() + { + var source = """ + using System; + + class Program + { + static void Main() + { + M(delegate { }); + } + + static void M(object o) { } + static void M(Action a) { } + } + """; + // Should also work with anonymous methods (delegate syntax) + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (7,9): error CS0411: The type arguments for method 'Program.M(Action)' cannot be inferred from the usage. Try specifying the type arguments explicitly. + // M(delegate { }); + Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "M").WithArguments("Program.M(System.Action)").WithLocation(7, 9) + ); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/21950")] + public void PreferDelegateOverloadForLambdaArgument_NoTypeInferenceFailure() + { + var source = """ + class Program + { + static void Main() + { + M(x => { }); + } + + static void M(string s) { } + static void M(object o) { } + } + """; + // When there's no delegate-accepting overload with type inference failure, + // should still report the bad argument error (no change from original behavior) + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (5,13): error CS1660: Cannot convert lambda expression to type 'string' because it is not a delegate type + // M(x => { }); + Diagnostic(ErrorCode.ERR_AnonMethToNonDel, "=>").WithArguments("lambda expression", "string").WithLocation(5, 13) + ); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/10672")] + public void Issue10672_TaskRunWithInvalidArgument() + { + var source = """ + using System.Threading.Tasks; + + class Program + { + static void Main() + { + Task.Run(() => { }, TaskCreationOptions.LongRunning); + } + } + """; + // Before the fix: CS1643: Not all code paths return a value in lambda expression of type 'Func' + // After the fix: CS1503: Argument 2: cannot convert from 'TaskCreationOptions' to 'CancellationToken' + // This correctly identifies the real problem (wrong second argument) instead of complaining about the lambda + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (7,29): error CS1503: Argument 2: cannot convert from 'System.Threading.Tasks.TaskCreationOptions' to 'System.Threading.CancellationToken' + // Task.Run(() => { }, TaskCreationOptions.LongRunning); + Diagnostic(ErrorCode.ERR_BadArgType, "TaskCreationOptions.LongRunning").WithArguments("2", "System.Threading.Tasks.TaskCreationOptions", "System.Threading.CancellationToken").WithLocation(7, 29) + ); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs index 1dd5864c414d2..ed2c6bed04dd0 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs @@ -13782,9 +13782,9 @@ static void Main() } ") .VerifyDiagnostics( - // (7,19): error CS0411: The type arguments for method 'Queryable.Select(IQueryable, Expression>)' cannot be inferred from the usage. Try specifying the type arguments explicitly. + // (7,19): error CS0411: The type arguments for method 'Enumerable.Select(IEnumerable, Func)' cannot be inferred from the usage. Try specifying the type arguments explicitly. // var q = 1.Select(z => z); - Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "Select").WithArguments("System.Linq.Queryable.Select(System.Linq.IQueryable, System.Linq.Expressions.Expression>)").WithLocation(7, 19)); + Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "Select").WithArguments("System.Linq.Enumerable.Select(System.Collections.Generic.IEnumerable, System.Func)").WithLocation(7, 19)); } [Fact]