Skip to content

Fix compiler option invalidation in incremental builds - #64712

Open
Jake Bailey (jakebailey) wants to merge 24 commits into
microsoft:mainfrom
jakebailey:compiler-option-invalidation
Open

Jake Bailey (jakebailey) wants to merge 24 commits into
microsoft:mainfrom
jakebailey:compiler-option-invalidation

Conversation

@jakebailey

@jakebailey Jake Bailey (jakebailey) commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

This started off as an off hand remark in a session where it mentioned that EOPT was not correctly handled as an emit-affecting option.

I had copilot vet every option, and it came up with a lot of cases where we were not correctly invalidating during incremental mode, did not store enough in tsbuildinfo, stored nonderministic data, etc.

The second commit contains the actual fixes.

Fixes #63223

Option-only edits can leave declarations, diagnostics, and dependent
projects inconsistent with a fresh build. These baselines capture the
existing behavior before correcting invalidation.

A subsequent source edit also demonstrates that option-only declaration
emit can leave stale shape signatures, even for an option already
classified as affecting emit.
Compiler option changes can affect declaration types and diagnostics
without altering source text or the dependency graph. Emit invalidation
must include these options and retain their values across build-info
round trips, including ordered resolution settings and strict defaults.

Option-only declaration writes must also refresh shape signatures.
Otherwise a later source edit can appear unchanged and leave dependent
files with stale types and diagnostics.
Copilot AI balanced review requested due to automatic review settings October 9, 2026 23:12
@typescript-automation typescript-automation Bot added Author: Team For Uncommitted Bug PR for untriaged, rejected, closed or missing bug labels Oct 9, 2026

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.

🟡 Changes recommended

paths invalidation omits its effective PathsBasePath, allowing stale incremental results for inherited configurations.

1 open finding
What changed in this PR

Improves incremental compiler-option invalidation and build-info serialization so option changes correctly refresh diagnostics and emitted declarations.

Changes:

  • Expands option invalidation and build-info metadata.
  • Preserves structured compiler options during build-info round trips.
  • Adds comparison tests and updates affected baselines.
File Description
tsc/​testdata/​baselines/​reference/​tscWatch/​commandLineWatch/​watch-handles-tsconfig-with-extends-base-modified.js Updates watch emit baseline.
tsc/​testdata/​baselines/​reference/​tsc/​projectReferences/​issues-a-nice-error-when-the-input-file-is-missing-when-module-reference-is-not-relative.js Records paths in build info.
tsc/​testdata/​baselines/​reference/​tsc/​libraryResolution/​with-config.js Records typeRoots.
tsc/​testdata/​baselines/​reference/​tsc/​libraryResolution/​with-config-with-libReplacement.js Records typeRoots with lib replacement.
tsc/​testdata/​baselines/​reference/​tsc/​declarationEmit/​reports-dts-generation-errors-with-incremental.js Records module resolution mode.
tsc/​testdata/​baselines/​reference/​tsc/​compilerOptionChanges/​strict-default-incremental-direction-1.js Covers strict-default invalidation.
tsc/​testdata/​baselines/​reference/​tsc/​compilerOptionChanges/​strict-default-incremental-direction-0.js Covers reverse strict-default transition.
tsc/​testdata/​baselines/​reference/​tsbuildWatch/​roots/​when-root-file-is-from-referenced-project.js Updates watched referenced-project behavior.
tsc/​testdata/​baselines/​reference/​tsbuildWatch/​roots/​when-root-file-is-from-referenced-project-and-shared-is-first.js Updates alternate project ordering.
tsc/​testdata/​baselines/​reference/​tsbuildWatch/​programUpdates/​works-with-extended-source-files.js Updates rebuild output tracking.
tsc/​testdata/​baselines/​reference/​tsbuildWatch/​moduleResolution/​handles-the-cache-correctly-when-two-projects-use-different-module-resolution-settings.js Persists resolution-sensitive options.
tsc/​testdata/​baselines/​reference/​tsbuildWatch/​libraryResolution/​with-config.js Records watched typeRoots.
tsc/​testdata/​baselines/​reference/​tsbuildWatch/​libraryResolution/​with-config-with-libReplacement.js Records watched replacement-library roots.
tsc/​testdata/​baselines/​reference/​tsbuildWatch/​dependencyUpdate/​watches-absolute-non-root-dependency-updates.js Persists absolute path mappings.
tsc/​testdata/​baselines/​reference/​tsbuild/​transitiveReferences/​builds-correctly.js Records transitive path mappings.
tsc/​testdata/​baselines/​reference/​tsbuild/​roots/​when-root-file-is-from-referenced-project.js Updates referenced-root build info.
tsc/​testdata/​baselines/​reference/​tsbuild/​roots/​when-root-file-is-from-referenced-project-and-shared-is-first.js Updates alternate referenced-root order.
tsc/​testdata/​baselines/​reference/​tsbuild/​projectReferenceRedirect/​uses-correct-project-reference-redirect-when-file-belongs-to-multiple-sub-projects.js Records redirect resolution settings.
tsc/​testdata/​baselines/​reference/​tsbuild/​moduleResolution/​type-reference-resolution-uses-correct-options-for-different-resolution-options-referenced-project.js Persists per-project type roots.
tsc/​testdata/​baselines/​reference/​tsbuild/​moduleResolution/​resolves-specifier-in-output-declaration-file-from-referenced-project-correctly.js Records declaration path mappings.
tsc/​testdata/​baselines/​reference/​tsbuild/​moduleResolution/​resolves-specifier-in-output-declaration-file-from-referenced-project-correctly-with-preserveSymlinks.js Updates symlink-resolution baseline.
tsc/​testdata/​baselines/​reference/​tsbuild/​moduleResolution/​resolution-from-d.ts-of-referenced-project.js Persists module resolution and paths.
tsc/​testdata/​baselines/​reference/​tsbuild/​libraryResolution/​with-config.js Records build-mode typeRoots.
tsc/​testdata/​baselines/​reference/​tsbuild/​libraryResolution/​with-config-with-libReplacement.js Records replacement-library type roots.
tsc/​testdata/​baselines/​reference/​tsbuild/​inferredTypeFromMonorepoReference/​inferred-type-from-referenced-project-that-references-another-project-in-monorepo.js Persists monorepo resolution mode.
tsc/​testdata/​baselines/​reference/​tsbuild/​dependencyUpdate/​rebuilds-when-absolute-non-root-dependency-is-updated.js Records absolute dependency mappings.
tsc/​testdata/​baselines/​reference/​tsbuild/​declarationEmitForReferencedProjectTypesSubpath/​declaration-emit-names-referenced-project-type-via-types-subpath.js Persists declaration resolution mode.
tsc/​testdata/​baselines/​reference/​tsbuild/​declarationEmit/​when-declaration-file-used-inferred-type-from-referenced-project.js Records declaration path mappings.
tsc/​testdata/​baselines/​reference/​tsbuild/​declarationEmit/​reports-dts-generation-errors-with-incremental.js Records resolution mode for diagnostics.
tsc/​testdata/​baselines/​reference/​fourslash/​state/​findAllRefsRootOfReferencedProjectDeclarationMaps.baseline Updates fourslash build-info state.
tsc/​internal/​tsoptions/​rawcompileroptions.go Extracts reusable raw-option decoding.
tsc/​internal/​tsoptions/​options_generated.go Expands generated invalidation and persistence logic.
tsc/​internal/​tsoptions/​compileroptions_test.go Tests deep and strict-default comparisons.
tsc/​internal/​execute/​tsctests/​readablebuildinfo.go Adapts readable build-info options.
tsc/​internal/​execute/​incremental/​snapshottobuildinfo.go Uses structured build-info options.
tsc/​internal/​execute/​incremental/​emitfileshandler.go Recomputes signatures after option changes.
tsc/​internal/​execute/​incremental/​buildInfo.go Adds typed compiler-option build info.
tsc/​internal/​execute/​incremental/​buildinfo_path_test.go Tests option JSON round trips.
tools/​scripts/​tsc/​options.ts Marks additional invalidating options.
tools/​scripts/​tsc/​options.test.ts Tests metadata and deep comparisons.
tools/​scripts/​tsc/​options-model.ts Documents invalidation metadata.
tools/​scripts/​tsc/​generate-options.ts Generates deep comparisons and validation.
Files not reviewed (1)
  • tsc/internal/tsoptions/options_generated.go: Generated file

🧠 Review effort: Balanced


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

Comment thread tools/scripts/tsc/options.ts
Comment thread tsc/internal/execute/incremental/emitfileshandler.go
@typescript-automation typescript-automation Bot added For Backlog Bug PRs that fix a backlog bug and removed For Uncommitted Bug PR for untriaged, rejected, closed or missing bug labels Oct 9, 2026
@jakebailey Jake Bailey (jakebailey) changed the title Test incremental invalidation after compiler option changes Fix compiler option invalidation in incremental builds Oct 9, 2026
Identical paths mappings mean different things when inherited from
configs in different directories. Switching only the extended config
can leave declarations naming an alias that no longer resolves to the
inferred type.

Capture both directions in build and incremental modes before fixing
invalidation and persistence of the inherited base directory.
Paths mappings depend on the directory of the config that defines them.
Comparing the mapping alone misses changes to that inherited directory
and can leave declarations naming aliases for the wrong inferred type.

Persist the derived base relative to build info and compare it whenever
paths participates in invalidation. Ignore unused bases when mappings
are absent or empty.

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.

🟡 Changes recommended

moduleDetection remains absent from invalidation metadata, allowing stale diagnostics and emitted module markers.

1 open finding
1 resolved since last review
Files not reviewed (1)
  • tsc/internal/tsoptions/options_generated.go: Generated file

🧠 Review effort: Balanced

return field;
};
return [`${value("oldOptions")} != ${value("newOptions")}`];
const expressions = [optionValuesDiffer(goType(option), value("oldOptions"), value("newOptions"))];
Replacement libraries can change overload order without changing their
contents or membership. Reuse the existing build-info file ordering to
invalidate stale state without persisting a duplicate library list.
All option-change regressions now match fresh compilation without
known-bug allowances. Refresh remaining trace baselines for the new
invalidation behavior.

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 Backlog Bug PRs that fix a backlog bug

Projects

Status: Not started

2 participants