From 03f70f7fcbd5efefd768180dae9fb2b63014a883 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Mon, 4 Nov 2024 09:56:03 -0800 Subject: [PATCH 1/4] Adjust semantic model for method group conversion --- .../Compilation/CSharpSemanticModel.cs | 6 + .../Test/Symbol/Symbols/ConversionTests.cs | 107 ++++++++++++++++++ 2 files changed, 113 insertions(+) diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs index c3405df8f8be7..e4209912c42ad 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs @@ -4294,6 +4294,12 @@ private OneOrMany GetMethodGroupSemanticSymbols( // we want to get the symbol that overload resolution chose for M, not the whole method group M. var conversion = (BoundConversion)boundNodeForSyntacticParent; + if (conversion.ConversionKind is not ConversionKind.MethodGroup && conversion.Operand is BoundConversion nestedConversion) + { + Debug.Assert(conversion.ConversionKind is ConversionKind.NoConversion || conversion.ExplicitCastInCode); + conversion = nestedConversion; + } + var method = conversion.SymbolOpt; if ((object)method != null) { diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/ConversionTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/ConversionTests.cs index c461c0a0129c9..1bf8644bd0d43 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/ConversionTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/ConversionTests.cs @@ -13,6 +13,7 @@ using Roslyn.Test.Utilities; using Xunit; using Basic.Reference.Assemblies; +using Microsoft.CodeAnalysis.Test.Utilities; namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Symbols { @@ -425,6 +426,112 @@ static void Main() Assert.True(conversion.IsNumeric); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/36377")] + public void GetSymbolInfo_ExplicitCastOnMethodGroup() + { + var src = """ +public sealed class C +{ + public static void M() + { + C x = (C)C.Test; + } + + public static int Test() => 1; + + public static explicit operator C(System.Func intDelegate) + { + return new C(); + } +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics(); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + var memberAccess = GetSyntax(tree, "C.Test"); + Assert.Equal("System.Int32 C.Test()", model.GetSymbolInfo(memberAccess).Symbol.ToTestDisplayString()); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/36377")] + public void GetSymbolInfo_TwoExplicitCastsOnMethodGroup() + { + var src = """ +public sealed class C +{ + public static void M() + { + D x = (D)(C)C.Test; + } + + public static int Test() => 1; + + public static explicit operator C(System.Func intDelegate) => throw null; +} +public sealed class D +{ + public static explicit operator D(C c) => throw null; +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics(); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + var memberAccess = GetSyntax(tree, "C.Test"); + Assert.Equal("System.Int32 C.Test()", model.GetSymbolInfo(memberAccess).Symbol.ToTestDisplayString()); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/36377")] + public void GetSymbolInfo_NoConversion() + { + var src = """ +public sealed class C +{ + public static void M() + { + int x = C.Test; + } + + public static int Test() => 1; +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (5,19): error CS0428: Cannot convert method group 'Test' to non-delegate type 'int'. Did you intend to invoke the method? + // int x = C.Test; + Diagnostic(ErrorCode.ERR_MethGrpToNonDel, "Test").WithArguments("Test", "int").WithLocation(5, 19)); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + var memberAccess = GetSyntax(tree, "C.Test"); + Assert.Null(model.GetSymbolInfo(memberAccess).Symbol); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/36377")] + public void GetSymbolInfo_MethodGroupConversion() + { + var src = """ +public sealed class C +{ + public static void M() + { + System.Func x = C.Test; + } + + public static int Test() => 1; +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics(); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + var memberAccess = GetSyntax(tree, "C.Test"); + Assert.Equal("System.Int32 C.Test()", model.GetSymbolInfo(memberAccess).Symbol.ToTestDisplayString()); + } + #region "Diagnostics" [Fact] public void VarianceRelationFail() From 90f20a3d535f51f4e58472745aa40cdea5966115 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Fri, 8 Nov 2024 10:09:54 -0800 Subject: [PATCH 2/4] Address feedback --- .../Compilation/CSharpSemanticModel.cs | 13 +-- .../Test/Symbol/Symbols/ConversionTests.cs | 97 ++++++++++++++++++- .../Test/Semantic/Semantics/Conversions.vb | 28 ++++++ 3 files changed, 131 insertions(+), 7 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs index e4209912c42ad..4ab3ceaa48867 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs @@ -4294,17 +4294,18 @@ private OneOrMany GetMethodGroupSemanticSymbols( // we want to get the symbol that overload resolution chose for M, not the whole method group M. var conversion = (BoundConversion)boundNodeForSyntacticParent; - if (conversion.ConversionKind is not ConversionKind.MethodGroup && conversion.Operand is BoundConversion nestedConversion) + MethodSymbol method = null; + if (conversion.ConversionKind == ConversionKind.MethodGroup) { - Debug.Assert(conversion.ConversionKind is ConversionKind.NoConversion || conversion.ExplicitCastInCode); - conversion = nestedConversion; + method = conversion.SymbolOpt; + } + else if (conversion.Operand is BoundConversion { ConversionKind: ConversionKind.MethodGroup } nestedMethodGroupConversion) + { + method = nestedMethodGroupConversion.SymbolOpt; } - var method = conversion.SymbolOpt; if ((object)method != null) { - Debug.Assert(conversion.ConversionKind == ConversionKind.MethodGroup); - if (conversion.IsExtensionMethod) { method = ReducedExtensionMethodSymbol.Create(method); diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/ConversionTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/ConversionTests.cs index 1bf8644bd0d43..b74116fbc2052 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/ConversionTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/ConversionTests.cs @@ -430,7 +430,7 @@ static void Main() public void GetSymbolInfo_ExplicitCastOnMethodGroup() { var src = """ -public sealed class C +public class C { public static void M() { @@ -454,6 +454,101 @@ public static explicit operator C(System.Func intDelegate) Assert.Equal("System.Int32 C.Test()", model.GetSymbolInfo(memberAccess).Symbol.ToTestDisplayString()); } + [Fact] + public void GetSymbolInfo_ImplicitUserDefinedConversionOnMethodGroup() + { + var src = """ +public class C +{ + public static void M() + { + C x = C.Test; + } + + public static int Test() => 1; + + public static implicit operator C(System.Func intDelegate) + { + return new C(); + } +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (5,17): error CS0428: Cannot convert method group 'Test' to non-delegate type 'C'. Did you intend to invoke the method? + // C x = C.Test; + Diagnostic(ErrorCode.ERR_MethGrpToNonDel, "Test").WithArguments("Test", "C").WithLocation(5, 17)); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + var memberAccess = GetSyntax(tree, "C.Test"); + Assert.Equal("C C.op_Implicit(System.Func intDelegate)", model.GetSymbolInfo(memberAccess).Symbol.ToTestDisplayString()); // Unexpected: Should be "void C.Test()" + var conversion = model.GetConversion(memberAccess); + Assert.Equal(ConversionKind.ExplicitUserDefined, conversion.Kind); + Assert.Equal(ConversionKind.MethodGroup, conversion.UserDefinedFromConversion.Kind); + Assert.Equal(ConversionKind.Identity, conversion.UserDefinedToConversion.Kind); + } + + [Fact] + public void GetSymbolInfo_ImplicitUserDefinedConversionOnMethodGroup_WithToConversion() + { + var src = """ +public struct C +{ + public static void M() + { + C? x = C.Test; + } + + public static int Test() => 1; + + public static implicit operator C(System.Func intDelegate) + { + return new C(); + } +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (5,18): error CS0428: Cannot convert method group 'Test' to non-delegate type 'C?'. Did you intend to invoke the method? + // C? x = C.Test; + Diagnostic(ErrorCode.ERR_MethGrpToNonDel, "Test").WithArguments("Test", "C?").WithLocation(5, 18)); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + var memberAccess = GetSyntax(tree, "C.Test"); + Assert.Null(model.GetSymbolInfo(memberAccess).Symbol); // Unexpected: Should be "void C.Test()" + var conversion = model.GetConversion(memberAccess); + Assert.Equal(ConversionKind.ExplicitUserDefined, conversion.Kind); + Assert.Equal(ConversionKind.MethodGroup, conversion.UserDefinedFromConversion.Kind); + Assert.Equal(ConversionKind.Identity, conversion.UserDefinedToConversion.Kind); // Unexpected: Should be a ImplicitNullable conversion + } + + [Fact] + public void GetSymbolInfo_MethodGroupConversionInLocalDeclaration() + { + var src = """ +public class C +{ + public static void M() + { + System.Func x = C.Test; + } + + public static int Test() => 1; +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics(); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + var memberAccess = GetSyntax(tree, "C.Test"); + Assert.Equal("System.Int32 C.Test()", model.GetSymbolInfo(memberAccess).Symbol.ToTestDisplayString()); + var conversion = model.GetConversion(memberAccess); + Assert.Equal(ConversionKind.MethodGroup, conversion.Kind); + } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/36377")] public void GetSymbolInfo_TwoExplicitCastsOnMethodGroup() { diff --git a/src/Compilers/VisualBasic/Test/Semantic/Semantics/Conversions.vb b/src/Compilers/VisualBasic/Test/Semantic/Semantics/Conversions.vb index fcf63a3f1fe0b..009b9297c2c8e 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Semantics/Conversions.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Semantics/Conversions.vb @@ -5154,5 +5154,33 @@ True CompileAndVerify(compilation, expectedOutput:=expectedOutput).VerifyDiagnostics() End Sub + + Public Sub GetSymbolInfo_ExplicitCastOnMethodGroup() + Dim compilation = CreateCompilation( + + + ) + + compilation.AssertTheseEmitDiagnostics( +BC30581: 'AddressOf' expression cannot be converted to 'C' because 'C' is not a delegate type. + Dim x As C = DirectCast(AddressOf C.Test, C) + ~~~~~~~~~~~~~~~~ +) + End Sub + End Class End Namespace From ff612538d2efdab1852810221188f07fbc4e6bf5 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Fri, 8 Nov 2024 12:10:10 -0800 Subject: [PATCH 3/4] Add few more tests for existing issue and clarify expectations --- .../Test/Symbol/Symbols/ConversionTests.cs | 157 +++++++++++++++++- 1 file changed, 148 insertions(+), 9 deletions(-) diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/ConversionTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/ConversionTests.cs index b74116fbc2052..ee425b79b53d3 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/ConversionTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/ConversionTests.cs @@ -454,8 +454,8 @@ public static explicit operator C(System.Func intDelegate) Assert.Equal("System.Int32 C.Test()", model.GetSymbolInfo(memberAccess).Symbol.ToTestDisplayString()); } - [Fact] - public void GetSymbolInfo_ImplicitUserDefinedConversionOnMethodGroup() + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75833")] + public void GetSymbolInfo_ImplicitUserDefinedConversionOnMethodGroup_InLocalDeclaration() { var src = """ public class C @@ -482,15 +482,15 @@ public static implicit operator C(System.Func intDelegate) var tree = comp.SyntaxTrees.Single(); var model = comp.GetSemanticModel(tree); var memberAccess = GetSyntax(tree, "C.Test"); - Assert.Equal("C C.op_Implicit(System.Func intDelegate)", model.GetSymbolInfo(memberAccess).Symbol.ToTestDisplayString()); // Unexpected: Should be "void C.Test()" + Assert.Equal("C C.op_Implicit(System.Func intDelegate)", model.GetSymbolInfo(memberAccess).Symbol.ToTestDisplayString()); // Unexpected: Should be null var conversion = model.GetConversion(memberAccess); - Assert.Equal(ConversionKind.ExplicitUserDefined, conversion.Kind); + Assert.Equal(ConversionKind.ExplicitUserDefined, conversion.Kind); // Unexpected: Should be NoConversion or possibly Identity for error case Assert.Equal(ConversionKind.MethodGroup, conversion.UserDefinedFromConversion.Kind); Assert.Equal(ConversionKind.Identity, conversion.UserDefinedToConversion.Kind); } - [Fact] - public void GetSymbolInfo_ImplicitUserDefinedConversionOnMethodGroup_WithToConversion() + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75833")] + public void GetSymbolInfo_ImplicitUserDefinedConversionOnMethodGroup_WithToConversion_InLocalDeclaration() { var src = """ public struct C @@ -517,11 +517,150 @@ public static implicit operator C(System.Func intDelegate) var tree = comp.SyntaxTrees.Single(); var model = comp.GetSemanticModel(tree); var memberAccess = GetSyntax(tree, "C.Test"); - Assert.Null(model.GetSymbolInfo(memberAccess).Symbol); // Unexpected: Should be "void C.Test()" + Assert.Null(model.GetSymbolInfo(memberAccess).Symbol); + var conversion = model.GetConversion(memberAccess); + Assert.Equal(ConversionKind.ExplicitUserDefined, conversion.Kind); // Unexpected: Should be NoConversion or possibly Identity for error case + Assert.Equal(ConversionKind.MethodGroup, conversion.UserDefinedFromConversion.Kind); + Assert.Equal(ConversionKind.Identity, conversion.UserDefinedToConversion.Kind); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75833")] + public void GetSymbolInfo_ImplicitUserDefinedConversionOnMethodGroup_InAssignemnt() + { + var src = """ +public class C +{ + public static void M() + { + C x; + x = C.Test; + } + + public static int Test() => 1; + + public static implicit operator C(System.Func intDelegate) + { + return new C(); + } +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (6,15): error CS0428: Cannot convert method group 'Test' to non-delegate type 'C'. Did you intend to invoke the method? + // x = C.Test; + Diagnostic(ErrorCode.ERR_MethGrpToNonDel, "Test").WithArguments("Test", "C").WithLocation(6, 15)); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + var memberAccess = GetSyntax(tree, "C.Test"); + Assert.Equal("C C.op_Implicit(System.Func intDelegate)", model.GetSymbolInfo(memberAccess).Symbol.ToTestDisplayString()); // Unexpected: Should be null + var conversion = model.GetConversion(memberAccess); + Assert.Equal(ConversionKind.ExplicitUserDefined, conversion.Kind); // Unexpected: Should be NoConversion or possibly Identity for error case + Assert.Equal(ConversionKind.MethodGroup, conversion.UserDefinedFromConversion.Kind); + Assert.Equal(ConversionKind.Identity, conversion.UserDefinedToConversion.Kind); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75833")] + public void GetSymbolInfo_ImplicitUserDefinedConversionOnMethodGroup_WithToConversion_InAssignment() + { + var src = """ +public struct C +{ + public static void M() + { + C? x; + x = C.Test; + } + + public static int Test() => 1; + + public static implicit operator C(System.Func intDelegate) + { + return new C(); + } +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (6,15): error CS0428: Cannot convert method group 'Test' to non-delegate type 'C?'. Did you intend to invoke the method? + // x = C.Test; + Diagnostic(ErrorCode.ERR_MethGrpToNonDel, "Test").WithArguments("Test", "C?").WithLocation(6, 15)); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + var memberAccess = GetSyntax(tree, "C.Test"); + Assert.Null(model.GetSymbolInfo(memberAccess).Symbol); // Unexpected: Should be null var conversion = model.GetConversion(memberAccess); - Assert.Equal(ConversionKind.ExplicitUserDefined, conversion.Kind); + Assert.Equal(ConversionKind.ExplicitUserDefined, conversion.Kind); // Unexpected: Should be NoConversion or possibly Identity for error case Assert.Equal(ConversionKind.MethodGroup, conversion.UserDefinedFromConversion.Kind); - Assert.Equal(ConversionKind.Identity, conversion.UserDefinedToConversion.Kind); // Unexpected: Should be a ImplicitNullable conversion + Assert.Equal(ConversionKind.Identity, conversion.UserDefinedToConversion.Kind); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75833")] + public void GetSymbolInfo_ImplicitUserDefinedConversionOnMethodGroup_InInvocationArgument() + { + var src = """ +public class C +{ + public static void M() + { + M2(C.Test); + } + + public static void M2(C c) { } + + public static int Test() => 1; + + public static implicit operator C(System.Func intDelegate) => throw null; +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (5,12): error CS1503: Argument 1: cannot convert from 'method group' to 'C' + // M2(C.Test); + Diagnostic(ErrorCode.ERR_BadArgType, "C.Test").WithArguments("1", "method group", "C").WithLocation(5, 12)); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + var memberAccess = GetSyntax(tree, "C.Test"); + Assert.Null(model.GetSymbolInfo(memberAccess).Symbol); + var conversion = model.GetConversion(memberAccess); + Assert.Equal(ConversionKind.Identity, conversion.Kind); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/75833")] + public void GetSymbolInfo_ImplicitUserDefinedConversionOnMethodGroup_WithToConversion_InInvocationArgument() + { + var src = """ +public struct C +{ + public static void M() + { + M2(C.Test); + } + + public static void M2(C? c) { } + + public static int Test() => 1; + + public static implicit operator C(System.Func intDelegate) + { + return new C(); + } +} +"""; + var comp = CreateCompilation(src); + comp.VerifyEmitDiagnostics( + // (5,12): error CS1503: Argument 1: cannot convert from 'method group' to 'C?' + // M2(C.Test); + Diagnostic(ErrorCode.ERR_BadArgType, "C.Test").WithArguments("1", "method group", "C?").WithLocation(5, 12)); + + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + var memberAccess = GetSyntax(tree, "C.Test"); + Assert.Null(model.GetSymbolInfo(memberAccess).Symbol); + var conversion = model.GetConversion(memberAccess); + Assert.Equal(ConversionKind.Identity, conversion.Kind); } [Fact] From 29f5823ded69c0131acb0818ee9f577aa23df64f Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Fri, 8 Nov 2024 12:18:08 -0800 Subject: [PATCH 4/4] Semantic check in VB error scenario too --- .../VisualBasic/Test/Semantic/Semantics/Conversions.vb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Compilers/VisualBasic/Test/Semantic/Semantics/Conversions.vb b/src/Compilers/VisualBasic/Test/Semantic/Semantics/Conversions.vb index 009b9297c2c8e..89998c076ec36 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/Semantics/Conversions.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/Semantics/Conversions.vb @@ -5180,6 +5180,12 @@ BC30581: 'AddressOf' expression cannot be converted to 'C' because 'C' is not a Dim x As C = DirectCast(AddressOf C.Test, C) ~~~~~~~~~~~~~~~~ ) + + Dim tree = compilation.SyntaxTrees.Single() + Dim model = compilation.GetSemanticModel(tree) + Dim syntax = tree.GetRoot().DescendantNodes().OfType(Of UnaryExpressionSyntax)().Single() + Assert.Null(model.GetSymbolInfo(syntax).Symbol) + Assert.Null(model.GetSymbolInfo(syntax.Operand).Symbol) End Sub End Class