diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MethodTypeInference.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MethodTypeInference.cs index f983aa0279cf5e1f7309cbc9d6d129e44fa37e5a..4c6ac8c156ae0a20cfcc22d8929edc696b890777 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 Length => 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.Length; 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.Length; 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.Length; 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 c711907a2282487c3a9b7780684809bd19740356..0c3786843dcf63a798dea6c686f76a49bcccad28 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs @@ -2407,7 +2407,7 @@ internal EffectiveParameters(ImmutableArray types, ImmutableArray= parameters.Length) { continue; } @@ -2519,12 +2519,17 @@ private RefKind GetEffectiveParameterRefKind(ParameterSymbol parameter, RefKind 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 RefKind GetEffectiveParameterRefKind(ParameterSymbol parameter, RefKind // 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 5d9cd7ce5f13041da0409a958d27c3909ad852f1..9ad131333f850b72c61e4e1f5a740a975130308c 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs @@ -1963,5 +1963,62 @@ 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")); + } } }