From 7abdbb5391935046b984c917dc7d6e54540448b0 Mon Sep 17 00:00:00 2001 From: gafter Date: Mon, 13 Jun 2016 16:10:00 -0700 Subject: [PATCH] Improve lambda type inference for erroneous method groups We attempt to bind lambda args against every applicable delegate type that could be a matching parameter, and then select the "best" for error recovery. This also has the effect of removing unbound lambdas from the bound trees, replacing them with the "best" binding for error recovery. This makes the semantic model insensitive to any further trial bindings (i.e. mutation of the unbound lambda cache). Fixes #12063, #11979, #11901 Remove vestigal "extensionMethodsOfSameViabilityAreAvailable" thereby reducing coupling between internal compiler APIs. See also #7740 #5128, where the vestigal APIs were introduced. --- .../Portable/Binder/Binder_Expressions.cs | 64 ++---- .../Portable/Binder/Binder_Invocation.cs | 145 ++++++++----- .../Portable/Binder/MethodGroupResolution.cs | 12 +- .../Test/Semantic/Semantics/LambdaTests.cs | 195 ++++++++++++++++++ .../Semantics/PatternMatchingTests.cs | 3 + .../Test2/Expansion/NameExpansionTests.vb | 40 ++++ 6 files changed, 347 insertions(+), 112 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index e9e69fe8aea..ae54617a7bb 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -3115,8 +3115,7 @@ private static bool IsNegativeConstantForArraySize(BoundExpression expression) typeArguments: ImmutableArray.Empty, analyzedArguments: analyzedArguments, invokedAsExtensionMethod: false, - isDelegate: false, - extensionMethodsOfSameViabilityAreAvailable: false); + isDelegate: false); result.WasCompilerGenerated = initializerArgumentListOpt == null; return result; } @@ -4221,17 +4220,7 @@ private bool IsConstructorAccessible(MethodSymbol constructor, ref HashSet childNodes = ArrayBuilder.GetInstance(); - - if (candidateConstructors.Length == 1) - { - ImmutableArray args = BuildArgumentsForErrorRecovery(analyzedArguments, candidateConstructors[0].Parameters); - childNodes.AddRange(args); - } - else - { - childNodes.AddRange(BuildArgumentsForErrorRecovery(analyzedArguments)); - } - + childNodes.AddRange(BuildArgumentsForErrorRecovery(analyzedArguments, candidateConstructors)); if (boundInitializerOpt != null) { childNodes.Add(boundInitializerOpt); @@ -5537,8 +5526,7 @@ private static void CombineExtensionMethodArguments(BoundExpression receiver, An OverloadResolution.MethodInvocationOverloadResolution(methodGroup.Methods, methodGroup.TypeArguments, actualArguments, overloadResolutionResult, ref useSiteDiagnostics, isMethodGroupConversion, allowRefOmittedArguments); diagnostics.Add(expression, useSiteDiagnostics); var sealedDiagnostics = diagnostics.ToReadOnlyAndFree(); - var result = new MethodGroupResolution(methodGroup, null, overloadResolutionResult, actualArguments, methodGroup.ResultKind, sealedDiagnostics, - extensionMethodsOfSameViabilityAreAvailable: false); + var result = new MethodGroupResolution(methodGroup, null, overloadResolutionResult, actualArguments, methodGroup.ResultKind, sealedDiagnostics); // If the search in the current scope resulted in any applicable method (regardless of whether a best // applicable method could be determined) then our search is complete. Otherwise, store aside the @@ -6430,20 +6418,11 @@ private BoundExpression BindIndexedPropertyAccess(CSharpSyntaxNode syntax, Bound delegateTypeBeingInvoked: null); } - PropertySymbol property; - ImmutableArray arguments; - if (candidates.Length == 1) - { - property = candidates[0]; - arguments = BuildArgumentsForErrorRecovery(analyzedArguments, property.Parameters); - } - else - { - // A bad BoundIndexerAccess containing an ErrorPropertySymbol will produce better flow analysis results than - // a BoundBadExpression containing the candidate indexers. - property = CreateErrorPropertySymbol(candidates); - arguments = BuildArgumentsForErrorRecovery(analyzedArguments); - } + ImmutableArray arguments = BuildArgumentsForErrorRecovery(analyzedArguments, candidates); + + // A bad BoundIndexerAccess containing an ErrorPropertySymbol will produce better flow analysis results than + // a BoundBadExpression containing the candidate indexers. + PropertySymbol property = (candidates.Length == 1) ? candidates[0] : CreateErrorPropertySymbol(candidates); propertyAccess = BoundIndexerAccess.ErrorAccess( syntax, @@ -6537,8 +6516,7 @@ private ErrorPropertySymbol CreateErrorPropertySymbol(ImmutableArray BuildArgumentsForDynamicInvocatio } else { - result = BindInvocationExpressionContinued(node, expression, methodName, overloadResolutionResult, analyzedArguments, methodGroup, delegateType, diagnostics, false, queryClause); + result = BindInvocationExpressionContinued(node, expression, methodName, overloadResolutionResult, analyzedArguments, methodGroup, delegateType, diagnostics, queryClause); } overloadResolutionResult.Free(); @@ -482,8 +483,7 @@ private static bool HasApplicableConditionalMethod(OverloadResolutionResultMethod group if the invocation represents a potentially overloaded member. /// Delegate type if method group represents a delegate. /// Diagnostics. - /// - /// Indicates that there are additional extension method candidates of the same lookup viability in cases when - /// none of the instance methods are applicable to the argument list. - /// /// The syntax for the query clause generating this invocation expression, if any. /// BoundCall or error expression representing the invocation. private BoundCall BindInvocationExpressionContinued( @@ -782,7 +778,6 @@ private static void CheckRestrictedTypeReceiver(BoundExpression expression, Comp MethodGroup methodGroup, NamedTypeSymbol delegateTypeOpt, DiagnosticBag diagnostics, - bool extensionMethodsOfSameViabilityAreAvailable, CSharpSyntaxNode queryClause = null) { Debug.Assert(node != null); @@ -834,8 +829,7 @@ private static void CheckRestrictedTypeReceiver(BoundExpression expression, Comp } return CreateBadCall(node, methodGroup.Name, invokedAsExtensionMethod && analyzedArguments.Arguments.Count > 0 && (object)methodGroup.Receiver == (object)analyzedArguments.Arguments[0] ? null : methodGroup.Receiver, - GetOriginalMethods(result), methodGroup.ResultKind, methodGroup.TypeArguments.ToImmutable(), analyzedArguments, invokedAsExtensionMethod: invokedAsExtensionMethod, isDelegate: ((object)delegateTypeOpt != null), - extensionMethodsOfSameViabilityAreAvailable: extensionMethodsOfSameViabilityAreAvailable); + GetOriginalMethods(result), methodGroup.ResultKind, methodGroup.TypeArguments.ToImmutable(), analyzedArguments, invokedAsExtensionMethod: invokedAsExtensionMethod, isDelegate: ((object)delegateTypeOpt != null)); } // Otherwise, there were no dynamic arguments and overload resolution found a unique best candidate. @@ -1059,8 +1053,7 @@ private static NamedTypeSymbol GetDelegateType(BoundExpression expr) ImmutableArray typeArguments, AnalyzedArguments analyzedArguments, bool invokedAsExtensionMethod, - bool isDelegate, - bool extensionMethodsOfSameViabilityAreAvailable) + bool isDelegate) { MethodSymbol method; ImmutableArray args; @@ -1075,13 +1068,9 @@ private static NamedTypeSymbol GetDelegateType(BoundExpression expr) methods = constructedMethods.ToImmutableAndFree(); } - if (methods.Length == 1 && !extensionMethodsOfSameViabilityAreAvailable) + if (methods.Length == 1 && !IsUnboundGeneric(methods[0])) { - // If there is only one method in the group and no additional extension methods with the same lookup viability, - // we should attempt to bind to it. That includes binding any lambdas in the argument list against - // the method's parameter types. method = methods[0]; - args = BuildArgumentsForErrorRecovery(analyzedArguments, method.Parameters); } else { @@ -1090,61 +1079,113 @@ private static NamedTypeSymbol GetDelegateType(BoundExpression expr) ? receiver.Type : this.ContainingType; method = new ErrorMethodSymbol(methodContainer, returnType, name); - args = BuildArgumentsForErrorRecovery(analyzedArguments); } + args = BuildArgumentsForErrorRecovery(analyzedArguments, methods); var argNames = analyzedArguments.GetNames(); var argRefKinds = analyzedArguments.RefKinds.ToImmutableOrNull(); return BoundCall.ErrorCall(node, receiver, method, args, argNames, argRefKinds, isDelegate, invokedAsExtensionMethod: invokedAsExtensionMethod, originalMethods: methods, resultKind: resultKind); } - private static ImmutableArray BuildArgumentsForErrorRecovery(AnalyzedArguments analyzedArguments, ImmutableArray parameters) + private static bool IsUnboundGeneric(MethodSymbol method) { - ArrayBuilder oldArguments = analyzedArguments.Arguments; - int argumentCount = oldArguments.Count; - int parameterCount = parameters.Length; + return method.IsGenericMethod && method.ConstructedFrom() == method; + } - for (int i = 0; i < argumentCount; i++) + private static ImmutableArray BuildArgumentsForErrorRecovery(AnalyzedArguments analyzedArguments, ImmutableArray methods) + { + var parameterListList = ArrayBuilder>.GetInstance(); + foreach (var m in methods) { - BoundKind argumentKind = oldArguments[i].Kind; + if (!IsUnboundGeneric(m) && m.ParameterCount > 0) parameterListList.Add(m.Parameters); + } - if (argumentKind == BoundKind.UnboundLambda && i < parameterCount) - { - ArrayBuilder newArguments = ArrayBuilder.GetInstance(argumentCount); - newArguments.AddRange(oldArguments); + var result = BuildArgumentsForErrorRecovery(analyzedArguments, parameterListList); + parameterListList.Free(); + return result; + } - do - { - BoundExpression oldArgument = newArguments[i]; + private static ImmutableArray BuildArgumentsForErrorRecovery(AnalyzedArguments analyzedArguments, ImmutableArray properties) + { + var parameterListList = ArrayBuilder>.GetInstance(); + foreach (var m in properties) + { + if (m.ParameterCount > 0) parameterListList.Add(m.Parameters); + } + + var result = BuildArgumentsForErrorRecovery(analyzedArguments, parameterListList); + parameterListList.Free(); + return result; + } - if (i < parameterCount) + private static ImmutableArray BuildArgumentsForErrorRecovery(AnalyzedArguments analyzedArguments, IEnumerable> parameterListList) + { + // Since the purpose is to bind any unbound lambdas, we return early if there are none. + if (!analyzedArguments.Arguments.Any(e => e.Kind == BoundKind.UnboundLambda)) + { + return analyzedArguments.Arguments.ToImmutable(); + } + + int argumentCount = analyzedArguments.Arguments.Count; + ArrayBuilder newArguments = ArrayBuilder.GetInstance(argumentCount); + newArguments.AddRange(analyzedArguments.Arguments); + for (int i = 0; i < argumentCount; i++) + { + var argument = newArguments[i]; + if (argument.Kind == BoundKind.UnboundLambda) + { + // bind the argument against each applicable parameter + var unboundArgument = (UnboundLambda)argument; + foreach (var parameterList in parameterListList) + { + var parameterType = GetCorrespondingParameterType(analyzedArguments, i, parameterList); + if (parameterType?.Kind == SymbolKind.NamedType) { - switch (oldArgument.Kind) - { - case BoundKind.UnboundLambda: - NamedTypeSymbol parameterType = parameters[i].Type as NamedTypeSymbol; - if ((object)parameterType != null) - { - newArguments[i] = ((UnboundLambda)oldArgument).Bind(parameterType); - } - break; - } + var discarded = unboundArgument.Bind((NamedTypeSymbol)parameterType); } - - i++; } - while (i < argumentCount); - return newArguments.ToImmutableAndFree(); + // replace the unbound lambda with its best inferred bound version + newArguments[i] = unboundArgument.BindForErrorRecovery(); + } + } + + return newArguments.ToImmutableAndFree(); + } + + /// + /// Compute the type of the corresponding parameter, if any. This is used to improve error recovery, + /// for bad invocations, not for semantic analysis of correct invocations, so it is a heuristic. + /// If no parameter appears to correspond to the given argument, we return null. + /// + /// The analyzed argument list + /// The index of the argument + /// The parameter list to match against + /// The type of the corresponding parameter. + private static TypeSymbol GetCorrespondingParameterType(AnalyzedArguments analyzedArguments, int i, ImmutableArray parameterList) + { + string name = analyzedArguments.Name(i); + if (name != null) + { + // look for a parameter by that name + foreach (var parameter in parameterList) + { + if (parameter.Name == name) return parameter.Type; } + + return null; } - return oldArguments.ToImmutable(); + return (i < parameterList.Length) ? parameterList[i].Type : null; + // CONSIDER: should we handle variable argument lists? } + /// + /// Absent parameter types to bind the arguments, we simply use the arguments provided for error recovery. + /// private static ImmutableArray BuildArgumentsForErrorRecovery(AnalyzedArguments analyzedArguments) { - return BuildArgumentsForErrorRecovery(analyzedArguments, ImmutableArray.Empty); + return BuildArgumentsForErrorRecovery(analyzedArguments, Enumerable.Empty>()); } private BoundCall CreateBadCall( diff --git a/src/Compilers/CSharp/Portable/Binder/MethodGroupResolution.cs b/src/Compilers/CSharp/Portable/Binder/MethodGroupResolution.cs index ed32d9fe8a5..e69f84227d0 100644 --- a/src/Compilers/CSharp/Portable/Binder/MethodGroupResolution.cs +++ b/src/Compilers/CSharp/Portable/Binder/MethodGroupResolution.cs @@ -19,10 +19,9 @@ internal struct MethodGroupResolution public readonly AnalyzedArguments AnalyzedArguments; public readonly ImmutableArray Diagnostics; public readonly LookupResultKind ResultKind; - public readonly bool ExtensionMethodsOfSameViabilityAreAvailable; public MethodGroupResolution(MethodGroup methodGroup, ImmutableArray diagnostics) - : this(methodGroup, null, null, null, methodGroup.ResultKind, diagnostics, false) + : this(methodGroup, null, null, null, methodGroup.ResultKind, diagnostics) { } @@ -31,12 +30,12 @@ public MethodGroupResolution(MethodGroup methodGroup, ImmutableArray OverloadResolutionResult overloadResolutionResult, AnalyzedArguments analyzedArguments, ImmutableArray diagnostics) - : this(methodGroup, null, overloadResolutionResult, analyzedArguments, methodGroup.ResultKind, diagnostics, false) + : this(methodGroup, null, overloadResolutionResult, analyzedArguments, methodGroup.ResultKind, diagnostics) { } public MethodGroupResolution(Symbol otherSymbol, LookupResultKind resultKind, ImmutableArray diagnostics) - : this(null, otherSymbol, null, null, resultKind, diagnostics, false) + : this(null, otherSymbol, null, null, resultKind, diagnostics) { } @@ -46,8 +45,7 @@ public MethodGroupResolution(Symbol otherSymbol, LookupResultKind resultKind, Im OverloadResolutionResult overloadResolutionResult, AnalyzedArguments analyzedArguments, LookupResultKind resultKind, - ImmutableArray diagnostics, - bool extensionMethodsOfSameViabilityAreAvailable) + ImmutableArray diagnostics) { Debug.Assert((methodGroup == null) || (methodGroup.Methods.Count > 0)); Debug.Assert((methodGroup == null) || ((object)otherSymbol == null)); @@ -55,7 +53,6 @@ public MethodGroupResolution(Symbol otherSymbol, LookupResultKind resultKind, Im Debug.Assert(((object)otherSymbol == null) || (otherSymbol.Kind != SymbolKind.Method)); Debug.Assert(resultKind != LookupResultKind.Ambiguous); // HasAnyApplicableMethod is expecting Viable methods. Debug.Assert(!diagnostics.IsDefault); - Debug.Assert(!extensionMethodsOfSameViabilityAreAvailable || methodGroup == null || !methodGroup.IsExtensionMethodGroup); this.MethodGroup = methodGroup; this.OtherSymbol = otherSymbol; @@ -63,7 +60,6 @@ public MethodGroupResolution(Symbol otherSymbol, LookupResultKind resultKind, Im this.AnalyzedArguments = analyzedArguments; this.ResultKind = resultKind; this.Diagnostics = diagnostics; - this.ExtensionMethodsOfSameViabilityAreAvailable = extensionMethodsOfSameViabilityAreAvailable; } public bool IsEmpty diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs index 5e875d14111..7b488810763 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs @@ -2062,6 +2062,201 @@ public static class XThing 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")); + } + } + + [Fact] + [WorkItem(11901, "https://github.com/dotnet/roslyn/issues/11901")] + public void TestLambdaWithError15() + { + // 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 exactly one method in the method 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) {} +} +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; +} +"; + 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")); + } + } + + [Fact] + [WorkItem(11901, "https://github.com/dotnet/roslyn/issues/11901")] + public void TestLambdaWithError16() + { + // These tests ensure we use the substituted method to bind a lambda expression + // argument even when there are too many or too few arguments to an invocation, in the + // case when there is exactly one method in the method 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) {} +} +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; +} +"; + 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")); + } + } + + [Fact] + [WorkItem(12063, "https://github.com/dotnet/roslyn/issues/12063")] + public void TestLambdaWithError17() + { + var source = +@"using System; + +class Program +{ + static void Main(string[] args) + { + Ma(action: (x, y) => x.ToString(), t: string.Empty); + Mb(action: (x, y) => x.ToString(), t: string.Empty); + } + static void Ma(T t, Action action) { } + static void Mb(T t, Action action) { } + static void Mb() { } +} +"; + 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")); + } + } + + [Fact] + [WorkItem(12063, "https://github.com/dotnet/roslyn/issues/12063")] + public void TestLambdaWithError18() + { + var source = +@"using System; + +class Program +{ + static void Main(string[] args) + { + Ma(string.Empty, (x, y) => x.ToString()); + Mb(string.Empty, (x, y) => x.ToString()); + } + static void Ma(T t, Action action) { } + static void Mb(T t, Action action) { } + static void Mb() { } +} +"; + 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")); + } + } + + [Fact] + [WorkItem(12063, "https://github.com/dotnet/roslyn/issues/12063")] + public void TestLambdaWithError19() + { + var source = +@"using System; +using System.Linq.Expressions; + +class Program +{ + static void Main(string[] args) + { + Ma(string.Empty, (x, y) => x.ToString()); + Mb(string.Empty, (x, y) => x.ToString()); + Mc(string.Empty, (x, y) => x.ToString()); + } + static void Ma(T t, Expression> action) { } + static void Mb(T t, Expression> action) { } + static void Mb(T t, Action action) { } + static void Mc(T t, Expression> action) { } + static void Mc() { } +} "; var compilation = CreateCompilationWithMscorlibAndSystemCore(source); var tree = compilation.SyntaxTrees[0]; diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests.cs index c6aa1b6405d..9b006d67b74 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/PatternMatchingTests.cs @@ -1997,6 +1997,9 @@ void Test11() // (115,15): error CS0103: The name 'u9' does not exist in the current context // Dummy(u9); Diagnostic(ErrorCode.ERR_NameNotInContext, "u9").WithArguments("u9").WithLocation(115, 15), + // (108,50): error CS0165: Use of unassigned local variable 'z9' + // group x > y9 && 1 is var z9 && z9 == + Diagnostic(ErrorCode.ERR_UseDefViolation, "z9").WithArguments("z9").WithLocation(108, 50), // (121,24): error CS1931: The range variable 'y10' conflicts with a previous declaration of 'y10' // from y10 in new[] { 1 } Diagnostic(ErrorCode.ERR_QueryRangeVariableOverrides, "y10").WithArguments("y10").WithLocation(121, 24), diff --git a/src/EditorFeatures/Test2/Expansion/NameExpansionTests.vb b/src/EditorFeatures/Test2/Expansion/NameExpansionTests.vb index 797d5d83488..86774cdb88f 100644 --- a/src/EditorFeatures/Test2/Expansion/NameExpansionTests.vb +++ b/src/EditorFeatures/Test2/Expansion/NameExpansionTests.vb @@ -254,6 +254,46 @@ class C Mumble(new { x = 42 }, (a, y) => a.x); } } + + + Await TestAsync(input, expected, expandParameter:=True) + End Function + + + + Public Async Function TestCSharp_LambdaParameter_DontExpandAnonymousTypes2_variation() As Task + Dim input = + + + +using System; +class C +{ + static void Mumble<T>(T anonymousType, Action<T, int, int> lambda) { } + static void Mumble() { } // added to the test + + static void M() + { + Mumble(new { x = 42 }, {|Expand:(a, y) => a.x|}); + } +} + + + + + Dim expected = + +using System; +class C +{ + static void Mumble<T>(T anonymousType, Action<T, int, int> lambda) { } + static void Mumble() { } // added to the test + + static void M() + { + Mumble(new { x = 42 }, (a, y) => a.x); + } +} Await TestAsync(input, expected, expandParameter:=True) -- GitLab