-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Switch Roslyn protocol types to System.Text.Json serialization #73207
Conversation
ff777c9
to
ef5f44c
Compare
d0f8a35
to
84d40a8
Compare
have not switched to STJ on the client side
3fa1ec0
to
16e8689
Compare
|
||
// Retrieve the language of the request so we know how to deserialize it. | ||
var language = _target.GetLanguageForRequest(_method, request); | ||
public abstract MethodInfo GetEntryPoint(bool hasParameter); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I created two subclasses of AbstractLanguageServer, one for Newtonsoft and one for STJ (we still support webtools/razor using clasp using newtonsoft).
This can all be simplified once we move Razor/XAML over to our protocol types and no longer need to manually handle serialization
Any early numbers about it this improves anything? |
/// StreamJsonRpc entry point for all handler methods. | ||
/// The optional parameters allow StreamJsonRpc to call into the same method for any kind of request / notification (with any number of params or response). | ||
/// </summary> | ||
private async Task<JToken?> ExecuteRequestAsync(JToken? request = null, CancellationToken cancellationToken = default) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is the same as before, just moved into its own subclass
|
||
namespace Microsoft.CommonLanguageServerProtocol.Framework; | ||
|
||
internal abstract class SystemTextJsonLanguageServer<TRequestContext>(JsonRpc jsonRpc, JsonSerializerOptions options, ILspLogger logger) : AbstractLanguageServer<TRequestContext>(jsonRpc, logger) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is all new for STJ support.
/// <summary> | ||
/// StreamJsonRpc entry point for handlers with parameters (and any response) type. | ||
/// </summary> | ||
private async Task<JsonElement?> ExecuteRequestAsync(JsonElement? request, CancellationToken cancellationToken = default) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
STJ uses JsonElement, which is a struct, whereas newtonsoft uses JToken (a class). This caused all sorts of nullability differences between the two impls, so 100x easier to separate them (even if there is a small amount of duplication) here.
And as mentioned, all of this is simplified once we switch Razor/XAML to our types and remove the custom serialization logic here.
|
||
namespace Roslyn.LanguageServer.Protocol | ||
{ | ||
using Newtonsoft.Json; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unnecessary, deleted in followup commit.
/// <summary> | ||
/// Converter used to deserialize objects dropping any <see cref="IProgress{T}"/> property. | ||
/// </summary> | ||
internal class DropProgressConverter : JsonConverter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unnecessary, delted in followup
@@ -99,7 +99,7 @@ | |||
<PackageVersion Include="Microsoft.VisualStudio.Language" Version="$(MicrosoftVisualStudioCoreVersion)" /> | |||
<PackageVersion Include="Microsoft.VisualStudio.Language.NavigateTo.Interfaces" Version="$(MicrosoftVisualStudioCoreVersion)" /> | |||
<PackageVersion Include="Microsoft.VisualStudio.Language.StandardClassification" Version="$(MicrosoftVisualStudioCoreVersion)" /> | |||
<PackageVersion Include="Microsoft.VisualStudio.LanguageServer.Client" Version="17.10.11-preview" /> | |||
<PackageVersion Include="Microsoft.VisualStudio.LanguageServer.Client" Version="17.10.72-preview" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
switching to newer version that has APIs using concrete types instead of JToken. Will make the transition to STJ on the client side easier later.
internal bool CallInitialized { get; init; } = true; | ||
internal object? ClientTarget { get; init; } = null; | ||
internal string? Locale { get; init; } = null; | ||
internal IEnumerable<DiagnosticAnalyzer>? AdditionalAnalyzers { get; init; } = null; | ||
internal IJsonRpcMessageFormatter? ClientMessageFormatter { get; init; } = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Allows us to use newtonsoft on the client side in tests for serialization
@@ -23,43 +25,37 @@ namespace Microsoft.VisualStudio.LanguageServices.DocumentOutline; | |||
using LspDocumentSymbol = DocumentSymbol; | |||
using Range = Roslyn.LanguageServer.Protocol.Range; | |||
|
|||
internal delegate Task<ManualInvocationResponse?> LanguageServiceBrokerCallback( | |||
ITextBuffer textBuffer, Func<JToken, bool> capabilitiesFilter, string languageServerName, string method, Func<ITextSnapshot, JToken> parameterFactory, CancellationToken cancellationToken); | |||
internal delegate Task<TResponse?> LanguageServiceBrokerCallback<TRequest, TResponse>(Request<TRequest, TResponse> request, CancellationToken cancellationToken); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
switches to the new lsp client API
string textViewFilePath, | ||
CancellationToken cancellationToken) | ||
{ | ||
ITextSnapshot? requestSnapshot = null; | ||
JToken ParameterFunction(ITextSnapshot snapshot) | ||
|
||
var request = new DocumentRequest<DocumentSymbolNewtonsoft.NewtonsoftRoslynDocumentSymbolParams, DocumentSymbolNewtonsoft.NewtonsoftRoslynDocumentSymbol[]>() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even though we switched to the new LSP client API that uses concrete types instead of JTOken - the streamjsonrpc instance on the client side is still newtonsoft. Therefore we have to use type definitions compatible with newtonsoft serialization
/// https://github.com/dotnet/roslyn/pull/72675 tracks opting in the client to STJ. | ||
/// TODO - everything in this type should be deleted once the client side is using STJ. | ||
/// </summary> | ||
internal class DocumentSymbolNewtonsoft |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to contain the spread of newtonsoft here by just copying the types we need and adding the newtonsoft attributes to them.
These can be deleted as soon as we switch to STJ on the client side
/// </summary> | ||
private async Task<JsonElement?> ExecuteRequestAsync(JsonElement? request, CancellationToken cancellationToken = default) | ||
{ | ||
var queue = target.GetRequestExecutionQueue(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Off topic: What's the general consensus on primary constructor usage?
I like them when they are accessed during field initializations (outside methods). However, I strongly like being able to easily differentiate class vs local data within methods. I guess if I had my druthers, for any primary ctor arg needed in a method we'd still have the field declaration using the underscore syntax that sets itself to the primary constructor arg.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
currently no rules. there are mixed feelings. I personally have no issues with captures. but i'm fairly used to lambdas, local functions, and JS/TS-style capture development. I can understand the desire around fields though.
Maybe a general rule would be that it's ok for fairly simple types with few methods. But once it gets complex enough, we assign to fields?
blocked on microsoft/vs-streamjsonrpc#1038 (found in test insertion) |
Fixes #72867 or at least is related to that issue, if we consider that there are still Newtonsoft-based parts for razor compatibility |
unblocked. Now pending investigation of method JIT regressions |
Updated PR validation is here https://dev.azure.com/devdiv/DevDiv/_git/VS/pullrequest/550816 (internal only) JIT regressions are approved, will merge as soon as CI completes. |
Woohoo! |
Switches the Roslyn server to use System.Text.Json serialization for all LSP messages.
I suggest reviewing one commit at a time
Tested C#/Razor/F#/TS in VS, document symbols in VS, and C#/Razor in VSCode.
TODO - PR validation in progress.
Initial results are showing an almost 25% reduction in allocations in the lightbulb speedometer tests. There are some methods JIT regressions, currently testing new optprof data to see if that helps.
Resolves #72867