Skip to content

Commit

Permalink
Fix Quick Info nullability display for backing fields (#77240)
Browse files Browse the repository at this point in the history
Co-authored-by: Cyrus Najmabadi <[email protected]>
  • Loading branch information
RikkiGibson and CyrusNajmabadi authored Feb 19, 2025
1 parent 51f74e4 commit c84d249
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 2 deletions.
115 changes: 115 additions & 0 deletions src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<NamespaceSymbol, Symbol> findSymbol = global =>
global.GetTypeMembers("C", 0).Single().
GetMembers("<P>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<NamespaceSymbol, Symbol> findSymbol = global =>
global.GetTypeMembers("C", 0).Single().
GetMembers("<I.P>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<NamespaceSymbol, Symbol> findSymbol = global =>
global.GetTypeMembers("C", 0).Single().
GetMembers("<I.P>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<NamespaceSymbol, Symbol> 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()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
9 changes: 7 additions & 2 deletions src/Features/Core/Portable/QuickInfo/QuickInfoUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ namespace Microsoft.CodeAnalysis.QuickInfo;

internal static class QuickInfoUtilities
{
/// <summary>
/// Display variable name only.
/// </summary>
private static readonly SymbolDisplayFormat s_nullableDisplayFormat = new SymbolDisplayFormat();

public static Task<QuickInfoItem> CreateQuickInfoItemAsync(SolutionServices services, SemanticModel semanticModel, TextSpan span, ImmutableArray<ISymbol> symbols, SymbolDescriptionOptions options, CancellationToken cancellationToken)
=> CreateQuickInfoItemAsync(services, semanticModel, span, symbols, supportedPlatforms: null, showAwaitReturn: false, flowState: NullableFlowState.None, options, onTheFlyDocsInfo: null, cancellationToken);

Expand Down Expand Up @@ -136,8 +141,8 @@ public static async Task<QuickInfoItem> 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
};

Expand Down

0 comments on commit c84d249

Please sign in to comment.