From c75fc55571b8d7c29121d643253fa5f00cef87f7 Mon Sep 17 00:00:00 2001 From: AlekseyTs Date: Mon, 20 Oct 2025 15:41:26 -0700 Subject: [PATCH 1/4] Fix handling of SymbolDisplayCompilerInternalOptions.UseArityForGenericTypes option for non-native symbol impolementations Fixes #80165 --- .../SymbolDisplayVisitor.Types.cs | 15 ++-- .../SymbolDisplay/SymbolDisplayTests.cs | 77 +++++++++++++++++++ .../SymbolDisplayVisitor.Types.vb | 14 ++-- .../SymbolDisplay/SymbolDisplayTests.vb | 71 +++++++++++++++++ 4 files changed, 167 insertions(+), 10 deletions(-) diff --git a/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs b/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs index fe0462cc6d6b2..8bdd99b6cf47b 100644 --- a/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs +++ b/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs @@ -402,12 +402,17 @@ private void AddNameAndTypeArgumentsOrParameters(INamedTypeSymbol symbol) if (Format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.UseArityForGenericTypes)) { - // Only the compiler can set the internal option and the compiler doesn't use other implementations of INamedTypeSymbol. - if (underlyingTypeSymbol?.MangleName == true) + if (symbol.Arity > 0) { - Debug.Assert(symbol.Arity > 0); - Builder.Add(CreatePart(InternalSymbolDisplayPartKind.Arity, null, - MetadataHelpers.GetAritySuffix(symbol.Arity))); + string suffix = MetadataHelpers.GetAritySuffix(symbol.Arity); + Debug.Assert(underlyingTypeSymbol?.MangleName != false); + + if (underlyingTypeSymbol is not null ? underlyingTypeSymbol.MangleName : (symbol.MetadataName == symbol.Name + suffix)) + { + Debug.Assert(symbol.Arity > 0); + Builder.Add(CreatePart(InternalSymbolDisplayPartKind.Arity, null, + suffix)); + } } } else if (symbol.Arity > 0 && Format.GenericsOptions.IncludesOption(SymbolDisplayGenericsOptions.IncludeTypeParameters)) diff --git a/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs b/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs index a234d69049153..bce35df651bc0 100644 --- a/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs @@ -9156,5 +9156,82 @@ public void PreprocessingSymbol() ], actual: displayParts); } + + [Theory, CombinatorialData] + [WorkItem("https://github.com/dotnet/roslyn/issues/80165")] + public void UseArityForGenericTypes_CSharpSymbol(bool useMetadata) + { + var text = +@" +class A +{ + class B { } +} + +class C +{ + class D { } + class E { } +} +"; + var format = SymbolDisplayFormat.CSharpErrorMessageFormat. + WithCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.UseArityForGenericTypes); + + Compilation comp; + if (useMetadata) + { + var libComp = CreateCompilation(text); + comp = CreateCompilation("", references: [libComp.EmitToImageReference()]); + } + else + { + comp = CreateCompilation(text); + } + + AssertEx.Equal("A", SymbolDisplay.ToDisplayString(comp.GetTypeByMetadataName("A"), format)); + AssertEx.Equal("A.B`1", SymbolDisplay.ToDisplayString(comp.GetTypeByMetadataName("A+B`1"), format)); + AssertEx.Equal("C`1", SymbolDisplay.ToDisplayString(comp.GetTypeByMetadataName("C`1"), format)); + AssertEx.Equal("C`1.D`1", SymbolDisplay.ToDisplayString(comp.GetTypeByMetadataName("C`1+D`1"), format)); + AssertEx.Equal("C`1.E", SymbolDisplay.ToDisplayString(comp.GetTypeByMetadataName("C`1+E"), format)); + } + + [Theory, CombinatorialData] + [WorkItem("https://github.com/dotnet/roslyn/issues/80165")] + public void UseArityForGenericTypes_VBSymbol(bool useMetadata) + { + var text = +@" +Class A + Class B(Of T1) + End Class +End Class + +Class C(Of T2) + Class D(Of T3) + End Class + Class E + End Class +End Class +"; + var format = SymbolDisplayFormat.CSharpErrorMessageFormat. + WithCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.UseArityForGenericTypes); + + Compilation comp; + if (useMetadata) + { + var libComp = CreateVisualBasicCompilation(text); + comp = CreateVisualBasicCompilation("", referencedAssemblies: [libComp.EmitToImageReference()]); + } + else + { + comp = CreateVisualBasicCompilation("c", text); + } + + AssertEx.Equal("A", SymbolDisplay.ToDisplayString(comp.GetTypeByMetadataName("A"), format)); + AssertEx.Equal("A.B`1", SymbolDisplay.ToDisplayString(comp.GetTypeByMetadataName("A+B`1"), format)); + AssertEx.Equal("C`1", SymbolDisplay.ToDisplayString(comp.GetTypeByMetadataName("C`1"), format)); + AssertEx.Equal("C`1.D`1", SymbolDisplay.ToDisplayString(comp.GetTypeByMetadataName("C`1+D`1"), format)); + AssertEx.Equal("C`1.E", SymbolDisplay.ToDisplayString(comp.GetTypeByMetadataName("C`1+E"), format)); + } } } diff --git a/src/Compilers/VisualBasic/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.vb b/src/Compilers/VisualBasic/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.vb index 1f4e0d136a19f..6fb5f808b61a8 100644 --- a/src/Compilers/VisualBasic/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.vb +++ b/src/Compilers/VisualBasic/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.vb @@ -280,11 +280,15 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Dim isMissingMetadataType As Boolean = TypeOf symbol Is MissingMetadataTypeSymbol If Format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.UseArityForGenericTypes) Then - ' Only the compiler can set the internal option and the compiler doesn't use other implementations of INamedTypeSymbol. - If DirectCast(symbol, NamedTypeSymbol).MangleName Then - Debug.Assert(symbol.Arity > 0) - Builder.Add(CreatePart(InternalSymbolDisplayPartKind.Arity, Nothing, - MetadataHelpers.GenericTypeNameManglingChar & symbol.Arity.ToString(), False)) + If symbol.Arity > 0 Then + Dim suffix As String = MetadataHelpers.GetAritySuffix(symbol.Arity) + Dim vbNamedType = TryCast(symbol, NamedTypeSymbol) + Debug.Assert(If(vbNamedType?.MangleName, True) = True) + + If If(vbNamedType IsNot Nothing, vbNamedType.MangleName, symbol.MetadataName.Equals(symbol.Name + suffix)) Then + Builder.Add(CreatePart(InternalSymbolDisplayPartKind.Arity, Nothing, + suffix, False)) + End If End If ElseIf symbol.Arity > 0 AndAlso Format.GenericsOptions.IncludesOption(SymbolDisplayGenericsOptions.IncludeTypeParameters) AndAlso Not skipTypeArguments Then If isMissingMetadataType OrElse symbol.IsUnboundGenericType Then diff --git a/src/Compilers/VisualBasic/Test/Symbol/SymbolDisplay/SymbolDisplayTests.vb b/src/Compilers/VisualBasic/Test/Symbol/SymbolDisplay/SymbolDisplayTests.vb index f4405372c477e..da3382f436888 100644 --- a/src/Compilers/VisualBasic/Test/Symbol/SymbolDisplay/SymbolDisplayTests.vb +++ b/src/Compilers/VisualBasic/Test/Symbol/SymbolDisplay/SymbolDisplayTests.vb @@ -6146,6 +6146,77 @@ static class E AssertEx.Equal("Public Sub E.$8048A6C8BE30A622530249B904B537EB(Of T).M()", SymbolDisplay.ToDisplayString(skeletonM, format)) End Sub + + + Public Sub UseArityForGenericTypes_CSharpSymbol(useMetadata As Boolean) + Dim text = +" +class A +{ + class B { } +} + +class C +{ + class D { } + class E { } +} +" + Dim format = SymbolDisplayFormat.VisualBasicErrorMessageFormat. + WithCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.UseArityForGenericTypes) + + Dim comp As Compilation + If useMetadata Then + Dim libComp = CreateCSharpCompilation("c", text) + comp = CreateCSharpCompilation("d", code:="", referencedAssemblies:=libComp.References.Concat(libComp.EmitToImageReference())) + Else + comp = CreateCSharpCompilation("c", text) + End If + + AssertEx.Equal("A", SymbolDisplay.ToDisplayString(comp.GetTypeByMetadataName("A"), format)) + AssertEx.Equal("A.B`1", SymbolDisplay.ToDisplayString(comp.GetTypeByMetadataName("A+B`1"), format)) + AssertEx.Equal("C`1", SymbolDisplay.ToDisplayString(comp.GetTypeByMetadataName("C`1"), format)) + AssertEx.Equal("C`1.D`1", SymbolDisplay.ToDisplayString(comp.GetTypeByMetadataName("C`1+D`1"), format)) + AssertEx.Equal("C`1.E", SymbolDisplay.ToDisplayString(comp.GetTypeByMetadataName("C`1+E"), format)) + End Sub + + + + Public Sub UseArityForGenericTypes_VBSymbol(useMetadata As Boolean) + Dim source = +" +Class A + Class B(Of T1) + End Class +End Class + +Class C(Of T2) + Class D(Of T3) + End Class + Class E + End Class +End Class +" + Dim format = SymbolDisplayFormat.VisualBasicErrorMessageFormat. + WithCompilerInternalOptions(SymbolDisplayCompilerInternalOptions.UseArityForGenericTypes) + + Dim comp As Compilation + If useMetadata Then + Dim libComp = CreateCompilation(source) + comp = CreateCompilation("", references:={libComp.EmitToImageReference()}) + Else + comp = CreateCompilation(source) + End If + + Dim c = DirectCast(comp.GlobalNamespace.GetMembers("C").Single(), ITypeSymbol) + + AssertEx.Equal("A", SymbolDisplay.ToDisplayString(comp.GetTypeByMetadataName("A"), format)) + AssertEx.Equal("A.B`1", SymbolDisplay.ToDisplayString(comp.GetTypeByMetadataName("A+B`1"), format)) + AssertEx.Equal("C`1", SymbolDisplay.ToDisplayString(comp.GetTypeByMetadataName("C`1"), format)) + AssertEx.Equal("C`1.D`1", SymbolDisplay.ToDisplayString(comp.GetTypeByMetadataName("C`1+D`1"), format)) + AssertEx.Equal("C`1.E", SymbolDisplay.ToDisplayString(comp.GetTypeByMetadataName("C`1+E"), format)) + End Sub + #Region "Helpers" Private Shared Sub TestSymbolDescription( From 504496733e1e6655cc285b134f2072d79eadb5cc Mon Sep 17 00:00:00 2001 From: AlekseyTs Date: Mon, 20 Oct 2025 19:52:20 -0700 Subject: [PATCH 2/4] Fixup --- .../CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs | 1 - .../Portable/SymbolDisplay/SymbolDisplayVisitor.Types.vb | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs b/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs index 8bdd99b6cf47b..bc586c6b8d0a2 100644 --- a/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs +++ b/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs @@ -405,7 +405,6 @@ private void AddNameAndTypeArgumentsOrParameters(INamedTypeSymbol symbol) if (symbol.Arity > 0) { string suffix = MetadataHelpers.GetAritySuffix(symbol.Arity); - Debug.Assert(underlyingTypeSymbol?.MangleName != false); if (underlyingTypeSymbol is not null ? underlyingTypeSymbol.MangleName : (symbol.MetadataName == symbol.Name + suffix)) { diff --git a/src/Compilers/VisualBasic/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.vb b/src/Compilers/VisualBasic/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.vb index 6fb5f808b61a8..fb967447fb6c1 100644 --- a/src/Compilers/VisualBasic/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.vb +++ b/src/Compilers/VisualBasic/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.vb @@ -283,7 +283,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic If symbol.Arity > 0 Then Dim suffix As String = MetadataHelpers.GetAritySuffix(symbol.Arity) Dim vbNamedType = TryCast(symbol, NamedTypeSymbol) - Debug.Assert(If(vbNamedType?.MangleName, True) = True) If If(vbNamedType IsNot Nothing, vbNamedType.MangleName, symbol.MetadataName.Equals(symbol.Name + suffix)) Then Builder.Add(CreatePart(InternalSymbolDisplayPartKind.Arity, Nothing, From 6ee8ab99489d58fd503653652ce3d13f1bc197f8 Mon Sep 17 00:00:00 2001 From: AlekseyTs Date: Thu, 23 Oct 2025 07:57:03 -0700 Subject: [PATCH 3/4] Apply suggestion from @AlekseyTs --- .../CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs b/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs index f80bef0b9b2d8..80e9827a2d823 100644 --- a/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs @@ -9233,7 +9233,6 @@ End Class AssertEx.Equal("C`1.D`1", SymbolDisplay.ToDisplayString(comp.GetTypeByMetadataName("C`1+D`1"), format)); AssertEx.Equal("C`1.E", SymbolDisplay.ToDisplayString(comp.GetTypeByMetadataName("C`1+E"), format)); } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/36654")] public void MinimalNameWithConflict() { From c959ee9f4c43ee5591593101271e8cff9b393b21 Mon Sep 17 00:00:00 2001 From: AlekseyTs Date: Thu, 23 Oct 2025 08:01:32 -0700 Subject: [PATCH 4/4] Apply suggestion from @AlekseyTs --- .../CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs b/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs index 80e9827a2d823..39c2f701257b4 100644 --- a/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs @@ -9233,6 +9233,7 @@ End Class AssertEx.Equal("C`1.D`1", SymbolDisplay.ToDisplayString(comp.GetTypeByMetadataName("C`1+D`1"), format)); AssertEx.Equal("C`1.E", SymbolDisplay.ToDisplayString(comp.GetTypeByMetadataName("C`1+E"), format)); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/36654")] public void MinimalNameWithConflict() {