Skip to content

[LSP] Utilize list of all classification types + expose types to Razor#61395

Merged
allisonchou merged 6 commits intodotnet:mainfrom
allisonchou:ExposeClassificationsToRazor
Jun 2, 2022
Merged

[LSP] Utilize list of all classification types + expose types to Razor#61395
allisonchou merged 6 commits intodotnet:mainfrom
allisonchou:ExposeClassificationsToRazor

Conversation

@allisonchou
Copy link
Contributor

@allisonchou allisonchou commented May 19, 2022

  • Adds a new AllTypeNames array to the ClassificationTypeNames class. We make sure the array is kept up-to-date with the fields in the class via a test that utilizes reflection.
  • Other options like reflection and source generators were considered, but reflection adds perf costs and source generators adds unnecessary overhead.
  • Exposes legend to Razor so they can also programmatically utilize C# tokens.
  • When a new classification type is added, we'll unfortunately still need to update the LSP client. Hopefully this won't be needed anymore once https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1085998 is implemented. We'll also need to bump Razor's Roslyn package version manually.

Fixes #61237

@allisonchou allisonchou requested review from a team as code owners May 19, 2022 00:49
@ghost ghost added the Area-IDE label May 19, 2022
@allisonchou
Copy link
Contributor Author

allisonchou commented May 19, 2022

@sharwell I just saw your comment in the original issue about avoiding reflection, can you elaborate on why reflection is not preferred vs. source generators?

Copy link
Member

@dibarbet dibarbet left a comment

Choose a reason for hiding this comment

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

can probably remove this comment here - https://sourceroslyn.io/#Microsoft.CodeAnalysis.Workspaces/Classification/ClassificationTypeNames.cs,16

Source generator is an interesting idea - I do like that. Reflection here feels icky, but I can't explain a good reason why, so I'll leave that to Sam

Copy link
Contributor

@ryanbrandenburg ryanbrandenburg left a comment

Choose a reason for hiding this comment

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

Looks good AFAIK.

@sharwell
Copy link
Contributor

Not a big fan of runtime reflection. It can cause a bunch of perf-related items to show up. Source generator would be able to automate the mapping without introducing any runtime overhead.

@allisonchou allisonchou requested a review from a team as a code owner June 1, 2022 23:15
@allisonchou allisonchou changed the title [LSP] Use reflection for semantic token types + expose types to Razor [LSP] Utilize list of all classification types + expose types to Razor Jun 1, 2022
@allisonchou
Copy link
Contributor Author

Hey all, thanks for the comments so far. I updated the PR based on feedback + talking to @davidwengier who gave some good suggestions. Could I get another look at this PR? I changed the approach so we no longer use reflection or source generators. Instead, we add a new AllTypeNames array to the ClassificationTypeNames class. There are two reasons for taking this approach:

  • Runtime reflection, as Sam mentioned, introduces perf overhead.
  • Source generators add unnecessary maintenance overhead - we'd have to add a new source generator project, create the source generator, reference the project, etc. It just feels like unneeded cost when all we need at the end of the day is a complete list of tokens.

I added a reflection test to ensure AllTypeNames is always kept up to date with any added classification types.

@NTaylorMullen
Copy link

I added a reflection test to ensure AllTypeNames is always kept up to date with any added classification types.

Very cool approach. I like!

{
internal class RazorSemanticTokensAccessor
{
public static ImmutableArray<string> RoslynTokenTypes => SemanticTokensHelpers.AllTokenTypes;

Choose a reason for hiding this comment

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

One bit we'll want to keep in mind is that once Razor consumes this & wants to insert into VS4Mac we'll need to ensure that the corresponding Roslyn bits are there first. Just bringing it up because there's a lot of movement in that space recently 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great point. I'm unfamiliar with VS4Mac insertions, is there a regular insertion schedule that I can keep up with to ensure the Roslyn bits go in before the Razor bits?

Copy link
Member

Choose a reason for hiding this comment

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

No regular schedule, no. The build validation on VS Mac is good though, and will report a failure if there is an incompatibility between inserted versions of roslyn and razor.

/// Features/LanguageServer/Protocol/Handler/SemanticTokens/SemanticTokensHelpers.cs
/// </remarks>
public static ImmutableArray<string> AdditiveTypeNames { get; } = ImmutableArray.Create(StaticSymbol);
public static ImmutableArray<string> AdditiveTypeNames { get; } = ImmutableArray.Create(StaticSymbol, ReassignedVariable);

Choose a reason for hiding this comment

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

Curious: was ReassignedVariable a piece we had previously missed on the LSP side that we noticed / added here after the test caught the mismatch?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The new version of RoslynCustomTokenTypes basically takes the full list of classifications but subtracts the ones already mapped to LSP token types and the ones in AdditiveTypeNames. Previously, since we hardcoded RoslynCustomTokenTypes we just explicitly left out StaticSymbol/ReassignedVariable/etc. from the list. However, now that we're doing things more programmatically, we have to intentionally specify which classifications to not include.

[ClassificationTypeNames.StringLiteral] = LSP.SemanticTokenTypes.String,
};

public static readonly ImmutableArray<string> RoslynCustomTokenTypes = ClassificationTypeNames.AllTypeNames

Choose a reason for hiding this comment

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

I probably missed the context, is there a specific reason why this is more preferable than the prior (where we re-specified everything)? Asking only because I could also see a world where the "verification test" also goes as far as to ensure this SemanticTokensHelpers class is specifying everything in ClassificationTypeNames

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good question! IMO there's a couple of reasons why I think this is more preferable than prior:

  • AllTypeNames specifies every classification type and therefore can be easily re-used in non-LSP contexts. The previous RoslynCustomTokenTypes only specified a subset of classification types.
  • Before, if we wanted to add a new mapping in ClassificationTypeToSemanticTokenTypeMap, it would be a two-step process: we'd have to add the mapping, and then delete the entry in RoslynCustomTokenTypes. With the updated code, we can just add a new entry to ClassificationTypeToSemanticTokenTypeMap and be done (besides needing to update the LSP client/Razor)

Essentially, I think it's just cleaner and allows for easier future maintainability 😄

@allisonchou allisonchou merged commit 3cca4fd into dotnet:main Jun 2, 2022
@ghost ghost added this to the Next milestone Jun 2, 2022
@allisonchou allisonchou deleted the ExposeClassificationsToRazor branch June 2, 2022 18:32
@RikkiGibson RikkiGibson modified the milestones: Next, 17.3 P3 Jun 28, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[LSP] Simplify semantic tokens token types legend

8 participants