Skip to content

Commit b070534

Browse files
committed
Preserve checking globals before declaration signature generation
Incremental checking must not lose a missing-global error merely because signature generation discovers it first. Retain each affected file’s checking diagnostics before computing its declaration shape, without collecting incidental emit-only globals or checking emit-only builds.
1 parent 87d2e03 commit b070534

15 files changed

Lines changed: 5120 additions & 67 deletions

‎tsc/internal/compiler/program.go‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -826,10 +826,16 @@ func (p *Program) GetSemanticDiagnostics(ctx context.Context, sourceFile *ast.So
826826
// GetSemanticDiagnosticsForIncremental includes newly discovered globals in each
827827
// file's cached diagnostics and leaves noEmit filtering to the builder.
828828
func (p *Program) GetSemanticDiagnosticsForIncremental(ctx context.Context, sourceFiles []*ast.SourceFile) map[*ast.SourceFile][]*ast.Diagnostic {
829-
allDiags := p.collectCheckerDiagnosticsFromFiles(ctx, sourceFiles, func(ctx context.Context, c *checker.Checker, file *ast.SourceFile) []*ast.Diagnostic {
829+
collect := func(ctx context.Context, c *checker.Checker, file *ast.SourceFile) []*ast.Diagnostic {
830830
return p.getBindAndCheckDiagnosticsWithChecker(ctx, c, file, true /*includeDeferredGlobals*/)
831-
})
831+
}
832832
result := make(map[*ast.SourceFile][]*ast.Diagnostic, len(sourceFiles))
833+
if len(sourceFiles) == 1 {
834+
file := sourceFiles[0]
835+
result[file] = p.collectCheckerDiagnostics(ctx, file, collect)
836+
return result
837+
}
838+
allDiags := p.collectCheckerDiagnosticsFromFiles(ctx, sourceFiles, collect)
833839
for i, diags := range allDiags {
834840
result[sourceFiles[i]] = filterAndSortDiagnostics(diags)
835841
}

‎tsc/internal/execute/incremental/affectedfileshandler.go‎

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,16 @@ func (c dtsMayChange) addFileToAffectedFilesPendingEmit(filePath tspath.PathKey,
2222
}
2323

2424
type updatedSignature struct {
25-
mu sync.Mutex
26-
signature string
27-
kind SignatureUpdateKind
25+
mu sync.Mutex
26+
signature string
27+
kind SignatureUpdateKind
28+
semanticDiagnostics *DiagnosticsOrBuildInfoDiagnosticsWithFileName
2829
}
2930

3031
type affectedFilesHandler struct {
3132
ctx context.Context
3233
program *Program
34+
collectSemanticDiagnostics bool
3335
hasAllFilesExcludingDefaultLibraryFile atomic.Bool
3436
updatedSignatures collections.SyncMap[tspath.PathKey, *updatedSignature]
3537
dtsMayChange []dtsMayChange
@@ -101,6 +103,13 @@ func (h *affectedFilesHandler) updateShapeSignature(file *ast.SourceFile, useFil
101103
// JSON files have no declaration output from which to compute a shape
102104
// signature, so use the file version to conservatively invalidate dependents.
103105
if !file.IsDeclarationFile && !ast.IsJsonSourceFile(file) && !useFileVersionAsSignature {
106+
if h.collectSemanticDiagnostics && !h.program.snapshot.options.NoCheck.IsTrue() {
107+
// Signature generation can discover globals before checking does. Keep the
108+
// checking diagnostics first, without retaining incidental emit-only errors.
109+
update.semanticDiagnostics = &DiagnosticsOrBuildInfoDiagnosticsWithFileName{
110+
diagnostics: h.program.program.GetSemanticDiagnosticsForIncremental(h.ctx, core.SingleElementSlice(file))[file],
111+
}
112+
}
104113
update.signature = h.computeDtsSignature(file)
105114
}
106115
// Default is to use file version as signature
@@ -347,6 +356,12 @@ func (h *affectedFilesHandler) updateSnapshot() {
347356
h.program.snapshot.semanticDiagnosticsPerFile.Delete(file)
348357
return true
349358
})
359+
h.updatedSignatures.Range(func(filePath tspath.PathKey, update *updatedSignature) bool {
360+
if update.semanticDiagnostics != nil && h.filesToRemoveDiagnostics.Has(filePath) {
361+
h.program.snapshot.semanticDiagnosticsPerFile.Store(filePath, update.semanticDiagnostics)
362+
}
363+
return true
364+
})
350365
for _, change := range h.dtsMayChange {
351366
for filePath, emitKind := range change {
352367
h.program.snapshot.addFileToAffectedFilesPendingEmit(filePath, emitKind)
@@ -356,12 +371,12 @@ func (h *affectedFilesHandler) updateSnapshot() {
356371
h.program.snapshot.buildInfoEmitPending.Store(true)
357372
}
358373

359-
func collectAllAffectedFiles(ctx context.Context, program *Program) {
374+
func collectAllAffectedFiles(ctx context.Context, program *Program, collectSemanticDiagnostics bool) {
360375
if program.snapshot.changedFilesSet.Size() == 0 {
361376
return
362377
}
363378

364-
handler := affectedFilesHandler{ctx: ctx, program: program}
379+
handler := affectedFilesHandler{ctx: ctx, program: program, collectSemanticDiagnostics: collectSemanticDiagnostics}
365380
wg := core.NewWorkGroup(handler.program.program.SingleThreaded())
366381
var result collections.SyncSet[*ast.SourceFile]
367382
program.snapshot.changedFilesSet.Range(func(file tspath.PathKey) bool {

‎tsc/internal/execute/incremental/emitfileshandler.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ func (h *emitFilesHandler) emitBuildInfo(options compiler.EmitOptions, result *c
116116

117117
func (h *emitFilesHandler) emitFilesIncremental(options compiler.EmitOptions) []*compiler.EmitResult {
118118
// Get all affected files
119-
collectAllAffectedFiles(h.ctx, h.program)
119+
collectAllAffectedFiles(h.ctx, h.program, false /*collectSemanticDiagnostics*/)
120120
if h.ctx.Err() != nil {
121121
return nil
122122
}

‎tsc/internal/execute/incremental/program.go‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -275,13 +275,15 @@ func (p *Program) Emit(ctx context.Context, options compiler.EmitOptions) *compi
275275
func (p *Program) collectSemanticDiagnosticsOfAffectedFiles(ctx context.Context, file *ast.SourceFile) {
276276
if p.snapshot.canUseIncrementalState() {
277277
// Get all affected files
278-
collectAllAffectedFiles(ctx, p)
278+
collectAllAffectedFiles(ctx, p, true /*collectSemanticDiagnostics*/)
279279
if ctx.Err() != nil {
280280
return
281281
}
282-
283282
if p.snapshot.semanticDiagnosticsPerFile.Size() == len(p.program.GetSourceFiles()) {
284-
// If we have all the files,
283+
if p.snapshot.checkPending && !p.snapshot.options.NoCheck.IsTrue() {
284+
p.snapshot.checkPending = false
285+
p.snapshot.buildInfoEmitPending.Store(true)
286+
}
285287
return
286288
}
287289
}

‎tsc/internal/execute/tsctests/tsc_test.go‎

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2326,12 +2326,10 @@ func TestTscIncremental(t *testing.T) {
23262326
edit: func(sys *TestSys) {
23272327
sys.appendFile("/home/src/workspaces/project/repro.ts", "\n// comment-only edit\n")
23282328
},
2329-
expectedDiff: "Like Strada, signature generation produces the missing-global diagnostic before semantic checking, so it is excluded from the file's semantic diagnostics.",
23302329
},
23312330
{
2332-
caption: "no change",
2333-
edit: noChange.edit,
2334-
expectedDiff: "Like Strada, the cached semantic diagnostics do not include the missing-global diagnostic produced during signature generation.",
2331+
caption: "no change",
2332+
edit: noChange.edit,
23352333
},
23362334
{
23372335
caption: "delete build info to restore the semantic diagnostic",
@@ -2394,12 +2392,10 @@ func TestTscIncremental(t *testing.T) {
23942392
edit: func(sys *TestSys) {
23952393
sys.appendFile("/home/src/workspaces/project/repro.ts", "\n// comment-only edit\n")
23962394
},
2397-
expectedDiff: "Signature generation produces the missing-global diagnostic before semantic checking, so the build loses it.",
23982395
},
23992396
{
2400-
caption: "no change",
2401-
edit: noChange.edit,
2402-
expectedDiff: "The cached semantic diagnostics do not include the missing-global diagnostic produced during signature generation.",
2397+
caption: "no change",
2398+
edit: noChange.edit,
24032399
},
24042400
newTscEdit("remove the generator", func(sys *TestSys) {
24052401
sys.writeFileNoError("/home/src/workspaces/project/repro.ts", `export const value = 1;`)
@@ -2548,6 +2544,44 @@ func TestTscIncremental(t *testing.T) {
25482544
},
25492545
}
25502546

2547+
for _, checkers := range []int{1, 2} {
2548+
for _, build := range []bool{false, true} {
2549+
args := []string{}
2550+
if build {
2551+
args = []string{"--build"}
2552+
}
2553+
test := &tscInput{
2554+
subScenario: fmt.Sprintf("global diagnostics with declaration emit checkers %d build %t", checkers, build),
2555+
files: FileMap{
2556+
"/home/src/workspaces/project/tsconfig.json": fmt.Sprintf(`{"compilerOptions": {"declaration": true, "incremental": true, "checkers": %d}}`, checkers),
2557+
"/home/src/workspaces/project/generator.ts": `export function* values() { yield 1; }`,
2558+
"/home/src/workspaces/project/index.ts": `import { values } from "./generator"; export const get = values;`,
2559+
},
2560+
commandLineArgs: args,
2561+
edits: []*tscEdit{
2562+
newTscEdit("edit both modules without changing their types", func(sys *TestSys) {
2563+
sys.appendFile("/home/src/workspaces/project/generator.ts", "\n// generator comment\n")
2564+
sys.appendFile("/home/src/workspaces/project/index.ts", "\n// consumer comment\n")
2565+
}),
2566+
noChange,
2567+
newTscEdit("remove the generator", func(sys *TestSys) {
2568+
sys.writeFileNoError("/home/src/workspaces/project/generator.ts", `export function values() { return 1; }`)
2569+
}),
2570+
noChange,
2571+
newTscEdit("restore the generator", func(sys *TestSys) {
2572+
sys.writeFileNoError("/home/src/workspaces/project/generator.ts", `export function* values() { yield 2; }`)
2573+
}),
2574+
noChange,
2575+
},
2576+
}
2577+
watchTest := *test
2578+
watchTest.subScenario += " watch"
2579+
watchTest.commandLineArgs = append(args, "--watch")
2580+
watchTest.edits = core.Filter(test.edits, func(edit *tscEdit) bool { return edit != noChange })
2581+
testCases = append(testCases, test, &watchTest)
2582+
}
2583+
}
2584+
25512585
for _, test := range testCases {
25522586
test.run(t, "incremental")
25532587
}

‎tsc/testdata/baselines/reference/tsbuild/incremental/global-diagnostics-survive-build-signature-generation.js‎

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -191,10 +191,14 @@ export function* values() { yield 1; }
191191

192192

193193
tsgo --build
194-
ExitStatus:: Success
194+
ExitStatus:: DiagnosticsPresent_OutputsGenerated
195195
Output::
196+
error TS2318: Cannot find global type 'IterableIterator'.
197+
198+
Found 1 error.
199+
196200
//// [/home/src/workspaces/project/tsconfig.tsbuildinfo] *modified*
197-
{"version":"FakeTSVersion","errors":true,"root":[2],"fileNames":["lib.es2026.full.d.ts","./repro.ts"],"fileInfos":[{"version":"8859c12c614ce56ba9a18e58384a198f-/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }\ninterface ReadonlyArray<T> {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"303d5119b8941b887ef975780b83caec-export function* values() { yield 1; }\n// comment-only edit\n","signature":"b9f7a0d32d9887fdd864bf1c0dcd3591-export declare function values(): {};\n","impliedNodeFormat":1}],"affectedFilesPendingEmit":[2]}
201+
{"version":"FakeTSVersion","errors":true,"root":[2],"fileNames":["lib.es2026.full.d.ts","./repro.ts"],"fileInfos":[{"version":"8859c12c614ce56ba9a18e58384a198f-/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }\ninterface ReadonlyArray<T> {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"303d5119b8941b887ef975780b83caec-export function* values() { yield 1; }\n// comment-only edit\n","signature":"b9f7a0d32d9887fdd864bf1c0dcd3591-export declare function values(): {};\n","impliedNodeFormat":1}],"semanticDiagnosticsPerFile":[[2,[{"noFile":true,"code":2318,"category":1,"messageKey":"Cannot_find_global_type_0_2318","messageArgs":["IterableIterator"]}]]],"affectedFilesPendingEmit":[2]}
198202
//// [/home/src/workspaces/project/tsconfig.tsbuildinfo.readable.baseline.txt] *modified*
199203
{
200204
"version": "FakeTSVersion",
@@ -236,14 +240,30 @@ Output::
236240
}
237241
}
238242
],
243+
"semanticDiagnosticsPerFile": [
244+
[
245+
"./repro.ts",
246+
[
247+
{
248+
"noFile": true,
249+
"code": 2318,
250+
"category": 1,
251+
"messageKey": "Cannot_find_global_type_0_2318",
252+
"messageArgs": [
253+
"IterableIterator"
254+
]
255+
}
256+
]
257+
]
258+
],
239259
"affectedFilesPendingEmit": [
240260
[
241261
"./repro.ts",
242262
"Js",
243263
2
244264
]
245265
],
246-
"size": 1136
266+
"size": 1295
247267
}
248268

249269
tsconfig.json::
@@ -253,22 +273,17 @@ Signatures::
253273
(computed .d.ts) /home/src/workspaces/project/repro.ts
254274

255275

256-
Diff:: Signature generation produces the missing-global diagnostic before semantic checking, so the build loses it.
257-
--- nonIncremental.output.txt
258-
+++ incremental.output.txt
259-
@@ -1,4 +0,0 @@
260-
-error TS2318: Cannot find global type 'IterableIterator'.
261-
-
262-
-Found 1 error.
263-
-
264-
265276
Edit [2]:: no change
266277

267278
tsgo --build
268-
ExitStatus:: Success
279+
ExitStatus:: DiagnosticsPresent_OutputsGenerated
269280
Output::
281+
error TS2318: Cannot find global type 'IterableIterator'.
282+
283+
Found 1 error.
284+
270285
//// [/home/src/workspaces/project/tsconfig.tsbuildinfo] *modified*
271-
{"version":"FakeTSVersion","root":[2],"fileNames":["lib.es2026.full.d.ts","./repro.ts"],"fileInfos":[{"version":"8859c12c614ce56ba9a18e58384a198f-/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }\ninterface ReadonlyArray<T> {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"303d5119b8941b887ef975780b83caec-export function* values() { yield 1; }\n// comment-only edit\n","signature":"b9f7a0d32d9887fdd864bf1c0dcd3591-export declare function values(): {};\n","impliedNodeFormat":1}],"affectedFilesPendingEmit":[2]}
286+
{"version":"FakeTSVersion","root":[2],"fileNames":["lib.es2026.full.d.ts","./repro.ts"],"fileInfos":[{"version":"8859c12c614ce56ba9a18e58384a198f-/// <reference no-default-lib=\"true\"/>\ninterface Boolean {}\ninterface Function {}\ninterface CallableFunction {}\ninterface NewableFunction {}\ninterface IArguments {}\ninterface Number { toExponential: any; }\ninterface Object {}\ninterface RegExp {}\ninterface String { charAt: any; }\ninterface Array<T> { length: number; [n: number]: T; }\ninterface ReadonlyArray<T> {}\ninterface SymbolConstructor {\n (desc?: string | number): symbol;\n for(name: string): symbol;\n readonly toStringTag: symbol;\n}\ndeclare var Symbol: SymbolConstructor;\ninterface Symbol {\n readonly [Symbol.toStringTag]: string;\n}\ndeclare const console: { log(msg: any): void; };","affectsGlobalScope":true,"impliedNodeFormat":1},{"version":"303d5119b8941b887ef975780b83caec-export function* values() { yield 1; }\n// comment-only edit\n","signature":"b9f7a0d32d9887fdd864bf1c0dcd3591-export declare function values(): {};\n","impliedNodeFormat":1}],"semanticDiagnosticsPerFile":[[2,[{"noFile":true,"code":2318,"category":1,"messageKey":"Cannot_find_global_type_0_2318","messageArgs":["IterableIterator"]}]]],"affectedFilesPendingEmit":[2]}
272287
//// [/home/src/workspaces/project/tsconfig.tsbuildinfo.readable.baseline.txt] *modified*
273288
{
274289
"version": "FakeTSVersion",
@@ -309,30 +324,37 @@ Output::
309324
}
310325
}
311326
],
327+
"semanticDiagnosticsPerFile": [
328+
[
329+
"./repro.ts",
330+
[
331+
{
332+
"noFile": true,
333+
"code": 2318,
334+
"category": 1,
335+
"messageKey": "Cannot_find_global_type_0_2318",
336+
"messageArgs": [
337+
"IterableIterator"
338+
]
339+
}
340+
]
341+
]
342+
],
312343
"affectedFilesPendingEmit": [
313344
[
314345
"./repro.ts",
315346
"Js",
316347
2
317348
]
318349
],
319-
"size": 1122
350+
"size": 1281
320351
}
321352

322353
tsconfig.json::
323354
SemanticDiagnostics::
324355
Signatures::
325356

326357

327-
Diff:: The cached semantic diagnostics do not include the missing-global diagnostic produced during signature generation.
328-
--- nonIncremental.output.txt
329-
+++ incremental.output.txt
330-
@@ -1,4 +0,0 @@
331-
-error TS2318: Cannot find global type 'IterableIterator'.
332-
-
333-
-Found 1 error.
334-
-
335-
336358
Edit [3]:: remove the generator
337359
//// [/home/src/workspaces/project/repro.ts] *modified*
338360
export const value = 1;

0 commit comments

Comments
 (0)