Skip to content

[api] Ensure completion symbols returned to API always come from the current snapshot - #64554

Merged
Andrew Branch (andrewbranch) merged 6 commits into
microsoft:mainfrom
andrewbranch:api-completion-symbols-fix
Sep 30, 2026
Merged

Andrew Branch (andrewbranch) merged 6 commits into
microsoft:mainfrom
andrewbranch:api-completion-symbols-fix

Conversation

@andrewbranch

Copy link
Copy Markdown
Member

Fixes #64518 (comment):

This is actually just a more obvious symptom of an existing bug. In main, every completion symbol from the temporary snapshot get registered by the parent snapshot's snapshotData record. That means the caller can get completions, assume the returned symbols belong to the current snapshot, and use the current snapshot's checker on them. If the symbol being operated on is transient, that will probably cause incorrect results or a crash.

If an API method is going to return objects from a new snapshot, that new snapshot must be returned to the client as well, so the objects can be operated on. getCompletionsAtPosition only optionally includes symbols, and only advances the snapshot if auto-imports are both needed for the position and not already prepared. Whether or not to include auto-imports ever should be configurable, but currently is not by the API.

A proper fix needs to address this from a few angles:

  • Be able to configure auto-imports in the API (perhaps per call, definitely by applying user preferences to a snapshot)
  • Be able to create a snapshot that is prepared to auto-import a file

I think with both of those, you can keep getCompletionsAtPosition's return value as is and not have it return a snapshot, or bifurcate the method into one variant that returns a snapshot and one that doesn't:

  • If includeSymbols is false, use the same logic that exists today, with auto-import retry as needed.
  • If includeSymbols is true, disable auto-import retry and return an error if the current snapshot isn't prepared. The caller either needs to disable auto-imports or explicitly create a prepared snapshot.

This PR adds exactly all of that. Since UserPreferences is now exposed to the API, the second commit generates both the Go and the TS from a schema. Rather than complicate the schema/generation to preserve the Go type exactly as it was, I had the field names and enum constants generated from the actual JSON properties/values that VS Code expects, which accounts for the renaming. I can add more metadata to the schema to undo that, but I kind of prefer the consistency.

This comment was marked as resolved.

Comment thread tools/gen-preferences/main.go Outdated
Comment thread tsc/internal/ls/languageservice.go Outdated
Comment thread tsc/internal/api/session.go Outdated
return nil, e
}
if params.Preferences != nil {
langSvc.SetUserPreferences(langSvc.UserPreferences().WithCompletionPreferences(*params.Preferences))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If I'm understanding this correctly, we're bringing back preferences sent along with requests? The LSP doesn't work this way so I'm at least moderately concerned we're creating a bad situation for ourselves by having multiple concepts here, but maybe I'm not understanding.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

No, you're understanding. This isn't LSP so I don't see the need for that constraint here in the direct access language service. I can remove it, but it seems useful. The alternative is you have to do a whole extra round trip snapshot clone just to control the preferences for one completions request. The way LSP works makes sense because there's a single persistent state for preferences. I'm not sure it makes as much sense when state forks arbitrarily.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Removed. We can always add it later if there's a need.

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.

Copilot review overview

🟡 Changes recommended

The generated public preference contract rejects a valid organizeImportsCaseFirst: false value, and the symbol-ownership regression test does not exercise the returned symbol handle.

Review effort: Balanced
Findings: 1 High severity · 1 Medium severity · 1 Low severity

Open (3)
Resolved since last review (1)

Comment thread tools/userPreferences.schema.json
Comment thread packages/typescript/test/async/api.test.ts
Comment thread tsc/internal/api/proto.go
@andrewbranch

Copy link
Copy Markdown
Member Author

Ahaha oh no, I got caught doing code analysis with a regular expression. Someone take my compiler engineer license away 😦

Error in typescript:release in 2s
Error: Found external imports in .d.ts files:
  dist/api/userPreferences.generated.d.ts:20: external import declaration "fs"

@andrewbranch
Andrew Branch (andrewbranch) added this pull request to the merge queue Sep 30, 2026
Merged via the queue into microsoft:main with commit 43521c8 Sep 30, 2026
29 checks passed
@andrewbranch
Andrew Branch (andrewbranch) deleted the api-completion-symbols-fix branch September 30, 2026 21:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Author: Team For Uncommitted Bug PR for untriaged, rejected, closed or missing bug

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants