Skip to content

Preserve inferred project ATA state - #64327

Draft
Jake Bailey (jakebailey) wants to merge 12 commits into
microsoft:mainfrom
jakebailey:preserve-inferred-ata
Draft

Jake Bailey (jakebailey) wants to merge 12 commits into
microsoft:mainfrom
jakebailey:preserve-inferred-ata

Conversation

@jakebailey

Copy link
Copy Markdown
Member

I noticed in our scripts dir (no tsconfig), that clicking on a file flashed errors. I closed it, opened another file, and the errors appeared and disappeared again! If I opened another file without closing the first, that one was fine.

The issue is that we lose the ATA state when inferred projects go away, so reopening causes ATA to happen again.

Save the state and apply it directly to inferred projects if present.

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

Cached ATA state can be incorrectly reused across unrelated roots or changed manifests.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
What changed in this PR

Preserves ATA results when inferred projects are recreated.

Changes:

  • Stores and restores inferred-project typings state.
  • Adds a reopening regression test.
File Description
projectcollectionbuilder.go Transfers cached ATA state.
projectcollection.go Stores cached ATA state.
project.go Defines ATA state capture/application.
ata/​ata_test.go Tests immediate typings restoration.

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Comment thread tsc/internal/project/projectcollectionbuilder.go Outdated

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

Cache validation misses filesystem invalidation and changed ATA inputs, and content-mapped roots use incompatible hashes.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 3 Medium severity

Open (3)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Invalidate ATA cache when inferred-project options change

tsc/​internal/​project/​project.go:214

Root names and contents are not the complete ATA input. If inferred-project compiler options change while no inferred project exists, the cached state is left untouched; reopening the same roots applies typings computed under the old options before ATA is queued in the background (and resolution-affecting options can also change the unresolved-import set). Preserve and compare the relevant command-line/mapping inputs, or invalidate this cache when those inputs change.

Comment thread tsc/internal/project/project.go Outdated
Comment thread tsc/internal/project/projectcollectionbuilder.go Outdated

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

Stale ATA results can still overwrite invalidated configured or synthetic project state, and key new branches lack regression coverage.

Get a fresh assessment by requesting another Copilot review.

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

Open (3)
Resolved since last review (3)

Comment thread tsc/internal/project/projectcollectionbuilder.go Outdated
Comment thread tsc/internal/project/ata/discovertypings.go Outdated
Comment thread tsc/internal/project/projectcollectionbuilder.go

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 invalidation tombstone map grows indefinitely and adds increasing cloning cost to every snapshot.

Get a fresh assessment by requesting another Copilot review.

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

Open (4)

Comment thread tsc/internal/project/projectcollectionbuilder.go Outdated

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

Unbuilt inferred-project transitions can discard valid cached results or temporarily unregister their watches.

Get a fresh assessment by requesting another Copilot review.

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

Open (6)
Resolved since last review (1)

Comment thread tsc/internal/project/projectcollection.go Outdated
Comment thread tsc/internal/project/projectcollectionbuilder.go Outdated
Comment thread tsc/internal/project/projectcollectionbuilder.go Outdated

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

ATA results can invalidate themselves during concurrent watcher events, and some installed-typing changes bypass invalidation.

Get a fresh assessment by requesting another Copilot review.

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

Open (5)
Resolved since last review (3)

Comment thread tsc/internal/project/projectcollectionbuilder.go Outdated
Comment thread tsc/internal/project/projectcollectionbuilder.go Outdated
Typings cache writes are outputs of acquisition, not discovery changes, and can arrive in the same flush as their result. Keep those events available to the program while excluding them from ATA invalidation, without losing real package-owned inputs or full invalidations.
Installed packages can change their declaration entry points without a
version change or deletion of the old file. Preserve package identities
so active and dormant projects reject obsolete typings without treating
an install’s own cache writes as stale discovery inputs.

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.

Comment thread tsc/internal/project/project.go Outdated
Comment thread tsc/internal/project/projectcollectionbuilder.go Outdated
Comment thread tsc/internal/project/snapshot.go
Equivalent loose files should retain acquired typings without restoring
results made obsolete while no inferred project exists. Compare effective
discovery inputs and revalidate installation demand before acceptance.

Directory deletion notifications must also invalidate ATA-only manifests
that are not represented in the compiler file cache.

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

🔵 Needs a closer look

Asynchronous result ordering, watcher lifetimes, and cross-snapshot invalidation require human verification.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
Resolved since last review (2)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Avoid rebuilding watch list and filename key for every path

tsc/​internal/​project/​projectcollectionbuilder.go:545

This maps and concatenates the same watch list for every changed, created, or deleted URI. Because the helper runs for each project during snapshot updates, large unrelated event batches repeatedly allocate and copy the entire list even when nothing matches. Build the combined slice once outside affectsWatch, and compute fileNameKey once per event rather than once per watched path.

Unrelated event batches should not rebuild the same discovery watch list
for every notification. Bound watch-list allocations per batch while
preserving ancestor deletion handling.

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

🔵 Needs a closer look

Asynchronous snapshot and watcher lifecycle changes need human review, with unresolved correctness and performance concerns.

Review effort: Balanced
Findings: None

Resolved since last review (1)
Previously missed (3)

In code that hasn't changed since last review

Medium severity Resolve only demanded packages during ATA requests

tsc/​internal/​project/​ata/​ata.go:192

Each ATA request creates a fresh resolver and resolves every package in the shared typings cache before determining this project's demand. As that cache accumulates packages from other projects, even a request needing one package performs module-resolution and filesystem work for all cached packages. Resolve only demanded names, while retaining the fresh entry-point check for those names so package metadata changes are still detected.

Medium severity Preserve cached typings for scoped dependencies

tsc/​internal/​project/​ata/​discovertypings.go:110

Scoped dependencies are discovered as @a/b, but the cache and registry use a__b. This new membership check omits an already-installed scoped dependency from cachedTypingPaths, while filterTypings skips reinstalling it because the mangled cache entry is current. Subsequent ATA results therefore lose its typings. Iterate the demanded names, use module.MangleScopedPackageName for cache and registry lookups, and update the original discovery key. Add a regression test with a cached scoped dependency.

Medium severity Avoid repeated dependency discovery while holding snapshotMu

tsc/​internal/​project/​projectcollectionbuilder.go:1132

IsCurrent repeats the filesystem discovery already performed in the background, including dependency-manifest parsing and, when no dependencies are listed, traversal of node_modules. Here it runs inside Snapshot.Clone while Session.updateSnapshot holds snapshotMu. Large dependency trees therefore add synchronous filesystem work to file-open and language-service requests and block snapshot readers. Track discovery inputs and their invalidation generation so results can be checked here without repeating full discovery under the lock.

ATA requests should scale with the project's demand, not the size of the
shared typings cache or dependency trees traversed again under the
snapshot lock. Scoped dependencies must retain their cached typings
across subsequent acquisitions.

Keep discovery in the background and establish provisional watch coverage
before dispatch. Reject results whose coverage was discarded or whose
package-owned declaration availability changed during installation.

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

🔵 Needs a closer look

Asynchronous result handling and watcher invalidation across project lifetimes require final human review.

Review effort: Balanced
Findings: None

Previously missed (1)

In code that hasn't changed since last review

Medium severity Track lexical and real paths for symlinked package invalidation

tsc/​internal/​project/​projectcollectionbuilder.go:550

These comparisons miss real-path events for symlinked discovery packages. For example, let /workspace/node_modules/foo point to /vendor/foo, with foo discovered from dependencies but not imported by the program. After closing the last file, changing /vendor/foo/package.json to provide its own typings does not match the saved /workspace/node_modules watch input. Discovery reads the manifest through request.FS, so snapshot alias expansion need not cover it. Reopening can therefore restore obsolete @types/foo roots and skip discovery. Retain both lexical and real paths for discovered manifests/packages, use them for watch registration and invalidation, and add a regression test for a manifest change while the inferred project is inactive.

Discovery can read symlinked dependency manifests that never enter the
compiler host's alias cache. Real-path changes must invalidate ATA even
when its project is closed or its first installation is still pending.

Coverage must be established before discovery reads, not merely when
installation finishes. Shared watch references are not proof of client
registration, so watch deltas also need snapshot-ordered acknowledgement.

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

🔵 Needs a closer look

Snapshot lifetimes, cache invalidation, and asynchronous watch ordering require human review, with a cache-restoration issue still unresolved.

Review effort: Balanced
Findings: 1 High severity

Open (1)

Comment thread tsc/internal/project/projectcollectionbuilder.go Outdated
A declaration's continued existence does not prove it is still the typing
entry point. A symlinked package manifest can change outside the package
directory while the inferred project is closed, without invalidating its
saved ATA state.

Dormant restoration needs fresh entry-point validation, and cache watch
coverage needs to include the real manifest target.

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.

🔵 Needs a closer look

Cross-snapshot watch sequencing needs human review, and dormant-state validation remains unresolved.

0 open findings

1 resolved since last review
Previously missed (1)

In code that hasn't changed since last review

Medium severity Preserve missing typing paths to prevent stale ATA fallback reuse

tsc/​internal/​project/​project.go:252

MissingTypingFiles is checked when applying an ATA result, but is not retained in Project or the dormant state. If ATA selects cached @types/foo because the package's own types target is missing, then that target appears while the project is closed, reopening before its watch event arrives still accepts the old fallback here. The reopened program can therefore use stale typings until discovery runs again. Preserve the missing paths through result application, cloning, and caching, and reject reuse if any now exists. Add a close/create/reopen regression test without a delivered watch event.

🧠 Review effort: Balanced

Package-owned declarations can appear while an inferred project is
closed, before their watch events arrive. Retain missing declaration
paths in installed ATA state so reopening does not restore an obsolete
cached fallback, including when installation finishes after closing.

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.

🔵 Needs a closer look

Coordinating asynchronous watch registration and cached ATA validity across project lifetimes warrants final human review and full validation.

0 open findings

🧠 Review effort: Balanced

This branch has not been deployed

No deployments
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: Not started

Development

Successfully merging this pull request may close these issues.

2 participants