Skip to content

[ty] Move script environment initialization to the CLI and LSP - #28038

Open
MichaReiser wants to merge 4 commits into
micha/uv-project-sync-servicefrom
micha/ty-script-preparation-watching
Open

[ty] Move script environment initialization to the CLI and LSP#28038
MichaReiser wants to merge 4 commits into
micha/uv-project-sync-servicefrom
micha/ty-script-preparation-watching

Conversation

@MichaReiser

@MichaReiser MichaReiser commented Aug 25, 2026

Copy link
Copy Markdown
Member

Summary

This PR moves from a lazy PEP 723 script discovery do eagerly discover all scripts as part of the project's file discovery.

There are two motivations for doing this:

  1. Initializing the script's environment before checking the script is not sufficient. Other files can import the script, and type inference can then resolve file_settings and python_version_with_source which are both environment dependent (the former less so, but uv can tell us the python version). Initializing all scripts before we start checking ensures that type inference sees the right environment even when a script has been imported. This is the bug that Zsolt identified in my initial PEP 723 PR.
  2. Today, the LSP only initialized scripts when the user opened them. However, that means that their environment isn't available as part of find references and workspace symbols. This can result in missed references (note, making find references return all project and script references when searching for references of a shared dependency requires a few changes to find references as well, but this sets the foundation for it). Initializing all environments when opening the project ensures the environments are available when running find references (or the user can rerun find references once all syncs are complete, but they don't have to open every script just to make sure that find references will show the references in them)
  3. Knowing all scripts simplifies file watching because we now have distinct points where script environments can change, and may require updating the watched paths.

Now, there are a few downsides as well, but I think this is the right trade off (honestly, I don't know how to fix 1 easily without it, and 3 was the main reason why I considered this approach even before Zsolt found the bug).

  • Unlike before, the LSP now indexes and reads all project files. This can mean that startup now takes a second or so on very large projects where it was instant before, because we didn't do any form of indexing. This also means that the LSP retains more memory for projects that don't use workspace diagnostics (or find references), because the LSP now loads all files into memory to determine whether a file is a script. This is inherent to the approach. I suspect that we can mitigate the "all source in memory" in the future by only storing compressed source text, which should reduce memory usage by about 70%.
  • We now sync all scripts on LSP startup. I could see this become a problem in workspaces with many (with 100 or 1000s scripts). If this becomes a problem, I think we can a) limit script sync to on-open, accepting that find-references etc. may require opening the relevant scripts and/or b) allow users to selectively sync scripts by using a LSP command. For now, I think syncing all scripts should be fine, given that uv is fast (the first time might take a while)
  • I had to make the uv sync queue unbounded. It's still bounded by the number of scripts and projects. But we no longer apply back pressure if the sync requests start piling up. The reason I had to remove it is because we don't want a didChange or didOpen to block, only because there are already 8 queued scripts. But more importantly, we don't want to block the LSP startup until all scripts have synchronized, but queuing them up during startup would just have done that (because the main loop blocks everytime time the queue is full). I think this is okay, and I'll add another PR on top of this one that adds cancellation support for sync requests. I also think that our previous back pressure wasn't as impactful as I thought
  • We now sync scripts before checking the files. That means, ty no longer makes progress on checking other project files while the syncs are pending. This is the one I'm the most bumped about. It was so cool that ty often checked the entire project while waiting on uv to finish syncing. To mitigate, I increased the worker pool to N/4 where N is max parallelism and, while testing, I also found that syncinc all scripts, at least in Ruff's repo, is instant once uv is warm.

I think this also gets us into a position where adding support for nested projects becomes much easier, because they'll naturally fit in. We'll discover nested projects as part of indexing Files (we'd need to move that off Project), synchronizing their environments can happen at the exactly same points as synchronizing scripts. The biggest remaining lift is to change ty_project and ProjectDatabase to no longer assume that there's a single Project (but there is still one main project).

Test Plan

CLI

Screen.Recording.2026-08-27.at.16.29.49.mov
Screen.Recording.2026-08-27.at.16.36.21.mov

@MichaReiser MichaReiser added the ty Multi-file analysis & type inference label Aug 25, 2026
@astral-sh-bot

astral-sh-bot Bot commented Aug 25, 2026

Copy link
Copy Markdown

Typing conformance results

No changes detected ✅

Current numbers
The percentage of diagnostics emitted that were expected errors held steady at 97.69%. The percentage of expected errors that received a diagnostic held steady at 93.71%. The number of fully passing files held steady at 110/136.

@astral-sh-bot

astral-sh-bot Bot commented Aug 25, 2026

Copy link
Copy Markdown

Memory usage report

Memory usage unchanged ✅

@astral-sh-bot

astral-sh-bot Bot commented Aug 25, 2026

Copy link
Copy Markdown

ecosystem-analyzer results

No diagnostic changes detected ✅

Large timing changes:

Project Old Time New Time Change
dd-trace-py 2.91s 1.20s -59%

Flaky changes detected. This PR summary excludes flaky changes; see the HTML report for details.

Full report with detailed diff (timing results)

@MichaReiser
MichaReiser force-pushed the micha/ty-script-preparation-watching branch from 96be0e7 to 5323898 Compare August 25, 2026 17:31
@astral-sh-bot

astral-sh-bot Bot commented Aug 25, 2026

Copy link
Copy Markdown

ruff-ecosystem results

Linter (stable)

✅ ecosystem check detected no linter changes.

Linter (preview)

✅ ecosystem check detected no linter changes.

Formatter (stable)

✅ ecosystem check detected no format changes.

Formatter (preview)

✅ ecosystem check detected no format changes.

@MichaReiser
MichaReiser force-pushed the micha/ty-script-preparation-watching branch 3 times, most recently from ea88848 to 94b0ebf Compare August 26, 2026 16:44
@codspeed-hq

codspeed-hq Bot commented Aug 26, 2026

Copy link
Copy Markdown

Performance comparison unavailable

The base and head of this comparison were measured with different runner settings, so their benchmark values are not directly comparable.

What changed between base and head:

Re-run the base with the same settings to get a valid performance comparison.


Comparing micha/ty-script-preparation-watching (94b0ebf) with micha/uv-project-sync-service (e01c4ed)

Open in CodSpeed

@MichaReiser
MichaReiser force-pushed the micha/ty-script-preparation-watching branch 2 times, most recently from e8d77af to 518956b Compare August 27, 2026 09:58
Classify PEP 723 scripts during file discovery and keep their membership
updated when files or inline metadata change. Expose an iterable script
view that also includes explicitly opened files.

Restore the saved file classification when an editor overlay is closed,
so discarded script metadata cannot leave an ordinary file excluded.

Keep environment initialization and program selection unchanged.
@MichaReiser
MichaReiser force-pushed the micha/ty-script-preparation-watching branch from 518956b to 33b7a37 Compare August 27, 2026 10:27
Comment on lines +244 to +246
let exclude_script = is_script
&& project.settings(self).src().exclude_scripts
&& !project.is_file_explicitly_included(self, file);

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.

This is sort of an unrelated fix. Previously, we always added a script to the known project files even when exclude_scripts was on, which was wrong. We now respect the exclude_scripts setting

@MichaReiser
MichaReiser force-pushed the micha/ty-script-preparation-watching branch 4 times, most recently from 642d7f1 to fd65ba2 Compare August 27, 2026 13:24

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.

We now create a single progress indicator to sync scripts. This is important now, because VS Code otherwise shows one progress bar per script, which could get annoying real quick in a workspace with many scripts

@MichaReiser
MichaReiser force-pushed the micha/ty-script-preparation-watching branch 2 times, most recently from 61a7e15 to 09e911f Compare August 27, 2026 14:40
Request script synchronization from CLI and language-server startup and
file lifecycle handling. Analysis uses the available environment without
starting or waiting for uv, and poll_sync applies completed results.

The CLI waits for requested synchronization before checking. The language
server waits to report initial semantic diagnostics while other editor
operations can continue. Document pulls return empty reports until the
initial environment is available, then receive a diagnostic refresh.

Remove blocking initialization and its separate worker mode. Submission
does not wait for queue space; per-owner coalescing and the worker limit
keep outstanding work bounded by environments rather than edits.
Show completed and total script synchronizations in the CLI and language
server. Give CLI synchronization batches and checking passes independent
progress bars, retaining a separate status line for each running uv command.

Keep one LSP indicator from the first queued script until all requests
finish, displaying the last started script. Count a request only after
its final result is handled, and retain its reporter across replacements.
Return the project refresh path from apply_changes instead of submitting
the uv request while applying filesystem changes. Let the CLI and language
server schedule the refresh alongside script synchronization.

Remove the progress factory from the database change API and update the
watcher integration helper to submit the returned refresh requests.
Preserve the existing refresh conditions, ancestor fallback, and uv
coordination.
@MichaReiser
MichaReiser force-pushed the micha/ty-script-preparation-watching branch from 09e911f to aed1c7f Compare August 27, 2026 14:45
@MichaReiser
MichaReiser marked this pull request as ready for review August 27, 2026 14:48
@MichaReiser
MichaReiser requested a review from a team as a code owner August 27, 2026 14:48
@MichaReiser
MichaReiser requested review from a team as code owners August 27, 2026 14:48
@astral-sh-bot
astral-sh-bot Bot requested a review from charliermarsh August 27, 2026 14:48
@MichaReiser
MichaReiser requested review from Gankra and zsol and removed request for charliermarsh August 27, 2026 14:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant