Skip to content

Do not overwrite computed .d.ts signatures with file versions; add disableUseFileVersionAsSignature to BuilderProgramHost#63665

Closed
kaiguogit wants to merge 1 commit into
microsoft:mainfrom
kaiguogit:fix-signature-downgrade
Closed

Do not overwrite computed .d.ts signatures with file versions; add disableUseFileVersionAsSignature to BuilderProgramHost#63665
kaiguogit wants to merge 1 commit into
microsoft:mainfrom
kaiguogit:fix-signature-downgrade

Conversation

@kaiguogit

Copy link
Copy Markdown

Fixes #63664

Problem

BuilderState.updateShapeSignature, when called with useFileVersionAsSignature: true (the handleDtsMayChangeOf path), overwrites a previously computed .d.ts signature with the file's text version hash. The next time that file is edited, the freshly computed .d.ts signature is compared against the stored text hash — a guaranteed mismatch — so an edit that does not change the file's shape (e.g. whitespace) falsely reads as a shape change and invalidates the file's entire dependent closure:

  • with const enum exports (invalidateJsFiles), the whole transitive closure is queued for full JS re-emit;
  • every file visited by the walk gets its signature overwritten the same way, so the problem re-seeds itself and persists across processes via .tsbuildinfo.

See #63664 for a standalone repro (500-file closure re-emitted for a whitespace-only edit; the identical edit right afterwards emits 1 file) and real-world measurements from a large Angular workspace (70–95s watch rebuilds for whitespace edits; angular/angular-cli#33619).

Changes

  1. updateShapeSignature: never downgrade a computed signature. When falling back from computing a .d.ts signature, keep an existing computed signature instead of replacing it with the file version. New internal SignatureInfo.KeptExistingSignature records this for the test harness.
  2. BuilderProgramHost.disableUseFileVersionAsSignature (new public option, default off): plumbed through to BuilderState.create (previously hardcoded false; the language service already uses true internally). Lets builder-API consumers (e.g. the Angular CLI) opt into computing real .d.ts signatures from the initial program onward, making change detection precise from the very first edit at the cost of declaration-signature computation during the initial build.

With the flag enabled, the repro from #63664 emits 1 file for every whitespace edit including the first; without it, change (1) still prevents previously computed signatures from being corrupted, so false invalidations stop recurring once a file's signature has been computed.

Baselines

  • Signature-info labels change from (used version) / (computed .d.ts during emit) to (kept existing computed signature) where the new branch applies; stored signature values in these scenarios are the .d.ts hashes that were already computed.
  • A few new -discrepancies baselines appear (buildinfo of an incremental build now retains computed signatures where a clean build stores version hashes). The outputs are identical; the discrepancy is limited to signature bookkeeping state. Flagging explicitly for review — happy to adjust the approach if this divergence is undesirable.

Tests

tsbuild (877), tscWatch (454), tsbuildWatch (82), tsc (1831) suites pass locally after baseline updates. Happy to add a dedicated API unit test for the new host option — pointers to the preferred harness location welcome.

…sableUseFileVersionAsSignature to BuilderProgramHost

When handleDtsMayChangeOf updates a file's shape signature with
useFileVersionAsSignature=true, a previously computed .d.ts signature was
overwritten with the file's text version hash. The next comparison against a
freshly computed .d.ts signature is then a guaranteed mismatch, so an edit
that does not change the file's shape (e.g. whitespace) falsely invalidates
the file's entire dependent closure: with const enum exports the whole
closure is re-emitted, and every visited file's signature is overwritten the
same way, making the problem self-perpetuating across rebuilds and across
sessions via .tsbuildinfo.

Changes:
- BuilderState.updateShapeSignature: when falling back from computing a
  signature, keep an existing computed signature instead of downgrading it to
  the file version (new SignatureInfo.KeptExistingSignature).
- BuilderProgramHost: new disableUseFileVersionAsSignature option, plumbed to
  BuilderState.create (previously hardcoded to false), so API consumers such
  as build tools can opt into computing real .d.ts signatures from the
  initial program onward, making change detection precise from the first
  edit.

Fixes microsoft#63664
Copilot AI review requested due to automatic review settings July 21, 2026 22:08
@github-project-automation github-project-automation Bot moved this to Not started in PR Backlog Jul 21, 2026
@typescript-automation typescript-automation Bot added the For Uncommitted Bug PR for untriaged, rejected, closed or missing bug label Jul 21, 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.

Pull request overview

This PR addresses an incremental-builder regression where BuilderState.updateShapeSignature could overwrite an existing computed .d.ts shape signature with the file’s text-version hash (via the handleDtsMayChangeOf path). That “downgrade” guarantees a mismatch on the next edit (even whitespace-only), incorrectly treating the file as shape-changed and potentially invalidating/re-emitting an overly large dependent closure; the PR also exposes an opt-in host flag for always computing .d.ts shape signatures from the initial build.

Changes:

  • Update BuilderState.updateShapeSignature to avoid replacing an existing computed signature with the file-version hash (introducing SignatureInfo.KeptExistingSignature for test baselining).
  • Add BuilderProgramHost.disableUseFileVersionAsSignature and plumb it into builder state creation.
  • Update a large set of watch/incremental baselines (including new *-discrepancies files) to reflect changed signature bookkeeping.

Reviewed changes

Copilot reviewed 73 out of 73 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/baselines/reference/tscWatch/sourceOfProjectReferenceRedirect/with-simple-project-when-solution-is-already-built.js Baseline updated for new signature-info labeling (“kept existing computed signature”).
tests/baselines/reference/tscWatch/projectsWithReferences/on-transitive-references.js Baseline updated for preserved computed signatures in watch output.
tests/baselines/reference/tscWatch/projectsWithReferences/on-transitive-references-with-nodenext.js Baseline updated for preserved computed signatures in watch output.
tests/baselines/reference/tscWatch/projectsWithReferences/on-transitive-references-in-different-folders.js Baseline updated for preserved computed signatures in watch output.
tests/baselines/reference/tscWatch/projectsWithReferences/on-transitive-references-in-different-folders-with-no-files-clause.js Baseline updated for preserved computed signatures in watch output.
tests/baselines/reference/tscWatch/emitAndErrorUpdates/isolatedModulesAndD/transitive-exports/yes-circular-import/exports.js Baseline updated for preserved computed signatures in watch output.
tests/baselines/reference/tscWatch/emitAndErrorUpdates/isolatedModulesAndD/transitive-exports/yes-circular-import/exports-with-incremental.js Baseline updated for preserved computed signatures in watch output.
tests/baselines/reference/tscWatch/emitAndErrorUpdates/isolatedModulesAndD/transitive-exports/no-circular-import/export.js Baseline updated for preserved computed signatures in watch output.
tests/baselines/reference/tscWatch/emitAndErrorUpdates/isolatedModulesAndD/transitive-exports/no-circular-import/export-with-incremental.js Baseline updated for preserved computed signatures in watch output.
tests/baselines/reference/tscWatch/emitAndErrorUpdates/isolatedModulesAndD/file-not-exporting-a-deep-multilevel-import-that-changes.js Baseline updated for preserved computed signatures in watch output.
tests/baselines/reference/tscWatch/emitAndErrorUpdates/isolatedModulesAndD/file-not-exporting-a-deep-multilevel-import-that-changes-with-incremental.js Baseline updated for preserved computed signatures in watch output.
tests/baselines/reference/tscWatch/emitAndErrorUpdates/isolatedModulesAndD/deepImportChanges/errors-for-.ts-change.js Baseline updated for preserved computed signatures in watch output.
tests/baselines/reference/tscWatch/emitAndErrorUpdates/isolatedModulesAndD/deepImportChanges/errors-for-.ts-change-with-incremental.js Baseline updated for preserved computed signatures in watch output.
tests/baselines/reference/tscWatch/emitAndErrorUpdates/isolatedModulesAndD/deepImportChanges/errors-for-.d.ts-change.js Baseline updated for preserved computed signatures in watch output.
tests/baselines/reference/tscWatch/emitAndErrorUpdates/isolatedModulesAndD/deepImportChanges/errors-for-.d.ts-change-with-incremental.js Baseline updated for preserved computed signatures in watch output.
tests/baselines/reference/tscWatch/emitAndErrorUpdates/defaultAndD/transitive-exports/yes-circular-import/exports.js Baseline updated for preserved computed signatures in watch output.
tests/baselines/reference/tscWatch/emitAndErrorUpdates/defaultAndD/transitive-exports/yes-circular-import/exports-with-incremental.js Baseline updated for preserved computed signatures in watch output.
tests/baselines/reference/tscWatch/emitAndErrorUpdates/defaultAndD/transitive-exports/no-circular-import/export.js Baseline updated for preserved computed signatures in watch output.
tests/baselines/reference/tscWatch/emitAndErrorUpdates/defaultAndD/transitive-exports/no-circular-import/export-with-incremental.js Baseline updated for preserved computed signatures in watch output.
tests/baselines/reference/tscWatch/emitAndErrorUpdates/defaultAndD/file-not-exporting-a-deep-multilevel-import-that-changes.js Baseline updated for preserved computed signatures in watch output.
tests/baselines/reference/tscWatch/emitAndErrorUpdates/defaultAndD/file-not-exporting-a-deep-multilevel-import-that-changes-with-incremental.js Baseline updated for preserved computed signatures in watch output.
tests/baselines/reference/tscWatch/emitAndErrorUpdates/defaultAndD/deepImportChanges/errors-for-.ts-change.js Baseline updated for preserved computed signatures in watch output.
tests/baselines/reference/tscWatch/emitAndErrorUpdates/defaultAndD/deepImportChanges/errors-for-.ts-change-with-incremental.js Baseline updated for preserved computed signatures in watch output.
tests/baselines/reference/tscWatch/emitAndErrorUpdates/defaultAndD/deepImportChanges/errors-for-.d.ts-change.js Baseline updated for preserved computed signatures in watch output.
tests/baselines/reference/tscWatch/emitAndErrorUpdates/defaultAndD/deepImportChanges/errors-for-.d.ts-change-with-incremental.js Baseline updated for preserved computed signatures in watch output.
tests/baselines/reference/tscWatch/emitAndErrorUpdates/default/transitive-exports/yes-circular-import/exports.js Baseline updated for preserved computed signatures in watch output.
tests/baselines/reference/tscWatch/emitAndErrorUpdates/default/transitive-exports/no-circular-import/export.js Baseline updated for preserved computed signatures in watch output.
tests/baselines/reference/tscWatch/emitAndErrorUpdates/default/file-not-exporting-a-deep-multilevel-import-that-changes.js Baseline updated for preserved computed signatures in watch output.
tests/baselines/reference/tscWatch/emitAndErrorUpdates/default/deepImportChanges/errors-for-.ts-change.js Baseline updated for preserved computed signatures in watch output.
tests/baselines/reference/tscWatch/emitAndErrorUpdates/default/deepImportChanges/errors-for-.ts-change-with-incremental.js Baseline updated for updated buildinfo signature bookkeeping.
tests/baselines/reference/tscWatch/emit/emit-for-configured-projects/should-return-cascaded-affected-file-list.js Baseline updated for preserved computed signatures in watch output.
tests/baselines/reference/tscWatch/emit/emit-file-content/should-emit-specified-file.js Baseline updated for preserved computed signatures in watch output.
tests/baselines/reference/tsc/noEmit/multiFile/changes-with-initial-noEmit-incremental.js Baseline updated for buildinfo signatures (more computed signatures retained).
tests/baselines/reference/tsc/noEmit/multiFile/changes-with-initial-noEmit-incremental-declaration.js Baseline updated for buildinfo signatures (declaration mode).
tests/baselines/reference/tsc/noEmit/multiFile/changes-with-initial-noEmit-composite.js Baseline updated for buildinfo signatures (composite mode).
tests/baselines/reference/tsc/noEmit/multiFile/changes-with-initial-noEmit-composite-discrepancies.js Discrepancy baseline updated to match new signature bookkeeping.
tests/baselines/reference/tsc/noEmit/multiFile/changes-incremental-declaration.js Baseline updated for buildinfo signatures across incremental steps.
tests/baselines/reference/tsc/noEmit/multiFile/changes-composite.js Baseline updated for buildinfo signatures across incremental steps.
tests/baselines/reference/tsc/noEmit/multiFile/changes-composite-discrepancies.js Discrepancy baseline updated to match new signature bookkeeping.
tests/baselines/reference/tsc/incremental/with-const-enums.js Baseline updated for buildinfo signature bookkeeping.
tests/baselines/reference/tsc/incremental/with-const-enums-with-preserveConstEnums.js Baseline updated for buildinfo signature bookkeeping.
tests/baselines/reference/tsc/incremental/with-aliased-in-different-file-const-enums.js Baseline updated for buildinfo signature bookkeeping.
tests/baselines/reference/tsc/incremental/with-aliased-in-different-file-const-enums-with-preserveConstEnums.js Baseline updated for buildinfo signature bookkeeping.
tests/baselines/reference/tsc/incremental/with-aliased-const-enums.js Baseline updated for buildinfo signature bookkeeping.
tests/baselines/reference/tsc/incremental/with-aliased-const-enums-with-preserveConstEnums.js Baseline updated for buildinfo signature bookkeeping.
tests/baselines/reference/tsc/incremental/change-to-type-that-gets-used-as-global-through-export-in-another-file.js Baseline updated for signature bookkeeping; emitSignatures now recorded in buildinfo.
tests/baselines/reference/tsc/incremental/change-to-type-that-gets-used-as-global-through-export-in-another-file-through-indirect-import.js Baseline updated for signature bookkeeping; emitSignatures now recorded in buildinfo.
tests/baselines/reference/tsc/incremental/change-to-type-that-gets-used-as-global-through-export-in-another-file-through-indirect-import-discrepancies.js New discrepancy baseline capturing clean vs incremental buildinfo signature differences.
tests/baselines/reference/tsc/incremental/change-to-type-that-gets-used-as-global-through-export-in-another-file-discrepancies.js New discrepancy baseline capturing clean vs incremental buildinfo signature differences.
tests/baselines/reference/tsc/cancellationToken/when-using-state.js Baseline updated for preserved computed signatures in builder output.
tests/baselines/reference/tsc/cancellationToken/when-emitting-buildInfo.js Baseline updated for preserved computed signatures in builder output.
tests/baselines/reference/tsbuild/noEmit/multiFile/changes-with-initial-noEmit-incremental.js Baseline updated for buildinfo signatures (tsbuild harness).
tests/baselines/reference/tsbuild/noEmit/multiFile/changes-with-initial-noEmit-incremental-declaration.js Baseline updated for buildinfo signatures (tsbuild harness, declaration mode).
tests/baselines/reference/tsbuild/noEmit/multiFile/changes-with-initial-noEmit-composite.js Baseline updated for buildinfo signatures (tsbuild harness, composite mode).
tests/baselines/reference/tsbuild/noEmit/multiFile/changes-with-initial-noEmit-composite-discrepancies.js Discrepancy baseline updated to match new signature bookkeeping.
tests/baselines/reference/tsbuild/noEmit/multiFile/changes-incremental-declaration.js Baseline updated for buildinfo signatures across incremental steps.
tests/baselines/reference/tsbuild/noEmit/multiFile/changes-composite.js Baseline updated for buildinfo signatures across incremental steps.
tests/baselines/reference/tsbuild/noEmit/multiFile/changes-composite-discrepancies.js Discrepancy baseline updated to match new signature bookkeeping.
tests/baselines/reference/tsbuild/inferredTypeFromTransitiveModule/reports-errors-in-files-affected-by-change-in-signature-with-isolatedModules-discrepancies.js New discrepancy baseline capturing signature differences for inferred-type scenario.
tests/baselines/reference/tsbuild/inferredTypeFromTransitiveModule/inferred-type-from-transitive-module.js Baseline updated for buildinfo signatures in inferred-type scenario.
tests/baselines/reference/tsbuild/inferredTypeFromTransitiveModule/inferred-type-from-transitive-module-with-isolatedModules.js Baseline updated for buildinfo signatures in inferred-type scenario.
tests/baselines/reference/tsbuild/inferredTypeFromTransitiveModule/inferred-type-from-transitive-module-with-isolatedModules-discrepancies.js New discrepancy baseline capturing clean vs incremental signature differences.
tests/baselines/reference/tsbuild/inferredTypeFromTransitiveModule/inferred-type-from-transitive-module-discrepancies.js New discrepancy baseline capturing clean vs incremental signature differences.
src/testRunner/unittests/helpers/baseline.ts Test harness updated to display/validate KeptExistingSignature.
src/compiler/builderState.ts Core logic change: avoid overwriting computed signatures with file-version signatures; add enum value.
src/compiler/builderPublic.ts Add public host option disableUseFileVersionAsSignature.
src/compiler/builder.ts Plumb disableUseFileVersionAsSignature from host into BuilderState.create.

Comment on lines +454 to +467
// Do not overwrite a previously computed .d.ts signature with the file version:
// replacing it would make the next comparison against a freshly computed
// signature a guaranteed mismatch, falsely invalidating the file's entire
// dependent closure even for edits that do not change the file's shape.
// Keep the stored signature instead; use the version only when no computed
// signature exists.
if (!sourceFile.isDeclarationFile && !state.useFileVersionAsSignature && prevSignature !== undefined && prevSignature !== sourceFile.version) {
latestSignature = prevSignature;
if (host.storeSignatureInfo) (state.signatureInfo ??= new Map()).set(sourceFile.resolvedPath, SignatureInfo.KeptExistingSignature);
}
else {
latestSignature = sourceFile.version;
if (host.storeSignatureInfo) (state.signatureInfo ??= new Map()).set(sourceFile.resolvedPath, SignatureInfo.UsedVersion);
}
@kaiguogit

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

@MartinJohns

Copy link
Copy Markdown
Contributor

This looks like AI. Besides that, you made the changes in the wrong repository.

@RyanCavanaugh

Copy link
Copy Markdown
Member

The TypeScript repo is closed for development; PR should be in the typescript-go repo. Please see the top section of CONTRIBUTING.md and pinned issue #62963.

@github-project-automation github-project-automation Bot moved this from Not started to Done in PR Backlog Jul 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

Status: Done

4 participants