From c84d24994631fbb18c1a3be631e6c5fa6f771398 Mon Sep 17 00:00:00 2001 From: Rikki Gibson Date: Tue, 18 Feb 2025 22:24:37 -0800 Subject: [PATCH] Fix Quick Info nullability display for backing fields (#77240) Co-authored-by: Cyrus Najmabadi --- .../SymbolDisplay/SymbolDisplayTests.cs | 115 ++++++++++++++++++ .../QuickInfo/SemanticQuickInfoSourceTests.cs | 38 ++++++ .../Portable/QuickInfo/QuickInfoUtilities.cs | 9 +- 3 files changed, 160 insertions(+), 2 deletions(-) diff --git a/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs b/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs index add91eb5d5684..a234d69049153 100644 --- a/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs @@ -2164,6 +2164,121 @@ class C { SymbolDisplayPartKind.Keyword); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77219")] + public void TestPropertyBackingField_Minimal_01() + { + var text = @" +#nullable enable +class C { + string P { get; set; } } +"; + + Func findSymbol = global => + global.GetTypeMembers("C", 0).Single(). + GetMembers("

k__BackingField").Single(); + + var format = new SymbolDisplayFormat( + memberOptions: + SymbolDisplayMemberOptions.IncludeExplicitInterface); + + TestSymbolDescription( + text, + findSymbol, + format, + "P.field", + SymbolDisplayPartKind.PropertyName, + SymbolDisplayPartKind.Punctuation, + SymbolDisplayPartKind.Keyword); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77219")] + public void TestPropertyBackingField_Minimal_02() + { + var text = @" +#nullable enable + +interface I { + string P { get; set; } +} + +class C : I { + string I.P { get; set; } } +"; + + Func findSymbol = global => + global.GetTypeMembers("C", 0).Single(). + GetMembers("k__BackingField").Single(); + + var format = new SymbolDisplayFormat( + memberOptions: + SymbolDisplayMemberOptions.IncludeExplicitInterface); + + TestSymbolDescription( + text, + findSymbol, + format, + "I.P.field", + SymbolDisplayPartKind.InterfaceName, + SymbolDisplayPartKind.Punctuation, + SymbolDisplayPartKind.PropertyName, + SymbolDisplayPartKind.Punctuation, + SymbolDisplayPartKind.Keyword); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77219")] + public void TestPropertyBackingField_Minimal_03() + { + var text = @" +#nullable enable + +interface I { + string P { get; set; } +} + +class C : I { + string I.P { get; set; } } +"; + + Func findSymbol = global => + global.GetTypeMembers("C", 0).Single(). + GetMembers("k__BackingField").Single(); + + var format = new SymbolDisplayFormat(); + + TestSymbolDescription( + text, + findSymbol, + format, + "P.field", + SymbolDisplayPartKind.PropertyName, + SymbolDisplayPartKind.Punctuation, + SymbolDisplayPartKind.Keyword); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77219")] + public void TestOrdinaryField_Minimal() + { + var text = @" +#nullable enable + +class C { + string F; +"; + + Func findSymbol = global => + global.GetTypeMembers("C", 0).Single(). + GetMembers("F").Single(); + + var format = new SymbolDisplayFormat(); + + TestSymbolDescription( + text, + findSymbol, + format, + "F", + SymbolDisplayPartKind.FieldName); + } + [Fact] public void TestPropertyBackingFieldFromCompilationReference() { diff --git a/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs b/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs index f4c7b4efab924..8e6fd350f3e08 100644 --- a/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs +++ b/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs @@ -7998,6 +7998,44 @@ void N() NullabilityAnalysis(string.Format(FeaturesResources._0_is_not_null_here, "s"))); } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77219")] + public async Task NullableBackingFieldThatIsMaybeNull() + { + await TestWithOptionsAsync(TestOptions.RegularPreview, + """ + #nullable enable + + class X + { + string? P + { + get => $$field; + } + } + """, + MainDescription($"({FeaturesResources.field}) string? X.P.field"), + NullabilityAnalysis(string.Format(FeaturesResources._0_may_be_null_here, "P.field"))); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77219")] + public async Task NullableBackingFieldThatIsNotNull() + { + await TestWithOptionsAsync(TestOptions.RegularPreview, + """ + #nullable enable + + class X + { + string P + { + get => $$field; + } = "a"; + } + """, + MainDescription($"({FeaturesResources.field}) string X.P.field"), + NullabilityAnalysis(string.Format(FeaturesResources._0_is_not_null_here, "P.field"))); + } + [Fact] public async Task NullablePropertyThatIsMaybeNull() { diff --git a/src/Features/Core/Portable/QuickInfo/QuickInfoUtilities.cs b/src/Features/Core/Portable/QuickInfo/QuickInfoUtilities.cs index 865997ee60408..fdb53dfa525e5 100644 --- a/src/Features/Core/Portable/QuickInfo/QuickInfoUtilities.cs +++ b/src/Features/Core/Portable/QuickInfo/QuickInfoUtilities.cs @@ -18,6 +18,11 @@ namespace Microsoft.CodeAnalysis.QuickInfo; internal static class QuickInfoUtilities { + ///

+ /// Display variable name only. + /// + private static readonly SymbolDisplayFormat s_nullableDisplayFormat = new SymbolDisplayFormat(); + public static Task CreateQuickInfoItemAsync(SolutionServices services, SemanticModel semanticModel, TextSpan span, ImmutableArray symbols, SymbolDescriptionOptions options, CancellationToken cancellationToken) => CreateQuickInfoItemAsync(services, semanticModel, span, symbols, supportedPlatforms: null, showAwaitReturn: false, flowState: NullableFlowState.None, options, onTheFlyDocsInfo: null, cancellationToken); @@ -136,8 +141,8 @@ public static async Task CreateQuickInfoItemAsync( var nullableMessage = flowState switch { - NullableFlowState.MaybeNull => string.Format(FeaturesResources._0_may_be_null_here, symbol.Name), - NullableFlowState.NotNull => string.Format(FeaturesResources._0_is_not_null_here, symbol.Name), + NullableFlowState.MaybeNull => string.Format(FeaturesResources._0_may_be_null_here, symbol.ToDisplayString(s_nullableDisplayFormat)), + NullableFlowState.NotNull => string.Format(FeaturesResources._0_is_not_null_here, symbol.ToDisplayString(s_nullableDisplayFormat)), _ => null };