Skip to content

Fix Pylance settings handling on Dev18 - #8585

Closed
Stella Huang (StellaHuang95) wants to merge 3 commits into
microsoft:mainfrom
StellaHuang95:fix/8578-custom-rpc-binding
Closed

Fix Pylance settings handling on Dev18#8585
Stella Huang (StellaHuang95) wants to merge 3 commits into
microsoft:mainfrom
StellaHuang95:fix/8578-custom-rpc-binding

Conversation

@StellaHuang95

Copy link
Copy Markdown
Contributor

Summary

Fixes #8578.

Checking Automatically add brackets to functions persisted correctly in Visual Studio, but Dev18 did not deliver the enabled setting to Pylance. As a result, Pylance continued returning plain completion text such as getcwd instead of a snippet such as getcwd($0).

Root cause

Dev18 uses System.Text.Json for eligible language clients, exposing three serialization and binding mismatches in the PTVS/Pylance configuration path:

  • Pylance sends workspace/configuration parameters as one object ({ "items": [...] }), but the custom StreamJsonRpc handler did not opt into whole-object parameter binding, so the request was rejected with -32602.
  • PTVS's outbound settings DTO used public fields. System.Text.Json does not serialize those fields by default, so the settings payload became {} even when the option was enabled.
  • The fallback workspace/didChangeConfiguration payload placed analysis settings directly under settings.analysis, while Pylance expects the section under settings.python.analysis.

Latest main also introduced a JToken signature-help middle layer. On Dev18, that type selects the Newtonsoft formatter for the whole language client, which conflicts with the STJ-compatible path needed for PTVS's initialize interception. The Dev18 implementation therefore uses JsonDocument, while the existing Dev17 JToken implementation remains unchanged.

Fix

  • Enable whole-object binding for object-shaped custom RPC messages, including workspace/configuration.
  • Deserialize custom payloads through a serializer-neutral helper that accepts both Newtonsoft and Dev18 STJ values.
  • Convert outbound Pylance settings fields to properties so both serializers emit the same JSON contract.
  • Send fallback configuration under the required python section.
  • Keep Dev18 signature-help interception on the System.Text.Json middle-layer contract while preserving the prior Dev17 path.
  • Align the custom Pylance progress notification handler signatures with their protocol shapes.

Risk

Risk is low because the changes are limited to the Pylance language-client serialization and custom RPC boundary:

  • Wire names and setting values are unchanged; the settings that were previously omitted are now serialized.
  • Whole-object binding is applied only to methods whose protocol parameters are object-shaped.
  • The fallback envelope now matches Pylance's documented section lookup.
  • Dev17 retains its existing signature-help implementation through conditional compilation.
  • Completion insertion behavior is not changed; this only allows Pylance to receive the existing option and return the intended snippet.

Validation

The fix was manually verified on Dev18: after enabling the option, function completions included parentheses as expected. No tests were added for this change.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1b6a821e-0a88-49fa-807e-874551b5a398
Copilot AI review requested due to automatic review settings July 22, 2026 20:17
@StellaHuang95
Stella Huang (StellaHuang95) requested a review from a team as a code owner July 22, 2026 20:17
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@heejaechang

Copy link
Copy Markdown

🔒 Automated review in progress — Heejae Chang (@heejaechang) is auto-reviewing this PR.

@StellaHuang95

Copy link
Copy Markdown
Contributor Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

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 pull request fixes Dev18 (System.Text.Json-based) configuration delivery to Pylance so that VS options (notably Automatically add brackets to functions) are correctly serialized/bound and reflected in Pylance completion behavior.

Changes:

  • Updated Dev18 signature-help interception to use a System.Text.Json-friendly contract (JsonDocument) while preserving the Dev17 JToken path.
  • Enabled whole-object parameter binding for object-shaped custom JSON-RPC messages and added a cross-serializer deserialization helper.
  • Updated settings payload shape/serialization (DTO fields → properties; fallback didChangeConfiguration now sends settings under the python section key).
Show a summary per file
File Description
Python/Product/PythonTools/PythonTools/LanguageServerClient/PythonSignatureHelpMiddleLayer.cs Switches Dev18 middle-layer generic to JsonDocument to avoid Newtonsoft selecting the wrong formatter.
Python/Product/PythonTools/PythonTools/LanguageServerClient/PythonLanguageClientCustomTarget.cs Adjusts JSON-RPC handlers to use single-object parameter binding and serializer-neutral deserialization.
Python/Product/PythonTools/PythonTools/LanguageServerClient/PythonLanguageClient.cs Updates fallback didChangeConfiguration payload to be section-keyed under python.
Python/Product/PythonTools/PythonTools/LanguageServerClient/LanguageServerSettings.cs Converts outbound settings DTO public fields to properties so STJ and Newtonsoft serialize consistently.

Review details

Comments suppressed due to low confidence (2)

Python/Product/PythonTools/PythonTools/LanguageServerClient/PythonLanguageClientCustomTarget.cs:175

  • OnWorkspaceConfiguration no longer catches exceptions. If Deserialize(...) or the WorkspaceConfiguration event handler throws, the exception can bubble out of the JSON-RPC target and potentially disrupt the language-client RPC loop; the previous implementation returned null on failure.
        public async Task<object> OnWorkspaceConfiguration(object arg) {
            var reqParams = Deserialize<WorkspaceConfiguration.ConfigurationParams>(arg);
            if (this.WorkspaceConfiguration != null && reqParams != null) {
                var eventArgs = new ConfigurationArgs { requestParams = reqParams, requestResult = null };
                await this.WorkspaceConfiguration.InvokeAsync(this, eventArgs);

Python/Product/PythonTools/PythonTools/LanguageServerClient/PythonLanguageClientCustomTarget.cs:190

  • Deserialize<T> relies on arg.ToString() to produce JSON for non-JToken values. For System.Text.Json payloads this is not guaranteed (e.g., JsonDocument.ToString() is not JSON), which can cause deserialization failures and break configuration/registration handling.
            // Dev18 supplies System.Text.Json values; older formatters supply JToken.
            return arg is JToken token
                ? token.ToObject<T>()
                : JsonConvert.DeserializeObject<T>(arg.ToString());
        }
  • Files reviewed: 4/4 changed files
  • Comments generated: 1
  • Review effort level: Low

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 22, 2026 20:29
@StellaHuang95

Copy link
Copy Markdown
Contributor Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Approved via Review Center.

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.

Review details

Comments suppressed due to low confidence (2)

Python/Product/PythonTools/PythonTools/LanguageServerClient/PythonLanguageClientCustomTarget.cs:190

  • Deserialize falls back to JsonConvert.DeserializeObject(arg.ToString()) for System.Text.Json payloads. If StreamJsonRpc supplies a JsonDocument (or other STJ container that doesn’t override ToString()), arg.ToString() won’t be valid JSON, causing deserialization to fail (and silently break telemetry/config handling). Prefer extracting raw JSON via GetRawText() for JsonElement/JsonDocument before passing to JsonConvert.
            // Dev18 supplies System.Text.Json values; older formatters supply JToken.
            return arg is JToken token
                ? token.ToObject<T>()
                : JsonConvert.DeserializeObject<T>(arg.ToString());
        }

Python/Product/PythonTools/PythonTools/LanguageServerClient/PythonLanguageClientCustomTarget.cs:116

  • OnTelemetryEvent previously swallowed all exceptions during logging/notification handling. The new implementation can let exceptions from Trace/_logger/PylanceException (or event handlers) bubble out of the JsonRpc dispatch, which can destabilize the LSP connection. Consider restoring a defensive try/catch around the logging + AnalysisComplete notification (and fix the current mis-indentation on the invoke line).
            Trace.WriteLine(arg);
            if (telemetry.Exception == null) {
                _logger?.LogEvent(telemetry.EventName, telemetry.Properties, telemetry.Measurements);
            } else {
                _logger?.LogFault(new PylanceException(telemetry.EventName, telemetry.Exception.stack), telemetry.EventName, false);
  • Files reviewed: 4/4 changed files
  • Comments generated: 1
  • Review effort level: Low

Comment on lines +171 to +179
public async Task<object> OnWorkspaceConfiguration(object arg) {
var reqParams = Deserialize<WorkspaceConfiguration.ConfigurationParams>(arg);
if (this.WorkspaceConfiguration != null && reqParams != null) {
var eventArgs = new ConfigurationArgs { requestParams = reqParams, requestResult = null };
await this.WorkspaceConfiguration.InvokeAsync(this, eventArgs);
return eventArgs.requestResult;
}
return null;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Approved via Review Center.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1b6a821e-0a88-49fa-807e-874551b5a398
Copilot AI review requested due to automatic review settings July 22, 2026 21:56
@StellaHuang95

Copy link
Copy Markdown
Contributor Author

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

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.

Review details

Comments suppressed due to low confidence (3)

Python/Product/PythonTools/PythonTools/LanguageServerClient/PythonLanguageClientCustomTarget.cs:116

  • OnTelemetryEvent no longer guards the logging/event-invocation block with a catch. If a logger implementation or an AnalysisComplete subscriber throws, the exception can escape the JSON-RPC target and potentially tear down the RPC connection. Wrap the processing block in a try/catch (matching the prior behavior) so malformed/unexpected telemetry can’t destabilize the client.
            Trace.WriteLine(arg);
            if (telemetry.Exception == null) {
                _logger?.LogEvent(telemetry.EventName, telemetry.Properties, telemetry.Measurements);
            } else {
                _logger?.LogFault(new PylanceException(telemetry.EventName, telemetry.Exception.stack), telemetry.EventName, false);

Python/Product/PythonTools/PythonTools/LanguageServerClient/PythonLanguageClientCustomTarget.cs:144

  • Changing python/endProgress to a parameterless method can cause JSON-RPC binding failures (-32602) if the server sends a params object (even an empty one). Consider taking a single optional parameter to remain compatible with both shapes.
        [JsonRpcMethod("python/endProgress")]
        public async Task OnEndProgressAsync() {
            await _joinableTaskContext.Factory.SwitchToMainThreadAsync();

Python/Product/PythonTools/PythonTools/LanguageServerClient/PythonLanguageClientCustomTarget.cs:175

  • OnWorkspaceConfiguration previously swallowed deserialization/handler exceptions and returned null. The new implementation can now throw (e.g., JsonException from Deserialize or exceptions from WorkspaceConfiguration handlers), which risks breaking the JSON-RPC connection. Reintroduce a catch and return null on failure, similar to OnWorkspaceFolders.
        [JsonRpcMethod("workspace/configuration", UseSingleObjectParameterDeserialization = true)]
        public async Task<object> OnWorkspaceConfiguration(object arg) {
            var reqParams = Deserialize<WorkspaceConfiguration.ConfigurationParams>(arg);
            if (this.WorkspaceConfiguration != null && reqParams != null) {
                var eventArgs = new ConfigurationArgs { requestParams = reqParams, requestResult = null };
                await this.WorkspaceConfiguration.InvokeAsync(this, eventArgs);
  • Files reviewed: 6/6 changed files
  • Comments generated: 3
  • Review effort level: Low

Comment on lines 126 to 128
[JsonRpcMethod("python/beginProgress")]
#pragma warning disable IDE0060 // Remove unused parameter
public void OnBeginProgressAsync(JToken arg) {
#pragma warning restore IDE0060 // Remove unused parameter
public void OnBeginProgressAsync() {
}
Comment on lines +186 to 190
// Dev18 supplies System.Text.Json values; older formatters supply JToken.
return arg is JToken token
? token.ToObject<T>()
: JsonConvert.DeserializeObject<T>(arg.ToString());
}
Comment on lines +246 to +250
<Reference Include="System.Text.Json, Version=8.0.0.5, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51" Condition="'$(VSTarget)' == '18.0'">
<HintPath>$(PackagesPath)\System.Text.Json.8.0.5\lib\net462\System.Text.Json.dll</HintPath>
<Private>false</Private>
<SpecificVersion>true</SpecificVersion>
</Reference>
auto-merge was automatically disabled July 22, 2026 22:04

Pull request was closed

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@sonarqubecloud

Copy link
Copy Markdown

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Approved via Review Center.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Checking "Automatically add brackets to functions" has no effect.

3 participants