diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MethodTypeInference.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MethodTypeInference.cs
index f983aa0279cf5..1e96458ad3f7c 100644
--- a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MethodTypeInference.cs
+++ b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MethodTypeInference.cs
@@ -97,6 +97,12 @@ private enum Dependency
private Dependency[,] _dependencies; // Initialized lazily
private bool _dependenciesDirty;
+ ///
+ /// For error recovery, we allow a mismatch between the number of arguments and parameters
+ /// during type inference. This sometimes enables inferring the type for a lambda parameter.
+ ///
+ private int NumberArgumentsToProcess => System.Math.Min(_arguments.Length, _formalParameterTypes.Length);
+
public static MethodTypeInferenceResult Infer(
Binder binder,
ImmutableArray methodTypeParameters,
@@ -524,14 +530,13 @@ private void InferTypeArgsFirstPhase(Binder binder, ref HashSet
{
Debug.Assert(!_formalParameterTypes.IsDefault);
Debug.Assert(!_arguments.IsDefault);
- Debug.Assert(_arguments.Length == _formalParameterTypes.Length);
// We expect that we have been handed a list of arguments and a list of the
// formal parameter types they correspond to; all the details about named and
// optional parameters have already been dealt with.
// SPEC: For each of the method arguments Ei:
- for (int arg = 0; arg < _arguments.Length; arg++)
+ for (int arg = 0, length = this.NumberArgumentsToProcess; arg < length; arg++)
{
var argument = _arguments[arg];
@@ -795,7 +800,7 @@ private void MakeOutputTypeInferences(Binder binder, ref HashSet
// SPEC: where the output types contain unfixed type parameters but the input
// SPEC: types do not, an output type inference is made from Ei to Ti.
- for (int arg = 0; arg < _arguments.Length; arg++)
+ for (int arg = 0, length = this.NumberArgumentsToProcess; arg < length; arg++)
{
var formalType = _formalParameterTypes[arg];
var argument = _arguments[arg];
@@ -1067,7 +1072,7 @@ private bool DependsDirectlyOn(int iParam, int jParam)
Debug.Assert(IsUnfixed(iParam));
Debug.Assert(IsUnfixed(jParam));
- for (int iArg = 0; iArg < _arguments.Length; iArg++)
+ for (int iArg = 0, length = this.NumberArgumentsToProcess; iArg < length; iArg++)
{
var formalParameterType = _formalParameterTypes[iArg];
var argument = _arguments[iArg];
diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs
index c711907a22824..aba271e1998fe 100644
--- a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs
+++ b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs
@@ -2406,8 +2406,8 @@ private EffectiveParameters GetEffectiveParametersInNormalForm(
for (int arg = 0; arg < argumentCount; ++arg)
{
int parm = argToParamMap.IsDefault ? arg : argToParamMap[arg];
- // If this is the __arglist parameter, just skip it.
- if (parm == parameters.Length)
+ // If this is the __arglist parameter, or an extra argument in error situations, just skip it.
+ if (parm >= parameters.Length)
{
continue;
}
@@ -2519,12 +2519,17 @@ internal MemberResolutionResult IsMemberApplicableInNormalForm
var argumentAnalysis = AnalyzeArguments(member, arguments, isMethodGroupConversion, expanded: false);
if (!argumentAnalysis.IsValid)
{
- // When we are producing more complete results, and a required parameter is missing, we push on
- // to type inference so that lambda arguments can be bound to their delegate-typed parameters,
- // thus improving the API and intellisense experience.
- if (!completeResults || argumentAnalysis.Kind != ArgumentAnalysisResultKind.RequiredParameterMissing)
+ switch (argumentAnalysis.Kind)
{
- return new MemberResolutionResult(member, leastOverriddenMember, MemberAnalysisResult.ArgumentParameterMismatch(argumentAnalysis));
+ case ArgumentAnalysisResultKind.RequiredParameterMissing:
+ case ArgumentAnalysisResultKind.NoCorrespondingParameter:
+ if (!completeResults) goto default;
+ // When we are producing more complete results, and we have the wrong number of arguments, we push on
+ // through type inference so that lambda arguments can be bound to their delegate-typed parameters,
+ // thus improving the API and intellisense experience.
+ break;
+ default:
+ return new MemberResolutionResult(member, leastOverriddenMember, MemberAnalysisResult.ArgumentParameterMismatch(argumentAnalysis));
}
}
@@ -2824,10 +2829,6 @@ private MemberAnalysisResult IsApplicable(
// treating the method as inapplicable.
paramCount = arguments.Arguments.Count;
}
- else
- {
- Debug.Assert(paramCount == arguments.Arguments.Count);
- }
// For each argument in A, the parameter passing mode of the argument (i.e., value, ref, or out) is
// identical to the parameter passing mode of the corresponding parameter, and
diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs
index 5d9cd7ce5f130..2f2b5715fbf3b 100644
--- a/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs
+++ b/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs
@@ -1963,5 +1963,118 @@ public TSource FirstOrDefault(Func predicate, params TSource[] de
Assert.Equal("String", typeInfo.Type.Name);
Assert.NotEmpty(typeInfo.Type.GetMembers("Replace"));
}
+
+ [Fact]
+ [WorkItem(557, "https://github.com/dotnet/roslyn/issues/557")]
+ public void TestLambdaWithError11()
+ {
+ var source =
+@"using System.Linq;
+
+public static class Program
+{
+ public static void Main()
+ {
+ var x = new {
+ X = """".Select(c => c.
+ Y = 0,
+ };
+ }
+}
+";
+ var compilation = CreateCompilationWithMscorlibAndSystemCore(source);
+ var tree = compilation.SyntaxTrees[0];
+ var sm = compilation.GetSemanticModel(tree);
+ var lambda = tree.GetCompilationUnitRoot().DescendantNodes().OfType().Single();
+ var eReference = lambda.Body.DescendantNodes().OfType().First();
+ Assert.Equal("c", eReference.ToString());
+ var typeInfo = sm.GetTypeInfo(eReference);
+ Assert.Equal(TypeKind.Struct, typeInfo.Type.TypeKind);
+ Assert.Equal("Char", typeInfo.Type.Name);
+ Assert.NotEmpty(typeInfo.Type.GetMembers("IsHighSurrogate")); // check it is the char we know and love
+ }
+
+ [Fact]
+ [WorkItem(5498, "https://github.com/dotnet/roslyn/issues/5498")]
+ public void TestLambdaWithError12()
+ {
+ var source =
+@"using System.Linq;
+
+class Program
+{
+ static void Main(string[] args)
+ {
+ var z = args.Select(a => a.
+ var foo =
+ }
+}";
+ var compilation = CreateCompilationWithMscorlibAndSystemCore(source);
+ var tree = compilation.SyntaxTrees[0];
+ var sm = compilation.GetSemanticModel(tree);
+ var lambda = tree.GetCompilationUnitRoot().DescendantNodes().OfType().Single();
+ var eReference = lambda.Body.DescendantNodes().OfType().First();
+ Assert.Equal("a", eReference.ToString());
+ var typeInfo = sm.GetTypeInfo(eReference);
+ Assert.Equal(TypeKind.Class, typeInfo.Type.TypeKind);
+ Assert.Equal("String", typeInfo.Type.Name);
+ Assert.NotEmpty(typeInfo.Type.GetMembers("Replace"));
+ }
+
+ [WorkItem(5498, "https://github.com/dotnet/roslyn/issues/5498")]
+ [WorkItem(11358, "https://github.com/dotnet/roslyn/issues/11358")]
+ [Fact]
+ public void TestLambdaWithError13()
+ {
+ // These tests ensure we attempt to perform type inference and bind a lambda expression
+ // argument even when there are too many or too few arguments to an invocation, in the
+ // case when there is more than one method in the method group.
+ // See https://github.com/dotnet/roslyn/issues/11901 for the case of one method in the group
+ var source =
+@"using System;
+
+class Program
+{
+ static void Main(string[] args)
+ {
+ Thing t = null;
+ t.X1(x => x, 1); // too many args
+ t.X2(x => x); // too few args
+ t.M2(string.Empty, x => x, 1); // too many args
+ t.M3(string.Empty, x => x); // too few args
+ }
+}
+public class Thing
+{
+ public void M2(T x, Func func) {}
+ public void M3(T x, Func func, T y) {}
+
+ // Ensure we have more than one method in the method group
+ public void M2() {}
+ public void M3() {}
+}
+public static class XThing
+{
+ public static Thing X1(this Thing self, Func func) => null;
+ public static Thing X2(this Thing self, Func func, int i) => null;
+
+ // Ensure we have more than one method in the method group
+ public static void X1(this object self) {}
+ public static void X2(this object self) {}
+}
+";
+ var compilation = CreateCompilationWithMscorlibAndSystemCore(source);
+ var tree = compilation.SyntaxTrees[0];
+ var sm = compilation.GetSemanticModel(tree);
+ foreach (var lambda in tree.GetRoot().DescendantNodes().OfType())
+ {
+ var reference = lambda.Body.DescendantNodesAndSelf().OfType().First();
+ Assert.Equal("x", reference.ToString());
+ var typeInfo = sm.GetTypeInfo(reference);
+ Assert.Equal(TypeKind.Class, typeInfo.Type.TypeKind);
+ Assert.Equal("String", typeInfo.Type.Name);
+ Assert.NotEmpty(typeInfo.Type.GetMembers("Replace"));
+ }
+ }
}
}