Skip to content

Unsafe evolution: symbol display - #84706

Merged
jjonescz merged 10 commits into
dotnet:mainfrom
jjonescz:Unsafe-46-SymbolDisplay
Aug 20, 2026
Merged

jjonescz merged 10 commits into
dotnet:mainfrom
jjonescz:Unsafe-46-SymbolDisplay

Conversation

@jjonescz

@jjonescz jjonescz commented Jul 30, 2026 •

Copy link
Copy Markdown
Member

Include unsafe in modifiers produced by SymbolDisplay. In a follow-up PR, I'd like to use this to update PublicApiAnalyzer to include unsafe in its signatures.

Test plan: #81207

Microsoft Reviewers: Open in CodeFlow

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
1 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates C# symbol display so that, when SymbolDisplayMemberOptions.IncludeModifiers is requested and the containing module uses the updated memory safety rules, members that are explicitly caller-unsafe (CallerUnsafeMode.Explicit) include the unsafe modifier in ToDisplayString() output. The accompanying tests extend existing Unsafe Evolution coverage to assert the new display behavior across a variety of member kinds.

Changes:

  • Emit unsafe in SymbolDisplayVisitor.AddMemberModifiersIfNeeded for explicitly caller-unsafe members under updated memory safety rules.
  • Extend UnsafeEvolutionTests with validators/assertions verifying modifier display differences between legacy vs updated rule sets (methods, properties/accessors, indexers, events, ctors, fields, and extern cases).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/Compilers/CSharp/Test/CSharp15/UnsafeEvolutionTests.cs Adds targeted assertions/validators ensuring ToDisplayString(...IncludeModifiers...) includes unsafe only under updated memory safety rules and only for explicitly caller-unsafe members.
src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Members.cs Adds unsafe keyword emission in member modifier display when GetCallerUnsafeMode(...) == CallerUnsafeMode.Explicit and the containing module uses updated memory safety rules.

@jjonescz
jjonescz marked this pull request as ready for review July 31, 2026 14:21
@jjonescz
jjonescz requested a review from a team as a code owner July 31, 2026 14:21
Copilot AI review requested due to automatic review settings July 31, 2026 14:21
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).
1 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

AddSpace();
}

if (symbol is Symbols.PublicModel.Symbol { UnderlyingSymbol: { ContainingModule.UseUpdatedMemorySafetyRules: true } internalSymbol } &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UnderlyingSymbol

We prefer to implement SymbolDisplay through public API

@jjonescz jjonescz Jul 31, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know, but I've seen this pattern of using internal symbols elsewhere in SymbolDisplayVisitor.

Also, public API review already approved a bool-only shape of the public API and I think I want to only add unsafe modifier for CallerUnsafeMode.Explicit. I guess I could check whether the ContainingModule uses updated memory safety rules when a public API for that is available. Or we could revisit this in API review.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be fine to proceed with internal symbols access with a follow-up issue.

}

if (symbol is Symbols.PublicModel.Symbol { UnderlyingSymbol: { ContainingModule.UseUpdatedMemorySafetyRules: true } internalSymbol } &&
internalSymbol.GetCallerUnsafeMode(ConsList<FieldSymbol>.Empty) == CallerUnsafeMode.Explicit)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably need a new display option to add the modifier.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SymbolDisplayMemberOptions.IncludeModifiers seems appropriate for this.

@AlekseyTs AlekseyTs Jul 31, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't there already a behavior for this flag?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, it's not a new flag. I guess we could document this as a breaking change. Although it's a new behavior in a sense - unsafe will be only added for new code (code that opts into unsafe evolution and uses the keyword to annotate caller-unsafe members).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be clear, SymbolDisplayMemberOptions.IncludeModifiers is the flag this is already gated on (see line 934 above).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although it's a new behavior in a sense - unsafe will be only added for new code (code that opts into unsafe evolution and uses the keyword to annotate caller-unsafe members).

This sounds reasonable to me. At the very least this should be said explicitly. Either in a comment here, or in PR description. Or both.

@AlekseyTs

Copy link
Copy Markdown
Contributor

Done with a quick glance (commit 6)

Copilot AI review requested due to automatic review settings July 31, 2026 15:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

@jjonescz
jjonescz requested a review from 333fred August 3, 2026 08:28
@jjonescz

jjonescz commented Aug 6, 2026

Copy link
Copy Markdown
Member Author

@333fred @RikkiGibson for reviews, thanks

@jjonescz
jjonescz requested a review from RikkiGibson August 10, 2026 08:59

@333fred 333fred left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, assuming we address Aleksey's comment about documentation.

@jjonescz

Copy link
Copy Markdown
Member Author

assuming we address Aleksey's comment about documentation.

That should have been already addressed in commit b738bc2

@jjonescz

Copy link
Copy Markdown
Member Author

@RikkiGibson for a second review, thanks

1 similar comment
@jjonescz

Copy link
Copy Markdown
Member Author

@RikkiGibson for a second review, thanks

var localFunctions = tree.GetRoot().DescendantNodes().OfType<LocalFunctionStatementSyntax>().Select(s => model.GetDeclaredSymbol(s)!).ToArray();
Assert.Equal(2, localFunctions.Length);

AssertEx.Equal("void M1()", localFunctions[0].ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat.AddMemberOptions(SymbolDisplayMemberOptions.IncludeModifiers)));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't follow why this display is missing the unsafe modifier, when, the declaration has the modifier, and new unsafe rules are being used.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Local functions are for some reason excluded from SymbolDisplayMemberOptions.IncludeModifiers:

(containingType.TypeKind != TypeKind.Interface && !IsEnumMember(symbol) && !IsLocalFunction(symbol))))

AssertEx.Equal("int C.P1", comp.GetMember("C.P1").ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat.AddMemberOptions(SymbolDisplayMemberOptions.IncludeModifiers)));
AssertEx.Equal("unsafe int C.P1.get", comp.GetMember("C.get_P1").ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat.AddMemberOptions(SymbolDisplayMemberOptions.IncludeModifiers)));
AssertEx.Equal("int C.P2", comp.GetMember("C.P2").ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat.AddMemberOptions(SymbolDisplayMemberOptions.IncludeModifiers)));
AssertEx.Equal("int C.P2.get", comp.GetMember("C.get_P2").ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat.AddMemberOptions(SymbolDisplayMemberOptions.IncludeModifiers)));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Consider also checking the display of the setter.

@RikkiGibson RikkiGibson left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with some minor nits/questions. Feel free to address in follow up or similar if desired.

Discussed a bit offline and noted that it would be good for presence of 'unsafe' modifier in Quick Info, when we get to that, to indicate that the item requires unsafe context to be used. So, for example, it would be good to include it in the display there, for a legacy method with pointers in signature.

Copilot AI review requested due to automatic review settings August 19, 2026 13:27
@jjonescz
jjonescz requested a review from RikkiGibson August 19, 2026 13:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Copilot AI review requested due to automatic review settings August 19, 2026 13:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Members.cs:964

  • The new comment suggests unsafe is only shown for members explicitly annotated with the unsafe keyword, but the condition uses symbol.RequiresUnsafeContext, which can also be true for implicitly-requires-unsafe members (e.g., pointers in signature via CallerUnsafeMode.Implicit). Consider updating the comment to describe the actual behavior so future readers don’t infer a narrower contract than the code implements.
                // unsafe is added only for new code (code that opts into unsafe evolution and uses the keyword to annotate caller-unsafe members)

@jjonescz

Copy link
Copy Markdown
Member Author

@RikkiGibson or @333fred for another look, thanks; I needed to fixup a name after merge

@jjonescz
jjonescz merged commit 7de096b into dotnet:main Aug 20, 2026
21 checks passed
@jjonescz
jjonescz deleted the Unsafe-46-SymbolDisplay branch August 20, 2026 17:28
@jjonescz jjonescz added this to the 18.11 milestone Aug 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants