From f70ec7963156ac77adc6af8e0e6eb1f289d61567 Mon Sep 17 00:00:00 2001 From: Neal Gafter Date: Thu, 2 Jun 2016 11:24:43 -0700 Subject: [PATCH] Attempt type inference when there are no applicable methods because there are arguments missing, to force lambda binding for error recovery. Fixes #11053, #11358 --- .../CSharp/Portable/Binder/Binder.cs | 37 ---- .../OverloadResolution/OverloadResolution.cs | 47 +++-- .../Test/Semantic/Semantics/LambdaTests.cs | 186 +++++++++++++++++- 3 files changed, 220 insertions(+), 50 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.cs b/src/Compilers/CSharp/Portable/Binder/Binder.cs index d713081a1f810..1b9ce3d7f08e8 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.cs @@ -605,43 +605,6 @@ private static ThreeState ReportDiagnosticsIfObsoleteInternal(DiagnosticBag diag return ThreeState.True; } - internal void ResolveOverloads( - ImmutableArray members, - ImmutableArray typeArguments, - ImmutableArray arguments, - OverloadResolutionResult result, - ref HashSet useSiteDiagnostics, - bool allowRefOmittedArguments) - where TMember : Symbol - { - var methodsBuilder = ArrayBuilder.GetInstance(members.Length); - methodsBuilder.AddRange(members); - - var typeArgumentsBuilder = ArrayBuilder.GetInstance(typeArguments.Length); - typeArgumentsBuilder.AddRange(typeArguments); - - var analyzedArguments = AnalyzedArguments.GetInstance(); - var unusedDiagnostics = DiagnosticBag.GetInstance(); - foreach (var argumentSyntax in arguments) - { - BindArgumentAndName(analyzedArguments, unusedDiagnostics, false, argumentSyntax, allowArglist: false); - } - - OverloadResolution.MethodOrPropertyOverloadResolution( - methodsBuilder, - typeArgumentsBuilder, - analyzedArguments, - result, - isMethodGroupConversion: false, - allowRefOmittedArguments: allowRefOmittedArguments, - useSiteDiagnostics: ref useSiteDiagnostics); - - methodsBuilder.Free(); - typeArgumentsBuilder.Free(); - analyzedArguments.Free(); - unusedDiagnostics.Free(); - } - internal bool IsSymbolAccessibleConditional( Symbol symbol, AssemblySymbol within, diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs index 18179989c79af..c711907a22824 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs @@ -252,17 +252,16 @@ private void PerformMemberOverloadResolution( // Also note that less derived members are not actually removed - they are simply flagged. ReportUseSiteDiagnostics(results, ref useSiteDiagnostics); - // SPEC: If the resulting set of candidate methods is empty, then further processing along the following steps are abandoned, - // SPEC: and instead an attempt is made to process the invocation as an extension method invocation. If this fails, then no - // SPEC: applicable methods exist, and a binding-time error occurs. + // SPEC: If the resulting set of candidate methods is empty, then further processing along the following steps are abandoned, + // SPEC: and instead an attempt is made to process the invocation as an extension method invocation. If this fails, then no + // SPEC: applicable methods exist, and a binding-time error occurs. if (RemainingCandidatesCount(results) == 0) { - // UNDONE: Extension methods! return; } - // SPEC: The best method of the set of candidate methods is identified. If a single best method cannot be identified, - // SPEC: the method invocation is ambiguous, and a binding-time error occurs. + // SPEC: The best method of the set of candidate methods is identified. If a single best method cannot be identified, + // SPEC: the method invocation is ambiguous, and a binding-time error occurs. RemoveWorseMembers(results, arguments, ref useSiteDiagnostics); @@ -494,7 +493,7 @@ private void AddMemberToCandidateSet( // Second, we need to determine if the method is applicable in its normal form or its expanded form. var normalResult = (allowUnexpandedForm || !IsValidParams(leastOverriddenMember)) - ? IsMemberApplicableInNormalForm(member, leastOverriddenMember, typeArguments, arguments, isMethodGroupConversion, allowRefOmittedArguments, inferWithDynamic, ref useSiteDiagnostics) + ? IsMemberApplicableInNormalForm(member, leastOverriddenMember, typeArguments, arguments, isMethodGroupConversion, allowRefOmittedArguments, inferWithDynamic, ref useSiteDiagnostics, completeResults: completeResults) : default(MemberResolutionResult); var result = normalResult; @@ -2511,7 +2510,8 @@ internal MemberResolutionResult IsMemberApplicableInNormalForm bool isMethodGroupConversion, bool allowRefOmittedArguments, bool inferWithDynamic, - ref HashSet useSiteDiagnostics) + ref HashSet useSiteDiagnostics, + bool completeResults = false) where TMember : Symbol { // AnalyzeArguments matches arguments to parameter names and positions. @@ -2519,7 +2519,13 @@ internal MemberResolutionResult IsMemberApplicableInNormalForm var argumentAnalysis = AnalyzeArguments(member, arguments, isMethodGroupConversion, expanded: false); if (!argumentAnalysis.IsValid) { - return new MemberResolutionResult(member, leastOverriddenMember, MemberAnalysisResult.ArgumentParameterMismatch(argumentAnalysis)); + // 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) + { + return new MemberResolutionResult(member, leastOverriddenMember, MemberAnalysisResult.ArgumentParameterMismatch(argumentAnalysis)); + } } // Check after argument analysis, but before more complicated type inference and argument type validation. @@ -2552,12 +2558,21 @@ internal MemberResolutionResult IsMemberApplicableInNormalForm // The member passed to the following call is returned in the result (possibly a constructed version of it). // The applicability is checked based on effective parameters passed in. - return IsApplicable( + var applicableResult = IsApplicable( member, leastOverriddenMember, typeArguments, arguments, originalEffectiveParameters, constructedEffectiveParameters, argumentAnalysis.ArgsToParamsOpt, hasAnyRefOmittedArgument, ref useSiteDiagnostics, inferWithDynamic); + + // If we were producing complete results and had missing arguments, we pushed on in order to call IsApplicable for + // type inference and lambda binding. In that case we still need to return the argument mismatch failure here. + if (completeResults && !argumentAnalysis.IsValid) + { + return new MemberResolutionResult(member, leastOverriddenMember, MemberAnalysisResult.ArgumentParameterMismatch(argumentAnalysis)); + } + + return applicableResult; } private MemberResolutionResult IsMemberApplicableInExpandedForm( @@ -2802,7 +2817,17 @@ private MemberAnalysisResult IsApplicable( // We add a "virtual parameter" for the __arglist. int paramCount = parameters.ParameterTypes.Length + (isVararg ? 1 : 0); - Debug.Assert(paramCount == arguments.Arguments.Count); + if (arguments.Arguments.Count < paramCount) + { + // For improved error recovery, we perform type inference even when the argument + // list is of the wrong length. The caller is expected to detect and handle that, + // 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 e2d4055094ce8..5d9cd7ce5f130 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs @@ -1768,8 +1768,7 @@ public class MyArgumentType // (6,57): error CS1002: ; expected // var handler = new MyDelegateType((s, e) => { e. }); Diagnostic(ErrorCode.ERR_SemicolonExpected, "}").WithLocation(6, 57) - ) - ; + ); var tree = compilation.SyntaxTrees[0]; var sm = compilation.GetSemanticModel(tree); var lambda = tree.GetCompilationUnitRoot().DescendantNodes().OfType().Single(); @@ -1780,6 +1779,189 @@ public class MyArgumentType Assert.Equal(TypeKind.Class, typeInfo.Type.TypeKind); Assert.NotEmpty(typeInfo.Type.GetMembers("SomePublicMember")); } + + [Fact] + [WorkItem(11053, "https://github.com/dotnet/roslyn/issues/11053")] + [WorkItem(11358, "https://github.com/dotnet/roslyn/issues/11358")] + public void TestLambdaWithError07() + { + var source = +@"using System; +using System.Collections.Generic; + +public static class Program +{ + public static void Main() + { + var parameter = new List(); + var result = parameter.FirstOrDefault(x => x. ); + } +} + +public static class Enumerable +{ + public static TSource FirstOrDefault(this IEnumerable source, TSource defaultValue) + { + return default(TSource); + } + + public static TSource FirstOrDefault(this IEnumerable source, Func predicate, TSource defaultValue) + { + return default(TSource); + } +}"; + var compilation = CreateCompilationWithMscorlibAndSystemCore(source).VerifyDiagnostics( + // (9,55): error CS1001: Identifier expected + // var result = parameter.FirstOrDefault(x => x. ); + Diagnostic(ErrorCode.ERR_IdentifierExpected, ")").WithLocation(9, 55) + ); + 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("x", 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")); + } + + [Fact] + [WorkItem(11053, "https://github.com/dotnet/roslyn/issues/11053")] + [WorkItem(11358, "https://github.com/dotnet/roslyn/issues/11358")] + public void TestLambdaWithError08() + { + var source = +@"using System; +using System.Collections.Generic; + +public static class Program +{ + public static void Main() + { + var parameter = new List(); + var result = parameter.FirstOrDefault(x => x. ); + } +} + +public static class Enumerable +{ + public static TSource FirstOrDefault(this IEnumerable source, params TSource[] defaultValue) + { + return default(TSource); + } + + public static TSource FirstOrDefault(this IEnumerable source, Func predicate, params TSource[] defaultValue) + { + return default(TSource); +} +}"; + var compilation = CreateCompilationWithMscorlibAndSystemCore(source).VerifyDiagnostics( + // (9,55): error CS1001: Identifier expected + // var result = parameter.FirstOrDefault(x => x. ); + Diagnostic(ErrorCode.ERR_IdentifierExpected, ")").WithLocation(9, 55) + ); + 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("x", 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")); + } + + [Fact] + [WorkItem(11053, "https://github.com/dotnet/roslyn/issues/11053")] + [WorkItem(11358, "https://github.com/dotnet/roslyn/issues/11358")] + public void TestLambdaWithError09() + { + var source = +@"using System; + +public static class Program +{ + public static void Main() + { + var parameter = new MyList(); + var result = parameter.FirstOrDefault(x => x. ); + } +} + +public class MyList +{ + public TSource FirstOrDefault(TSource defaultValue) + { + return default(TSource); + } + + public TSource FirstOrDefault(Func predicate, TSource defaultValue) + { + return default(TSource); + } +} +"; + var compilation = CreateCompilationWithMscorlibAndSystemCore(source).VerifyDiagnostics( + // (8,55): error CS1001: Identifier expected + // var result = parameter.FirstOrDefault(x => x. ); + Diagnostic(ErrorCode.ERR_IdentifierExpected, ")").WithLocation(8, 55) + ); + 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("x", 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")); + } + + [Fact] + [WorkItem(11053, "https://github.com/dotnet/roslyn/issues/11053")] + [WorkItem(11358, "https://github.com/dotnet/roslyn/issues/11358")] + public void TestLambdaWithError10() + { + var source = +@"using System; + +public static class Program +{ + public static void Main() + { + var parameter = new MyList(); + var result = parameter.FirstOrDefault(x => x. ); } } +public class MyList +{ + public TSource FirstOrDefault(params TSource[] defaultValue) + { + return default(TSource); + } + + public TSource FirstOrDefault(Func predicate, params TSource[] defaultValue) + { + return default(TSource); + } +} +"; + var compilation = CreateCompilationWithMscorlibAndSystemCore(source).VerifyDiagnostics( + // (8,55): error CS1001: Identifier expected + // var result = parameter.FirstOrDefault(x => x. ); + Diagnostic(ErrorCode.ERR_IdentifierExpected, ")").WithLocation(8, 55) + ); + 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("x", 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")); + } + } +}