Do not overwrite computed .d.ts signatures with file versions; add disableUseFileVersionAsSignature to BuilderProgramHost#63665
Conversation
…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
There was a problem hiding this comment.
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.updateShapeSignatureto avoid replacing an existing computed signature with the file-version hash (introducingSignatureInfo.KeptExistingSignaturefor test baselining). - Add
BuilderProgramHost.disableUseFileVersionAsSignatureand plumb it into builder state creation. - Update a large set of watch/incremental baselines (including new
*-discrepanciesfiles) 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. |
| // 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); | ||
| } |
|
@microsoft-github-policy-service agree |
|
This looks like AI. Besides that, you made the changes in the wrong repository. |
|
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. |
Fixes #63664
Problem
BuilderState.updateShapeSignature, when called withuseFileVersionAsSignature: true(thehandleDtsMayChangeOfpath), 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:const enumexports (invalidateJsFiles), the whole transitive closure is queued for full JS re-emit;.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
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 internalSignatureInfo.KeptExistingSignaturerecords this for the test harness.BuilderProgramHost.disableUseFileVersionAsSignature(new public option, default off): plumbed through toBuilderState.create(previously hardcodedfalse; the language service already usestrueinternally). 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
(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.-discrepanciesbaselines 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.