From 4ed1916fb70faab4f7493da84efae6fd91ece90f Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Tue, 25 Jul 2023 17:58:41 -0700 Subject: [PATCH] Only use methods that are fully substituted and pass constraint checks --- .../Portable/Binder/Binder_Expressions.cs | 166 +++-- .../LocalRewriter/LocalRewriter_Call.cs | 2 +- .../CSharp/Portable/Symbols/MethodSymbol.cs | 10 +- .../Symbols/ReducedExtensionMethodSymbol.cs | 14 +- .../Semantic/Semantics/DelegateTypeTests.cs | 579 ++++++++++++++++-- 5 files changed, 641 insertions(+), 130 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 79ce444e68689..1a97588655d8e 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -9869,98 +9869,136 @@ static bool isCandidateUnique(ref MethodSymbol? method, MethodSymbol candidate) return GetUniqueSignatureFromMethodGroup_CSharp10(node); } - MethodSymbol? method = selectMethodForSignature(node); - - if (method is null) + MethodSymbol? foundMethod = null; + var typeArguments = node.TypeArgumentsOpt; + if (node.ResultKind == LookupResultKind.Viable) { - return null; - } + foreach (var instanceMethod in node.Methods) + { + switch (node.ReceiverOpt) + { + case BoundTypeExpression: + case null: // if `using static Class` is in effect, the receiver is missing + if (!instanceMethod.IsStatic) continue; + break; + case BoundThisReference { WasCompilerGenerated: true }: + break; + default: + if (instanceMethod.IsStatic) continue; + break; + } - int arity = node.TypeArgumentsOpt.IsDefaultOrEmpty ? 0 : node.TypeArgumentsOpt.Length; - if (method.Arity != arity) - { - return null; - } - else if (arity > 0) - { - return method.ConstructedFrom.Construct(node.TypeArgumentsOpt); - } + int arity = typeArguments.IsDefaultOrEmpty ? 0 : typeArguments.Length; + if (instanceMethod.Arity != arity) + { + // We have no way of inferring type arguments, so if the given type arguments + // don't match the method's arity, the method is not a candidate + continue; + } - return method; + var substituted = typeArguments.IsDefaultOrEmpty ? instanceMethod : instanceMethod.Construct(typeArguments); + if (!satisfiesConstraintChecks(substituted)) + { + continue; + } - static bool isCandidateUnique(ref MethodSymbol? method, MethodSymbol candidate) - { - if (method is null) - { - method = candidate; - return true; + if (!isCandidateUnique(ref foundMethod, substituted)) + { + return null; + } } - if (MemberSignatureComparer.MethodGroupSignatureComparer.Equals(method, candidate)) + + if (foundMethod is not null) { - return true; + return foundMethod; } - method = null; - return false; } - MethodSymbol? selectMethodForSignature(BoundMethodGroup node) + if (node.SearchExtensionMethods) { - MethodSymbol? method = null; - if (node.ResultKind == LookupResultKind.Viable) + var receiver = node.ReceiverOpt!; + var methodGroup = MethodGroup.GetInstance(); + foreach (var scope in new ExtensionMethodScopes(this)) { - foreach (var m in node.Methods) + methodGroup.Clear(); + PopulateExtensionMethodsFromSingleBinder(scope, methodGroup, node.Syntax, receiver, node.Name, typeArguments, BindingDiagnosticBag.Discarded); + foreach (var extensionMethod in methodGroup.Methods) { - switch (node.ReceiverOpt) + var substituted = typeArguments.IsDefaultOrEmpty ? extensionMethod : extensionMethod.Construct(typeArguments); + + var reduced = substituted.ReduceExtensionMethod(receiver.Type, Compilation, out bool wasFullyInferred); + if (reduced is null) { - case BoundTypeExpression: - case null: // if `using static Class` is in effect, the receiver is missing - if (!m.IsStatic) continue; - break; - case BoundThisReference { WasCompilerGenerated: true }: - break; - default: - if (m.IsStatic) continue; - break; + // Extension method was not applicable + continue; } - if (!isCandidateUnique(ref method, m)) + if (typeArguments.IsDefaultOrEmpty && !wasFullyInferred) { + // Extension method was not fully inferred + continue; + } + + if (!satisfiesConstraintChecks(reduced)) + { + continue; + } + + var wasUnique = isCandidateUnique(ref foundMethod, reduced); + if (!wasUnique) + { + methodGroup.Free(); return null; } } - if (method is not null) + if (foundMethod is not null) { - return method; + methodGroup.Free(); + return foundMethod; } } + methodGroup.Free(); + } - if (node.SearchExtensionMethods) + return null; + + static bool isCandidateUnique(ref MethodSymbol? foundMethod, MethodSymbol candidate) + { + if (foundMethod is null) { - var receiver = node.ReceiverOpt!; - foreach (var scope in new ExtensionMethodScopes(this)) - { - var methodGroup = MethodGroup.GetInstance(); - PopulateExtensionMethodsFromSingleBinder(scope, methodGroup, node.Syntax, receiver, node.Name, node.TypeArgumentsOpt, BindingDiagnosticBag.Discarded); - foreach (var m in methodGroup.Methods) - { - if (m.ReduceExtensionMethod(receiver.Type, Compilation) is { } reduced && - !isCandidateUnique(ref method, reduced)) - { - methodGroup.Free(); - return null; - } - } - methodGroup.Free(); + foundMethod = candidate; + return true; + } + if (MemberSignatureComparer.MethodGroupSignatureComparer.Equals(foundMethod, candidate)) + { + return true; + } + foundMethod = null; + return false; + } - if (method is not null) - { - return method; - } - } + bool satisfiesConstraintChecks(MethodSymbol method) + { + if (method.Arity == 0) + { + return true; } - return null; + var diagnosticsBuilder = ArrayBuilder.GetInstance(); + ArrayBuilder? useSiteDiagnosticsBuilder = null; + + bool constraintsSatisfied = ConstraintsHelper.CheckMethodConstraints( + method, + new ConstraintsHelper.CheckConstraintsArgs(this.Compilation, this.Conversions, includeNullability: false, location: NoLocation.Singleton, diagnostics: null), + diagnosticsBuilder, + nullabilityDiagnosticsBuilderOpt: null, + ref useSiteDiagnosticsBuilder); + + diagnosticsBuilder.Free(); + useSiteDiagnosticsBuilder?.Free(); + + return constraintsSatisfied; } } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs index 495bf0b7fc98e..8c5af0ded252d 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs @@ -202,7 +202,7 @@ private void InterceptCallAndAdjustArguments( // we need to take special care to intercept with the extension method as though it is being called in reduced form. Debug.Assert(receiverOpt is not BoundTypeExpression || method.IsStatic); var needToReduce = receiverOpt is not (null or BoundTypeExpression) && interceptor.IsExtensionMethod; - var symbolForCompare = needToReduce ? ReducedExtensionMethodSymbol.Create(interceptor, receiverOpt!.Type, _compilation) : interceptor; + var symbolForCompare = needToReduce ? ReducedExtensionMethodSymbol.Create(interceptor, receiverOpt!.Type, _compilation, out _) : interceptor; if (!MemberSignatureComparer.InterceptorsComparer.Equals(method, symbolForCompare)) { diff --git a/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs index 29f6a7b4f5a71..d44c1812aee3f 100644 --- a/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/MethodSymbol.cs @@ -733,6 +733,11 @@ public override TResult Accept(CSharpSymbolVisitor visitor) return visitor.VisitMethod(this); } + public MethodSymbol ReduceExtensionMethod(TypeSymbol receiverType, CSharpCompilation compilation) + { + return ReduceExtensionMethod(receiverType, compilation, wasFullyInferred: out _); + } + /// /// If this is an extension method that can be applied to a receiver of the given type, /// returns a reduced extension method symbol thus formed. Otherwise, returns null. @@ -740,7 +745,7 @@ public override TResult Accept(CSharpSymbolVisitor visitor) /// The compilation in which constraints should be checked. /// Should not be null, but if it is null we treat constraints as we would in the latest /// language version. - public MethodSymbol ReduceExtensionMethod(TypeSymbol receiverType, CSharpCompilation compilation) + public MethodSymbol ReduceExtensionMethod(TypeSymbol receiverType, CSharpCompilation compilation, out bool wasFullyInferred) { if ((object)receiverType == null) { @@ -749,10 +754,11 @@ public MethodSymbol ReduceExtensionMethod(TypeSymbol receiverType, CSharpCompila if (!this.IsExtensionMethod || this.MethodKind == MethodKind.ReducedExtension || receiverType.IsVoidType()) { + wasFullyInferred = false; return null; } - return ReducedExtensionMethodSymbol.Create(this, receiverType, compilation); + return ReducedExtensionMethodSymbol.Create(this, receiverType, compilation, out wasFullyInferred); } /// diff --git a/src/Compilers/CSharp/Portable/Symbols/ReducedExtensionMethodSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/ReducedExtensionMethodSymbol.cs index c1618634eb3b1..3cf406aa12b21 100644 --- a/src/Compilers/CSharp/Portable/Symbols/ReducedExtensionMethodSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/ReducedExtensionMethodSymbol.cs @@ -34,7 +34,7 @@ internal sealed class ReducedExtensionMethodSymbol : MethodSymbol /// /// Compilation used to check constraints. /// The latest language version is assumed if this is null. - public static MethodSymbol Create(MethodSymbol method, TypeSymbol receiverType, CSharpCompilation compilation) + public static MethodSymbol Create(MethodSymbol method, TypeSymbol receiverType, CSharpCompilation compilation, out bool wasFullyInferred) { Debug.Assert(method.IsExtensionMethod && method.MethodKind != MethodKind.ReducedExtension); Debug.Assert(method.ParameterCount > 0); @@ -42,7 +42,7 @@ public static MethodSymbol Create(MethodSymbol method, TypeSymbol receiverType, var useSiteInfo = CompoundUseSiteInfo.DiscardedDependencies; - method = InferExtensionMethodTypeArguments(method, receiverType, compilation, ref useSiteInfo); + method = InferExtensionMethodTypeArguments(method, receiverType, compilation, ref useSiteInfo, out wasFullyInferred); if ((object)method == null) { return null; @@ -109,19 +109,22 @@ private ReducedExtensionMethodSymbol(MethodSymbol reducedFrom) /// are not satisfied, the return value is null. /// /// Compilation used to check constraints. The latest language version is assumed if this is null. - private static MethodSymbol InferExtensionMethodTypeArguments(MethodSymbol method, TypeSymbol thisType, CSharpCompilation compilation, ref CompoundUseSiteInfo useSiteInfo) + private static MethodSymbol InferExtensionMethodTypeArguments(MethodSymbol method, TypeSymbol thisType, CSharpCompilation compilation, + ref CompoundUseSiteInfo useSiteInfo, out bool wasFullyInferred) { Debug.Assert(method.IsExtensionMethod); Debug.Assert((object)thisType != null); if (!method.IsGenericMethod || method != method.ConstructedFrom) { + wasFullyInferred = true; return method; } // We never resolve extension methods on a dynamic receiver. if (thisType.IsDynamic()) { + wasFullyInferred = false; return null; } @@ -158,6 +161,7 @@ private static MethodSymbol InferExtensionMethodTypeArguments(MethodSymbol metho if (typeArgs.IsDefault) { + wasFullyInferred = false; return null; } @@ -215,12 +219,14 @@ private static MethodSymbol InferExtensionMethodTypeArguments(MethodSymbol metho if (!success) { + wasFullyInferred = false; return null; } // For the purpose of construction we use original type parameters in place of type arguments that we couldn't infer from the first argument. ImmutableArray typeArgsForConstruct = typeArgs; - if (typeArgs.Any(static t => !t.HasType)) + wasFullyInferred = !typeArgs.Any(static t => !t.HasType); + if (!wasFullyInferred) { typeArgsForConstruct = typeArgs.ZipAsArray( method.TypeParameters, diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/DelegateTypeTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/DelegateTypeTests.cs index bdf8c38930102..3930d8709dba8 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/DelegateTypeTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/DelegateTypeTests.cs @@ -1141,9 +1141,7 @@ static void Main() @"#nullable enable internal object? F() => throw null!; #nullable disable", "internal new object F() => throw null;", "F", "F", null, "System.Func"); // different nullability - yield return getData("internal void F() { }", "internal void F() { }", "F", "F"); // different arity yield return getData("internal void F() { }", "internal void F() { }", "F", "F", null, "System.Action"); // different arity - yield return getData("internal void F() { }", "internal void F() { }", "F", "F"); // different arity yield return getData("internal void F() { }", "internal void F() { }", "F", "F", null, "System.Action"); // different arity yield return getData("internal void F() { }", "internal void F() { }", "F", "F", null, "System.Action"); // different arity yield return getData("internal void F() { }", "internal void F() { }", "F", "F", null, "System.Action"); // different arity @@ -1222,6 +1220,41 @@ partial class B : A Assert.Equal(SpecialType.System_Delegate, typeInfo.ConvertedType!.SpecialType); } + [Fact] + public void MethodGroup_BaseAndDerivedTypes_1() + { + var source = """ +new B().M(); + +partial class B +{ + public void M() + { + System.Delegate d = F; + System.Console.Write(d.GetDelegateTypeName()); + } +} +abstract class A +{ + internal void F() { } +} +partial class B : A +{ + internal void F() { } +} +"""; + var comp = CreateCompilation(new[] { source, s_utils }, parseOptions: TestOptions.RegularPreview, options: TestOptions.ReleaseExe); + CompileAndVerify(comp, expectedOutput: "System.Action"); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var expr = tree.GetRoot().DescendantNodes().OfType().Single().Initializer!.Value; + var typeInfo = model.GetTypeInfo(expr); + Assert.Null(typeInfo.Type); + Assert.Equal(SpecialType.System_Delegate, typeInfo.ConvertedType!.SpecialType); + } + public static IEnumerable GetExtensionMethodsSameScopeData() { yield return getData("internal static void F(this object x) { }", "internal static void F(this string x) { }", "string.Empty.F", "F", null, "B.F", "System.Action"); // different parameter type @@ -1231,9 +1264,7 @@ partial class B : A yield return getData("internal static void F(this object x, ref object y) { }", "internal static void F(this object x, object y) { }", "this.F", "F"); // different parameter ref kind yield return getData("internal static object F(this object x) => throw null;", "internal static ref object F(this object x) => throw null;", "this.F", "F"); // different return ref kind yield return getData("internal static ref object F(this object x) => throw null;", "internal static object F(this object x) => throw null;", "this.F", "F"); // different return ref kind - yield return getData("internal static void F(this object x, object y) { }", "internal static void F(this object x, T y) { }", "this.F", "F"); // different arity yield return getData("internal static void F(this object x, object y) { }", "internal static void F(this object x, T y) { }", "this.F", "F", null, "B.F", "System.Action"); // different arity - yield return getData("internal static void F(this object x) { }", "internal static void F(this object x) { }", "this.F", "F"); // different arity yield return getData("internal static void F(this object x) { }", "internal static void F(this object x) { }", "this.F", "F", null, "A.F", "System.Action"); // different arity yield return getData("internal static void F(this T t) where T : class { }", "internal static void F(this T t) { }", "this.F", "F", new[] @@ -1249,13 +1280,6 @@ partial class B : A // System.Delegate d = this.F; Diagnostic(ErrorCode.ERR_AmbigCall, "this.F").WithArguments("A.F(T)", "B.F(T)").WithLocation(5, 29) }); // different type parameter constraints - yield return getData("internal static void F(this T t) where T : class { }", "internal static void F(this T t) where T : struct { }", "this.F", "F", - new[] - { - // (5,34): error CS0123: No overload for 'F' matches delegate 'Action' - // System.Delegate d = this.F; - Diagnostic(ErrorCode.ERR_MethDelegateMismatch, "F").WithArguments("F", "System.Action").WithLocation(5, 34) - }); // different type parameter constraints static object?[] getData(string methodA, string methodB, string methodGroupExpression, string methodGroupOnly, DiagnosticDescription[]? expectedDiagnostics = null, string? expectedMethod = null, string? expectedType = null) { @@ -1321,6 +1345,135 @@ static class B Assert.Null(symbolInfo.Symbol); } + [Fact] + public void MethodGroup_ExtensionMethodsSameScope_1() + { + var source = """ +new Program().M(); + +partial class Program +{ + public void M() + { + System.Delegate d = this.F; + System.Console.Write("{0}: {1}", d.GetDelegateMethodName(), d.GetDelegateTypeName()); + } +} +static class A +{ + internal static void F(this object x, object y) { } +} +static class B +{ + internal static void F(this object x, T y) { } +} +"""; + var comp = CreateCompilation(new[] { source, s_utils }, parseOptions: TestOptions.RegularPreview, options: TestOptions.ReleaseExe); + comp.VerifyDiagnostics(); + // ILVerify: Unrecognized arguments for delegate .ctor. + CompileAndVerify(comp, expectedOutput: "A.F: System.Action", verify: Verification.FailsILVerify); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var expr = tree.GetRoot().DescendantNodes().OfType().Single().Initializer!.Value; + var typeInfo = model.GetTypeInfo(expr); + Assert.Null(typeInfo.Type); + Assert.Equal(SpecialType.System_Delegate, typeInfo.ConvertedType!.SpecialType); + + var symbolInfo = model.GetSymbolInfo(expr); + // https://github.com/dotnet/roslyn/issues/52870: GetSymbolInfo() should return resolved method from method group. + Assert.Null(symbolInfo.Symbol); + + Assert.Equal(new[] { "void System.Object.F(System.Object y)", "void System.Object.F(T y)" }, + model.GetMemberGroup(expr).ToTestDisplayStrings()); + } + + [Fact] + public void MethodGroup_ExtensionMethodsSameScope_2() + { + var source = """ +new Program().M(); + +partial class Program +{ + public void M() + { + System.Delegate d = this.F; + System.Console.Write("{0}: {1}", d.GetDelegateMethodName(), d.GetDelegateTypeName()); + } +} +static class A +{ + internal static void F(this object x) { } +} +static class B +{ + internal static void F(this object x) { } +} +"""; + var comp = CreateCompilation(new[] { source, s_utils }, parseOptions: TestOptions.RegularPreview, options: TestOptions.ReleaseExe); + comp.VerifyDiagnostics(); + // ILVerify: Unrecognized arguments for delegate .ctor. + CompileAndVerify(comp, expectedOutput: "B.F: System.Action", verify: Verification.FailsILVerify); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var expr = tree.GetRoot().DescendantNodes().OfType().Single().Initializer!.Value; + var typeInfo = model.GetTypeInfo(expr); + Assert.Null(typeInfo.Type); + Assert.Equal(SpecialType.System_Delegate, typeInfo.ConvertedType!.SpecialType); + + var symbolInfo = model.GetSymbolInfo(expr); + // https://github.com/dotnet/roslyn/issues/52870: GetSymbolInfo() should return resolved method from method group. + Assert.Null(symbolInfo.Symbol); + + Assert.Equal(new[] { "void System.Object.F()", "void System.Object.F()" }, + model.GetMemberGroup(expr).ToTestDisplayStrings()); + } + + [Fact] + public void MethodGroup_ExtensionMethodsSameScope_3() + { + var source = """ +new Program().M(); + +partial class Program +{ + public void M() + { + System.Delegate d = this.F; + System.Console.Write("{0}: {1}", d.GetDelegateMethodName(), d.GetDelegateTypeName()); + } +} +static class A +{ + internal static void F(this T t) where T : class { } +} +static class B +{ + internal static void F(this T t) where T : struct { } +} +"""; + var comp = CreateCompilation(new[] { source, s_utils }, parseOptions: TestOptions.RegularPreview, options: TestOptions.ReleaseExe); + comp.VerifyDiagnostics( + // 0.cs(7,34): error CS8917: The delegate type could not be inferred. + // System.Delegate d = this.F; + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "F").WithLocation(7, 34) + ); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var expr = tree.GetRoot().DescendantNodes().OfType().Single().Initializer!.Value; + var typeInfo = model.GetTypeInfo(expr); + Assert.Null(typeInfo.Type); + Assert.Equal(SpecialType.System_Delegate, typeInfo.ConvertedType!.SpecialType); + + var symbolInfo = model.GetSymbolInfo(expr); + // https://github.com/dotnet/roslyn/issues/52870: GetSymbolInfo() should return resolved method from method group. + Assert.Null(symbolInfo.Symbol); + Assert.Empty(model.GetMemberGroup(expr)); + } + public static IEnumerable GetExtensionMethodsDifferentScopeData_CSharp10() { yield return getData("internal static void F(this object x) { }", "internal static void F(this object x) { }", "this.F", "F", null, "A.F", "System.Action"); // hiding @@ -1428,13 +1581,6 @@ static class B yield return getData("internal static void F(this object x) { }", "internal static void F(this object x) { }", "this.F", "F", null, "A.F", "System.Action"); // different arity yield return getData("internal static void F(this T t) where T : class { }", "internal static void F(this T t) { }", "this.F", "F", null, "A.F", "System.Action"); // different type parameter constraints yield return getData("internal static void F(this T t) { }", "internal static void F(this T t) where T : class { }", "this.F", "F", null, "A.F", "System.Action"); // different type parameter constraints - yield return getData("internal static void F(this T t) where T : class { }", "internal static void F(this T t) where T : struct { }", "this.F", "F", - new[] - { - // (6,34): error CS0123: No overload for 'F' matches delegate 'Action' - // System.Delegate d = this.F; - Diagnostic(ErrorCode.ERR_MethDelegateMismatch, "F").WithArguments("F", "System.Action").WithLocation(6, 34) - }); // different type parameter constraints static object?[] getData(string methodA, string methodB, string methodGroupExpression, string methodGroupOnly, DiagnosticDescription[]? expectedDiagnostics = null, string? expectedMethod = null, string? expectedType = null) { @@ -1838,15 +1984,56 @@ static class B internal static void F(this object x) { } } } +"""; + var comp = CreateCompilation(new[] { source, s_utils }, parseOptions: useCSharp13 ? TestOptions.RegularNext : TestOptions.RegularPreview); + comp.VerifyDiagnostics(); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var expr = tree.GetRoot().DescendantNodes().OfType().Single().Initializer!.Value; + var typeInfo = model.GetTypeInfo(expr); + Assert.Null(typeInfo.Type); + Assert.Equal(SpecialType.System_Delegate, typeInfo.ConvertedType!.SpecialType); + + var symbolInfo = model.GetSymbolInfo(expr); + // https://github.com/dotnet/roslyn/issues/52870: GetSymbolInfo() should return resolved method from method group. + Assert.Null(symbolInfo.Symbol); + } + + [Theory, CombinatorialData] + public void MethodGroup_ExtensionMethodsDifferentScope_CSharp13_9(bool useCSharp13) + { + var source = """ +using N; + +new Program().M(); + +partial class Program +{ + public void M() + { + System.Delegate d = this.F; + System.Console.Write("{0}: {1}", d.GetDelegateMethodName(), d.GetDelegateTypeName()); + } +} + +static class A +{ + internal static void F(this T t) where T : class { } +} +namespace N +{ + static class B + { + internal static void F(this T t) where T : struct { } + } +} """; var comp = CreateCompilation(new[] { source, s_utils }, parseOptions: useCSharp13 ? TestOptions.RegularNext : TestOptions.RegularPreview); comp.VerifyDiagnostics( - // 0.cs(1,1): hidden CS8019: Unnecessary using directive. - // using N; - Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using N;").WithLocation(1, 1), // 0.cs(9,34): error CS8917: The delegate type could not be inferred. - // System.Delegate d = this.F; - Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "F").WithLocation(9, 34) + // System.Delegate d = this.F; + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "F").WithLocation(9, 34) ); var tree = comp.SyntaxTrees[0]; @@ -2291,9 +2478,6 @@ public static void M(this C c) { } public void MethodGroup_ScopeByScope_NoTypeArguments_ExtensionMethodHasZeroArity(bool useCSharp13) { var source = """ -System.Action x = new C().M; -x(); - var z = new C().M; z(); @@ -2306,31 +2490,27 @@ public static class E { public static void M(this C c) { - System.Console.Write("E.M "); + System.Console.Write("E.M"); } } """; var comp = CreateCompilation(source, parseOptions: TestOptions.Regular12); comp.VerifyDiagnostics( - // (4,9): error CS8917: The delegate type could not be inferred. + // (1,9): error CS8917: The delegate type could not be inferred. // var z = new C().M; - Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "new C().M").WithLocation(4, 9) + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "new C().M").WithLocation(1, 9) ); comp = CreateCompilation(source, parseOptions: useCSharp13 ? TestOptions.RegularNext : TestOptions.RegularPreview); - comp.VerifyDiagnostics( - // (4,9): error CS8917: The delegate type could not be inferred. - // var z = new C().M; - Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "new C().M").WithLocation(4, 9) - ); + comp.VerifyDiagnostics(); var tree = comp.SyntaxTrees[0]; var model = comp.GetSemanticModel(tree); var memberAccess = GetSyntaxes(tree, "new C().M").Last(); var typeInfo = model.GetTypeInfo(memberAccess); Assert.Null(typeInfo.Type); - Assert.True(typeInfo.ConvertedType!.IsErrorType()); - Assert.Null(model.GetSymbolInfo(memberAccess).Symbol); + Assert.Equal("System.Action", typeInfo.ConvertedType!.ToTestDisplayString()); + Assert.Equal("void C.M()", model.GetSymbolInfo(memberAccess).Symbol.ToTestDisplayString()); Assert.Equal(["void C.M()", "void C.M()"], model.GetMemberGroup(memberAccess).ToTestDisplayStrings()); } @@ -2369,19 +2549,15 @@ public static class E2 ); comp = CreateCompilation(source, parseOptions: useCSharp13 ? TestOptions.RegularNext : TestOptions.RegularPreview); - comp.VerifyDiagnostics( - // (6,9): error CS8917: The delegate type could not be inferred. - // var z = new C().M; - Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "new C().M").WithLocation(6, 9) - ); + comp.VerifyDiagnostics(); var tree = comp.SyntaxTrees[0]; var model = comp.GetSemanticModel(tree); var memberAccess = GetSyntaxes(tree, "new C().M").Last(); var typeInfo = model.GetTypeInfo(memberAccess); Assert.Null(typeInfo.Type); - Assert.True(typeInfo.ConvertedType!.IsErrorType()); - Assert.Null(model.GetSymbolInfo(memberAccess).Symbol); + Assert.Equal("System.Action", typeInfo.ConvertedType!.ToTestDisplayString()); + Assert.Equal("void C.M()", model.GetSymbolInfo(memberAccess).Symbol.ToTestDisplayString()); Assert.Equal(["void C.M()", "void C.M()"], model.GetMemberGroup(memberAccess).ToTestDisplayStrings()); } @@ -2423,10 +2599,7 @@ public static class E2 comp.VerifyDiagnostics( // (1,1): hidden CS8019: Unnecessary using directive. // using N; - Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using N;").WithLocation(1, 1), - // (6,9): error CS8917: The delegate type could not be inferred. - // var z = new C().M; - Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "new C().M").WithLocation(6, 9) + Diagnostic(ErrorCode.HDN_UnusedUsingDirective, "using N;").WithLocation(1, 1) ); var tree = comp.SyntaxTrees[0]; @@ -2434,8 +2607,8 @@ public static class E2 var memberAccess = GetSyntaxes(tree, "new C().M").Last(); var typeInfo = model.GetTypeInfo(memberAccess); Assert.Null(typeInfo.Type); - Assert.True(typeInfo.ConvertedType!.IsErrorType()); - Assert.Null(model.GetSymbolInfo(memberAccess).Symbol); + Assert.Equal("System.Action", typeInfo.ConvertedType!.ToTestDisplayString()); + Assert.Equal("void C.M()", model.GetSymbolInfo(memberAccess).Symbol.ToTestDisplayString()); Assert.Equal(["void C.M()", "void C.M()"], model.GetMemberGroup(memberAccess).ToTestDisplayStrings()); } @@ -2643,11 +2816,15 @@ public static void M(this T t) } """; var comp = CreateCompilation(source); - comp.VerifyDiagnostics( - // (1,9): error CS8917: The delegate type could not be inferred. - // var d = new object().M; - Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "new object().M").WithLocation(1, 9) - ); + comp.VerifyDiagnostics(); + // ILVerify: Unrecognized arguments for delegate .ctor. + CompileAndVerify(comp, expectedOutput: "ran", verify: Verification.FailsILVerify); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var memberAccess = GetSyntax(tree, "new object().M"); + Assert.Equal("void System.Object.M()", model.GetSymbolInfo(memberAccess).Symbol.ToTestDisplayString()); + Assert.Equal(new[] { "void System.Object.M()" }, model.GetMemberGroup(memberAccess).ToTestDisplayStrings()); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/69222")] @@ -2666,13 +2843,48 @@ public static void M(this C t) System.Console.Write("ran"); } } +"""; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + // ILVerify: Unrecognized arguments for delegate .ctor. + CompileAndVerify(comp, expectedOutput: "ran", verify: Verification.FailsILVerify); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var memberAccess = GetSyntax(tree, "new C().M"); + Assert.Equal("void C.M()", + model.GetSymbolInfo(memberAccess).Symbol.ToTestDisplayString()); + + Assert.Equal(new[] { "void C.M()" }, + model.GetMemberGroup(memberAccess).ToTestDisplayStrings()); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/69222")] + public void MethodGroup_GenericExtensionMethod_UnsubstitutedTypeParameter() + { + var source = """ +var d = new C().M; +d(); + +class C { } + +static class E +{ + public static void M(this C c) { } +} """; var comp = CreateCompilation(source); comp.VerifyDiagnostics( // (1,9): error CS8917: The delegate type could not be inferred. - // var d = new C().M; - Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "new C().M").WithLocation(1, 9) + // var d = new C().M; + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "new C().M").WithLocation(1, 9) ); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var memberAccess = GetSyntax(tree, "new C().M"); + Assert.Null(model.GetSymbolInfo(memberAccess).Symbol); + Assert.Equal(new[] { "void C.M()" }, model.GetMemberGroup(memberAccess).ToTestDisplayStrings()); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/69222")] @@ -2680,19 +2892,268 @@ public void MethodGroup_GenericInstanceMethod_Constraint() { var source = """ var x = new C().M; +x(); + +public class C +{ + public void M() + { + System.Console.Write("ran"); + } + + public void M(object o) where T : class { } +} +"""; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + CompileAndVerify(comp, expectedOutput: "ran"); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var memberAccess = GetSyntax(tree, "new C().M"); + Assert.Equal("void C.M()", model.GetSymbolInfo(memberAccess).Symbol.ToTestDisplayString()); + + Assert.Equal(new[] { "void C.M()", "void C.M(System.Object o)" }, + model.GetMemberGroup(memberAccess).ToTestDisplayStrings()); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/69222")] + public void MethodGroup_GenericInstanceMethod_Constraint_Nullability_Ambiguity() + { + var source = """ +#nullable enable +var x = new C().M; +x(); public class C { public void M() { } + public void M(object o) where T : class { } } """; var comp = CreateCompilation(source); comp.VerifyDiagnostics( - // (1,9): error CS8917: The delegate type could not be inferred. - // var x = new C().M; - Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "new C().M").WithLocation(1, 9) + // (2,9): error CS8917: The delegate type could not be inferred. + // var x = new C().M; + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "new C().M").WithLocation(2, 9) ); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var memberAccess = GetSyntax(tree, "new C().M"); + Assert.Null(model.GetSymbolInfo(memberAccess).Symbol); + + Assert.Equal(new[] { "void C.M()", "void C.M(System.Object o)" }, + model.GetMemberGroup(memberAccess).ToTestDisplayStrings()); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/69222")] + public void MethodGroup_GenericInstanceMethod_Constraint_Nullability_Warn() + { + var source = """ +#nullable enable +var x = new C().M; +x(); + +public class C +{ + public void M() where T : class + { + System.Console.Write("ran"); + } +} +"""; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (2,9): warning CS8634: The type 'object?' cannot be used as type parameter 'T' in the generic type or method 'C.M()'. Nullability of type argument 'object?' doesn't match 'class' constraint. + // var x = new C().M; + Diagnostic(ErrorCode.WRN_NullabilityMismatchInTypeParameterReferenceTypeConstraint, "new C().M").WithArguments("C.M()", "T", "object?").WithLocation(2, 9) + ); + CompileAndVerify(comp, expectedOutput: "ran"); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var memberAccess = GetSyntax(tree, "new C().M"); + Assert.Equal("void C.M()", model.GetSymbolInfo(memberAccess).Symbol.ToTestDisplayString()); + Assert.Equal(new[] { "void C.M()" }, model.GetMemberGroup(memberAccess).ToTestDisplayStrings()); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/69222")] + public void MethodGroup_GenericExtensionMethod_Constraint_ImplicitTypeArguments() + { + var source = """ +var x = new object().M; +x(); + +static class E1 +{ + public static void M(this T t) + { + System.Console.Write("ran"); + } +} + +static class E2 +{ + public static void M(this T t, object ignored) where T : struct { } +} +"""; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + // ILVerify: Unrecognized arguments for delegate .ctor. + CompileAndVerify(comp, expectedOutput: "ran", verify: Verification.FailsILVerify); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var memberAccess = GetSyntax(tree, "new object().M"); + Assert.Equal("void System.Object.M()", model.GetSymbolInfo(memberAccess).Symbol.ToTestDisplayString()); + Assert.Equal(new[] { "void System.Object.M()" }, model.GetMemberGroup(memberAccess).ToTestDisplayStrings()); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/69222")] + public void MethodGroup_GenericExtensionMethod_Constraint_ExplicitTypeArguments() + { + var source = """ +var x = new object().M; +x(); + +static class E1 +{ + public static void M(this T t) + { + System.Console.Write("ran"); + } +} + +static class E2 +{ + public static void M(this T t, object ignored) where T : struct { } +} +"""; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics(); + // ILVerify: Unrecognized arguments for delegate .ctor. + CompileAndVerify(comp, expectedOutput: "ran", verify: Verification.FailsILVerify); + + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + var memberAccess = GetSyntax(tree, "new object().M"); + Assert.Equal("void System.Object.M()", model.GetSymbolInfo(memberAccess).Symbol.ToTestDisplayString()); + + Assert.Equal(new[] { "void System.Object.M()", "void System.Object.M(System.Object ignored)" }, + model.GetMemberGroup(memberAccess).ToTestDisplayStrings()); + } + + [Fact] + public void GenericExtensionMethod_ArityCountsInSignature() + { + // Arity counts as part of "signature" so we have two different signatures even after reducing extension methods + var source = """ +var x = new object().F; + +static class B +{ + internal static void F(this T x) { } +} + +static class A +{ + internal static void F(this object x) { } +} +"""; + CreateCompilation(source, parseOptions: TestOptions.Regular11).VerifyDiagnostics( + // (1,9): error CS8917: The delegate type could not be inferred. + // var x = new object().F; + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "new object().F").WithLocation(1, 9)); + + CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics( + // (1,9): error CS8917: The delegate type could not be inferred. + // var x = new object().F; + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "new object().F").WithLocation(1, 9)); + } + + [Fact] + public void GenericExtensionMethod_Constraint() + { + // In C# 12, a method group that cannot be successfully substituted (ie. respecting constraints) + // does not contribute to the natural type determination + var source = """ +var x = new C().F; + +class C { } + +static class E +{ + public static void F(this C c) where T : struct { } +} +"""; + CreateCompilation(source, parseOptions: TestOptions.Regular11).VerifyDiagnostics( + // (1,9): error CS0453: The type 'object' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'E.F(C)' + // var x = new C().F; + Diagnostic(ErrorCode.ERR_ValConstraintNotSatisfied, "new C().F").WithArguments("E.F(C)", "T", "object").WithLocation(1, 9)); + + CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics( + // (1,9): error CS8917: The delegate type could not be inferred. + // var x = new C().F; + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "new C().F").WithLocation(1, 9)); + } + + [Fact] + public void GenericInstanceMethod_Constraint() + { + // In C# 12, a method that cannot be successfully substituted (ie. respecting constraints) + // does not contribute to the natural type determination + var source = """ +var x = new C().F; + +class C +{ + public void F() where T : struct { } +} +"""; + CreateCompilation(source, parseOptions: TestOptions.Regular11).VerifyDiagnostics( + // (1,9): error CS0453: The type 'object' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'C.F()' + // var x = new C().F; + Diagnostic(ErrorCode.ERR_ValConstraintNotSatisfied, "new C().F").WithArguments("C.F()", "T", "object").WithLocation(1, 9)); + + CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics( + // (1,9): error CS8917: The delegate type could not be inferred. + // var x = new C().F; + Diagnostic(ErrorCode.ERR_CannotInferDelegateType, "new C().F").WithLocation(1, 9)); + } + + [Fact, WorkItem("https://github.com/dotnet/csharplang/discussions/129")] + public void BoundInferenceFromMethodGroup() + { + var source = """ +int result1 = Test(IsEven); // 1 +bool result2 = Test2(IsEven); +int result3 = Test3(IsEven); // 2 +bool result4 = Test4(IsEven); +System.Func result5 = Test5(IsEven); + +delegate bool Predicate(T t); +delegate T Predicate2(int i); + +partial class Program +{ + public static bool IsEven(int x) => x % 2 == 0; + + public static T Test(System.Func predicate) => throw null; + public static T Test2(System.Func predicate) => throw null; + public static T Test3(Predicate predicate) => throw null; + public static T Test4(Predicate2 predicate) => throw null; + public static T Test5(T predicate) => throw null; +} +"""; + CreateCompilation(source).VerifyDiagnostics( + // (1,15): error CS0411: The type arguments for method 'Program.Test(Func)' cannot be inferred from the usage. Try specifying the type arguments explicitly. + // int result1 = Test(IsEven); // 1 + Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "Test").WithArguments("Program.Test(System.Func)").WithLocation(1, 15), + // (3,15): error CS0411: The type arguments for method 'Program.Test3(Predicate)' cannot be inferred from the usage. Try specifying the type arguments explicitly. + // int result3 = Test3(IsEven); // 2 + Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "Test3").WithArguments("Program.Test3(Predicate)").WithLocation(3, 15)); } [Fact]