Repository navigation
Unsafe evolution: symbol display - #84706
Conversation
|
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. |
There was a problem hiding this comment.
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
unsafeinSymbolDisplayVisitor.AddMemberModifiersIfNeededfor explicitly caller-unsafe members under updated memory safety rules. - Extend
UnsafeEvolutionTestswith 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. |
|
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. |
| AddSpace(); | ||
| } | ||
|
|
||
| if (symbol is Symbols.PublicModel.Symbol { UnderlyingSymbol: { ContainingModule.UseUpdatedMemorySafetyRules: true } internalSymbol } && |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
We probably need a new display option to add the modifier.
There was a problem hiding this comment.
SymbolDisplayMemberOptions.IncludeModifiers seems appropriate for this.
There was a problem hiding this comment.
Isn't there already a behavior for this flag?
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
To be clear, SymbolDisplayMemberOptions.IncludeModifiers is the flag this is already gated on (see line 934 above).
There was a problem hiding this comment.
Although it's a new behavior in a sense -
unsafewill 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.
|
Done with a quick glance (commit 6) |
|
@333fred @RikkiGibson for reviews, thanks |
333fred
left a comment
There was a problem hiding this comment.
LGTM, assuming we address Aleksey's comment about documentation.
That should have been already addressed in commit b738bc2 |
|
@RikkiGibson for a second review, thanks |
1 similar comment
|
@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))); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Local functions are for some reason excluded from SymbolDisplayMemberOptions.IncludeModifiers:
| 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))); |
There was a problem hiding this comment.
nit: Consider also checking the display of the setter.
RikkiGibson
left a comment
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
unsafeis only shown for members explicitly annotated with theunsafekeyword, but the condition usessymbol.RequiresUnsafeContext, which can also be true for implicitly-requires-unsafe members (e.g., pointers in signature viaCallerUnsafeMode.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)
|
@RikkiGibson or @333fred for another look, thanks; I needed to fixup a name after merge |
Include
unsafein modifiers produced by SymbolDisplay. In a follow-up PR, I'd like to use this to update PublicApiAnalyzer to includeunsafein its signatures.Test plan: #81207
Microsoft Reviewers: Open in CodeFlow