tsc-ts 0.0.8 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +1 -1
  2. package/dist/api/compiler/ast.d.ts +2 -0
  3. package/dist/api/compiler/ast.js +3 -0
  4. package/dist/api/compiler/checker-calls.js +5 -1
  5. package/dist/api/compiler/checker-grammar.js +3 -2
  6. package/dist/api/compiler/checker-inference.js +8 -2
  7. package/dist/api/compiler/checker-members.js +8 -3
  8. package/dist/api/compiler/checker-source-file.d.ts +12 -0
  9. package/dist/api/compiler/checker-source-file.js +35 -0
  10. package/dist/api/compiler/checker-symbols.js +2 -1
  11. package/dist/api/compiler/checker-type-factory.js +8 -2
  12. package/dist/api/compiler/checker.d.ts +17 -1
  13. package/dist/api/compiler/checker.js +49 -1
  14. package/dist/api/compiler/compile-program.d.ts +26 -2
  15. package/dist/api/compiler/compile-program.js +100 -7
  16. package/dist/api/compiler/diagnostics.d.ts +22 -0
  17. package/dist/api/compiler/diagnostics.js +86 -4
  18. package/dist/api/compiler/file-loader.d.ts +13 -3
  19. package/dist/api/compiler/file-loader.js +106 -30
  20. package/dist/api/compiler/link-tables.d.ts +7 -0
  21. package/dist/api/compiler/link-tables.js +22 -0
  22. package/dist/api/compiler/parallel-parse.d.ts +3 -2
  23. package/dist/api/compiler/parallel-parse.js +17 -5
  24. package/dist/api/compiler/parser.js +15 -14
  25. package/dist/api/compiler/printer/emit-context.d.ts +2 -0
  26. package/dist/api/compiler/printer/emit-context.js +4 -0
  27. package/dist/api/compiler/printer/printer.d.ts +48 -2
  28. package/dist/api/compiler/printer/printer.js +238 -14
  29. package/dist/api/compiler/program-checker.d.ts +22 -0
  30. package/dist/api/compiler/program-checker.js +39 -2
  31. package/dist/api/compiler/program.d.ts +2 -6
  32. package/dist/api/compiler/program.js +5 -30
  33. package/dist/api/compiler/relations.js +10 -0
  34. package/dist/api/compiler/symbol.d.ts +1 -1
  35. package/dist/api/compiler/symbol.js +8 -10
  36. package/dist/api/library/diagnostics.d.ts +10 -0
  37. package/dist/api/library/diagnostics.js +20 -0
  38. package/package.json +9 -9
  39. package/src/compiler/ast.ts +3 -0
  40. package/src/compiler/checker-calls.ts +3 -1
  41. package/src/compiler/checker-grammar.ts +3 -2
  42. package/src/compiler/checker-inference.ts +7 -2
  43. package/src/compiler/checker-members.ts +9 -3
  44. package/src/compiler/checker-source-file.ts +34 -0
  45. package/src/compiler/checker-symbols.ts +2 -1
  46. package/src/compiler/checker-type-factory.ts +4 -2
  47. package/src/compiler/checker.ts +45 -1
  48. package/src/compiler/compile-program.ts +121 -7
  49. package/src/compiler/diagnostics.ts +84 -3
  50. package/src/compiler/file-loader.ts +106 -29
  51. package/src/compiler/incremental.ts +80 -13
  52. package/src/compiler/link-tables.ts +32 -0
  53. package/src/compiler/parallel-check.ts +326 -62
  54. package/src/compiler/parallel-emit.ts +320 -0
  55. package/src/compiler/parallel-parse.ts +16 -5
  56. package/src/compiler/parse-thread.ts +3 -0
  57. package/src/compiler/parser.ts +15 -16
  58. package/src/compiler/printer/emit-context.ts +5 -0
  59. package/src/compiler/printer/printer.ts +231 -16
  60. package/src/compiler/program-checker.ts +40 -2
  61. package/src/compiler/program.ts +5 -25
  62. package/src/compiler/relations.ts +7 -0
  63. package/src/compiler/symbol.ts +15 -10
  64. package/src/compiler/thread-entry.ts +61 -3
  65. package/src/compiler/tsc-build.ts +63 -9
  66. package/src/compiler/tsc-cli.ts +5 -0
  67. package/src/compiler/tsc-compile.ts +278 -3
  68. package/src/library/diagnostics.ts +23 -0
@@ -33,7 +33,14 @@ import {
33
33
  type OutputWriter,
34
34
  ProgramEmitter,
35
35
  } from './compile-program.js';
36
- import { Diagnostic, DiagnosticFile, RepopulateInfo } from './diagnostics.js';
36
+ import {
37
+ Diagnostic,
38
+ DiagnosticFile,
39
+ type DiagnosticFormatOptions,
40
+ formatDiagnostic,
41
+ formattedReports,
42
+ RepopulateInfo,
43
+ } from './diagnostics.js';
37
44
  import { EmitOnly, EmitResult } from './emitter.js';
38
45
  import { getEmitDeclarations, isIncremental } from './options.js';
39
46
  import { DeclarationSignatureWriter } from './incremental.js';
@@ -65,7 +72,8 @@ export function residentMB(): number[] {
65
72
  * (`CheckerResult.reserved`). With `emits` false the thread doesn't emit the files it checks, where
66
73
  * the main thread emits the program (`checkersEmit`). `emitKinds`, by file index, is what the emit of
67
74
  * each file includes (an `EmitOnly`, or -1 for none) where not every output of every file is emitted
68
- * (an incremental program's rebuild); `undefined` for all of them.
75
+ * (an incremental program's rebuild); `undefined` for all of them. `units` are the statement ranges of
76
+ * declaration files the order has positions for (`CheckPlan.units`).
69
77
  */
70
78
  export type CheckerSpawn = (
71
79
  program: Program | undefined,
@@ -78,6 +86,7 @@ export type CheckerSpawn = (
78
86
  emits: boolean,
79
87
  emitKinds: number[] | undefined,
80
88
  checking: boolean,
89
+ units: number[],
81
90
  ) => Worker;
82
91
 
83
92
  export class ParallelCheck {
@@ -113,6 +122,10 @@ export class CheckerResult {
113
122
  /** The positions of a group the thread took and had not checked when it stopped at its file limit. */
114
123
  readonly reserved: number[] = [],
115
124
  readonly emits: EmitResult[] = [],
125
+ /** The statement ranges of declaration files it checked (`rangeUnits`). */
126
+ readonly units: number[] = [],
127
+ /** Those of `units` after which its checker had diagnostics in their file (`checkSourceFileRange`). */
128
+ readonly reportingUnits: number[] = [],
116
129
  ) {}
117
130
  }
118
131
 
@@ -138,10 +151,13 @@ export const recycleFiles: number = Math.max(0, Number(process.env.TSC_TS_RECYCL
138
151
  * text (index, -1 when the program has the file), pos, end, code, category, message,
139
152
  * skippedOnNoEmit, the argument count and indices, its repopulate kind (0 for none) followed by the
140
153
  * module reference (index), mode and package name (index) of other kinds, then its message chain and
141
- * related information as lists. An emit result is emitSkipped, the emitted files, errors and
142
- * unsupported files as lists of indices, then its diagnostics and its declaration signatures as a list
143
- * of indices. `files` and `nums` are typed arrays, which postMessage copies as one block of memory
144
- * each; it copies an array of numbers element by element.
154
+ * related information as lists. A diagnostic of a reported list (the files' diagnostic and declaration
155
+ * diagnostic lists, the global diagnostics and an emit result's diagnostics) is followed by its text in
156
+ * the main thread's report format (`Diagnostic.formatted`, an index, -1 for none). An emit result is
157
+ * emitSkipped, the emitted files, errors and unsupported files as lists of indices, then its
158
+ * diagnostics and its declaration signatures as a list of indices. `files` and `nums` are typed
159
+ * arrays, which postMessage copies as one block of memory each; it copies an array of numbers element
160
+ * by element.
145
161
  */
146
162
  export type PackedResult = {
147
163
  readonly index: number;
@@ -152,6 +168,8 @@ export type PackedResult = {
152
168
  readonly stats: number[];
153
169
  readonly more: boolean;
154
170
  readonly reserved: number[];
171
+ readonly units: Int32Array;
172
+ readonly reportingUnits: Int32Array;
155
173
  };
156
174
 
157
175
  class ResultPacker {
@@ -159,7 +177,11 @@ class ResultPacker {
159
177
  readonly strs: string[] = [];
160
178
  private readonly indices = new Map<string, number>();
161
179
 
162
- constructor(private readonly program: Program) {}
180
+ constructor(
181
+ private readonly program: Program,
182
+ /** The main thread's report format, which this thread formats the reported diagnostics in. */
183
+ private readonly format: DiagnosticFormatOptions | undefined,
184
+ ) {}
163
185
 
164
186
  private str(text: string): number {
165
187
  let k = this.indices.get(text);
@@ -176,6 +198,21 @@ class ResultPacker {
176
198
  for (const d of diagnostics) this.diagnostic(d);
177
199
  }
178
200
 
201
+ /** A list the main thread reports: each diagnostic with its text in the report format. */
202
+ reported(diagnostics: readonly Diagnostic[]): void {
203
+ const nums = this.nums;
204
+ const format = this.format;
205
+ nums.push(diagnostics.length);
206
+ for (const d of diagnostics) {
207
+ this.diagnostic(d);
208
+ if (format === undefined) nums.push(-1);
209
+ else {
210
+ nums.push(this.strs.length);
211
+ this.strs.push(formatDiagnostic(d, format));
212
+ }
213
+ }
214
+ }
215
+
179
216
  private strList(texts: readonly string[]): void {
180
217
  this.nums.push(texts.length);
181
218
  for (const text of texts) this.nums.push(this.str(text));
@@ -186,7 +223,7 @@ class ResultPacker {
186
223
  this.strList(result.emittedFiles);
187
224
  this.strList(result.errors);
188
225
  this.strList(result.unsupported);
189
- this.list(result.diagnostics);
226
+ this.reported(result.diagnostics);
190
227
  this.strList(result.declarationSignatures);
191
228
  this.strList(result.heldOutputs);
192
229
  }
@@ -218,11 +255,11 @@ class ResultPacker {
218
255
 
219
256
  /** Packs `result` (on the checker thread) for `postMessage`. */
220
257
  export function packResult(result: CheckerResult, program: Program): PackedResult {
221
- const packer = new ResultPacker(program);
222
- for (const list of result.diagnostics) packer.list(list);
258
+ const packer = new ResultPacker(program, formattedReports.format);
259
+ for (const list of result.diagnostics) packer.reported(list);
223
260
  packer.nums.push(result.declarations.length);
224
- for (const list of result.declarations) packer.list(list);
225
- packer.list(result.globals);
261
+ for (const list of result.declarations) packer.reported(list);
262
+ packer.reported(result.globals);
226
263
  packer.nums.push(result.emits.length);
227
264
  for (const emit of result.emits) packer.emit(emit);
228
265
  return {
@@ -234,6 +271,8 @@ export function packResult(result: CheckerResult, program: Program): PackedResul
234
271
  stats: result.stats,
235
272
  more: result.more,
236
273
  reserved: result.reserved,
274
+ units: Int32Array.from(result.units),
275
+ reportingUnits: Int32Array.from(result.reportingUnits),
237
276
  };
238
277
  }
239
278
 
@@ -262,6 +301,19 @@ class ResultUnpacker {
262
301
  return result;
263
302
  }
264
303
 
304
+ /** A list `ResultPacker.reported` packed. */
305
+ reported(): Diagnostic[] {
306
+ const n = this.next();
307
+ const result: Diagnostic[] = [];
308
+ for (let k = 0; k < n; k++) {
309
+ const d = this.diagnostic();
310
+ const text = this.next();
311
+ if (text >= 0) d.formatted = this.packed.strs[text]!;
312
+ result.push(d);
313
+ }
314
+ return result;
315
+ }
316
+
265
317
  private diagnostic(): Diagnostic {
266
318
  const strs = this.packed.strs;
267
319
  const name = this.next();
@@ -315,7 +367,7 @@ class ResultUnpacker {
315
367
  this.strList(result.emittedFiles);
316
368
  this.strList(result.errors);
317
369
  this.strList(result.unsupported);
318
- for (const d of this.list()) result.diagnostics.push(d);
370
+ for (const d of this.reported()) result.diagnostics.push(d);
319
371
  this.strList(result.declarationSignatures);
320
372
  this.strList(result.heldOutputs);
321
373
  return result;
@@ -329,11 +381,11 @@ export function unpackResult(
329
381
  ): CheckerResult {
330
382
  const unpacker = new ResultUnpacker(packed, program, files);
331
383
  const diagnostics: Diagnostic[][] = [];
332
- for (let k = 0; k < packed.files.length; k++) diagnostics.push(unpacker.list());
384
+ for (let k = 0; k < packed.files.length; k++) diagnostics.push(unpacker.reported());
333
385
  const declarations: Diagnostic[][] = [];
334
386
  const nd = unpacker.count();
335
- for (let k = 0; k < nd; k++) declarations.push(unpacker.list());
336
- const globals = unpacker.list();
387
+ for (let k = 0; k < nd; k++) declarations.push(unpacker.reported());
388
+ const globals = unpacker.reported();
337
389
  const emits: EmitResult[] = [];
338
390
  const ne = unpacker.count();
339
391
  for (let k = 0; k < ne; k++) emits.push(unpacker.emit());
@@ -348,6 +400,8 @@ export function unpackResult(
348
400
  packed.more,
349
401
  packed.reserved,
350
402
  emits,
403
+ Array.from(packed.units),
404
+ Array.from(packed.reportingUnits),
351
405
  );
352
406
  }
353
407
 
@@ -638,8 +692,8 @@ class CheckCosts {
638
692
  readonly libraries: number[][],
639
693
  /** What resolving a library costs a checker the first time (`libraryFirstUseShare` of the text it reaches), by index. */
640
694
  readonly libraryCost: number[],
641
- /** The program files (indices) each file of `order` imports, by index. */
642
- readonly imports: (number[] | undefined)[],
695
+ /** The program files (indices) each file imports (`ProcessedFiles.addedFiles`), by index. */
696
+ readonly imports: readonly (readonly number[])[],
643
697
  ) {}
644
698
  }
645
699
 
@@ -684,34 +738,18 @@ function checkCosts(checked: CheckedProgram, order: number[]): CheckCosts {
684
738
  }
685
739
  const length = textLengths(program);
686
740
  for (const i of order) cost[i] = length[i]!;
687
- const indexOf = new Map<SourceFile, number>();
688
- for (let i = 0; i < files.length; i++) indexOf.set(files[i]!, i);
689
- // A file resolved by its own file name is that file (its path is the path of that name).
690
- const byFileName = new Map<string, SourceFile>();
691
- for (const file of files) byFileName.set(file.fileName, file);
692
741
  const checkedFile: boolean[] = [];
693
742
  for (let i = 0; i < files.length; i++) checkedFile.push(false);
694
743
  for (const i of order) checkedFile[i] = true;
695
744
  // Libraries: unchecked modules that are not default libraries (globals every checker has).
696
745
  const isLibrary = (i: number): boolean =>
697
746
  !checkedFile[i]! && !program.isSourceFileDefaultLibrary(files[i]!.path) && isExternalModuleFile(files[i]!);
698
- const edges: (number[] | undefined)[] = [];
699
- const edgesOf = (i: number): number[] => {
700
- let result = edges[i];
701
- if (result === undefined) {
702
- result = [];
703
- for (const target of program.getResolvedFilesOf(files[i]!, byFileName)) {
704
- const k = indexOf.get(target);
705
- if (k !== undefined && k !== i) result.push(k);
706
- }
707
- edges[i] = result;
708
- }
709
- return result;
710
- };
747
+ // The files each file imports: those it added to the program (`ProcessedFiles.addedFiles`).
748
+ const edges = program.addedFiles();
711
749
  const importers = new Map<number, number>();
712
750
  for (const i of order) {
713
751
  const libraries: number[] = [];
714
- for (const k of edgesOf(i)) if (isLibrary(k) && libraries.indexOf(k) < 0) libraries.push(k);
752
+ for (const k of edges[i]!) if (isLibrary(k) && libraries.indexOf(k) < 0) libraries.push(k);
715
753
  librariesOf[i] = libraries;
716
754
  for (const k of libraries) importers.set(k, (importers.get(k) ?? 0) + 1);
717
755
  }
@@ -726,7 +764,7 @@ function checkCosts(checked: CheckedProgram, order: number[]): CheckCosts {
726
764
  while (stack.length > 0) {
727
765
  const k = stack.pop()!;
728
766
  total += length[k]!;
729
- for (const t of edgesOf(k))
767
+ for (const t of edges[k]!)
730
768
  if (!seen.has(t) && isLibrary(t)) {
731
769
  seen.add(t);
732
770
  stack.push(t);
@@ -1161,6 +1199,12 @@ export class CheckPlan {
1161
1199
  readonly references: number,
1162
1200
  /** For each position, the number of files of the group starting there (0 elsewhere). */
1163
1201
  readonly groups: number[] = [],
1202
+ /**
1203
+ * The statement ranges of declaration files that several checkers check (`rangeUnits`): for each,
1204
+ * the file's index and its first and end statement. Position `k` of `order` is range `u` where
1205
+ * `order[k]` is `-1 - u`.
1206
+ */
1207
+ readonly units: number[] = [],
1164
1208
  ) {}
1165
1209
  }
1166
1210
 
@@ -1310,16 +1354,100 @@ export function checkGroups(
1310
1354
  return groups;
1311
1355
  }
1312
1356
 
1357
+ /**
1358
+ * How many ranges a declaration file checked in statement ranges (`rangeUnits`) has per checker's share
1359
+ * of the program's estimated cost, and at most per checker.
1360
+ */
1361
+ const rangesPerShare = 2;
1362
+ const rangesPerChecker = 2;
1363
+
1364
+ /**
1365
+ * The statement ranges that `count` checkers check declaration files in, as `CheckPlan.units`: those of
1366
+ * the files of `costs` whose estimated cost is more than a checker's share, which one checker would
1367
+ * finish long after the others. lib.dom.d.ts (2.4 MB) is such a file in a small program: one checker
1368
+ * took 150 ms on it while 15 waited. Each range is about half a share, of about equal text, and the
1369
+ * last one ends the file (`checkSourceFileRange`). `grouped` files (`checkGroups`) are checked whole.
1370
+ * TSC_TS_RANGES=0 checks every file whole; TSC_TS_RANGES=all checks every checked declaration file in
1371
+ * ranges, `count` of them.
1372
+ */
1373
+ export function rangeUnits(
1374
+ checked: CheckedProgram,
1375
+ count: number,
1376
+ costs: CheckCosts,
1377
+ grouped: ReadonlySet<number> = new Set<number>(),
1378
+ ): number[] {
1379
+ const units: number[] = [];
1380
+ const mode = process.env.TSC_TS_RANGES;
1381
+ if (count < 2 || mode === '0') return units;
1382
+ const files = checked.program.getSourceFiles();
1383
+ const cost = costs.cost;
1384
+ let all = 0;
1385
+ for (const i of costs.order) all += cost[i]!;
1386
+ const share = all / count;
1387
+ for (const i of costs.order) {
1388
+ const file = files[i]!;
1389
+ if (!file.isDeclarationFile || grouped.has(i) || (mode !== 'all' && cost[i]! <= share)) continue;
1390
+ const statements = file.statements.nodes;
1391
+ const n = Math.min(
1392
+ statements.length,
1393
+ mode === 'all' ? count : Math.min(rangesPerChecker * count, Math.ceil((rangesPerShare * cost[i]!) / share)),
1394
+ );
1395
+ if (n < 2) continue;
1396
+ const text = file.text.length;
1397
+ let start = 0;
1398
+ for (let r = 1; r <= n && start < statements.length; r++) {
1399
+ let end = statements.length;
1400
+ if (r < n) {
1401
+ const target = (text * r) / n;
1402
+ end = start + 1;
1403
+ while (end < statements.length && statements[end - 1]!.end < target) end++;
1404
+ }
1405
+ units.push(i, start, end);
1406
+ start = end;
1407
+ }
1408
+ }
1409
+ return units;
1410
+ }
1411
+
1412
+ /** The estimated cost of range `u` of `units`, its file's share by text. */
1413
+ function unitCost(checked: CheckedProgram, costs: CheckCosts, units: readonly number[], u: number): number {
1414
+ const file = checked.program.getSourceFiles()[units[3 * u]!]!;
1415
+ return (costs.cost[units[3 * u]!]! * unitLength(file, units, u)) / Math.max(1, file.text.length);
1416
+ }
1417
+
1418
+ /** The text length of the statements of range `u` of `units` of `file`. */
1419
+ function unitLength(file: SourceFile, units: readonly number[], u: number): number {
1420
+ const statements = file.statements.nodes;
1421
+ return statements[units[3 * u + 2]! - 1]!.end - statements[units[3 * u + 1]!]!.pos;
1422
+ }
1423
+
1313
1424
  /**
1314
1425
  * Plans checking with `count` checkers: the order to check files in, the costliest files and the
1315
1426
  * groups (`checkGroups`; at least `count` entries, and every file costing more than a 32nd of a
1316
1427
  * checker's share), costliest first, from the shared queue; then the others split, in program
1317
- * order, into one range of about equal cost per checker, each costliest first.
1428
+ * order, into one range of about equal cost per checker, each costliest first. With `inRanges`, the
1429
+ * statement ranges of large declaration files (`rangeUnits`) replace them in the shared queue.
1318
1430
  */
1319
- export function planSchedule(checked: CheckedProgram, count: number, costs = costOrder(checked)): CheckPlan {
1431
+ export function planSchedule(
1432
+ checked: CheckedProgram,
1433
+ count: number,
1434
+ costs = costOrder(checked),
1435
+ inRanges = false,
1436
+ ): CheckPlan {
1320
1437
  const order = costs.order;
1321
1438
  const cost = costs.cost;
1322
- if (order.length <= count) return new CheckPlan(order, -1, [], [], [], [], 0);
1439
+ if (order.length <= count) {
1440
+ const units = inRanges ? rangeUnits(checked, count, costs) : [];
1441
+ if (units.length === 0) return new CheckPlan(order, -1, [], [], [], [], 0);
1442
+ // The ranges take the place of their files, all in one shared queue, costliest first.
1443
+ const split = new Set<number>();
1444
+ for (let u = 0; u < units.length; u += 3) split.add(units[u]!);
1445
+ const entries: number[] = order.filter((i) => !split.has(i));
1446
+ for (let u = 0; u < units.length / 3; u++) entries.push(-1 - u);
1447
+ const costOf = (k: number): number => (k < 0 ? unitCost(checked, costs, units, -1 - k) : cost[k]!);
1448
+ entries.sort((a, b) => costOf(b) - costOf(a));
1449
+ return new CheckPlan(entries, -1, [], [], [], [], 0, [], units);
1450
+ }
1323
1451
  let all = 0;
1324
1452
  for (const i of order) all += cost[i]!;
1325
1453
  // Files that cost more than a 32nd of a checker's share go first, costliest first: at the end of
@@ -1330,11 +1458,15 @@ export function planSchedule(checked: CheckedProgram, count: number, costs = cos
1330
1458
  const groups = checkGroups(checked, count, groupLoad, costs);
1331
1459
  const grouped = new Set<number>();
1332
1460
  for (const group of groups) for (const i of group) grouped.add(i);
1461
+ const units = inRanges ? rangeUnits(checked, count, costs, grouped) : [];
1462
+ const split = new Set<number>();
1463
+ for (let u = 0; u < units.length; u += 3) split.add(units[u]!);
1333
1464
  const entries: number[][] = groups.slice();
1334
- for (const i of order.slice(0, heavyCount)) if (!grouped.has(i)) entries.push([i]);
1465
+ for (const i of order.slice(0, heavyCount)) if (!grouped.has(i) && !split.has(i)) entries.push([i]);
1466
+ for (let u = 0; u < units.length / 3; u++) entries.push([-1 - u]);
1335
1467
  const entryCost = (entry: number[]): number => {
1336
1468
  let sum = 0;
1337
- for (const i of entry) sum += cost[i]!;
1469
+ for (const i of entry) sum += i < 0 ? unitCost(checked, costs, units, -1 - i) : cost[i]!;
1338
1470
  return sum;
1339
1471
  };
1340
1472
  entries.sort((a, b) => entryCost(b) - entryCost(a) || a[0]! - b[0]!);
@@ -1347,7 +1479,7 @@ export function planSchedule(checked: CheckedProgram, count: number, costs = cos
1347
1479
  }
1348
1480
  const rest = order
1349
1481
  .slice(heavyCount)
1350
- .filter((i) => !grouped.has(i))
1482
+ .filter((i) => !grouped.has(i) && !split.has(i))
1351
1483
  .sort((a, b) => a - b);
1352
1484
  heavyCount = heavy.length;
1353
1485
  let total = 0;
@@ -1378,9 +1510,9 @@ export function planSchedule(checked: CheckedProgram, count: number, costs = cos
1378
1510
  const libraries: number[][] = [];
1379
1511
  let references = 0;
1380
1512
  for (const i of planned) {
1381
- lengths.push(textLength[i]!);
1513
+ lengths.push(i < 0 ? unitLength(files[units[-3 * i - 3]!]!, units, -1 - i) : textLength[i]!);
1382
1514
  const ids: number[] = [];
1383
- for (const k of costs.libraries[i]!) {
1515
+ for (const k of costs.libraries[i < 0 ? units[-3 * i - 3]! : i]!) {
1384
1516
  let id = libraryIds.get(k);
1385
1517
  if (id === undefined) {
1386
1518
  id = libraryCosts.length;
@@ -1398,7 +1530,7 @@ export function planSchedule(checked: CheckedProgram, count: number, costs = cos
1398
1530
  process.stderr.write(
1399
1531
  `checkers: group of ${group.length}: ${group.map((i) => files[i]!.fileName.split('/').slice(-2).join('/')).join(' ')}\n`,
1400
1532
  );
1401
- return new CheckPlan(planned, heavyCount, ranges, lengths, libraries, libraryCosts, references, groupAt);
1533
+ return new CheckPlan(planned, heavyCount, ranges, lengths, libraries, libraryCosts, references, groupAt, units);
1402
1534
  }
1403
1535
 
1404
1536
  /** With TSC_TS_CHECKERS_TRACE: the single-threaded checker's statistics, like a checker thread's. */
@@ -1422,7 +1554,8 @@ export function traceSingleChecker(checked: CheckedProgram): void {
1422
1554
  * A checker thread (`onMainThread` false) also sends back its global diagnostics. At `limit` files
1423
1555
  * it stops; `reserved` are the positions of a group its predecessor on `index` had not checked then.
1424
1556
  * With `emit` false it doesn't emit the files it checks (`checkersEmit`). With `checking` false it
1425
- * only emits them (`startParallelCheck`).
1557
+ * only emits them (`startParallelCheck`). A position of the order below 0 is a statement range of a
1558
+ * declaration file, `-1 - u` for the range at `3 * u` in `units` (`CheckPlan.units`).
1426
1559
  */
1427
1560
  export function checkFromQueue(
1428
1561
  checked: CheckedProgram,
@@ -1436,6 +1569,7 @@ export function checkFromQueue(
1436
1569
  emit: boolean = true,
1437
1570
  emitKinds: readonly number[] | undefined = undefined,
1438
1571
  checking: boolean = true,
1572
+ units: readonly number[] = [],
1439
1573
  ): CheckerResult {
1440
1574
  const start = performance.now();
1441
1575
  const readyAt = Date.now();
@@ -1446,6 +1580,8 @@ export function checkFromQueue(
1446
1580
  const diagnostics: Diagnostic[][] = [];
1447
1581
  const declarations: Diagnostic[][] = [];
1448
1582
  const emits: EmitResult[] = [];
1583
+ const unitsDone: number[] = [];
1584
+ const reportingUnits: number[] = [];
1449
1585
  // Emit (or declaration emit) of a file right after checking it, with this checker.
1450
1586
  const emitting = checkersEmit(checked, emit);
1451
1587
  // Outputs are held for the main thread under `noEmitOnError`, where emit waits for every file's diagnostics,
@@ -1487,6 +1623,28 @@ export function checkFromQueue(
1487
1623
  const k = takePosition(counter, order.length, index, plan);
1488
1624
  if (k < 0) break;
1489
1625
  const i = order[k]!;
1626
+ if (i < 0) {
1627
+ // A statement range of a declaration file, which has nothing to emit.
1628
+ const u = -1 - i;
1629
+ const file = files[units[3 * u]!]!;
1630
+ const rangeStart = verbose || timeline ? performance.now() : 0;
1631
+ const reports = checking && checked.checkRange(file, units[3 * u + 1]!, units[3 * u + 2]!);
1632
+ unitsDone.push(u);
1633
+ if (reports) reportingUnits.push(u);
1634
+ // A range doesn't block emit under `noEmitOnError`: what its checker found may be suppressed
1635
+ // (`// @ts-ignore`) or not the file's, which this thread doesn't know. The main thread decides
1636
+ // from the file's diagnostics, and the outputs are held for it.
1637
+ if (timeline)
1638
+ markAt(
1639
+ rangeStart,
1640
+ `range ${(performance.now() - rangeStart).toFixed(1)} pos ${k} statements ${units[3 * u + 1]}-${units[3 * u + 2]} ${file.fileName}`,
1641
+ );
1642
+ if (verbose)
1643
+ process.stderr.write(
1644
+ `checkers: range ${index} ${(performance.now() - rangeStart).toFixed(1)}ms statements ${units[3 * u + 1]}-${units[3 * u + 2]} ${file.fileName}\n`,
1645
+ );
1646
+ continue;
1647
+ }
1490
1648
  done.push(i);
1491
1649
  const fileStart = verbose || timeline ? performance.now() : 0;
1492
1650
  // Without `checking` the file is only emitted: declaration emit checks what it needs.
@@ -1510,22 +1668,36 @@ export function checkFromQueue(
1510
1668
  } else if (writer !== undefined) {
1511
1669
  const emitStart = timeline ? performance.now() : 0;
1512
1670
  const kind = (emitKinds === undefined ? EmitOnly.All : emitKinds[i]!) as EmitOnly;
1671
+ // Global diagnostics found while emitting are not reported: the program collects them
1672
+ // before it emits.
1673
+ const checker = checked.checker;
1513
1674
  if (kind < 0) emits.push(new EmitResult());
1514
1675
  else if (signatures !== undefined) {
1515
1676
  signatures.file = files[i]!;
1516
- const result = emitter!.emit([files[i]!], kind, signatures);
1677
+ const result = checker.withoutGlobalDiagnostics(() => emitter!.emit([files[i]!], kind, signatures));
1517
1678
  for (const s of signatures.take()) result.declarationSignatures.push(s);
1518
1679
  if (holding !== undefined) for (const o of holding.take()) result.heldOutputs.push(o);
1519
1680
  emits.push(result);
1520
1681
  } else {
1521
- const result = emitter!.emit([files[i]!], kind, writer);
1682
+ const result = checker.withoutGlobalDiagnostics(() => emitter!.emit([files[i]!], kind, writer));
1522
1683
  if (holding !== undefined) for (const o of holding.take()) result.heldOutputs.push(o);
1523
1684
  emits.push(result);
1524
1685
  }
1525
1686
  if (timeline) markAt(emitStart, `emit ${(performance.now() - emitStart).toFixed(1)} ${files[i]!.fileName}`);
1526
- } else if (emitter !== undefined) declarations.push(declarationDiagnosticsOfFile(emitter, files[i]!));
1687
+ } else if (emitter !== undefined)
1688
+ declarations.push(
1689
+ checked.checker.withoutGlobalDiagnostics(() => declarationDiagnosticsOfFile(emitter, files[i]!)),
1690
+ );
1691
+ // What emitting found and forgot can stay cached in this checker (a missing global type, an
1692
+ // instantiation too deep), where checking a later file would report it: a new checker continues
1693
+ // the schedule, as at the file limit.
1694
+ if (checked.checker.forgottenGlobalDiagnostics() > 0) {
1695
+ more = true;
1696
+ left = reservedBy(index).splice(0);
1697
+ break;
1698
+ }
1527
1699
  }
1528
- mark(`checked ${done.length} files`);
1700
+ mark(`checked ${done.length} files, ${unitsDone.length} ranges`);
1529
1701
  // The main thread reports its own global diagnostics (CheckedProgram.getGlobalDiagnostics).
1530
1702
  const globals = onMainThread ? [] : checked.getGlobalDiagnostics().slice();
1531
1703
  let stats: number[] = [];
@@ -1564,6 +1736,8 @@ export function checkFromQueue(
1564
1736
  more,
1565
1737
  left,
1566
1738
  emits,
1739
+ unitsDone,
1740
+ reportingUnits,
1567
1741
  );
1568
1742
  }
1569
1743
 
@@ -1694,7 +1868,11 @@ export function shareWithCheckers(checked: CheckedProgram): void {
1694
1868
  /**
1695
1869
  * Checks `checked` on `checkers` threads and calls `done` once they are done. With `emits`, each thread
1696
1870
  * emits the files it checks (`emitKinds` limits what). With `checking` false, the threads only emit:
1697
- * for a program whose report has no semantic diagnostics, so that no file is checked in full.
1871
+ * for a program whose report has no semantic diagnostics, so that no file is checked in full. With
1872
+ * `emitsTo`, the emit results go only there, by file index, and nothing else the threads find reaches
1873
+ * the program: a declaration thread (parallel-emit.ts), while this thread's checker checks. With
1874
+ * `declarationsTo`, the same holds for the declaration errors of a `noEmit` program
1875
+ * (`checkersReportDeclarations`).
1698
1876
  */
1699
1877
  export function startParallelCheck(
1700
1878
  checked: CheckedProgram,
@@ -1703,6 +1881,8 @@ export function startParallelCheck(
1703
1881
  emits: boolean = true,
1704
1882
  emitKinds: number[] | undefined = undefined,
1705
1883
  checking: boolean = true,
1884
+ emitsTo: (EmitResult | undefined)[] | undefined = undefined,
1885
+ declarationsTo: (Diagnostic[] | undefined)[] | undefined = undefined,
1706
1886
  ): void {
1707
1887
  let count = checkers;
1708
1888
  const start = performance.now();
@@ -1721,23 +1901,29 @@ export function startParallelCheck(
1721
1901
  }
1722
1902
  const orderStart = performance.now();
1723
1903
  const costs = costOrder(checked);
1724
- let plan = planSchedule(checked, count, costs);
1904
+ let plan = planSchedule(checked, count, costs, checking);
1725
1905
  const fewer = libraryBoundCheckers(checked, plan, costs, count);
1726
1906
  if (fewer < count) {
1727
1907
  count = fewer;
1728
- plan = planSchedule(checked, count, costs);
1908
+ plan = planSchedule(checked, count, costs, checking);
1729
1909
  }
1730
1910
  const order = plan.order;
1911
+ const units = plan.units;
1731
1912
  const counter = scheduleOf(plan, count);
1732
1913
  if (trace) {
1733
1914
  let top = '';
1734
- for (let k = 0; k < Math.min(5, order.length); k++) top += ' ' + files[order[k]!]!.fileName;
1915
+ for (let k = 0; k < Math.min(5, order.length); k++)
1916
+ top +=
1917
+ ' ' +
1918
+ (order[k]! < 0 ? `range ${-1 - order[k]!} of ` : '') +
1919
+ files[order[k]! < 0 ? units[-3 * order[k]! - 3]! : order[k]!]!.fileName;
1735
1920
  process.stderr.write(
1736
- `checkers: ordered ${order.length} files in ${(performance.now() - orderStart).toFixed(1)}ms, first:${top}\n`,
1921
+ `checkers: ordered ${order.length} positions (${units.length / 3} statement ranges) in ${(performance.now() - orderStart).toFixed(1)}ms, first:${top}\n`,
1737
1922
  );
1738
1923
  }
1739
1924
  if (shared) {
1740
1925
  publish(order);
1926
+ publish(units);
1741
1927
  if (emitKinds !== undefined) publish(emitKinds);
1742
1928
  }
1743
1929
  mark(`planned ${order.length} files, ${plan.heavy} heavy`);
@@ -1749,15 +1935,26 @@ export function startParallelCheck(
1749
1935
  perFileDeclarations.push(undefined);
1750
1936
  perFileEmits.push(undefined);
1751
1937
  }
1938
+ // For each statement range (`CheckPlan.units`): 0 until checked, 1 once checked, 2 if its checker then had
1939
+ // diagnostics in the file.
1940
+ const perUnit: number[] = [];
1941
+ for (let u = 0; u < units.length / 3; u++) perUnit.push(0);
1752
1942
  const revival = shared ? undefined : new Revival(program);
1753
1943
  const mainChecks = mainThreadChecks(shared);
1754
1944
  const firstThread = mainChecks ? 1 : 0;
1755
1945
  let remaining = count - firstThread;
1756
1946
  let ownDone = false;
1757
1947
  const finish = (): void => {
1948
+ if (emitsTo !== undefined || declarationsTo !== undefined) {
1949
+ mark('emitted on checker threads');
1950
+ done(performance.now() - start);
1951
+ return;
1952
+ }
1758
1953
  if (checking) {
1954
+ joinRanges(checked, units, perUnit, perFile);
1759
1955
  // A thread that failed may have taken files it never checked: check them here.
1760
- for (const i of order) if (perFile[i] === undefined) perFile[i] = checked.unfilteredDiagnosticsOfFile(files[i]!);
1956
+ for (const i of order)
1957
+ if (i >= 0 && perFile[i] === undefined) perFile[i] = checked.unfilteredDiagnosticsOfFile(files[i]!);
1761
1958
  checked.checkedInParallel = perFile;
1762
1959
  }
1763
1960
  mark('all checked');
@@ -1783,16 +1980,34 @@ export function startParallelCheck(
1783
1980
  done(performance.now() - start);
1784
1981
  };
1785
1982
  const record = (result: CheckerResult): void => {
1983
+ if (declarationsTo !== undefined) {
1984
+ for (let k = 0; k < result.declarations.length; k++) declarationsTo[result.files[k]!] = result.declarations[k]!;
1985
+ if (trace)
1986
+ process.stderr.write(
1987
+ `checkers: emit-only thread ${result.index} found the declaration errors of ${result.declarations.length} files in ${result.checkTime.toFixed(1)}ms, done at ${(performance.now() - start).toFixed(1)}ms rss ${result.stats[7] ?? '?'}MB\n`,
1988
+ );
1989
+ return;
1990
+ }
1991
+ if (emitsTo !== undefined) {
1992
+ for (let k = 0; k < result.emits.length; k++) emitsTo[result.files[k]!] = result.emits[k]!;
1993
+ if (trace)
1994
+ process.stderr.write(
1995
+ `checkers: emit-only thread ${result.index} emitted ${result.emits.length} files in ${result.checkTime.toFixed(1)}ms, done at ${(performance.now() - start).toFixed(1)}ms rss ${result.stats[7] ?? '?'}MB\n`,
1996
+ );
1997
+ return;
1998
+ }
1786
1999
  for (let k = 0; k < result.files.length; k++) perFile[result.files[k]!] = result.diagnostics[k]!;
1787
2000
  for (let k = 0; k < result.declarations.length; k++)
1788
2001
  perFileDeclarations[result.files[k]!] = result.declarations[k]!;
1789
2002
  for (let k = 0; k < result.emits.length; k++) perFileEmits[result.files[k]!] = result.emits[k]!;
2003
+ for (const u of result.units) perUnit[u] = 1;
2004
+ for (const u of result.reportingUnits) perUnit[u] = 2;
1790
2005
  for (const d of result.globals) checked.parallelGlobalDiagnostics.push(d);
1791
2006
  if (trace) {
1792
2007
  const st = result.stats;
1793
2008
  const rel = (t: number | undefined): string => (t === undefined ? '?' : String(t - epoch));
1794
2009
  process.stderr.write(
1795
- `checkers: thread ${result.index} checked ${result.files.length} files in ${result.checkTime.toFixed(1)}ms, done at ${(performance.now() - start).toFixed(1)}ms` +
2010
+ `checkers: thread ${result.index} checked ${result.files.length} files and ${result.units.length} ranges in ${result.checkTime.toFixed(1)}ms, done at ${(performance.now() - start).toFixed(1)}ms` +
1796
2011
  ` [start ${rel(st[0])} ready ${rel(st[1])} end ${rel(st[2])} types ${st[3]} inst ${st[4]} user ${Math.round(st[5] ?? 0)} sys ${Math.round(st[6] ?? 0)} rss ${st[7]}MB symlinks ${st[8]} nodelinks ${st[9]} sigs ${st[10]} rel ${st[11]}]\n`,
1797
2012
  );
1798
2013
  }
@@ -1814,6 +2029,7 @@ export function startParallelCheck(
1814
2029
  emits,
1815
2030
  emitKinds,
1816
2031
  checking,
2032
+ units,
1817
2033
  );
1818
2034
  worker.on('message', (message: unknown) => {
1819
2035
  mark(`result from c${i}`);
@@ -1842,12 +2058,58 @@ export function startParallelCheck(
1842
2058
  mark('reporting checker created');
1843
2059
  const emittedHere =
1844
2060
  !mainChecks && emitKinds === undefined ? emitUncheckedFiles(checked, order, emits) : new Map<number, EmitResult>();
1845
- if (mainChecks)
1846
- record(checkFromQueue(checked, 0, new Int32Array(counter), order, epoch, true, 0, [], emits, emitKinds, checking));
2061
+ if (mainChecks) {
2062
+ const own = checkFromQueue(
2063
+ checked,
2064
+ 0,
2065
+ new Int32Array(counter),
2066
+ order,
2067
+ epoch,
2068
+ true,
2069
+ 0,
2070
+ [],
2071
+ emits,
2072
+ emitKinds,
2073
+ checking,
2074
+ units,
2075
+ );
2076
+ record(own);
2077
+ // A thread continues where this checker stopped (`checkFromQueue`).
2078
+ if (own.more) {
2079
+ remaining++;
2080
+ spawn(0, own.reserved);
2081
+ }
2082
+ }
1847
2083
  ownDone = true;
1848
2084
  if (remaining === 0) finish();
1849
2085
  }
1850
2086
 
2087
+ /**
2088
+ * Sets the diagnostics in `perFile` of each file checked in statement ranges (`units`): none from its
2089
+ * checkers where every range was checked and found no diagnostics in it (`perUnit`, by range:
2090
+ * `checkSourceFileRange`); otherwise a new checker on this thread checks it whole.
2091
+ */
2092
+ function joinRanges(
2093
+ checked: CheckedProgram,
2094
+ units: readonly number[],
2095
+ perUnit: readonly number[],
2096
+ perFile: (Diagnostic[] | undefined)[],
2097
+ ): void {
2098
+ const files = checked.program.getSourceFiles();
2099
+ const trace = process.env.TSC_TS_CHECKERS_TRACE !== undefined;
2100
+ // A file's ranges are consecutive.
2101
+ for (let u = 0; u < perUnit.length;) {
2102
+ const i = units[3 * u]!;
2103
+ let clean = true;
2104
+ for (; u < perUnit.length && units[3 * u] === i; u++) if (perUnit[u] !== 1) clean = false;
2105
+ if (clean) perFile[i] = checked.unfilteredDiagnosticsOfCleanRanges(files[i]!);
2106
+ else {
2107
+ if (trace) process.stderr.write(`checkers: checking ${files[i]!.fileName} whole: its ranges have diagnostics\n`);
2108
+ perFile[i] = checked.unfilteredDiagnosticsOfFileOnNewChecker(files[i]!);
2109
+ }
2110
+ }
2111
+ }
2112
+
1851
2113
  /**
1852
2114
  * Emits, on this thread and into memory, the files that checker threads emit nothing for because
1853
2115
  * they don't check them (`skipTypeChecking`, such as a file with `// @ts-nocheck`), while the threads
@@ -1867,7 +2129,7 @@ function emitUncheckedFiles(
1867
2129
  const files = program.getSourceFiles();
1868
2130
  const inOrder: boolean[] = [];
1869
2131
  for (let i = 0; i < files.length; i++) inOrder.push(false);
1870
- for (const i of order) inOrder[i] = true;
2132
+ for (const i of order) if (i >= 0) inOrder[i] = true;
1871
2133
  let emitter: ProgramEmitter | undefined = undefined;
1872
2134
  let holding: HoldingWriter | undefined = undefined;
1873
2135
  for (let i = 0; i < files.length; i++) {
@@ -1909,6 +2171,8 @@ class Revival {
1909
2171
  copy.more,
1910
2172
  copy.reserved,
1911
2173
  copy.emits.map((emit) => this.emit(emit)),
2174
+ copy.units,
2175
+ copy.reportingUnits,
1912
2176
  );
1913
2177
  }
1914
2178