Unions: add basic IDE support#82526
Conversation
|
|
||
| struct_declaration | ||
| : attribute_list* modifier* 'struct' identifier_token type_parameter_list? parameter_list? base_list? type_parameter_constraint_clause* '{'? member_declaration* '}'? ';'? | ||
| : attribute_list* modifier* ('struct' | 'union') identifier_token type_parameter_list? parameter_list? base_list? type_parameter_constraint_clause* '{'? member_declaration* '}'? ';'? |
There was a problem hiding this comment.
This is very weird that this is part of struct_declaration when it changes that keyword. I would expect a different type-decl here.
There was a problem hiding this comment.
My PR is still a draft, it's focused on IDE changes, but is stacked on top of the PR that adds declaration/parsing support. You probably meant to comment there: #82500
There was a problem hiding this comment.
Fwiw, I only noticed this in julien' ide pr, but reusing StructDecl here with a different keyword/kind doesn't fit how I think TypeDecl is intended to be used. Like with RecordDecl, I think this should be it's own TypeDecl subclass. So keep the new UnionDecl syntaxkind, but pair withe a UnionDecl subclass.
If I can be included in the API review, that would be appreciated. Thanks.
@333fred fyi, tnx.
| // For extension declarations, there must be a parameter list | ||
| var paramList = CurrentToken.Kind == SyntaxKind.OpenParenToken || isExtension | ||
| ? ParseParenthesizedParameterList(forExtension: isExtension) : null; | ||
| ? ParseParenthesizedParameterList(forExtensionOrUnion: isExtension || isUnion) : null; |
There was a problem hiding this comment.
Imo,this is very difficult to understand precedence here. I would parenthesize for clarity.
|
Weird. I couldn't find the place where union keyword was recommended. |
|
@dotnet/roslyn-ide for review. Thanks |
|
Consider also adding tests to SyntaxNormalizerTests #Closed |
| SyntaxKind.PrivateKeyword, | ||
| SyntaxKind.ProtectedKeyword, | ||
| SyntaxKind.UnsafeKeyword, | ||
| SyntaxKind.FileKeyword, |
There was a problem hiding this comment.
Will add. Wasn't sure.
What about ref modifier?
There was a problem hiding this comment.
What about ref modifier?
This is open to interpretation. Depending whether it is meant to be one of the struct_modifier modifiers. It looks like in the current standard spec ref modifier is listed explicitly. I guess, assuming that it is not allowed should be good for now.
| context.IsTypeDeclarationContext( | ||
| validModifiers: s_validModifiers, | ||
| validTypeDeclarations: SyntaxKindSet.NonEnumTypeDeclarations, | ||
| canBePartial: false, |
|
Done with review pass (commit 7) #Closed |
| await TestAsync(testHost, composition, content, async w => | ||
| { | ||
| var item = (await _aggregator.GetItemsAsync("Goo")).Single(x => x.Kind != "Method"); | ||
| VerifyNavigateToResultItem(item, "Goo", "[|Goo|]", PatternMatchKind.Exact, NavigateToItemKind.Structure, Glyph.StructureInternal); |
There was a problem hiding this comment.
This is highly likely worth using another glyph. I believe c++ has a union glyph. We should incorporate into Roslyn as well and align on that. #Resolved
There was a problem hiding this comment.
Added a new glyph using existing union image (see pic in OP)
| public sealed class UnionKeywordRecommenderTests : KeywordRecommenderTests | ||
| { | ||
| private static readonly CSharpParseOptions s_options = CSharpNextParseOptions; | ||
| private static readonly CSharpParseOptions s_scriptOptions = CSharpNextScriptParseOptions; |
There was a problem hiding this comment.
I'm very confused by the need for all the different options. I would not expect that to change anything about the recommending here. #Resolved
There was a problem hiding this comment.
The parsing for union declarations is conditional on LangVer
There was a problem hiding this comment.
I do not expect anything related to script handling. :-)
In a case like this, I would just recommend that either the base class ask which langversion to use, and an override sets it, or have a helper method here that passes that value down, and all tests in this file use that.
We try to avoid repetition in these test methods. So having all/most tests have to pass these values explicitly is undesirable. Tnx
There was a problem hiding this comment.
I changed the implicit options in RecommenderTests (base type for these tests) to use "preview". That's the strategy used by compiler tests (all tests use "preview" unless otherwise specified)
| return NavigateToItemKind.Property; | ||
| case DeclaredSymbolInfoKind.Struct: | ||
| case DeclaredSymbolInfoKind.Union: | ||
| return NavigateToItemKind.Structure; |
There was a problem hiding this comment.
We should likely have a different kind here. Users are going to consider unions their own thing. #WontFix
There was a problem hiding this comment.
I added a new NavigateToItemKind, but I'm not really sure what's the expected impact. How do we observe this in the editor?
It looks like we would need some changes on the VS or VS SDK side to keep this separation the whole way through. I'm still mapping the union case to CodeSearchResultType.Structure in RoslynNavigateToSearchCallback.
Similarly, I didn't fully understand how this NavigateToItemKind should interact with LSP. I'm still mapping the union case to LSP.SymbolKind.Struct in ProtocolConversions.NavigateToKindToSymbolKind
There was a problem hiding this comment.
I ended up reverting the NavigateToItemKind for now. Left some PROTOTYPE comments to follow-up.
There was a problem hiding this comment.
so if NavigateToItemKind is on the roslyn side, we'll want to ahve a new .Union prop on it. If it's the point that we're marshal'ing that to a VS value, we can move to .Structure if they haven't exposed .Union yet. But then an internal convo should happen on them adding that. :)
|
|
||
| <PropertyGroup Condition="'$(DotNetBuildSourceOnly)' != 'true'"> | ||
| <DefineConstants>$(DefineConstants),ROSLYN_4_12_OR_LOWER</DefineConstants> | ||
| <DefineConstants>$(DefineConstants),ROSLYN_4_12_OR_LOWER,ROSLYN_5_5_OR_LOWER</DefineConstants> |
There was a problem hiding this comment.
Why both constants here? #Resolved
| SyntaxKind.RecordDeclaration => ClassificationTypeNames.RecordClassName, | ||
| SyntaxKind.RecordStructDeclaration => ClassificationTypeNames.RecordStructName, | ||
| SyntaxKind.StructDeclaration => ClassificationTypeNames.StructName, | ||
| SyntaxKind.UnionDeclaration => ClassificationTypeNames.StructName, |
There was a problem hiding this comment.
I think this should have it's own name. Users will want to color these differently. #Resolved
| @@ -103,6 +111,10 @@ public static int GetArity(this MemberDeclarationSyntax member) | |||
| case SyntaxKind.ClassDeclaration: | |||
| #if !ROSLYN_4_12_OR_LOWER | |||
There was a problem hiding this comment.
Are we not beyond this yet? #Resolved
|
@AlekseyTs I made some changes suggested by Cyrus to support a better IDE experience. But that required some compiler-side changes. If you feel this is too much snowballing/risk for the upcoming deadline, we can truncate this PR just before the commit that adds |
|
The vs side will likely have to expose the right kind. Oleg may be a good starting point. :-) You can map to that once exposed. |
| return SymbolDisplayPartKind.RecordClassName; | ||
| case TypeKind.Struct when symbol.IsRecord: | ||
| return SymbolDisplayPartKind.RecordStructName; | ||
| case TypeKind.Struct when symbol.IsUnion: |
There was a problem hiding this comment.
So, this matches what we did with records. We use record in symbol display even if it wasn't originally declared with record syntax. Symbol-display takes a semantics-forward approach here :)
There was a problem hiding this comment.
So, this matches what we did with records.
I do not think it does. There is no way to declare a record struct without using a "record struct declaration"
There was a problem hiding this comment.
Thera are also class unions
There was a problem hiding this comment.
I would have thought that anything from metadata wouldn't be something whose originating declaration we would know. It would just have some shape that makes it a 'record', regardless of the originating language or originating syntax.
For records, i thought it was just the presence of a proper cloning method. Once we find that, things like symbol-display use record. That seems like a completely reasonable (and expected) correspondence here with unions.
There was a problem hiding this comment.
I'm saying that the symbol display reflects semantically what it is. It's not syntax display. If the type had the right data to be considered a union, then displaying it that way is desirable :-)
There was a problem hiding this comment.
Here's what I'd propose: 1. split the PR into most basic support for purpose of this week's preview (I opened #82604 for that, it cuts off from this PR just before the IsUnion addition), 2. discuss and implement more advanced handling implemented in this PR as a follow-up next week
Personally, I'm leaning towards displaying all union types with union (we might even display the list of cases) but this needs broader unions WG and IDE discussion.
There was a problem hiding this comment.
That works for me. Note: more ammunition for the symbolic view of things. For things like ValueTuple<int, int> or Nullable<int> we show these in the IDE as (int, int) and int?. It's the normal view that if symbolically/semantically these are the same to the language that we then show the symbolic view of them.
Another way to think about it. If we're reading it from metadata and would treat it the same as the source-decl form, then we present it that way.
Happy to discuss this in WG/IDE though. But having written lots of SymbolDisplay (and many/most of the consumption points for it), I'm confident we'll just end up here :)
There was a problem hiding this comment.
None of the arguments resonate with me. And frankly I don't find them applicable. For me, this is not about metadata vs, source view.
There was a problem hiding this comment.
I'm not saying it is exactly these things. I'm stating we have a lot of precedence for "symbol display" to present a *symbolic/semantic perspective" over a particular ISymbol. We aim to cater to that and have ample precedence since this aspect was developed in roslyn in the first place. Furthermore, a huge amount of the design of SymbolDisplay came exactly from the needs and perspective of the IDE, since we present symbols in so many circumstances and have strong opinions on how a symbol should be presented. This is why the api surface area of SymbolDisplay is so rich in the first place. Including going beyond just text, but also to be a typed description of pieces, intended for rich presentation in things like quick info.
I'm stating that given our history here, and how we've treated pretty much all other symbols, we will likely land on the perspective that we should present anything the language would consider "a union" in a consistent fashion.
| var comp = CreateCompilation([src, UnionAttributeSource]); | ||
| comp.VerifyEmitDiagnostics(); | ||
|
|
||
| Assert.True(comp.GetTypeByMetadataName("S1").IsUnionType); |
There was a problem hiding this comment.
This comment is applicable to many other similar changes in this file
In general I am Ok with adding |
I think that's fine (if i'm understanding you correctly). Specifically, it sounds like this returns if the compiler semantically thinks of something as a union, even if it wasn't syntactically declared using the new syntax. I think it's fine for these features to drive off the semantic side. |
And I don't think this. |
| context.IsGlobalStatementContext || | ||
| context.IsTypeDeclarationContext( | ||
| validModifiers: s_validModifiers, | ||
| validTypeDeclarations: SyntaxKindSet.NonEnumTypeDeclarations, |
There was a problem hiding this comment.
just making sure that this is intentional (the 'enum' part) :)
| visitor.WriteString(symbol.MetadataName); | ||
| visitor.WriteSymbolKey(symbol.ContainingType); | ||
| #if !ROSLYN_4_12_OR_LOWER | ||
| #if !OLDER_ROSLYN |
CyrusNajmabadi
left a comment
There was a problem hiding this comment.
IDE side lgtm. Thanks!
unionkeywordRelates to test plan #81074
Tested the classification in red:
