Skip to content

Commit

Permalink
Fix symbol display for backing fields (#76000)
Browse files Browse the repository at this point in the history
  • Loading branch information
RikkiGibson authored Nov 22, 2024
1 parent 14244f6 commit 2c4b274
Show file tree
Hide file tree
Showing 9 changed files with 227 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,15 @@ public override void VisitField(IFieldSymbol symbol)
AddPunctuation(SyntaxKind.DotToken);
}

if (symbol.ContainingType.TypeKind == TypeKind.Enum)
if (!Format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.UseMetadataMemberNames)
&& symbol is Symbols.PublicModel.FieldSymbol
&& symbol.AssociatedSymbol is IPropertySymbol associatedProperty)
{
AddPropertyNameAndParameters(associatedProperty);
AddPunctuation(SyntaxKind.DotToken);
AddKeyword(SyntaxKind.FieldKeyword);
}
else if (symbol.ContainingType.TypeKind == TypeKind.Enum)
{
Builder.Add(CreatePart(SymbolDisplayPartKind.EnumMemberName, symbol, symbol.Name));
}
Expand Down Expand Up @@ -321,7 +329,7 @@ public override void VisitMethod(IMethodSymbol symbol)
// If we're using the metadata format, then include the return type.
// Otherwise we eschew it since it is redundant in a conversion
// signature.
if (Format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.UseMetadataMethodNames))
if (Format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.UseMetadataMemberNames))
{
goto default;
}
Expand All @@ -332,7 +340,7 @@ public override void VisitMethod(IMethodSymbol symbol)
// If we're using the metadata format, then include the return type.
// Otherwise we eschew it since it is redundant in a conversion
// signature.
if (Format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.UseMetadataMethodNames) ||
if (Format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.UseMetadataMemberNames) ||
tryGetUserDefinedOperatorTokenKind(symbol.MetadataName) == SyntaxKind.None)
{
goto default;
Expand Down Expand Up @@ -462,7 +470,7 @@ public override void VisitMethod(IMethodSymbol symbol)
// Note: we are using the metadata name also in the case that
// symbol.containingType is null (which should never be the case here) or is an
// anonymous type (which 'does not have a name').
var name = Format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.UseMetadataMethodNames) || symbol.ContainingType == null || symbol.ContainingType.IsAnonymousType
var name = Format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.UseMetadataMemberNames) || symbol.ContainingType == null || symbol.ContainingType.IsAnonymousType
? symbol.Name
: symbol.ContainingType.Name;

Expand All @@ -476,7 +484,7 @@ public override void VisitMethod(IMethodSymbol symbol)
var partKind = GetPartKindForConstructorOrDestructor(symbol);

// Note: we are using the metadata name also in the case that symbol.containingType is null, which should never be the case here.
if (Format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.UseMetadataMethodNames) || symbol.ContainingType == null)
if (Format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.UseMetadataMemberNames) || symbol.ContainingType == null)
{
Builder.Add(CreatePart(partKind, symbol, symbol.Name));
}
Expand All @@ -491,7 +499,7 @@ public override void VisitMethod(IMethodSymbol symbol)
{
AddExplicitInterfaceIfNeeded(symbol.ExplicitInterfaceImplementations);

if (!Format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.UseMetadataMethodNames) &&
if (!Format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.UseMetadataMemberNames) &&
symbol.GetSymbol()?.OriginalDefinition is SourceUserDefinedOperatorSymbolBase sourceUserDefinedOperatorSymbolBase)
{
var operatorName = symbol.MetadataName;
Expand Down Expand Up @@ -520,7 +528,7 @@ public override void VisitMethod(IMethodSymbol symbol)
case MethodKind.UserDefinedOperator:
case MethodKind.BuiltinOperator:
{
if (Format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.UseMetadataMethodNames))
if (Format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.UseMetadataMemberNames))
{
Builder.Add(CreatePart(SymbolDisplayPartKind.MethodName, symbol, symbol.MetadataName));
}
Expand All @@ -541,7 +549,7 @@ public override void VisitMethod(IMethodSymbol symbol)
}
case MethodKind.Conversion:
{
if (Format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.UseMetadataMethodNames))
if (Format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.UseMetadataMemberNames))
{
Builder.Add(CreatePart(SymbolDisplayPartKind.MethodName, symbol, symbol.MetadataName));
}
Expand Down
178 changes: 175 additions & 3 deletions src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1448,7 +1448,7 @@ class C {

var format = new SymbolDisplayFormat(
memberOptions: SymbolDisplayMemberOptions.IncludeType,
compilerInternalOptions: SymbolDisplayCompilerInternalOptions.UseMetadataMethodNames);
compilerInternalOptions: SymbolDisplayCompilerInternalOptions.UseMetadataMemberNames);

TestSymbolDescription(
text,
Expand Down Expand Up @@ -2049,8 +2049,10 @@ class C
SymbolDisplayPartKind.Punctuation);
}

[Fact]
public void TestPropertyGetAccessor()
[Theory]
[InlineData(SymbolDisplayCompilerInternalOptions.None)]
[InlineData(SymbolDisplayCompilerInternalOptions.UseMetadataMemberNames)]
internal void TestPropertyGetAccessor(SymbolDisplayCompilerInternalOptions internalOptions)
{
var text = @"
class C {
Expand All @@ -2062,6 +2064,7 @@ class C {
GetMembers("get_P").Single();

var format = new SymbolDisplayFormat(
internalOptions,
memberOptions:
SymbolDisplayMemberOptions.IncludeAccessibility |
SymbolDisplayMemberOptions.IncludeContainingType |
Expand Down Expand Up @@ -2123,6 +2126,175 @@ class C {
SymbolDisplayPartKind.Keyword);
}

[Fact]
public void TestPropertyBackingField()
{
var text = @"
#nullable enable
class C {
string P { get; set; } }
";

Func<NamespaceSymbol, Symbol> findSymbol = global =>
global.GetTypeMembers("C", 0).Single().
GetMembers("<P>k__BackingField").Single();

var format = new SymbolDisplayFormat(
memberOptions:
SymbolDisplayMemberOptions.IncludeAccessibility |
SymbolDisplayMemberOptions.IncludeContainingType |
SymbolDisplayMemberOptions.IncludeExplicitInterface |
SymbolDisplayMemberOptions.IncludeModifiers |
SymbolDisplayMemberOptions.IncludeParameters |
SymbolDisplayMemberOptions.IncludeType);

TestSymbolDescription(
text,
findSymbol,
format,
"private String C.P.field",
SymbolDisplayPartKind.Keyword,
SymbolDisplayPartKind.Space,
SymbolDisplayPartKind.ClassName,
SymbolDisplayPartKind.Space,
SymbolDisplayPartKind.ClassName,
SymbolDisplayPartKind.Punctuation,
SymbolDisplayPartKind.PropertyName,
SymbolDisplayPartKind.Punctuation,
SymbolDisplayPartKind.Keyword);
}

[Fact]
public void TestPropertyBackingFieldFromCompilationReference()
{
var text = @"
#nullable enable
class C {
string P { get; set; } }
";

var format = new SymbolDisplayFormat(
memberOptions: SymbolDisplayMemberOptions.IncludeParameters | SymbolDisplayMemberOptions.IncludeModifiers | SymbolDisplayMemberOptions.IncludeAccessibility | SymbolDisplayMemberOptions.IncludeType,
parameterOptions: SymbolDisplayParameterOptions.IncludeType | SymbolDisplayParameterOptions.IncludeName | SymbolDisplayParameterOptions.IncludeDefaultValue,
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes);

var comp1 = CreateCompilation(text);
var comp2 = CreateCompilation("", references: [comp1.ToMetadataReference()], options: TestOptions.DebugDll.WithMetadataImportOptions(MetadataImportOptions.All));

var prop = comp2.GetMember<FieldSymbol>("C.<P>k__BackingField").GetPublicSymbol();
var parts = SymbolDisplay.ToDisplayParts(prop, format);

Verify(
parts,
"private string P.field",
SymbolDisplayPartKind.Keyword,
SymbolDisplayPartKind.Space,
SymbolDisplayPartKind.Keyword,
SymbolDisplayPartKind.Space,
SymbolDisplayPartKind.PropertyName,
SymbolDisplayPartKind.Punctuation,
SymbolDisplayPartKind.Keyword);
}

[Fact]
public void TestPropertyBackingField_UseMetadataMethodNames()
{
var text = @"
#nullable enable
class C {
string P { get; set; } }
";

Func<NamespaceSymbol, Symbol> findSymbol = global =>
global.GetTypeMembers("C", 0).Single().
GetMembers("<P>k__BackingField").Single();

var format = new SymbolDisplayFormat(
compilerInternalOptions: SymbolDisplayCompilerInternalOptions.UseMetadataMemberNames,
memberOptions:
SymbolDisplayMemberOptions.IncludeAccessibility |
SymbolDisplayMemberOptions.IncludeContainingType |
SymbolDisplayMemberOptions.IncludeExplicitInterface |
SymbolDisplayMemberOptions.IncludeModifiers |
SymbolDisplayMemberOptions.IncludeParameters |
SymbolDisplayMemberOptions.IncludeType);

TestSymbolDescription(
text,
findSymbol,
format,
"private String C.<P>k__BackingField",
SymbolDisplayPartKind.Keyword,
SymbolDisplayPartKind.Space,
SymbolDisplayPartKind.ClassName,
SymbolDisplayPartKind.Space,
SymbolDisplayPartKind.ClassName,
SymbolDisplayPartKind.Punctuation,
SymbolDisplayPartKind.FieldName);
}

[Theory]
[InlineData(SymbolDisplayCompilerInternalOptions.None)]
[InlineData(SymbolDisplayCompilerInternalOptions.UseMetadataMemberNames)]
internal void TestPropertyBackingFieldFromMetadata(SymbolDisplayCompilerInternalOptions internalOptions)
{
// Metadata symbols do not associate the backing field with the property, so the metadata name is always displayed.
var text = @"
#nullable enable
class C {
string P { get; set; } }
";

var format = new SymbolDisplayFormat(
compilerInternalOptions: internalOptions,
memberOptions: SymbolDisplayMemberOptions.IncludeParameters | SymbolDisplayMemberOptions.IncludeModifiers | SymbolDisplayMemberOptions.IncludeAccessibility | SymbolDisplayMemberOptions.IncludeType,
parameterOptions: SymbolDisplayParameterOptions.IncludeType | SymbolDisplayParameterOptions.IncludeName | SymbolDisplayParameterOptions.IncludeDefaultValue,
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes);

var comp1 = CreateCompilation(text);
var comp2 = CreateCompilation("", references: [comp1.EmitToImageReference()], options: TestOptions.DebugDll.WithMetadataImportOptions(MetadataImportOptions.All));

var prop = comp2.GetMember<FieldSymbol>("C.<P>k__BackingField").GetPublicSymbol();
var parts = SymbolDisplay.ToDisplayParts(prop, format);

Verify(
parts,
"private string <P>k__BackingField",
SymbolDisplayPartKind.Keyword,
SymbolDisplayPartKind.Space,
SymbolDisplayPartKind.Keyword,
SymbolDisplayPartKind.Space,
SymbolDisplayPartKind.FieldName);
}

[Fact]
public void TestPropertyBackingFieldVB()
{
var text = @"
Class A
Public Property Prop As String
End Class";

var format = new SymbolDisplayFormat(
memberOptions: SymbolDisplayMemberOptions.IncludeParameters | SymbolDisplayMemberOptions.IncludeModifiers | SymbolDisplayMemberOptions.IncludeAccessibility | SymbolDisplayMemberOptions.IncludeType,
parameterOptions: SymbolDisplayParameterOptions.IncludeType | SymbolDisplayParameterOptions.IncludeName | SymbolDisplayParameterOptions.IncludeDefaultValue,
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes);

var comp = CreateVisualBasicCompilation("c", text);
var a = (ITypeSymbol)comp.GlobalNamespace.GetMembers("A").Single();
var goo = a.GetMembers("_Prop").Single();
var parts = SymbolDisplay.ToDisplayParts(goo, format);

Verify(
parts,
"private string _Prop",
SymbolDisplayPartKind.Keyword,
SymbolDisplayPartKind.Space,
SymbolDisplayPartKind.Keyword,
SymbolDisplayPartKind.Space,
SymbolDisplayPartKind.FieldName);
}

[Fact]
public void TestMemberEventAll()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1076,11 +1076,11 @@ record C
AssertEx.Equal(new[] {
"System.Type! C.EqualityContract.get",
"System.Type! C.EqualityContract { get; }",
"System.Int32 C.<X>k__BackingField",
"System.Int32 C.X.field",
"System.Int32 C.X { get; init; }",
"System.Int32 C.X.get",
"void C.X.init",
"System.String! C.<Y>k__BackingField",
"System.String! C.Y.field",
"System.String! C.Y { get; init; }",
"System.String! C.Y.get",
"void C.Y.init",
Expand Down
2 changes: 1 addition & 1 deletion src/Compilers/Core/Portable/CodeGen/CompilationTestData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public ImmutableDictionary<string, MethodData> GetMethodsByName()

private static readonly SymbolDisplayFormat _testDataKeyFormat = new SymbolDisplayFormat(
compilerInternalOptions:
SymbolDisplayCompilerInternalOptions.UseMetadataMethodNames |
SymbolDisplayCompilerInternalOptions.UseMetadataMemberNames |
SymbolDisplayCompilerInternalOptions.IncludeContainingFileForFileTypes,
globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.OmittedAsContaining,
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ internal enum SymbolDisplayCompilerInternalOptions
None = 0,

/// <summary>
/// ".ctor" instead of "Goo"
/// - ".ctor" instead of "Goo"
/// - "&lt;Prop&gt;k__backingField" instead of "Prop.field" (for C# backing fields)
/// </summary>
UseMetadataMethodNames = 1 << 0,
UseMetadataMemberNames = 1 << 0,

/// <summary>
/// "List`1" instead of "List&lt;T&gt;" ("List(of T)" in VB). Overrides GenericsOptions on
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ public class SymbolDisplayFormat
SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier,
compilerInternalOptions:
SymbolDisplayCompilerInternalOptions.IncludeScriptType |
SymbolDisplayCompilerInternalOptions.UseMetadataMethodNames |
SymbolDisplayCompilerInternalOptions.UseMetadataMemberNames |
SymbolDisplayCompilerInternalOptions.FlagMissingMetadataTypes |
SymbolDisplayCompilerInternalOptions.IncludeCustomModifiers |
SymbolDisplayCompilerInternalOptions.IncludeContainingFileForFileTypes);
Expand Down Expand Up @@ -257,7 +257,7 @@ public class SymbolDisplayFormat
miscellaneousOptions:
SymbolDisplayMiscellaneousOptions.UseSpecialTypes |
SymbolDisplayMiscellaneousOptions.ExpandValueTuple,
compilerInternalOptions: SymbolDisplayCompilerInternalOptions.UseMetadataMethodNames);
compilerInternalOptions: SymbolDisplayCompilerInternalOptions.UseMetadataMemberNames);

/// <summary>
/// Used to normalize explicit interface implementation member names.
Expand Down
Loading

0 comments on commit 2c4b274

Please sign in to comment.