diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index e9e69fe8aea24abc63e40387741e7176d6ced85e..ae54617a7bb17405c34ce6b21f9b9840e7bb41aa 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 ed32d9fe8a57e9123884073bccf0dca69573ff8a..e69f84227d01e7f33ce8c74c2826ea50dedce7a0 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 5e875d1411125000a7549fea3d1e415d0f9c3215..7b4888107633df3e391ad2a1abea951cdec9ea8e 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 c6aa1b6405d1c1fcd11b9c6aa20c2eccb21d363e..9b006d67b7424565d3743fea29da9485de7850a0 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 797d5d834882375c2dc1998b581f882faa2fd70f..86774cdb88fb579bfe0b3c60316988182c6b9af4 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)