tsc-ts 0.1.0 → 0.1.1

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 (60) hide show
  1. package/README.md +1 -1
  2. package/dist/api/compiler/ast.d.ts +9 -3
  3. package/dist/api/compiler/ast.js +17 -12
  4. package/dist/api/compiler/checker-classes.js +2 -1
  5. package/dist/api/compiler/checker-contextual.d.ts +20 -0
  6. package/dist/api/compiler/checker-contextual.js +111 -1
  7. package/dist/api/compiler/checker-expressions.js +14 -1
  8. package/dist/api/compiler/checker-jsx.js +2 -1
  9. package/dist/api/compiler/checker-type-factory.js +53 -12
  10. package/dist/api/compiler/checker.d.ts +5 -1
  11. package/dist/api/compiler/checker.js +10 -4
  12. package/dist/api/compiler/compile-program.d.ts +5 -0
  13. package/dist/api/compiler/compile-program.js +8 -0
  14. package/dist/api/compiler/expr-record.d.ts +11 -0
  15. package/dist/api/compiler/expr-record.js +53 -0
  16. package/dist/api/compiler/identifier-name.d.ts +9 -0
  17. package/dist/api/compiler/identifier-name.js +10 -0
  18. package/dist/api/compiler/jsdoc.js +4 -2
  19. package/dist/api/compiler/parser.d.ts +2 -1
  20. package/dist/api/compiler/parser.js +10 -8
  21. package/dist/api/compiler/printer/factory.js +3 -2
  22. package/dist/api/compiler/printer/printer.js +3 -2
  23. package/dist/api/compiler/reparser.js +4 -3
  24. package/dist/api/compiler/scanner.d.ts +8 -0
  25. package/dist/api/compiler/scanner.js +58 -42
  26. package/dist/api/compiler/shared-results.d.ts +376 -143
  27. package/dist/api/compiler/shared-results.js +1550 -773
  28. package/dist/api/compiler/transformers/declarations.js +2 -1
  29. package/package.json +9 -9
  30. package/src/compiler/ast.ts +19 -12
  31. package/src/compiler/cgroup.ts +157 -0
  32. package/src/compiler/checker-classes.ts +2 -1
  33. package/src/compiler/checker-contextual.ts +104 -0
  34. package/src/compiler/checker-expressions.ts +16 -1
  35. package/src/compiler/checker-jsx.ts +2 -1
  36. package/src/compiler/checker-type-factory.ts +53 -14
  37. package/src/compiler/checker.ts +11 -4
  38. package/src/compiler/compile-program.ts +8 -0
  39. package/src/compiler/decoder.ts +2 -1
  40. package/src/compiler/doctor-report.ts +133 -0
  41. package/src/compiler/expr-record.ts +59 -0
  42. package/src/compiler/identifier-name.ts +13 -0
  43. package/src/compiler/jsdoc.ts +4 -2
  44. package/src/compiler/parallel-check.ts +353 -146
  45. package/src/compiler/parallel-emit.ts +3 -3
  46. package/src/compiler/parser.ts +10 -8
  47. package/src/compiler/printer/factory.ts +3 -2
  48. package/src/compiler/printer/printer.ts +4 -2
  49. package/src/compiler/reparser.ts +7 -3
  50. package/src/compiler/run-config.ts +1383 -0
  51. package/src/compiler/run-record-read.ts +87 -0
  52. package/src/compiler/run-records.ts +575 -0
  53. package/src/compiler/scanner.ts +57 -41
  54. package/src/compiler/shared-results.ts +1626 -896
  55. package/src/compiler/thread-entry.ts +3 -1
  56. package/src/compiler/transformers/declarations.ts +2 -1
  57. package/src/compiler/tsc-build.ts +64 -6
  58. package/src/compiler/tsc-cli.ts +33 -4
  59. package/src/compiler/tsc-compile.ts +213 -8
  60. package/src/compiler/tsc-watch.ts +25 -4
@@ -1,5 +1,5 @@
1
- // Results one checker thread computed, as portable records other checker threads translate into their own types
2
- // (TSC_TS_SHARE=1).
1
+ // Results one checker thread computed, as records in its number log that other checker threads translate into their
2
+ // own types (TSC_TS_SHARE=1).
3
3
  //
4
4
  // A checker exports the result of a declaration-level computation (the value type of a variable or property, or the
5
5
  // return type of a declared signature) as records: each type the result reaches through its identity (constituents,
@@ -7,7 +7,7 @@
7
7
  // template, string mapping and substitution types), down to types every checker can get by itself (intrinsic types,
8
8
  // declared types of symbols, types of declarations and type nodes). Another checker translates the records through
9
9
  // its own interning caches, so each type still has one object in it.
10
- import { getSymbol, type Node, NodeVisitor, type SourceFile } from './ast.js';
10
+ import { getSymbol, getType, type Node, NodeVisitor, type SourceFile } from './ast.js';
11
11
  import { getSourceFileOfNode } from './binder-utilities.js';
12
12
  import type { Checker } from './checker.js';
13
13
  import { getDeclaredTypeOfSymbol } from './checker-declared-types.js';
@@ -74,6 +74,7 @@ import { findResolutionCycleStartIndex, TypeSystemPropertyName } from './checker
74
74
  import { typeToString } from './type-printer.js';
75
75
  import { getTypeOfSymbol } from './checker-symbol-types.js';
76
76
  import { getReturnTypeOfSignature } from './checker-signatures.js';
77
+ import { LinkTable } from './link-tables.js';
77
78
 
78
79
  // Functions, classes, enums and modules have anonymous types that are cheap to create; their members are not. (A
79
80
  // function: a top-level constant computed from an enum member of a module in an import cycle doesn't compile natively.)
@@ -82,9 +83,9 @@ function sharedValueFlags(): SymbolFlags {
82
83
  }
83
84
 
84
85
  const enum RecordKind {
85
- /** A type the checker creates when it starts: `num` is its position in `initialTypes`. */
86
+ /** A type the checker creates when it starts: its position in `initialTypes`. */
86
87
  Initial,
87
- /** A regular literal type: `flags`, `text` or `num`. */
88
+ /** A regular literal type: its flags, then its value as a double or its text. */
88
89
  Literal,
89
90
  /** The fresh type of the literal type `refs[0]`. */
90
91
  FreshLiteral,
@@ -102,24 +103,24 @@ const enum RecordKind {
102
103
  NodeType,
103
104
  /** A reference to the generic type `refs[0]` with the type arguments `refs[1..]`. */
104
105
  Reference,
105
- /** The tuple target with the element flags `nums`, labeled by `nodes`, readonly when `num` is 1. */
106
+ /** The tuple target: readonly, then the element flags and label nodes. */
106
107
  TupleTarget,
107
108
  /**
108
- * The instantiation of the anonymous, mapped or deferred reference type `refs[0]` with the `num` outer type arguments
109
- * after it, then the return types its signatures had resolved (built after it).
109
+ * The instantiation of the anonymous, mapped or deferred reference type `refs[0]` with the outer type arguments
110
+ * after it, then the return types its signatures had resolved (built after it); the node declares it.
110
111
  */
111
112
  Instantiation,
112
- /** A union of `refs`, with the origin `num` (a record index, or -1). */
113
+ /** A union of `refs` with the origin record, or -1. */
113
114
  Union,
114
115
  /** An intersection of `refs`. */
115
116
  Intersection,
116
- /** `keyof refs[0]` with the index flags `num`. */
117
+ /** `keyof refs[0]` with the index flags. */
117
118
  Index,
118
- /** `refs[0][refs[1]]` with the access flags `num`. */
119
+ /** `refs[0][refs[1]]` with the access flags. */
119
120
  IndexedAccess,
120
- /** The instantiation of the conditional type of `node` with outer type arguments `refs`, for constraints when `num` is 1. */
121
+ /** The instantiation of the conditional type of `node` with outer type arguments `refs`, for constraints when the tag is 1. */
121
122
  Conditional,
122
- /** A template literal type of `texts` and `refs`. */
123
+ /** A template literal type of its texts and `refs`. */
123
124
  Template,
124
125
  /** The string mapping `symbol` of `refs[0]`. */
125
126
  StringMapping,
@@ -129,102 +130,134 @@ const enum RecordKind {
129
130
  NoInfer,
130
131
  /**
131
132
  * The member for the constituent `refs[last]` of the distribution of the conditional type of `node` instantiated
132
- * with outer type arguments `refs[0..last)`, for constraints when `num` is 1.
133
+ * with outer type arguments `refs[0..last)`, for constraints when the tag is 1.
133
134
  */
134
135
  DistributedConditional,
135
136
  /**
136
137
  * An anonymous object type of the object literal `symbol` with members resolved when it was created (an object
137
- * literal type, or its regular, widened or spread form), with the object flags `flags`. Its members come with the
138
- * `CompleteObject` record that follows them.
138
+ * literal type, or its regular, widened or spread form): its object flags, then the record of its members.
139
139
  */
140
140
  AnonymousObject,
141
- /** The members of the `AnonymousObject` record `num`: the properties `refs` and the index infos `indexInfos`. */
141
+ /** The members of the `AnonymousObject` record whose index is written first: the properties `refs` and index infos. */
142
142
  CompleteObject,
143
143
  /**
144
- * A property symbol a checker created (`text` its name, `flags` its symbol flags, `num` its check flags): its
145
- * declarations `nodes`, value declaration `node`, type `refs[0]`, name type `refs[1]` (or -1), the symbol it was
146
- * made from `refs[2]` (a property record, or -1 for `symbol`, a symbol of the program) and its parent `aliasSymbol`.
144
+ * A property symbol a checker created: its symbol flags, check flags and name, its declarations, its type
145
+ * `refs[0]`, name type `refs[1]` (or -1), the symbol it was made from `refs[2]` (a property record, or -1 for the
146
+ * symbol of the program written with it) and its parent (its alias).
147
147
  */
148
148
  Property,
149
149
  }
150
150
 
151
- /** An index signature of an `AnonymousObject` record. */
152
- export class PortableIndexInfo {
153
- readonly keyType: number;
154
- readonly valueType: number;
155
- readonly isReadonly: boolean;
156
- readonly declaration: Node | undefined;
157
- readonly components: readonly Node[];
151
+ /**
152
+ * The bits of a record's header: its kind in the low five, and the fields that follow. A record is its refs (the
153
+ * count in the header, or a count word before them when it is `RefEscape`), then its alias when it has one (its symbol
154
+ * and its refs), then its symbol, its node, and the fields its kind has.
155
+ */
156
+ const enum RecordHeader {
157
+ KindMask = 0x1f,
158
+ HasAlias = 1 << 5,
159
+ NamingShift = 6,
160
+ NamingMask = 3 << NamingShift,
161
+ HasNode = 1 << 8,
162
+ /** The record's value declaration is its first declaration, so it is not written again (`Property`). */
163
+ ValueIsFirst = 1 << 9,
164
+ /** The alias symbol is written as the id of a symbol it merged, not as the symbol of a node. */
165
+ AliasMerged = 1 << 10,
166
+ /** The record is the constituent of a distribution's origin, not a type of its own (`Union`, `Intersection`). */
167
+ Constituent = 1 << 11,
168
+ /** The ref count from which the count has a word of its own. */
169
+ RefEscape = 0xffff,
170
+ }
158
171
 
159
- constructor(
160
- keyType: number,
161
- valueType: number,
162
- isReadonly: boolean,
163
- declaration: Node | undefined,
164
- components: readonly Node[],
165
- ) {
166
- this.keyType = keyType;
167
- this.valueType = valueType;
168
- this.isReadonly = isReadonly;
169
- this.declaration = declaration;
170
- this.components = components;
171
- }
172
+ /** How a record names a symbol. */
173
+ const enum SymbolNaming {
174
+ None,
175
+ /** The symbol of the node written with it. */
176
+ Node,
177
+ /** The export symbol of the symbol of the node written with it. */
178
+ Export,
179
+ /** A symbol the producer merged, by the id of a symbol it merged. */
180
+ Merged,
172
181
  }
173
182
 
174
- export class PortableRecord {
175
- readonly kind: RecordKind;
176
- readonly flags: number;
177
- readonly num: number;
178
- readonly text: string;
179
- symbol: Symbol | undefined;
180
- readonly node: Node | undefined;
181
- readonly refs: readonly number[];
182
- nums: readonly number[];
183
- nodes: readonly (Node | undefined)[];
184
- texts: readonly string[];
185
- /** For a symbol a checker merged: the id of a symbol it merged, which keys every checker's merged symbol. */
186
- mergedId: number;
187
- /** The alias: its symbol and the records of its type arguments. */
188
- aliasSymbol: Symbol | undefined;
189
- aliasMergedId: number;
190
- aliasRefs: readonly number[];
191
- indexInfos: readonly PortableIndexInfo[];
192
- /** The producer's type id, which keys the consumer's translations of the producer's types. */
193
- readonly id: number;
183
+ const noRefs: readonly number[] = [];
194
184
 
195
- constructor(
196
- kind: RecordKind,
197
- id: number,
198
- flags: number,
199
- num: number,
200
- text: string,
201
- symbol: Symbol | undefined,
202
- node: Node | undefined,
203
- refs: readonly number[],
204
- ) {
205
- this.kind = kind;
206
- this.id = id;
207
- this.flags = flags;
208
- this.num = num;
209
- this.text = text;
210
- this.symbol = symbol;
211
- this.node = node;
212
- this.refs = refs;
213
- this.nums = noNumbers;
214
- this.nodes = noNodes;
215
- this.texts = noTexts;
216
- this.mergedId = -1;
217
- this.aliasSymbol = undefined;
218
- this.aliasMergedId = -1;
219
- this.aliasRefs = noNumbers;
220
- this.indexInfos = noIndexInfos;
185
+ /**
186
+ * Writes records into a checker's store: int32 words at a cursor, strings into its string log, and each record's
187
+ * offset in its record table. The cursor is the writer's alone until it publishes a result.
188
+ */
189
+ export class StoreWriter {
190
+ readonly numbers: Int32Array;
191
+ readonly strings: Uint16Array;
192
+ readonly table: Int32Array;
193
+ readonly fileIndex: Map<SourceFile, number>;
194
+ /** The cursor in the number log, and in the string log. */
195
+ at = 0;
196
+ unitAt = 0;
197
+ /** Set when a record or a field didn't fit the store's configured capacity. */
198
+ full = false;
199
+
200
+ constructor(numbers: Int32Array, strings: Uint16Array, table: Int32Array, fileIndex: Map<SourceFile, number>) {
201
+ this.numbers = numbers;
202
+ this.strings = strings;
203
+ this.table = table;
204
+ this.fileIndex = fileIndex;
205
+ }
206
+
207
+ /** Returns to the cursors `at` and `unitAt`. */
208
+ reset(at: number, unitAt: number): void {
209
+ this.at = at;
210
+ this.unitAt = unitAt;
211
+ this.full = false;
212
+ }
213
+
214
+ /** Starts record `index` at the cursor: its header, and its offset in the record table. */
215
+ beginRecord(index: number, header: number): void {
216
+ if (index < this.table.length) this.table[index] = this.at + 1;
217
+ else this.full = true;
218
+ this.word(header);
219
+ }
220
+
221
+ word(value: number): void {
222
+ if (this.at < this.numbers.length) this.numbers[this.at] = value;
223
+ else this.full = true;
224
+ this.at++;
221
225
  }
222
- }
223
226
 
224
- const noNumbers: readonly number[] = [];
225
- const noNodes: readonly (Node | undefined)[] = [];
226
- const noTexts: readonly string[] = [];
227
- const noIndexInfos: readonly PortableIndexInfo[] = [];
227
+ /** A double as the two int32 halves of its bits. */
228
+ double(value: number): void {
229
+ const words = doubleWords();
230
+ words.f[0] = value;
231
+ this.word(words.i[0]!);
232
+ this.word(words.i[1]!);
233
+ }
234
+
235
+ /** A string as its offset and length in the string log. */
236
+ text(value: string): void {
237
+ const length = value.length;
238
+ if (this.unitAt + length <= this.strings.length) {
239
+ this.word(this.unitAt);
240
+ this.word(length);
241
+ for (let j = 0; j < length; j++) this.strings[this.unitAt++] = value.charCodeAt(j);
242
+ return;
243
+ }
244
+ this.full = true;
245
+ this.word(0);
246
+ this.word(0);
247
+ }
248
+
249
+ /** A node as its file, range and id, or -1 for none. The file must be one `fileIndex` has. */
250
+ node(node: Node | undefined): void {
251
+ if (node === undefined) {
252
+ this.word(-1);
253
+ return;
254
+ }
255
+ this.word(this.fileIndex.get(getSourceFileOfNode(node))!);
256
+ this.word(node.pos);
257
+ this.word(node.end);
258
+ this.word(node.id);
259
+ }
260
+ }
228
261
 
229
262
  /** The types every checker creates when it starts, in the same order; marker types are left out. */
230
263
  export function initialTypes(c: Checker): Type[] {
@@ -284,132 +317,269 @@ export function initialTypes(c: Checker): Type[] {
284
317
  ];
285
318
  }
286
319
 
320
+ /** The reason an export stops at its budget (`Exporter.limit`), which `post` returns: the result is not posted. */
321
+ export const budgetFailure = 'export budget';
322
+
287
323
  /** A checker's `Exporter.mergedOrigins`, kept while its merged symbols stay the same. */
288
324
  export class MergedOrigins {
289
325
  origins = new Map<Symbol, number>();
290
326
  size = -1;
291
327
  }
292
328
 
329
+ /** An index signature of an `AnonymousObject` record, waiting for the words of its `CompleteObject`. */
330
+ class IndexInfoWords {
331
+ readonly keyType: number;
332
+ readonly valueType: number;
333
+ readonly isReadonly: boolean;
334
+ readonly declaration: Node | undefined;
335
+ readonly components: readonly Node[];
336
+
337
+ constructor(
338
+ keyType: number,
339
+ valueType: number,
340
+ isReadonly: boolean,
341
+ declaration: Node | undefined,
342
+ components: readonly Node[],
343
+ ) {
344
+ this.keyType = keyType;
345
+ this.valueType = valueType;
346
+ this.isReadonly = isReadonly;
347
+ this.declaration = declaration;
348
+ this.components = components;
349
+ }
350
+ }
351
+
293
352
  /** Writes a result as records; `failure` names the first part that has no record kind. */
294
353
  export class Exporter {
295
354
  readonly c: Checker;
296
- records: PortableRecord[] = [];
297
- readonly indexes = new Map<Type, number>();
355
+ readonly writer: StoreWriter;
298
356
  /**
299
- * The index of `records[0]`. A checker's exporter keeps the indexes of the types it exported before
300
- * (`ThreadSharing.post`): their records are in its store already, and a result's records are only the new ones.
357
+ * The index of the first record of the export in progress. A checker's exporter keeps the records of the types it
358
+ * exported before (`ThreadSharing.post`): its store has them already, and a result's records are only the new ones.
301
359
  */
302
360
  base = 0;
361
+ /** The number of records the export in progress wrote. */
362
+ count = 0;
363
+ /** The record of each type, by type id, and of each property symbol, by symbol id. */
364
+ readonly indexes = new Map<number, number>();
365
+ readonly properties = new Map<number, number>();
303
366
  /** The types and properties the export in progress gave indexes, which a failed export forgets again. */
304
- readonly addedTypes: Type[] = [];
305
- readonly addedProperties: Symbol[] = [];
306
-
307
- /** Appends `record`; its index. */
308
- push(record: PortableRecord): number {
309
- const index = this.base + this.records.length;
310
- this.records.push(record);
311
- return index;
312
- }
313
- readonly initial: Map<Type, number>;
367
+ readonly addedTypes: number[] = [];
368
+ readonly addedProperties: number[] = [];
369
+ /**
370
+ * The most records the export in progress may add (`ThreadSharing.post` prices it with `kappa`); past it the export
371
+ * fails and the result is not posted.
372
+ */
373
+ limit = Number.POSITIVE_INFINITY;
374
+ readonly initial: Map<number, number>;
314
375
  failure: string | undefined = undefined;
315
376
 
316
377
  /** The ids above which nodes and symbols are the checker thread's own (lazily parsed JSDoc), or Infinity. */
317
378
  nodeLimit = Infinity;
318
379
  symbolLimit = Infinity;
319
380
 
320
- constructor(c: Checker, initial: Map<Type, number>) {
381
+ /** The ids of the symbols each merged symbol merged (`getMergedSymbol`), built on first use. */
382
+ mergedOrigins: Map<Symbol, number> | undefined = undefined;
383
+ /** Where the checker keeps `mergedOrigins` between exports. */
384
+ originsCache: MergedOrigins | undefined = undefined;
385
+
386
+ /** How the record in progress names its symbol and its alias, and the node it carries. */
387
+ private naming = SymbolNaming.None;
388
+ private namingMerged = -1;
389
+ private namingNode: Node | undefined = undefined;
390
+ private aliasNaming = SymbolNaming.None;
391
+ private aliasMerged = -1;
392
+ private aliasNode: Node | undefined = undefined;
393
+ private aliasRefs: readonly number[] = noRefs;
394
+ private node: Node | undefined = undefined;
395
+
396
+ constructor(c: Checker, initial: Map<number, number>, writer: StoreWriter) {
321
397
  this.c = c;
322
398
  this.initial = initial;
399
+ this.writer = writer;
323
400
  }
324
401
 
325
- /** Whether `node` is one every checker shares; fails if not. */
402
+ fail(reason: string): number {
403
+ if (this.failure === undefined) this.failure = reason;
404
+ return -1;
405
+ }
406
+
407
+ /** Whether `node` is one every checker shares, and can name; fails if not. */
326
408
  sharedNode(node: Node | undefined): boolean {
327
- if (node === undefined || node.id <= this.nodeLimit) return true;
328
- this.fail('node of the thread');
329
- return false;
409
+ if (node === undefined) return true;
410
+ if (node.id > this.nodeLimit) {
411
+ this.fail('node of the thread');
412
+ return false;
413
+ }
414
+ if (!this.writer.fileIndex.has(getSourceFileOfNode(node))) {
415
+ this.fail('node outside the program');
416
+ return false;
417
+ }
418
+ return true;
330
419
  }
331
420
 
332
- /** The ids of the symbols each merged symbol merged (`getMergedSymbol`), built on first use. */
333
- mergedOrigins: Map<Symbol, number> | undefined = undefined;
334
- /** Where the checker keeps `mergedOrigins` between exports. */
335
- originsCache: MergedOrigins | undefined = undefined;
421
+ /**
422
+ * The index of the next record of the store, or -1 if the result is over its export budget; the store's table must
423
+ * have room for it.
424
+ */
425
+ nextIndex(): number {
426
+ const index = this.base + this.count;
427
+ if (index >= this.limit) return this.fail(budgetFailure);
428
+ this.count++;
429
+ if (index >= this.writer.table.length) this.writer.full = true;
430
+ return index;
431
+ }
432
+
433
+ /** Records the type `t` at `index`. */
434
+ private memo(t: Type, index: number): void {
435
+ // A consumer's memo is by record index, so a type has one record: VERIFY runs check that it does.
436
+ if (shareVerifyValue !== undefined && this.indexes.has(t.id))
437
+ process.stderr.write('share-verify: type ' + t.id + ' has a record already\n');
438
+ this.indexes.set(t.id, index);
439
+ this.addedTypes.push(t.id);
440
+ }
336
441
 
337
442
  /**
338
- * Sets the symbol of `record`: a symbol of the program, which every checker shares, or a symbol the checker merged,
339
- * by the id of a symbol it merged. Other symbols are the checker's own.
443
+ * Sets how the record in progress names `symbol`: a symbol of the program by the node it is the symbol of, or a
444
+ * symbol the checker merged by the id of a symbol it merged. Fails for a symbol of the thread.
340
445
  */
341
- symbolOf(record: PortableRecord, symbol: Symbol, alias: boolean): boolean {
342
- let mergedId = -1;
343
- let shared: Symbol | undefined = symbol;
344
- if ((symbol.flags & SymbolFlags.Transient) === 0 && getSymbolId(symbol) > this.symbolLimit) {
345
- this.fail('symbol of the thread');
346
- return false;
347
- }
348
- if ((symbol.flags & SymbolFlags.Transient) !== 0) {
349
- let origins = this.mergedOrigins;
350
- if (origins === undefined) {
351
- const cache = this.originsCache;
352
- if (cache !== undefined && cache.size === this.c.mergedSymbols.size) origins = cache.origins;
446
+ nameSymbol(symbol: Symbol | undefined, alias: boolean): boolean {
447
+ let naming = SymbolNaming.None;
448
+ let merged = -1;
449
+ let node: Node | undefined = undefined;
450
+ if (symbol !== undefined) {
451
+ if ((symbol.flags & SymbolFlags.Transient) === 0) {
452
+ if (getSymbolId(symbol) > this.symbolLimit) {
453
+ this.fail('symbol of the thread');
454
+ return false;
455
+ }
456
+ const declaration = symbol.declaration;
457
+ if (declaration === undefined) {
458
+ this.fail('symbol without a declaration');
459
+ return false;
460
+ }
461
+ const local = getSymbol(declaration);
462
+ if (local === symbol) naming = SymbolNaming.Node;
463
+ else if (local !== undefined && local.exportSymbol === symbol) naming = SymbolNaming.Export;
353
464
  else {
354
- origins = new Map<Symbol, number>();
355
- for (const [id, target] of this.c.mergedSymbols) if (!origins.has(target)) origins.set(target, id);
356
- if (cache !== undefined) {
357
- cache.origins = origins;
358
- cache.size = this.c.mergedSymbols.size;
359
- }
465
+ this.fail('symbol not named by its declaration');
466
+ return false;
360
467
  }
361
- this.mergedOrigins = origins;
362
- }
363
- const origin = origins.get(symbol);
364
- if (origin === undefined) {
365
- this.fail('checker symbol ' + symbol.name);
366
- return false;
468
+ node = declaration;
469
+ } else {
470
+ let origins = this.mergedOrigins;
471
+ if (origins === undefined) origins = this.buildOrigins();
472
+ const origin = origins.get(symbol);
473
+ if (origin === undefined) {
474
+ this.fail('checker symbol ' + symbol.name);
475
+ return false;
476
+ }
477
+ naming = SymbolNaming.Merged;
478
+ merged = origin;
367
479
  }
368
- mergedId = origin;
369
- shared = undefined;
370
480
  }
371
481
  if (alias) {
372
- record.aliasSymbol = shared;
373
- record.aliasMergedId = mergedId;
482
+ this.aliasNaming = naming;
483
+ this.aliasMerged = merged;
484
+ this.aliasNode = node;
485
+ // The alias's type arguments are recorded after it is named (`aliasBits`). A record that names an alias which
486
+ // has none, such as a property naming its parent, keeps none of the last alias's.
487
+ this.aliasRefs = noRefs;
374
488
  } else {
375
- record.symbol = shared;
376
- record.mergedId = mergedId;
489
+ this.naming = naming;
490
+ this.namingMerged = merged;
491
+ this.namingNode = node;
377
492
  }
378
493
  return true;
379
494
  }
380
495
 
381
- /** `new PortableRecord` with the symbol `symbol` (see `symbolOf`); undefined if it is the checker's own. */
382
- withSymbol(
383
- kind: RecordKind,
384
- t: Type,
385
- symbol: Symbol,
386
- num: number,
387
- refs: readonly number[],
388
- ): PortableRecord | undefined {
389
- const record = new PortableRecord(kind, t.id, 0, num, '', undefined, undefined, refs);
390
- return this.symbolOf(record, symbol, false) ? record : undefined;
496
+ /** The ids of the symbols each merged symbol merged, from the checker's `mergedSymbols`. */
497
+ private buildOrigins(): Map<Symbol, number> {
498
+ const cache = this.originsCache;
499
+ let origins: Map<Symbol, number>;
500
+ if (cache !== undefined && cache.size === this.c.mergedSymbols.size) origins = cache.origins;
501
+ else {
502
+ origins = new Map<Symbol, number>();
503
+ for (const [id, target] of this.c.mergedSymbols) if (!origins.has(target)) origins.set(target, id);
504
+ if (cache !== undefined) {
505
+ cache.origins = origins;
506
+ cache.size = this.c.mergedSymbols.size;
507
+ }
508
+ }
509
+ this.mergedOrigins = origins;
510
+ return origins;
391
511
  }
392
512
 
393
- addWithSymbol(kind: RecordKind, t: Type, symbol: Symbol, num: number, refs: readonly number[]): number {
394
- const record = this.withSymbol(kind, t, symbol, num, refs);
395
- return record === undefined ? -1 : this.add(t, record);
513
+ /**
514
+ * Names the alias of the record in progress, after its type arguments are exported, and returns the header bits that
515
+ * name it; -1 if it can't be named.
516
+ */
517
+ private aliasBits(alias: TypeAlias | undefined, refs: readonly number[]): number {
518
+ if (alias === undefined) return 0;
519
+ if (!this.nameSymbol(alias.symbol, true)) return -1;
520
+ this.aliasRefs = refs;
521
+ return RecordHeader.HasAlias | (this.aliasNaming === SymbolNaming.Merged ? RecordHeader.AliasMerged : 0);
396
522
  }
397
523
 
398
- fail(reason: string): number {
399
- if (this.failure === undefined) this.failure = reason;
400
- return -1;
524
+ /**
525
+ * Starts the record at `index`: its header, its refs, its alias, its symbol and its node, all decided by `bits` and
526
+ * the fields set just before. A kind's own fields follow.
527
+ */
528
+ begin(kind: RecordKind, index: number, refs: readonly number[], bits: number): void {
529
+ const w = this.writer;
530
+ const n = refs.length;
531
+ w.beginRecord(index, kind | bits | (n < RecordHeader.RefEscape ? n << 16 : RecordHeader.RefEscape << 16));
532
+ if (n >= RecordHeader.RefEscape) w.word(n);
533
+ for (let i = 0; i < n; i++) w.word(refs[i]!);
534
+ if ((bits & RecordHeader.HasAlias) !== 0) {
535
+ if ((bits & RecordHeader.AliasMerged) !== 0) w.word(this.aliasMerged);
536
+ else {
537
+ w.word(this.aliasNaming === SymbolNaming.Export ? 1 : 0);
538
+ w.node(this.aliasNode);
539
+ }
540
+ const aliasRefs = this.aliasRefs;
541
+ w.word(aliasRefs.length);
542
+ for (let i = 0; i < aliasRefs.length; i++) w.word(aliasRefs[i]!);
543
+ }
544
+ const naming = bits & RecordHeader.NamingMask;
545
+ if (naming === SymbolNaming.Merged << RecordHeader.NamingShift) w.word(this.namingMerged);
546
+ else if (naming !== 0) w.node(this.namingNode);
547
+ if ((bits & RecordHeader.HasNode) !== 0) w.node(this.node);
401
548
  }
402
549
 
403
- add(t: Type, record: PortableRecord): number {
404
- if (!this.sharedNode(record.node)) return -1;
405
- for (const node of record.nodes) if (!this.sharedNode(node)) return -1;
406
- for (const info of record.indexInfos) {
407
- if (!this.sharedNode(info.declaration)) return -1;
408
- for (const node of info.components) if (!this.sharedNode(node)) return -1;
409
- }
410
- const index = this.push(record);
411
- this.indexes.set(t, index);
412
- this.addedTypes.push(t);
550
+ /** The record of `t` of `kind`, with `refs` and no fields of its own; its index. */
551
+ private plain(kind: RecordKind, t: Type, refs: readonly number[]): number {
552
+ const index = this.nextIndex();
553
+ this.memo(t, index);
554
+ this.begin(kind, index, refs, 0);
555
+ return index;
556
+ }
557
+
558
+ /** The record of the initial type `t`, which is `num`th in `initialTypes`. */
559
+ private initialType(t: Type, num: number): number {
560
+ const index = this.nextIndex();
561
+ this.memo(t, index);
562
+ this.begin(RecordKind.Initial, index, noRefs, 0);
563
+ this.writer.word(num);
564
+ return index;
565
+ }
566
+
567
+ /** The record of the type `t` of `kind` named by `symbol`, with `refs`; its index. */
568
+ private symbolType(kind: RecordKind, t: Type, symbol: Symbol, refs: readonly number[]): number {
569
+ if (!this.nameSymbol(symbol, false)) return -1;
570
+ const index = this.nextIndex();
571
+ this.memo(t, index);
572
+ this.begin(kind, index, refs, this.naming << RecordHeader.NamingShift);
573
+ return index;
574
+ }
575
+
576
+ /** The record of the type `t` of the type node `node`; its index. */
577
+ private nodeType(t: Type, node: Node): number {
578
+ if (!this.sharedNode(node)) return -1;
579
+ this.node = node;
580
+ const index = this.nextIndex();
581
+ this.memo(t, index);
582
+ this.begin(RecordKind.NodeType, index, noRefs, RecordHeader.HasNode);
413
583
  return index;
414
584
  }
415
585
 
@@ -422,157 +592,171 @@ export class Exporter {
422
592
  return refs;
423
593
  }
424
594
 
425
- aliasOf(record: PortableRecord, alias: TypeAlias | undefined): PortableRecord {
426
- if (alias === undefined) return record;
427
- const refs = this.types(alias.typeArguments);
428
- this.symbolOf(record, alias.symbol, true);
429
- record.aliasRefs = refs;
430
- return record;
431
- }
432
-
433
595
  type(t: Type): number {
434
596
  if (this.failure !== undefined) return -1;
435
- const known = this.indexes.get(t);
597
+ const known = this.indexes.get(t.id);
436
598
  if (known !== undefined) return known;
437
- const initial = this.initial.get(t);
438
- if (initial !== undefined)
439
- return this.add(t, new PortableRecord(RecordKind.Initial, t.id, 0, initial, '', undefined, undefined, noNumbers));
599
+ const initial = this.initial.get(t.id);
600
+ if (initial !== undefined) return this.initialType(t, initial);
440
601
  const c = this.c;
441
602
  const flags = t.flags;
442
603
  if ((flags & TypeFlags.Literal) !== 0 && (flags & TypeFlags.Boolean) === 0) return this.literal(t as LiteralType);
443
604
  if ((flags & TypeFlags.UniqueESSymbol) !== 0) {
444
605
  if (c.uniqueESSymbolTypes.get(t.symbol!) !== t) return this.fail('unique symbol');
445
- return this.addWithSymbol(RecordKind.UniqueSymbol, t, t.symbol!, 0, noNumbers);
606
+ return this.symbolType(RecordKind.UniqueSymbol, t, t.symbol!, noRefs);
446
607
  }
447
608
  if ((flags & TypeFlags.TypeParameter) !== 0) {
448
609
  const symbol = t.symbol;
449
610
  if (symbol === undefined) return this.fail('type parameter without a symbol');
450
611
  const declared = getDeclaredTypeOfSymbol(c, symbol);
451
- if (declared === t) return this.addWithSymbol(RecordKind.Declared, t, symbol, 0, noNumbers);
612
+ if (declared === t) return this.symbolType(RecordKind.Declared, t, symbol, noRefs);
452
613
  if ((declared.objectFlags & ObjectFlags.ClassOrInterface) !== 0 && (declared as InterfaceType).thisType === t)
453
- return this.addWithSymbol(RecordKind.ThisType, t, symbol, 0, noNumbers);
614
+ return this.symbolType(RecordKind.ThisType, t, symbol, noRefs);
454
615
  return this.fail('fresh type parameter');
455
616
  }
456
617
  if (t.symbol !== undefined && (flags & (TypeFlags.Union | TypeFlags.Enum)) !== 0) {
457
618
  // An enum type: the declared type of its symbol.
458
619
  if (getDeclaredTypeOfSymbol(c, t.symbol) !== t) return this.fail('enum type');
459
- return this.addWithSymbol(RecordKind.Declared, t, t.symbol, 0, noNumbers);
620
+ return this.symbolType(RecordKind.Declared, t, t.symbol, noRefs);
460
621
  }
461
622
  if ((flags & TypeFlags.Union) !== 0) return this.union(t as UnionType);
462
- if ((flags & TypeFlags.Intersection) !== 0) {
463
- const refs = this.types((t as UnionOrIntersectionType).types);
464
- if (this.failure !== undefined) return -1;
465
- const record = new PortableRecord(
466
- RecordKind.Intersection,
467
- t.id,
468
- t.objectFlags,
469
- 0,
470
- '',
471
- undefined,
472
- undefined,
473
- refs,
474
- );
475
- return this.add(t, this.aliasOf(record, t.alias));
476
- }
623
+ if ((flags & TypeFlags.Intersection) !== 0) return this.intersection(t as UnionOrIntersectionType);
477
624
  if ((flags & TypeFlags.Object) !== 0) return this.object(t as ObjectType);
478
625
  if ((flags & TypeFlags.Index) !== 0) {
479
626
  const target = this.type((t as IndexType).target);
480
627
  if (this.failure !== undefined) return -1;
481
- return this.add(
482
- t,
483
- new PortableRecord(RecordKind.Index, t.id, 0, (t as IndexType).indexFlags, '', undefined, undefined, [target]),
484
- );
628
+ const index = this.nextIndex();
629
+ this.memo(t, index);
630
+ this.begin(RecordKind.Index, index, [target], 0);
631
+ this.writer.word((t as IndexType).indexFlags);
632
+ return index;
485
633
  }
486
634
  if ((flags & TypeFlags.IndexedAccess) !== 0) {
487
635
  const access = t as IndexedAccessType;
488
636
  const refs = this.types([access.objectType, access.indexType]);
489
637
  if (this.failure !== undefined) return -1;
490
- const record = new PortableRecord(
491
- RecordKind.IndexedAccess,
492
- t.id,
493
- 0,
494
- this.c.indexedAccessKeyFlags!.get(t) ?? access.accessFlags,
495
- '',
496
- undefined,
497
- undefined,
498
- refs,
499
- );
500
- return this.add(t, this.aliasOf(record, t.alias));
638
+ const alias = t.alias;
639
+ const aliasRefs = alias === undefined ? noRefs : this.types(alias.typeArguments);
640
+ if (this.failure !== undefined) return -1;
641
+ const bits = this.aliasBits(alias, aliasRefs);
642
+ if (bits < 0) return -1;
643
+ const index = this.nextIndex();
644
+ this.memo(t, index);
645
+ this.begin(RecordKind.IndexedAccess, index, refs, bits);
646
+ this.writer.word(this.c.indexedAccessKeyFlags!.get(t) ?? access.accessFlags);
647
+ return index;
501
648
  }
502
649
  if ((flags & TypeFlags.Conditional) !== 0) return this.conditional(t as ConditionalType);
503
650
  if ((flags & TypeFlags.TemplateLiteral) !== 0) {
504
651
  const template = t as TemplateLiteralType;
505
652
  const refs = this.types(template.types);
506
653
  if (this.failure !== undefined) return -1;
507
- const record = new PortableRecord(RecordKind.Template, t.id, 0, 0, '', undefined, undefined, refs);
508
- record.texts = template.texts.slice();
509
- return this.add(t, record);
654
+ const index = this.nextIndex();
655
+ this.memo(t, index);
656
+ this.begin(RecordKind.Template, index, refs, 0);
657
+ const texts = template.texts;
658
+ this.writer.word(texts.length);
659
+ for (let i = 0; i < texts.length; i++) this.writer.text(texts[i]!);
660
+ return index;
510
661
  }
511
662
  if ((flags & TypeFlags.StringMapping) !== 0) {
512
663
  const target = this.type((t as StringMappingType).target);
513
664
  if (this.failure !== undefined) return -1;
514
- return this.addWithSymbol(RecordKind.StringMapping, t, t.symbol!, 0, [target]);
665
+ return this.symbolType(RecordKind.StringMapping, t, t.symbol!, [target]);
515
666
  }
516
667
  if ((flags & TypeFlags.Substitution) !== 0) {
517
668
  const substitution = t as SubstitutionType;
518
669
  if (isNoInferType(c, t)) {
519
670
  const base = this.type(substitution.baseType);
520
671
  if (this.failure !== undefined) return -1;
521
- return this.add(t, new PortableRecord(RecordKind.NoInfer, t.id, 0, 0, '', undefined, undefined, [base]));
672
+ return this.plain(RecordKind.NoInfer, t, [base]);
522
673
  }
523
674
  const refs = this.types([substitution.baseType, substitution.constraint]);
524
675
  if (this.failure !== undefined) return -1;
525
- return this.add(t, new PortableRecord(RecordKind.Substitution, t.id, 0, 0, '', undefined, undefined, refs));
676
+ return this.plain(RecordKind.Substitution, t, refs);
526
677
  }
527
678
  return this.fail('type flags ' + flags);
528
679
  }
529
680
 
530
- literal(t: LiteralType): number {
681
+ private literal(t: LiteralType): number {
531
682
  const regular = t.regularType;
532
683
  if (regular !== t) {
533
684
  if (regular.freshType !== t) return this.fail('literal');
534
685
  const index = this.type(regular);
535
686
  if (this.failure !== undefined) return -1;
536
- return this.add(t, new PortableRecord(RecordKind.FreshLiteral, t.id, 0, 0, '', undefined, undefined, [index]));
687
+ return this.plain(RecordKind.FreshLiteral, t, [index]);
537
688
  }
538
689
  if (t.symbol !== undefined) {
539
690
  if (getDeclaredTypeOfSymbol(this.c, t.symbol) !== t) return this.fail('enum literal');
540
- return this.addWithSymbol(RecordKind.EnumLiteral, t, t.symbol, 0, noNumbers);
541
- }
542
- return this.add(
543
- t,
544
- new PortableRecord(
545
- RecordKind.Literal,
546
- t.id,
547
- t.flags,
548
- t.numberValue,
549
- t.stringValue,
550
- undefined,
551
- undefined,
552
- noNumbers,
553
- ),
554
- );
691
+ return this.symbolType(RecordKind.EnumLiteral, t, t.symbol, noRefs);
692
+ }
693
+ const index = this.nextIndex();
694
+ this.memo(t, index);
695
+ this.begin(RecordKind.Literal, index, noRefs, 0);
696
+ const w = this.writer;
697
+ w.word(t.flags);
698
+ if ((t.flags & TypeFlags.NumberLiteral) !== 0) w.double(t.numberValue);
699
+ else w.text(t.stringValue);
700
+ return index;
701
+ }
702
+
703
+ /** The record of the constituents of a union's or intersection's origin, which is not a type: it has no alias. */
704
+ private originRecord(kind: RecordKind, flags: number, refs: readonly number[]): number {
705
+ const index = this.nextIndex();
706
+ this.begin(kind, index, refs, RecordHeader.Constituent);
707
+ this.writer.word(flags);
708
+ // A union's origin field is -1 here, as it is for a union with no origin: the constituents are refs, and the
709
+ // reader scans the field for records this record depends on.
710
+ if (kind === RecordKind.Union) this.writer.word(-1);
711
+ return index;
555
712
  }
556
713
 
557
- union(t: UnionType): number {
714
+ private union(t: UnionType): number {
558
715
  const refs = this.types(t.types);
716
+ if (this.failure !== undefined) return -1;
559
717
  let origin = -1;
560
- if (t.origin !== undefined) {
561
- const o = t.origin;
718
+ const o = t.origin;
719
+ if (o !== undefined) {
562
720
  if ((o.flags & TypeFlags.UnionOrIntersection) !== 0) {
563
721
  // Origins are not interned: a union key spells their constituents.
564
722
  const parts = this.types((o as UnionOrIntersectionType).types);
565
723
  if (this.failure !== undefined) return -1;
566
724
  const kind = (o.flags & TypeFlags.Union) !== 0 ? RecordKind.Union : RecordKind.Intersection;
567
- origin = this.push(new PortableRecord(kind, -1, o.objectFlags, -2, '', undefined, undefined, parts));
568
- } else origin = this.type(o);
725
+ origin = this.originRecord(kind, o.objectFlags, parts);
726
+ } else {
727
+ origin = this.type(o);
728
+ if (this.failure !== undefined) return -1;
729
+ }
569
730
  }
731
+ const alias = t.alias;
732
+ const aliasRefs = alias === undefined ? noRefs : this.types(alias.typeArguments);
733
+ if (this.failure !== undefined) return -1;
734
+ const bits = this.aliasBits(alias, aliasRefs);
735
+ if (bits < 0) return -1;
736
+ const index = this.nextIndex();
737
+ this.memo(t, index);
738
+ this.begin(RecordKind.Union, index, refs, bits);
739
+ this.writer.word(t.objectFlags);
740
+ this.writer.word(origin);
741
+ return index;
742
+ }
743
+
744
+ private intersection(t: UnionOrIntersectionType): number {
745
+ const refs = this.types(t.types);
746
+ if (this.failure !== undefined) return -1;
747
+ const alias = t.alias;
748
+ const aliasRefs = alias === undefined ? noRefs : this.types(alias.typeArguments);
570
749
  if (this.failure !== undefined) return -1;
571
- const record = new PortableRecord(RecordKind.Union, t.id, t.objectFlags, origin, '', undefined, undefined, refs);
572
- return this.add(t, this.aliasOf(record, t.alias));
750
+ const bits = this.aliasBits(alias, aliasRefs);
751
+ if (bits < 0) return -1;
752
+ const index = this.nextIndex();
753
+ this.memo(t, index);
754
+ this.begin(RecordKind.Intersection, index, refs, bits);
755
+ this.writer.word(t.objectFlags);
756
+ return index;
573
757
  }
574
758
 
575
- object(t: ObjectType): number {
759
+ private object(t: ObjectType): number {
576
760
  const c = this.c;
577
761
  const objectFlags = t.objectFlags;
578
762
  const symbol = t.symbol;
@@ -583,15 +767,15 @@ export class Exporter {
583
767
  if (target === t) {
584
768
  if ((objectFlags & ObjectFlags.Tuple) !== 0) return this.tupleTarget(t as TupleType);
585
769
  if (symbol === undefined || getDeclaredTypeOfSymbol(c, symbol) !== t) return this.fail('generic target');
586
- return this.addWithSymbol(RecordKind.Declared, t, symbol, 0, noNumbers);
770
+ return this.symbolType(RecordKind.Declared, t, symbol, noRefs);
587
771
  }
588
772
  const refs = [this.type(target), ...this.types(reference.resolvedTypeArguments ?? [])];
589
773
  if (this.failure !== undefined) return -1;
590
- return this.add(t, new PortableRecord(RecordKind.Reference, t.id, 0, 0, '', undefined, undefined, refs));
774
+ return this.plain(RecordKind.Reference, t, refs);
591
775
  }
592
776
  if ((objectFlags & ObjectFlags.ClassOrInterface) !== 0) {
593
777
  if (symbol === undefined || getDeclaredTypeOfSymbol(c, symbol) !== t) return this.fail('declared type');
594
- return this.addWithSymbol(RecordKind.Declared, t, symbol, 0, noNumbers);
778
+ return this.symbolType(RecordKind.Declared, t, symbol, noRefs);
595
779
  }
596
780
  if ((objectFlags & (ObjectFlags.ReverseMapped | ObjectFlags.EvolvingArray)) !== 0) return this.fail('object kind');
597
781
  if ((objectFlags & ObjectFlags.Instantiated) !== 0) return this.instantiation(t);
@@ -599,10 +783,9 @@ export class Exporter {
599
783
  return this.fail('object without a declaration, object flags ' + objectFlags);
600
784
  // The anonymous type of a function, class, enum or module; a variable's own type is what is being shared.
601
785
  if ((symbol.flags & sharedValueFlags()) === 0 && c.symbolTypes.get(getSymbolId(symbol)) === t)
602
- return this.addWithSymbol(RecordKind.SymbolType, t, symbol, 0, noNumbers);
786
+ return this.symbolType(RecordKind.SymbolType, t, symbol, noRefs);
603
787
  const declaration = (objectFlags & ObjectFlags.Mapped) !== 0 ? (t as MappedType).declaration : symbol.declaration;
604
- if (declaration !== undefined && c.resolvedTypes.get(declaration.id) === t)
605
- return this.add(t, new PortableRecord(RecordKind.NodeType, t.id, 0, 0, '', undefined, declaration, noNumbers));
788
+ if (declaration !== undefined && c.resolvedTypes.get(declaration.id) === t) return this.nodeType(t, declaration);
606
789
  if (
607
790
  (symbol.flags & SymbolFlags.ObjectLiteral) !== 0 &&
608
791
  (objectFlags & (ObjectFlags.Anonymous | ObjectFlags.MembersResolved)) ===
@@ -622,55 +805,58 @@ export class Exporter {
622
805
  }
623
806
 
624
807
  /** An object literal type, or one made from one, with its properties and index infos. */
625
- anonymousObject(t: ObjectType): number {
808
+ private anonymousObject(t: ObjectType): number {
626
809
  const c = this.c;
627
810
  if ((t.objectFlags & ObjectFlags.SideState) !== 0 || c.patternForType.has(t))
628
811
  return this.fail('object literal state');
629
812
  const resolved = t as StructuredType;
630
813
  if (resolved.callSignatures.length !== 0 || resolved.constructSignatures.length !== 0)
631
814
  return this.fail('object literal signatures');
632
- const shell = new PortableRecord(
633
- RecordKind.AnonymousObject,
634
- t.id,
635
- t.objectFlags & ~lazyObjectFlags(),
636
- 0,
637
- '',
638
- undefined,
639
- undefined,
640
- noNumbers,
641
- );
642
- if (!this.symbolOf(shell, t.symbol!, false)) return -1;
643
- const index = this.add(t, shell);
815
+ const shell = this.nextIndex();
816
+ this.memo(t, shell);
644
817
  const members: number[] = [];
645
818
  for (const prop of resolved.properties) {
646
819
  members.push(this.property(prop));
647
820
  if (this.failure !== undefined) return -1;
648
821
  }
649
- const infos: PortableIndexInfo[] = [];
822
+ const infos: IndexInfoWords[] = [];
650
823
  for (const info of resolved.indexInfos) {
651
824
  const key = this.type(info.keyType);
652
825
  const value = this.type(info.valueType);
653
826
  if (this.failure !== undefined) return -1;
654
- infos.push(new PortableIndexInfo(key, value, info.isReadonly, info.declaration, info.components.slice()));
827
+ if (!this.sharedNode(info.declaration)) return -1;
828
+ for (const node of info.components) if (!this.sharedNode(node)) return -1;
829
+ infos.push(new IndexInfoWords(key, value, info.isReadonly, info.declaration, info.components));
655
830
  }
656
- const complete = new PortableRecord(RecordKind.CompleteObject, -1, 0, index, '', undefined, undefined, members);
657
- complete.indexInfos = infos;
658
- this.push(complete);
659
- return index;
831
+ const complete = this.nextIndex();
832
+ if (!this.nameSymbol(t.symbol!, false)) return -1;
833
+ this.begin(RecordKind.AnonymousObject, shell, noRefs, this.naming << RecordHeader.NamingShift);
834
+ const w = this.writer;
835
+ w.word(t.objectFlags & ~lazyObjectFlags());
836
+ w.word(complete);
837
+ this.begin(RecordKind.CompleteObject, complete, members, 0);
838
+ w.word(shell);
839
+ w.word(infos.length);
840
+ for (const info of infos) {
841
+ w.word(info.keyType);
842
+ w.word(info.valueType);
843
+ w.word(info.isReadonly ? 1 : 0);
844
+ w.node(info.declaration);
845
+ w.word(info.components.length);
846
+ for (const node of info.components) w.node(node);
847
+ }
848
+ return shell;
660
849
  }
661
850
 
662
- /** The records of property symbols, by symbol. */
663
- readonly properties = new Map<Symbol, number>();
664
-
665
851
  /** A property symbol of an object literal type: a checker's own, made from one of the program or from another. */
666
- property(prop: Symbol): number {
667
- const known = this.properties.get(prop);
852
+ private property(prop: Symbol): number {
853
+ const id = getSymbolId(prop);
854
+ const known = this.properties.get(id);
668
855
  if (known !== undefined) return known;
669
856
  const c = this.c;
670
857
  if ((prop.flags & SymbolFlags.Transient) === 0) return this.fail('property of the program');
671
858
  if ((prop.checkFlags & ~(CheckFlags.Readonly | CheckFlags.Late)) !== 0)
672
859
  return this.fail('property check flags ' + prop.checkFlags);
673
- const id = getSymbolId(prop);
674
860
  const t = c.symbolTypes.get(id);
675
861
  if (t === undefined) return this.fail('property without a type');
676
862
  const type = this.type(t);
@@ -686,51 +872,51 @@ export class Exporter {
686
872
  if (this.failure !== undefined) return -1;
687
873
  } else program = source;
688
874
  }
689
- const record = new PortableRecord(
690
- RecordKind.Property,
691
- -1,
692
- prop.flags,
693
- prop.checkFlags,
694
- prop.name,
695
- program,
696
- prop.valueDeclaration,
697
- [type, name, target],
698
- );
699
- record.nodes = getDeclarations(prop).slice();
700
- if (prop.parent !== undefined && !this.symbolOf(record, prop.parent, true)) return -1;
701
- if (program !== undefined && getSymbolId(program) > this.symbolLimit) return this.fail('symbol of the thread');
875
+ const declarations = getDeclarations(prop);
702
876
  if (!this.sharedNode(prop.valueDeclaration)) return -1;
703
- for (const node of record.nodes) if (!this.sharedNode(node)) return -1;
704
- const index = this.push(record);
705
- this.properties.set(prop, index);
706
- this.addedProperties.push(prop);
877
+ for (const node of declarations) if (!this.sharedNode(node)) return -1;
878
+ const index = this.nextIndex();
879
+ this.properties.set(id, index);
880
+ this.addedProperties.push(id);
881
+ if (!this.nameSymbol(program, false)) return -1;
882
+ const value = prop.valueDeclaration;
883
+ const valueIsFirst = value !== undefined && declarations.length !== 0 && declarations[0] === value;
884
+ this.node = value;
885
+ let bits =
886
+ (this.naming << RecordHeader.NamingShift) |
887
+ (valueIsFirst ? RecordHeader.ValueIsFirst : value !== undefined ? RecordHeader.HasNode : 0);
888
+ if (prop.parent !== undefined) {
889
+ if (!this.nameSymbol(prop.parent, true)) return -1;
890
+ bits |= RecordHeader.HasAlias | (this.aliasMerged >= 0 ? RecordHeader.AliasMerged : 0);
891
+ }
892
+ this.begin(RecordKind.Property, index, [type, name, target], bits);
893
+ const w = this.writer;
894
+ w.word(prop.flags);
895
+ w.word(prop.checkFlags);
896
+ w.text(prop.name);
897
+ w.word(declarations.length);
898
+ for (const node of declarations) w.node(node);
707
899
  return index;
708
900
  }
709
901
 
710
- tupleTarget(t: TupleType): number {
711
- const record = new PortableRecord(
712
- RecordKind.TupleTarget,
713
- t.id,
714
- 0,
715
- t.readonly ? 1 : 0,
716
- '',
717
- undefined,
718
- undefined,
719
- noNumbers,
720
- );
721
- const nums: number[] = [];
722
- const nodes: (Node | undefined)[] = [];
723
- for (const info of t.elementInfos) {
724
- nums.push(info.flags);
725
- nodes.push(info.labeledDeclaration);
902
+ private tupleTarget(t: TupleType): number {
903
+ const index = this.nextIndex();
904
+ this.memo(t, index);
905
+ this.begin(RecordKind.TupleTarget, index, noRefs, 0);
906
+ const w = this.writer;
907
+ w.word(t.readonly ? 1 : 0);
908
+ const infos = t.elementInfos;
909
+ w.word(infos.length);
910
+ for (const info of infos) {
911
+ if (!this.sharedNode(info.labeledDeclaration)) return -1;
912
+ w.word(info.flags);
913
+ w.node(info.labeledDeclaration);
726
914
  }
727
- record.nums = nums;
728
- record.nodes = nodes;
729
- return this.add(t, record);
915
+ return index;
730
916
  }
731
917
 
732
918
  /** An instantiated anonymous or mapped type, or a deferred type reference: its target and outer type arguments. */
733
- instantiation(t: ObjectType): number {
919
+ private instantiation(t: ObjectType): number {
734
920
  const c = this.c;
735
921
  const objectFlags = t.objectFlags;
736
922
  if ((objectFlags & ObjectFlags.SingleSignatureType) !== 0) return this.fail('single signature type');
@@ -746,7 +932,7 @@ export class Exporter {
746
932
  target = t.target as ObjectType;
747
933
  }
748
934
  if (declaration !== undefined && t.mapper === undefined && c.resolvedTypes.get(declaration.id) === t)
749
- return this.add(t, new PortableRecord(RecordKind.NodeType, t.id, 0, 0, '', undefined, declaration, noNumbers));
935
+ return this.nodeType(t, declaration);
750
936
  if (declaration === undefined || target === undefined || t.mapper === undefined)
751
937
  return this.fail('instantiation, declaration ' + (declaration?.kind ?? -1) + ' target ' + (target !== undefined));
752
938
  const typeParameters = c.nodeLinks(declaration).outerTypeParameters;
@@ -758,10 +944,24 @@ export class Exporter {
758
944
  return this.fail('instantiation not found');
759
945
  const refs = [this.type(target), ...this.types(args)];
760
946
  if (this.failure !== undefined) return -1;
761
- const record = new PortableRecord(RecordKind.Instantiation, t.id, 0, args.length, '', undefined, declaration, refs);
762
- const index = this.add(t, this.aliasOf(record, t.alias));
947
+ // The alias and its type arguments are exported before the record, as the record names them: a consumer builds
948
+ // records in index order, and would find them unbuilt after it.
949
+ const alias = t.alias;
950
+ const aliasRefs = alias === undefined ? noRefs : this.types(alias.typeArguments);
951
+ if (this.failure !== undefined) return -1;
952
+ if (!this.sharedNode(declaration)) return -1;
953
+ // The record is written after the return types of its signatures, which are built right after it: its index is
954
+ // lower than theirs.
955
+ const index = this.nextIndex();
956
+ this.memo(t, index);
763
957
  // Only for an export that hasn't failed (its alias may have): optionalType would forget that failure.
764
- if (index >= 0 && this.failure === undefined) this.resolvedReturnTypes(t, refs);
958
+ if (this.failure === undefined) this.resolvedReturnTypes(t, refs);
959
+ if (this.failure !== undefined) return -1;
960
+ const bits = this.aliasBits(alias, aliasRefs);
961
+ if (bits < 0) return -1;
962
+ this.node = declaration;
963
+ this.begin(RecordKind.Instantiation, index, refs, bits | RecordHeader.HasNode);
964
+ this.writer.word(args.length);
765
965
  return index;
766
966
  }
767
967
 
@@ -771,7 +971,7 @@ export class Exporter {
771
971
  * inference stops at references whose type ids grow along its stack (`isDeeplyNestedType`), so a consumer that
772
972
  * created them later, when it resolved them, could stop an inference this checker made.
773
973
  */
774
- resolvedReturnTypes(t: ObjectType, refs: number[]): void {
974
+ private resolvedReturnTypes(t: ObjectType, refs: number[]): void {
775
975
  if ((t.objectFlags & ObjectFlags.MembersResolved) === 0) return;
776
976
  for (const signatures of [t.callSignatures, t.constructSignatures])
777
977
  for (const signature of signatures) {
@@ -788,7 +988,10 @@ export class Exporter {
788
988
  */
789
989
  optionalType(t: Type): number {
790
990
  if (this.failure !== undefined) return -1;
791
- const records = this.records.length;
991
+ const w = this.writer;
992
+ const at = w.at;
993
+ const unitAt = w.unitAt;
994
+ const count = this.count;
792
995
  const types = this.addedTypes.length;
793
996
  const properties = this.addedProperties.length;
794
997
  const index = this.type(t);
@@ -797,16 +1000,23 @@ export class Exporter {
797
1000
  for (let i = properties; i < this.addedProperties.length; i++) this.properties.delete(this.addedProperties[i]!);
798
1001
  this.addedTypes.length = types;
799
1002
  this.addedProperties.length = properties;
800
- this.records.length = records;
1003
+ this.count = count;
1004
+ w.at = at;
1005
+ w.unitAt = unitAt;
1006
+ // A store that ran out stays out: the export can't be posted.
801
1007
  this.failure = undefined;
802
1008
  return -1;
803
1009
  }
804
1010
 
805
- conditional(t: ConditionalType): number {
1011
+ /**
1012
+ * The record of the conditional type `t`: the conditional type of a type node, or the instantiation of one with
1013
+ * outer type arguments; -1 if it can't be exported.
1014
+ */
1015
+ private conditional(t: ConditionalType): number {
806
1016
  const root = t.root;
807
1017
  if (t.mapper === undefined) {
808
1018
  if (this.c.resolvedTypes.get(root.node.id) !== t) return this.fail('conditional type');
809
- return this.add(t, new PortableRecord(RecordKind.NodeType, t.id, 0, 0, '', undefined, root.node, noNumbers));
1019
+ return this.nodeType(t, root.node);
810
1020
  }
811
1021
  const args: Type[] = [];
812
1022
  for (const tp of root.outerTypeParameters) args.push(getMappedType(tp, t.mapper));
@@ -818,38 +1028,50 @@ export class Exporter {
818
1028
  if (keyed !== undefined) {
819
1029
  const refs = this.types(keyed.typeArguments);
820
1030
  if (this.failure !== undefined) return -1;
821
- const record = new PortableRecord(
822
- RecordKind.Conditional,
823
- t.id,
824
- 0,
825
- keyed.tag,
826
- '',
827
- undefined,
828
- keyed.root.node,
829
- refs,
830
- );
831
- return this.add(t, this.aliasOf(record, keyed.alias));
1031
+ const alias = keyed.alias;
1032
+ const aliasRefs = alias === undefined ? noRefs : this.types(alias.typeArguments);
1033
+ if (this.failure !== undefined) return -1;
1034
+ const bits = this.aliasBits(alias, aliasRefs);
1035
+ if (bits < 0) return -1;
1036
+ if (!this.sharedNode(keyed.root.node)) return -1;
1037
+ this.node = keyed.root.node;
1038
+ const index = this.nextIndex();
1039
+ this.memo(t, index);
1040
+ this.begin(RecordKind.Conditional, index, refs, bits | RecordHeader.HasNode);
1041
+ this.writer.word(keyed.tag);
1042
+ return index;
832
1043
  }
833
1044
  const distributed = this.c.distributedConditionals?.get(t);
834
1045
  if (distributed === undefined) return this.fail('conditional instantiation not found');
835
1046
  const refs = this.types([...distributed.typeArguments, distributed.constituent]);
836
1047
  if (this.failure !== undefined) return -1;
837
- const record = new PortableRecord(
838
- RecordKind.DistributedConditional,
839
- t.id,
840
- 0,
841
- distributed.tag,
842
- '',
843
- undefined,
844
- root.node,
845
- refs,
846
- );
847
- return this.add(t, this.aliasOf(record, distributed.alias));
1048
+ const alias = distributed.alias;
1049
+ const aliasRefs = alias === undefined ? noRefs : this.types(alias.typeArguments);
1050
+ if (this.failure !== undefined) return -1;
1051
+ const bits = this.aliasBits(alias, aliasRefs);
1052
+ if (bits < 0) return -1;
1053
+ if (!this.sharedNode(root.node)) return -1;
1054
+ this.node = root.node;
1055
+ const index = this.nextIndex();
1056
+ this.memo(t, index);
1057
+ this.begin(RecordKind.DistributedConditional, index, refs, bits | RecordHeader.HasNode);
1058
+ this.writer.word(distributed.tag);
1059
+ return index;
848
1060
  }
849
1061
  const refs = this.types(args);
850
1062
  if (this.failure !== undefined) return -1;
851
- const record = new PortableRecord(RecordKind.Conditional, t.id, 0, tag, '', undefined, root.node, refs);
852
- return this.add(t, this.aliasOf(record, t.alias));
1063
+ const alias = t.alias;
1064
+ const aliasRefs = alias === undefined ? noRefs : this.types(alias.typeArguments);
1065
+ if (this.failure !== undefined) return -1;
1066
+ const bits = this.aliasBits(alias, aliasRefs);
1067
+ if (bits < 0) return -1;
1068
+ if (!this.sharedNode(root.node)) return -1;
1069
+ this.node = root.node;
1070
+ const index = this.nextIndex();
1071
+ this.memo(t, index);
1072
+ this.begin(RecordKind.Conditional, index, refs, bits | RecordHeader.HasNode);
1073
+ this.writer.word(tag);
1074
+ return index;
853
1075
  }
854
1076
  }
855
1077
 
@@ -866,66 +1088,149 @@ function lazyObjectFlags(): ObjectFlags {
866
1088
  }
867
1089
 
868
1090
  /** Translates records into a checker's own types; one translation per producer type. */
1091
+ const noTypes: Type[] = [];
1092
+
1093
+ /** A type alias whose symbol and type arguments are read: its declared type first, as instantiating it does. */
1094
+ function typeAliasOf(c: Checker, symbol: Symbol, refs: Type[]): TypeAlias {
1095
+ // An instantiation of an alias follows its declared type, which records the alias's type parameters.
1096
+ getDeclaredTypeOfSymbol(c, symbol);
1097
+ return new TypeAlias(symbol, refs);
1098
+ }
1099
+
1100
+ /** Translates the records of another checker's store into this checker's types, reading them where they are. */
869
1101
  export class Translator {
870
1102
  readonly c: Checker;
871
- readonly translated = new Map<number, Type>();
1103
+ readonly store: StoreReader;
872
1104
  readonly initial: Type[];
873
1105
  /** Set when a record has no counterpart here; the translation is then abandoned. */
874
1106
  failed = false;
875
1107
 
876
- constructor(c: Checker) {
1108
+ constructor(c: Checker, store: StoreReader) {
877
1109
  this.c = c;
1110
+ this.store = store;
878
1111
  this.initial = initialTypes(c);
879
1112
  }
880
1113
 
881
- /** A property symbol made as the producer made it. */
882
- property(r: PortableRecord, types: Type[], symbols: (Symbol | undefined)[]): Symbol {
1114
+ /**
1115
+ * The symbol of `node`, named by `how`: the symbol of the node itself, or its export symbol. The record names
1116
+ * symbols of the program this way, and a consumer that hasn't the symbol fails the translation.
1117
+ */
1118
+ private namedSymbol(node: Node, how: number): Symbol | undefined {
1119
+ const local = getSymbol(node);
1120
+ return how === SymbolNaming.Export ? local?.exportSymbol : local;
1121
+ }
1122
+
1123
+ /** Abandons the record being read: the translation fails, and a later one starts afresh. */
1124
+ private abandon(): undefined {
1125
+ this.failed = true;
1126
+ return undefined;
1127
+ }
1128
+
1129
+ /** A property symbol made as the producer made it; undefined if its record can't be read here. */
1130
+ property(index: number): Symbol | undefined {
883
1131
  const c = this.c;
884
- const prop = c.newSymbolWithCheckFlags(r.flags & ~SymbolFlags.Transient, r.text, r.num);
885
- setDeclarations(prop, r.nodes as Node[]);
886
- prop.valueDeclaration = r.node;
887
- if (r.aliasSymbol !== undefined || r.aliasMergedId >= 0)
888
- prop.parent = r.aliasMergedId >= 0 ? c.mergedSymbols.get(r.aliasMergedId)! : r.aliasSymbol;
1132
+ const store = this.store;
1133
+ const reader = store.reader;
1134
+ if (!reader.begin(index)) return this.abandon();
1135
+ const f = reader.numbers;
1136
+ const type = store.types.get(f[reader.refAt]!);
1137
+ if (type === undefined) return this.abandon();
1138
+ const nameAt = f[reader.refAt + 1]!;
1139
+ const targetAt = f[reader.refAt + 2]!;
1140
+ let parent: Symbol | undefined = undefined;
1141
+ if (reader.aliasNaming >= 0) {
1142
+ if (reader.aliasNaming === SymbolNaming.Merged) parent = c.mergedSymbols.get(reader.aliasMergedId);
1143
+ else {
1144
+ const node = reader.readNode(reader.aliasNodeStart);
1145
+ if (node === undefined) return this.abandon();
1146
+ parent = this.namedSymbol(node, reader.aliasNaming);
1147
+ }
1148
+ if (parent === undefined) return this.abandon();
1149
+ }
1150
+ let source: Symbol | undefined = undefined;
1151
+ if (reader.symbolNaming === SymbolNaming.Merged) source = c.mergedSymbols.get(reader.symbolMergedId);
1152
+ else if (reader.symbolNaming !== SymbolNaming.None) {
1153
+ const node = reader.readNode(reader.symbolNodeStart);
1154
+ if (node === undefined) return this.abandon();
1155
+ source = this.namedSymbol(node, reader.symbolNaming);
1156
+ }
1157
+ if (reader.symbolNaming !== SymbolNaming.None && source === undefined) return this.abandon();
1158
+ let value: Node | undefined = undefined;
1159
+ if (reader.nodeStart >= 0) {
1160
+ value = reader.readNode(reader.nodeStart);
1161
+ if (value === undefined) return this.abandon();
1162
+ }
1163
+ let p = reader.fields;
1164
+ const flags = f[p++]!;
1165
+ const checkFlags = f[p++]!;
1166
+ const text = reader.textAt(p);
1167
+ p += 2;
1168
+ const count = f[p++]!;
1169
+ const declarations: Node[] = [];
1170
+ for (let i = 0; i < count; i++) {
1171
+ const node = reader.readNode(p);
1172
+ p += reader.nodeWords(p);
1173
+ if (node === undefined) return this.abandon();
1174
+ declarations.push(node);
1175
+ }
1176
+ if ((reader.header & RecordHeader.ValueIsFirst) !== 0) value = declarations[0];
1177
+ const prop = c.newSymbolWithCheckFlags(flags & ~SymbolFlags.Transient, text, checkFlags);
1178
+ setDeclarations(prop, declarations);
1179
+ prop.valueDeclaration = value;
1180
+ if (parent !== undefined) prop.parent = parent;
889
1181
  const id = getSymbolId(prop);
890
- c.symbolTypes.set(id, types[r.refs[0]!]!);
891
- if (r.refs[1]! >= 0) c.symbolNameTypes.set(id, types[r.refs[1]!]!);
892
- const target = r.refs[2]! >= 0 ? symbols[r.refs[2]!] : r.symbol;
1182
+ c.symbolTypes.set(id, type);
1183
+ if (nameAt >= 0) {
1184
+ const nameType = store.types.get(nameAt);
1185
+ if (nameType === undefined) return this.abandon();
1186
+ c.symbolNameTypes.set(id, nameType);
1187
+ }
1188
+ const target = targetAt >= 0 ? store.symbols.get(targetAt) : source;
893
1189
  if (target !== undefined) c.symbolTargets.set(id, target);
894
1190
  return prop;
895
1191
  }
896
1192
 
897
- /** Sets the members of the `AnonymousObject` record a `CompleteObject` record completes. */
898
- complete(r: PortableRecord, types: Type[], symbols: (Symbol | undefined)[]): void {
1193
+ /** Sets the members of the `AnonymousObject` record a `CompleteObject` record completes; false if it can't read it. */
1194
+ complete(index: number): boolean {
899
1195
  const c = this.c;
900
- const t = types[r.num]!;
901
- if ((t.objectFlags & ObjectFlags.MembersResolved) !== 0) return;
1196
+ const store = this.store;
1197
+ const reader = store.reader;
1198
+ if (!reader.begin(index)) return false;
1199
+ const f = reader.numbers;
1200
+ const refAt = reader.refAt;
1201
+ let p = reader.fields;
1202
+ const shell = store.types.get(f[p++]!);
1203
+ if (shell === undefined) return false;
1204
+ if ((shell.objectFlags & ObjectFlags.MembersResolved) !== 0) return true;
902
1205
  const members = new Map<string, Symbol>();
903
- for (const i of r.refs) {
904
- const prop = symbols[i]!;
1206
+ for (let i = 0; i < reader.refCount; i++) {
1207
+ const prop = store.symbols.get(f[refAt + i]!);
1208
+ if (prop === undefined) return false;
905
1209
  members.set(prop.name, prop);
906
1210
  }
907
- const infos = r.indexInfos.map(
908
- (info) =>
909
- new IndexInfo(
910
- types[info.keyType]!,
911
- types[info.valueType]!,
912
- info.isReadonly,
913
- info.declaration,
914
- info.components as Node[],
915
- ),
916
- );
917
- setStructuredTypeMembers(c, t, members, [], [], infos);
918
- keepPropertiesOnly(c, t as StructuredType, members, scannedLiteralPropertiesLimit);
919
- }
920
-
921
- record(r: PortableRecord, types: Type[]): Type {
922
- if (r.id >= 0) {
923
- const known = this.translated.get(r.id);
924
- if (known !== undefined) return known;
1211
+ const count = f[p++]!;
1212
+ const infos: IndexInfo[] = [];
1213
+ for (let i = 0; i < count; i++) {
1214
+ const keyType = store.types.get(f[p]!);
1215
+ const valueType = store.types.get(f[p + 1]!);
1216
+ const isReadonly = f[p + 2]! === 1;
1217
+ p += 3;
1218
+ const declaration = reader.readNode(p);
1219
+ p += reader.nodeWords(p);
1220
+ const components: Node[] = [];
1221
+ const componentCount = f[p++]!;
1222
+ for (let j = 0; j < componentCount; j++) {
1223
+ const node = reader.readNode(p);
1224
+ p += reader.nodeWords(p);
1225
+ if (node === undefined) return false;
1226
+ components.push(node);
1227
+ }
1228
+ if (keyType === undefined || valueType === undefined) return false;
1229
+ infos.push(new IndexInfo(keyType, valueType, isReadonly, declaration, components));
925
1230
  }
926
- const t = this.build(r, types);
927
- if (r.id >= 0 && !this.failed) this.translated.set(r.id, t);
928
- return t;
1231
+ setStructuredTypeMembers(c, shell, members, [], [], infos);
1232
+ keepPropertiesOnly(c, shell as StructuredType, members, scannedLiteralPropertiesLimit);
1233
+ return true;
929
1234
  }
930
1235
 
931
1236
  /**
@@ -945,90 +1250,136 @@ export class Translator {
945
1250
  }
946
1251
  }
947
1252
 
948
- alias(r: PortableRecord, types: Type[]): TypeAlias | undefined {
949
- if (r.aliasSymbol === undefined && r.aliasMergedId < 0) return undefined;
950
- const symbol = r.aliasMergedId >= 0 ? this.c.mergedSymbols.get(r.aliasMergedId)! : r.aliasSymbol!;
951
- // An instantiation of an alias follows its declared type, which records the alias's type parameters.
952
- getDeclaredTypeOfSymbol(this.c, symbol);
953
- return new TypeAlias(
954
- symbol,
955
- r.aliasRefs.map((i) => types[i]!),
956
- );
957
- }
958
-
959
- symbol(r: PortableRecord): Symbol {
960
- return r.mergedId >= 0 ? this.c.mergedSymbols.get(r.mergedId)! : r.symbol!;
961
- }
962
-
963
- build(r: PortableRecord, types: Type[]): Type {
1253
+ /** The type record `index`; undefined if it or a record it needs can't be translated here. */
1254
+ record(index: number): Type | undefined {
964
1255
  const c = this.c;
1256
+ const store = this.store;
1257
+ const reader = store.reader;
1258
+ if (!reader.begin(index)) return this.abandon();
1259
+ const f = reader.numbers;
1260
+ const header = reader.header;
1261
+ const kind = reader.kind;
1262
+ const n = reader.refCount;
965
1263
  // An instantiation's refs end with the return types of its signatures, which are built after it.
966
- const own = r.kind === RecordKind.Instantiation ? r.refs.slice(0, 1 + r.num) : r.refs;
967
- const refs = own.map((i) => types[i]!);
968
- switch (r.kind) {
1264
+ const count = kind === RecordKind.Instantiation ? Math.min(n, 1 + f[reader.fields]!) : n;
1265
+ const refs = new Array<Type>(count);
1266
+ for (let i = 0; i < count; i++) {
1267
+ const t = store.types.get(f[reader.refAt + i]!);
1268
+ if (t === undefined) return this.abandon();
1269
+ refs[i] = t;
1270
+ }
1271
+ let aliasSymbol: Symbol | undefined = undefined;
1272
+ let aliasRefs: Type[] = noTypes;
1273
+ if (reader.aliasNaming >= 0) {
1274
+ if (reader.aliasNaming === SymbolNaming.Merged) {
1275
+ aliasSymbol = c.mergedSymbols.get(reader.aliasMergedId);
1276
+ if (aliasSymbol === undefined) return this.abandon();
1277
+ } else {
1278
+ const aliasNode = reader.readNode(reader.aliasNodeStart);
1279
+ if (aliasNode === undefined) return this.abandon();
1280
+ aliasSymbol = this.namedSymbol(aliasNode, reader.aliasNaming);
1281
+ if (aliasSymbol === undefined) return this.abandon();
1282
+ }
1283
+ const aliasCount = reader.aliasRefCount;
1284
+ const types = new Array<Type>(aliasCount);
1285
+ for (let i = 0; i < aliasCount; i++) {
1286
+ const t = store.types.get(f[reader.aliasRefAt + i]!);
1287
+ if (t === undefined) return this.abandon();
1288
+ types[i] = t;
1289
+ }
1290
+ aliasRefs = types;
1291
+ }
1292
+ let symbol: Symbol | undefined = undefined;
1293
+ if (reader.symbolNaming === SymbolNaming.Merged) {
1294
+ symbol = c.mergedSymbols.get(reader.symbolMergedId);
1295
+ if (symbol === undefined) return this.abandon();
1296
+ } else if (reader.symbolNaming !== SymbolNaming.None) {
1297
+ const symbolNode = reader.readNode(reader.symbolNodeStart);
1298
+ if (symbolNode === undefined) return this.abandon();
1299
+ symbol = this.namedSymbol(symbolNode, reader.symbolNaming);
1300
+ if (symbol === undefined) return this.abandon();
1301
+ }
1302
+ let node: Node | undefined = undefined;
1303
+ if (reader.nodeStart >= 0) {
1304
+ node = reader.readNode(reader.nodeStart);
1305
+ if (node === undefined) return this.abandon();
1306
+ }
1307
+ let p = reader.fields;
1308
+ switch (kind) {
969
1309
  case RecordKind.Initial:
970
- return this.initial[r.num]!;
971
- case RecordKind.Literal:
972
- if ((r.flags & TypeFlags.StringLiteral) !== 0) return getStringLiteralType(c, r.text);
973
- if ((r.flags & TypeFlags.NumberLiteral) !== 0) return getNumberLiteralType(c, r.num);
974
- return getBigIntLiteralType(c, r.text);
1310
+ return this.initial[f[p]!];
1311
+ case RecordKind.Literal: {
1312
+ const flags = f[p++]!;
1313
+ if ((flags & TypeFlags.NumberLiteral) !== 0) return getNumberLiteralType(c, reader.doubleAt(p));
1314
+ const text = reader.textAt(p);
1315
+ return (flags & TypeFlags.StringLiteral) !== 0 ? getStringLiteralType(c, text) : getBigIntLiteralType(c, text);
1316
+ }
975
1317
  case RecordKind.FreshLiteral:
976
1318
  return getFreshTypeOfLiteralType(c, refs[0]!);
977
1319
  case RecordKind.EnumLiteral:
978
1320
  case RecordKind.Declared:
979
- return getDeclaredTypeOfSymbol(c, this.symbol(r));
1321
+ return getDeclaredTypeOfSymbol(c, symbol!);
980
1322
  case RecordKind.ThisType:
981
- return (getDeclaredTypeOfSymbol(c, this.symbol(r)) as InterfaceType).thisType!;
1323
+ return (getDeclaredTypeOfSymbol(c, symbol!) as InterfaceType).thisType!;
982
1324
  case RecordKind.UniqueSymbol:
983
- return getESSymbolLikeTypeForNode(c, this.symbol(r).valueDeclaration!);
1325
+ return getESSymbolLikeTypeForNode(c, symbol!.valueDeclaration!);
984
1326
  case RecordKind.SymbolType:
985
- return getTypeOfSymbol(c, this.symbol(r));
1327
+ return getTypeOfSymbol(c, symbol!);
986
1328
  case RecordKind.NodeType: {
987
- const node = r.node!;
988
- const cached = c.resolvedTypes.get(node.id);
1329
+ const cached = c.resolvedTypes.get(node!.id);
989
1330
  if (cached !== undefined) return cached;
990
- this.aliasOfNode(node);
991
- if (this.failed) return c.errorType;
992
- const t = getTypeFromTypeNode(c, node);
993
- return c.resolvedTypes.get(node.id) ?? t;
1331
+ this.aliasOfNode(node!);
1332
+ if (this.failed) return undefined;
1333
+ const t = getTypeFromTypeNode(c, node!);
1334
+ return c.resolvedTypes.get(node!.id) ?? t;
994
1335
  }
995
1336
  case RecordKind.Reference:
996
1337
  return createTypeReference(c, refs[0]!, refs.slice(1));
997
1338
  case RecordKind.TupleTarget: {
1339
+ const readonly = f[p++]! === 1;
1340
+ const infosCount = f[p++]!;
998
1341
  const infos: TupleElementInfo[] = [];
999
- for (let i = 0; i < r.nums.length; i++) infos.push(new TupleElementInfo(r.nums[i]!, r.nodes[i]));
1000
- return getTupleTargetType(c, infos, r.num === 1);
1342
+ for (let i = 0; i < infosCount; i++) {
1343
+ const flags = f[p++]!;
1344
+ const label = reader.readNode(p);
1345
+ p += reader.nodeWords(p);
1346
+ infos.push(new TupleElementInfo(flags, label));
1347
+ }
1348
+ return getTupleTargetType(c, infos, readonly);
1001
1349
  }
1002
1350
  case RecordKind.Instantiation: {
1351
+ const args = f[p++]!;
1003
1352
  const target = refs[0]!;
1004
- const typeParameters = outerTypeParametersOf(c, r.node!, target);
1353
+ const typeParameters = outerTypeParametersOf(c, node!, target);
1005
1354
  return instantiateTypeWithAlias(
1006
1355
  c,
1007
1356
  target,
1008
- newTypeMapper(typeParameters, refs.slice(1, 1 + r.num)),
1009
- this.alias(r, types),
1357
+ newTypeMapper(typeParameters, refs.slice(1, 1 + args)),
1358
+ aliasSymbol === undefined ? undefined : typeAliasOf(c, aliasSymbol, aliasRefs),
1010
1359
  );
1011
1360
  }
1012
1361
  case RecordKind.Union: {
1013
- if (r.num === -2) return newUnionType(c, r.flags & ObjectFlags.None, refs);
1014
- const origin = r.num >= 0 ? types[r.num] : undefined;
1362
+ const flags = f[p++]!;
1363
+ const originAt = f[p++]!;
1364
+ if ((header & RecordHeader.Constituent) !== 0) return newUnionType(c, flags & ObjectFlags.None, refs);
1015
1365
  return getUnionTypeFromSortedList(
1016
1366
  c,
1017
1367
  refs,
1018
- r.flags & (ObjectFlags.PrimitiveUnion | ObjectFlags.ContainsIntersections),
1019
- this.alias(r, types),
1020
- origin,
1368
+ flags & (ObjectFlags.PrimitiveUnion | ObjectFlags.ContainsIntersections),
1369
+ aliasSymbol === undefined ? undefined : typeAliasOf(c, aliasSymbol, aliasRefs),
1370
+ originAt >= 0 ? store.types.get(originAt) : undefined,
1021
1371
  );
1022
1372
  }
1023
1373
  case RecordKind.Intersection: {
1024
- if (r.num === -2) return newIntersectionType(c, ObjectFlags.None, refs);
1025
- const alias = this.alias(r, types);
1374
+ const flags = f[p++]!;
1375
+ if ((header & RecordHeader.Constituent) !== 0) return newIntersectionType(c, ObjectFlags.None, refs);
1376
+ const alias = aliasSymbol === undefined ? undefined : typeAliasOf(c, aliasSymbol, aliasRefs);
1026
1377
  const hash = typeListHash(refs, alias, 0);
1027
1378
  const existing = c.intersectionTypes.get(hash, refs, alias, 0);
1028
1379
  if (existing !== undefined) return existing;
1029
1380
  const result = newIntersectionType(
1030
1381
  c,
1031
- (r.flags & ObjectFlags.IsConstrainedTypeVariable) | getPropagatingFlagsOfTypes(refs, TypeFlags.Nullable),
1382
+ (flags & ObjectFlags.IsConstrainedTypeVariable) | getPropagatingFlagsOfTypes(refs, TypeFlags.Nullable),
1032
1383
  refs,
1033
1384
  );
1034
1385
  result.alias = alias;
@@ -1036,54 +1387,71 @@ export class Translator {
1036
1387
  return result;
1037
1388
  }
1038
1389
  case RecordKind.Index:
1039
- return getIndexTypeEx(c, refs[0]!, r.num);
1390
+ return getIndexTypeEx(c, refs[0]!, f[p]!);
1040
1391
  case RecordKind.IndexedAccess:
1041
- return getIndexedAccessTypeEx(c, refs[0]!, refs[1]!, r.num, undefined, this.alias(r, types));
1392
+ return getIndexedAccessTypeEx(
1393
+ c,
1394
+ refs[0]!,
1395
+ refs[1]!,
1396
+ f[p]!,
1397
+ undefined,
1398
+ aliasSymbol === undefined ? undefined : typeAliasOf(c, aliasSymbol, aliasRefs),
1399
+ );
1042
1400
  case RecordKind.Conditional: {
1043
- this.aliasOfNode(r.node!);
1044
- if (this.failed) return c.errorType;
1045
- const declared = c.resolvedTypes.get(r.node!.id) ?? getTypeFromTypeNode(c, r.node!);
1401
+ const tag = f[p++]!;
1402
+ this.aliasOfNode(node!);
1403
+ if (this.failed) return undefined;
1404
+ const declared = c.resolvedTypes.get(node!.id) ?? getTypeFromTypeNode(c, node!);
1046
1405
  const root = (declared as ConditionalType).root;
1047
1406
  return getConditionalTypeInstantiation(
1048
1407
  c,
1049
1408
  declared as ConditionalType,
1050
1409
  newTypeMapper(root.outerTypeParameters, refs),
1051
- r.num === 1,
1052
- this.alias(r, types),
1410
+ tag === 1,
1411
+ aliasSymbol === undefined ? undefined : typeAliasOf(c, aliasSymbol, aliasRefs),
1053
1412
  );
1054
1413
  }
1055
1414
  case RecordKind.DistributedConditional: {
1056
- this.aliasOfNode(r.node!);
1057
- if (this.failed) return c.errorType;
1058
- const declared = (c.resolvedTypes.get(r.node!.id) ?? getTypeFromTypeNode(c, r.node!)) as ConditionalType;
1415
+ const tag = f[p++]!;
1416
+ this.aliasOfNode(node!);
1417
+ if (this.failed) return undefined;
1418
+ const declared = (c.resolvedTypes.get(node!.id) ?? getTypeFromTypeNode(c, node!)) as ConditionalType;
1059
1419
  const root = declared.root;
1060
1420
  const args = refs.slice(0, refs.length - 1);
1061
- const alias = this.alias(r, types);
1062
- getConditionalTypeInstantiation(c, declared, newTypeMapper(root.outerTypeParameters, args), r.num === 1, alias);
1063
- const member = c.distributedByKey?.get(
1064
- distributedKey(root.node.id, args, alias, r.num, refs[refs.length - 1]!),
1065
- );
1066
- if (member === undefined) this.failed = true;
1067
- return member ?? c.errorType;
1421
+ const alias = aliasSymbol === undefined ? undefined : typeAliasOf(c, aliasSymbol, aliasRefs);
1422
+ getConditionalTypeInstantiation(c, declared, newTypeMapper(root.outerTypeParameters, args), tag === 1, alias);
1423
+ const member = c.distributedByKey?.get(distributedKey(root.node.id, args, alias, tag, refs[refs.length - 1]!));
1424
+ if (member === undefined) return this.abandon();
1425
+ return member;
1426
+ }
1427
+ case RecordKind.Template: {
1428
+ const textsCount = f[p++]!;
1429
+ const texts: string[] = [];
1430
+ for (let i = 0; i < textsCount; i++) {
1431
+ texts.push(reader.textAt(p));
1432
+ p += 2;
1433
+ }
1434
+ return getTemplateLiteralType(c, texts, refs);
1068
1435
  }
1069
- case RecordKind.Template:
1070
- return getTemplateLiteralType(c, r.texts.slice(), refs);
1071
1436
  case RecordKind.StringMapping:
1072
- return getStringMappingType(c, this.symbol(r), refs[0]!);
1437
+ return getStringMappingType(c, symbol!, refs[0]!);
1073
1438
  case RecordKind.Substitution:
1074
1439
  return getSubstitutionType(c, refs[0]!, refs[1]!);
1075
1440
  case RecordKind.NoInfer:
1076
1441
  return getNoInferType(c, refs[0]!);
1077
- case RecordKind.AnonymousObject:
1078
- return newObjectType(c, r.flags | ObjectFlags.Anonymous, this.symbol(r));
1442
+ case RecordKind.AnonymousObject: {
1443
+ // A translation that failed after this record opened the type still made it, and a record built then may name
1444
+ // it: the completion must fill that type, not a new one beside it.
1445
+ const known = store.types.get(index);
1446
+ if (known !== undefined) return known;
1447
+ return newObjectType(c, f[p]! | ObjectFlags.Anonymous, symbol);
1448
+ }
1079
1449
  case RecordKind.CompleteObject:
1080
1450
  case RecordKind.Property:
1081
- return c.errorType;
1451
+ return this.abandon();
1082
1452
  }
1083
1453
  }
1084
1454
  }
1085
-
1086
- /** The outer type parameters an instantiation of `target` (declared at `declaration`) maps, as instantiation computes them. */
1087
1455
  function outerTypeParametersOf(c: Checker, declaration: Node, target: Type): Type[] {
1088
1456
  const known = c.nodeLinks(declaration).outerTypeParameters;
1089
1457
  if (known !== undefined) return known;
@@ -1129,9 +1497,6 @@ export function isSharedReturnSignature(c: Checker, sig: Signature): boolean {
1129
1497
  // file: it hands the file on to another checker (`forbidden`, `handoff`), which then computes them itself, as the
1130
1498
  // checker of a file always does.
1131
1499
 
1132
- /** Whether checker threads share results (TSC_TS_SHARE=1). */
1133
- export const shareThreads = process.env.TSC_TS_SHARE === '1';
1134
-
1135
1500
  /**
1136
1501
  * What a checker thread needs to share results with the others; posted in its `workerData` (a plain object: class
1137
1502
  * instances arrive without their class).
@@ -1139,6 +1504,11 @@ export const shareThreads = process.env.TSC_TS_SHARE === '1';
1139
1504
  export interface ThreadSharingData {
1140
1505
  readonly index: number;
1141
1506
  readonly count: number;
1507
+ /**
1508
+ * Whether this run's posts are cost-bounded (`RunConfig.bounded`): it posts only results another checker demanded
1509
+ * and its takes build a bounded number of records. A run with it off keeps Phase 1's rule.
1510
+ */
1511
+ readonly bounded: boolean;
1142
1512
  /**
1143
1513
  * For each file, by file index: -2 if no checker checks it, -1 if checkers check it in statement ranges, 0 until a
1144
1514
  * checker takes it, and 1 + k once checker k took it.
@@ -1149,6 +1519,12 @@ export interface ThreadSharingData {
1149
1519
  * checker took the file: they never check it, and hand its positions on (`handoff`).
1150
1520
  */
1151
1521
  readonly forbidden: SharedArrayBuffer;
1522
+ /**
1523
+ * For each file, by file index, the checkers (one bit each, the same shape as `forbidden`) that looked for a result
1524
+ * for one of its declarations and found none. A checker posts a result only for a declaration whose file one of
1525
+ * them reads (`ThreadSharing.demanded`).
1526
+ */
1527
+ readonly wanted: SharedArrayBuffer;
1152
1528
  /**
1153
1529
  * Positions of the order a checker handed on: a lock, their number, the number of checkers that may still hand
1154
1530
  * positions on, then the positions.
@@ -1161,17 +1537,45 @@ export interface ThreadSharingData {
1161
1537
  readonly symbolIds: number;
1162
1538
  /**
1163
1539
  * Each checker's store of the records of the results it shares, by checker index: their numbers, and the code units
1164
- * of their strings. `used` has two counters per checker, how much of each it wrote. (Results go through shared
1165
- * memory: a message a worker receives synchronously makes the native build store the number fields of classes as
1166
- * doubles.)
1540
+ * of their strings. `used` has two counters per checker, `usedStride` int32s apart, how much of each it wrote; a
1541
+ * checker keeps its cursors in its own writer and writes them there only for the summary. (Results go through
1542
+ * shared memory: a message a worker receives synchronously makes the native build store the number fields of
1543
+ * classes as doubles.)
1167
1544
  */
1168
1545
  readonly numberLogs: SharedArrayBuffer[];
1169
1546
  readonly stringLogs: SharedArrayBuffer[];
1170
1547
  readonly used: SharedArrayBuffer;
1171
- /** For each checker, where each record of its store is in its number log, and each object's completion. */
1548
+ /** For each checker, where each record of its store is in its number log: a word per record. */
1172
1549
  readonly recordTables: SharedArrayBuffer[];
1550
+ /**
1551
+ * One int32, read by every checker before it posts or translates a result: 0 runs, 1 stops posting, 2
1552
+ * stops posting and translating. The main thread sets it (`startParallelCheck`'s window) when the
1553
+ * results are not worth what they cost; every switch keeps the diagnostics the same, because a
1554
+ * checker that does not take a posted result computes it itself.
1555
+ */
1556
+ readonly control: SharedArrayBuffer;
1557
+ /**
1558
+ * One row of `shareStatColumns` doubles per checker, for the counters the main thread reads while
1559
+ * checking runs (`ThreadSharing.writeStats`).
1560
+ */
1561
+ readonly stats: SharedArrayBuffer;
1173
1562
  }
1174
1563
 
1564
+ /**
1565
+ * The columns of a checker's row of `ThreadSharingData.stats`: the milliseconds of its outermost
1566
+ * computations by kind (0 owned, 1 computed here for a file another checker took, 2 translated, 3
1567
+ * other), of exporting, translating and unpacking results, the results it posted and translated, and
1568
+ * the milliseconds it saved by translating a result instead of computing it.
1569
+ */
1570
+ export const shareStatColumns = 10;
1571
+ export const shareStatKinds = 4;
1572
+ export const shareStatExport = 4;
1573
+ export const shareStatTranslation = 5;
1574
+ export const shareStatUnpack = 6;
1575
+ export const shareStatPosted = 7;
1576
+ export const shareStatTranslated = 8;
1577
+ export const shareStatSaved = 9;
1578
+
1175
1579
  /**
1176
1580
  * The size of each checker's logs: numbers (int32s) and string code units (2 bytes each), 8 MB and 2 MB. Only the
1177
1581
  * pages a checker writes take memory.
@@ -1182,6 +1586,9 @@ export const stringLogSize = 1 << 20;
1182
1586
  /** The number of records each checker's store can hold. */
1183
1587
  export const recordTableSize = 1 << 20;
1184
1588
 
1589
+ /** The int32s each checker gets in `ThreadSharingData.used`, a cache line each, for two counters. */
1590
+ export const usedStride = 16;
1591
+
1185
1592
  /** The number of entries of each result table: a power of two. */
1186
1593
  export const resultTableSize = 1 << 20;
1187
1594
 
@@ -1216,6 +1623,11 @@ export function isForbidden(forbidden: Int32Array, count: number, file: number,
1216
1623
  return (Atomics.load(forbidden, file * forbiddenWords(count) + (index >>> 5)) & (1 << (index & 31))) !== 0;
1217
1624
  }
1218
1625
 
1626
+ /** Whether checker `index` of `count` looked for a result for a declaration of file `file` and found none (`wanted`). */
1627
+ export function isWanted(wanted: Int32Array, count: number, file: number, index: number): boolean {
1628
+ return (Atomics.load(wanted, file * forbiddenWords(count) + (index >>> 5)) & (1 << (index & 31))) !== 0;
1629
+ }
1630
+
1219
1631
  /**
1220
1632
  * An open-addressing table in a SharedArrayBuffer, from a key (a symbol or node id) to a result's state: 0 until a
1221
1633
  * checker posts it, then `postedState`.
@@ -1268,8 +1680,110 @@ export class ResultTable {
1268
1680
  }
1269
1681
  }
1270
1682
 
1271
- /** TSC_TS_SHARE_MIN_MS: the shortest computation a checker posts, in milliseconds (32 divided by the checkers by default). */
1683
+ /** TSC_TS_SHARE_MIN_MS: the shortest computation a checker posts, in milliseconds (`shareFloorTotalMs` split by the checkers by default). */
1272
1684
  const shareMinValue = process.env.TSC_TS_SHARE_MIN_MS;
1685
+
1686
+ /**
1687
+ * The floor, in milliseconds of checker time split over the checkers that share: 1 ms with 4 checkers, 0.25 ms with
1688
+ * 16.
1689
+ */
1690
+ const shareFloorTotalMs = 4;
1691
+
1692
+ /** `TSC_TS_SHARE_<name>` as a number, or `fallback`. */
1693
+ function shareNumber(name: string, fallback: number): number {
1694
+ const value = process.env[name];
1695
+ if (value === undefined) return fallback;
1696
+ const parsed = Number(value);
1697
+ return Number.isFinite(parsed) ? parsed : fallback;
1698
+ }
1699
+
1700
+ /**
1701
+ * The posting rule's cost model: a post appends at most `(kappa*T - p0)/e` records (`T` the computation's time), a
1702
+ * take builds at most `lambda*T/c`, `p0` is what a post costs itself, `e` each record a post adds and `c` each record
1703
+ * a take builds, all in microseconds. The price instrumentation's replay (`TSC_TS_SHARE_PRICE`) fits them; the
1704
+ * `TSC_TS_SHARE_*` variables override them for measurement.
1705
+ *
1706
+ * Read where they are used, not once at module evaluation: this module is in an import cycle (it imports
1707
+ * `checker-symbols.js`, which imports `checker.js`, which imports this module), and scriptc rejects code that runs at
1708
+ * module evaluation there (SC1016: a class the cycle reaches can be accessed before its declaration runs), so a
1709
+ * `const kappa = shareNumber(...)` at the top level does not build.
1710
+ */
1711
+ function shareKappa(): number {
1712
+ return shareNumber('TSC_TS_SHARE_KAPPA', 0.25);
1713
+ }
1714
+ function shareLambda(): number {
1715
+ return shareNumber('TSC_TS_SHARE_LAMBDA', 0.3);
1716
+ }
1717
+ function sharePostUs(): number {
1718
+ return shareNumber('TSC_TS_SHARE_P0_US', 0.3);
1719
+ }
1720
+ function shareExportRecordUs(): number {
1721
+ return shareNumber('TSC_TS_SHARE_E_US', 1.2);
1722
+ }
1723
+ function shareBuildRecordUs(): number {
1724
+ return shareNumber('TSC_TS_SHARE_C_US', 6.59);
1725
+ }
1726
+ /**
1727
+ * Whether a run posts cost-bounded is its configuration's choice (`RunConfig.bounded`), not a rule here: off by
1728
+ * default, because a bounded post costs a run that gains nothing by it (T3 server's c16 check was 8-9% slower),
1729
+ * and turned on where the project's own runs measured it faster (`run-config.ts`). A bounded run keeps the 2-8 ms
1730
+ * floor's sharing gate, posts only results another checker demanded, and budgets both the records a post adds and
1731
+ * the records a take builds; an unbounded run keeps Phase 1's rule and posts every result over the floor.
1732
+ */
1733
+
1734
+ /** The floor's total in milliseconds before the cost-bounded rule (`shareFloorTotalMs`): 2 ms at 16, 8 ms at 4. */
1735
+ const shareFloorTotalMsPhase1 = 32;
1736
+
1737
+ /**
1738
+ * The rule that decides whether a checker posts a result it computed, and how much a consumer may build to take one.
1739
+ * The conditions that keep a take exact (the result's diagnostics, `forbidsLast`, the resolution stack) are checked
1740
+ * by the callers, as are the stop switch and the demand bits.
1741
+ */
1742
+ export interface PostingRule {
1743
+ /**
1744
+ * Whether a checker that just computed the result keyed `key` of a declaration in file `fileIndex` may post it, in
1745
+ * `ms`, with `diagnosticFiles` the files its diagnostics are in. The caller checks first that the computation met
1746
+ * no resolution cycle, that the stop switch is off, that (when the run is cost-bounded) another checker demanded the
1747
+ * file (`demanded`) and that no result for the key is posted.
1748
+ */
1749
+ posts(
1750
+ sharing: ThreadSharing,
1751
+ key: number,
1752
+ ms: number,
1753
+ diagnosticFiles: readonly number[],
1754
+ fileIndex: number,
1755
+ ): boolean;
1756
+
1757
+ /**
1758
+ * The most records a consumer may build to take a posted result whose producer computed it in `ms` milliseconds: 0
1759
+ * admits only takes that build nothing, because every record they need is in the consumer's caches already.
1760
+ */
1761
+ buildBudget(ms: number): number;
1762
+ }
1763
+
1764
+ /**
1765
+ * The rule of the design's Phase 2: a result is posted when it took at least the floor (`ThreadSharing.minMs`), and a
1766
+ * consumer builds at most `lambda*T/c` records to take one, where `T` is the producer's time for it.
1767
+ */
1768
+ export class CostBoundedRule implements PostingRule {
1769
+ /** `bounded` false is Phase 1's rule: the same floor, and a take that may build any number of records. */
1770
+ constructor(readonly bounded: boolean) {}
1771
+
1772
+ posts(
1773
+ sharing: ThreadSharing,
1774
+ _key: number,
1775
+ ms: number,
1776
+ _diagnosticFiles: readonly number[],
1777
+ _fileIndex: number,
1778
+ ): boolean {
1779
+ return ms >= sharing.minMs;
1780
+ }
1781
+
1782
+ buildBudget(ms: number): number {
1783
+ return this.bounded ? Math.floor((shareLambda() * ms * 1000) / shareBuildRecordUs()) : Number.POSITIVE_INFINITY;
1784
+ }
1785
+ }
1786
+
1273
1787
  // Debugging: TSC_TS_SHARE_BLOCK and TSC_TS_SHARE_ALLOW name parts of file names (comma-separated, each optionally with
1274
1788
  // a range of declaration positions, `name@start-end`) whose results are never or only translated, and
1275
1789
  // TSC_TS_SHARE_TRACE=1 prints each translation.
@@ -1277,7 +1791,8 @@ const shareBlockValue = process.env.TSC_TS_SHARE_BLOCK;
1277
1791
  const shareAllowValue = process.env.TSC_TS_SHARE_ALLOW;
1278
1792
  const shareTraceValue = process.env.TSC_TS_SHARE_TRACE;
1279
1793
  // Debugging: TSC_TS_SHARE_VERIFY=1 computes each result a checker could translate, translates it after, and prints
1280
- // the results whose translation prints differently.
1794
+ // the results whose translation prints differently; =2 checks that a translation creates types record by record, in
1795
+ // the order the producer created them.
1281
1796
  const shareVerifyValue = process.env.TSC_TS_SHARE_VERIFY;
1282
1797
 
1283
1798
  /** Checks a translation against the computation it replaces (TSC_TS_SHARE_VERIFY). */
@@ -1395,17 +1910,18 @@ export class IdBits {
1395
1910
 
1396
1911
  /**
1397
1912
  * The files of the diagnostics of results, by key (a symbol or declaration id), with a bit per key: most results have
1398
- * none, and the bit answers that without a lookup.
1913
+ * none, and the bit answers that without a lookup. A file is its index in the program (`Checker.fileIndex`), and -1
1914
+ * a diagnostic without a file.
1399
1915
  */
1400
1916
  export class FilesByKey {
1401
- private readonly files = new Map<number, readonly string[]>();
1917
+ private readonly files = new Map<number, readonly number[]>();
1402
1918
  private readonly keys = new IdBits();
1403
1919
 
1404
- get(key: number): readonly string[] | undefined {
1920
+ get(key: number): readonly number[] | undefined {
1405
1921
  return this.keys.has(key) ? this.files.get(key) : undefined;
1406
1922
  }
1407
1923
 
1408
- set(key: number, files: readonly string[]): void {
1924
+ set(key: number, files: readonly number[]): void {
1409
1925
  this.keys.add(key);
1410
1926
  this.files.set(key, files);
1411
1927
  }
@@ -1418,7 +1934,19 @@ export class ThreadSharing {
1418
1934
  readonly returns: ResultTable;
1419
1935
  /** TSC_TS_SHARE_MIN_MS: the shortest computation this checker posts. */
1420
1936
  readonly minMs: number;
1421
- readonly initialIndexes = new Map<Type, number>();
1937
+ /**
1938
+ * Whether this run is cost-bounded, which the run's configuration chose (`RunConfig.bounded`, off by default and
1939
+ * learned from the project's records). Without it the run keeps Phase 1's behavior: no demand bits, no export or
1940
+ * take budget, the 2-8 ms floor.
1941
+ */
1942
+ readonly bounded: boolean;
1943
+ /**
1944
+ * The rule that decides which of this checker's results are posted, and how much a take may build. A small-result
1945
+ * tier stacks on it by constructing its own rule here. Built per checker, not once at module evaluation, for the
1946
+ * reason the cost model's functions give (`shareKappa`).
1947
+ */
1948
+ readonly rule: PostingRule;
1949
+ readonly initialIndexes = new Map<number, number>();
1422
1950
  readonly initial: Type[];
1423
1951
  readonly numberLogs: Int32Array[] = [];
1424
1952
  readonly recordTables: Int32Array[] = [];
@@ -1426,9 +1954,26 @@ export class ThreadSharing {
1426
1954
  readonly used: Int32Array;
1427
1955
  readonly takers: Int32Array;
1428
1956
  readonly forbidden: Int32Array;
1957
+ /** The checkers that missed on a key in each file (`ThreadSharingData.wanted`). */
1958
+ readonly wanted: Int32Array;
1959
+ /** The files this checker noted a miss on, so that each is marked in `wanted` once. */
1960
+ private readonly wantedFiles = new IdBits();
1961
+ /** The stop switch of every checker (`ThreadSharingData.control`). */
1962
+ readonly control: Int32Array;
1963
+ /** This checker's row of the counters the main thread reads (`ThreadSharingData.stats`). */
1964
+ readonly stats: Float64Array;
1965
+ readonly row: number;
1429
1966
  /** The number of results this checker posted. */
1430
1967
  posted = 0;
1431
1968
  translated = 0;
1969
+ /**
1970
+ * Set while this checker exports a result into its store. A computation nested in the export walk can reach
1971
+ * another shared result: posting it would write its records in the middle of the record being exported, and an
1972
+ * export that failed would rewind over the posted result's words.
1973
+ */
1974
+ exporting = false;
1975
+ /** Shared results a nested computation left to the checkers that compute them. */
1976
+ nested = 0;
1432
1977
  /** Outermost shared-kind computations: nesting depth, the kind of the current one, and the time by kind. */
1433
1978
  depth = 0;
1434
1979
  kind = 0;
@@ -1438,6 +1983,33 @@ export class ThreadSharing {
1438
1983
  addOutermost(ms: number): void {
1439
1984
  this.kindMs[this.kind]! += ms;
1440
1985
  this.kind = 3;
1986
+ this.writeStats();
1987
+ }
1988
+
1989
+ /**
1990
+ * Copies this checker's counters into its row of `stats`, the memory the main thread reads them from
1991
+ * while checking runs. Called where a counter changes, a few times per result at most.
1992
+ */
1993
+ writeStats(): void {
1994
+ const s = this.stats;
1995
+ const row = this.row;
1996
+ for (let k = 0; k < shareStatKinds; k++) s[row + k] = this.kindMs[k]!;
1997
+ s[row + shareStatExport] = this.exportMs;
1998
+ s[row + shareStatTranslation] = this.translationMs;
1999
+ // A translation reads its records in place, so there is no separate unpacking time: shareStatUnpack stays 0.
2000
+ s[row + shareStatPosted] = this.posted;
2001
+ s[row + shareStatTranslated] = this.translated;
2002
+ }
2003
+
2004
+ /** Adds to the milliseconds this checker saved by translating results instead of computing them. */
2005
+ addSaved(ms: number): void {
2006
+ const at = this.row + shareStatSaved;
2007
+ this.stats[at] = this.stats[at]! + ms;
2008
+ }
2009
+
2010
+ /** Whether the main thread stopped translating posted results (control 2): this checker computes them. */
2011
+ stoppedTranslating(): boolean {
2012
+ return Atomics.load(this.control, 0) === 2;
1441
2013
  }
1442
2014
 
1443
2015
  notShareable = 0;
@@ -1449,17 +2021,26 @@ export class ThreadSharing {
1449
2021
  verifiedIdentity = 0;
1450
2022
  belowMin = 0;
1451
2023
  belowMinMs = 0;
2024
+ /** Results whose export passed the budget `kappa*T`, and translations that stopped at `lambda*T/c`. */
2025
+ overBudget = 0;
2026
+ overBudgetMs = 0;
2027
+ stoppedTranslations = 0;
2028
+ /** Results not posted because no checker that may take one missed on a key in the declaration's file. */
2029
+ notDemanded = 0;
1452
2030
  exportFailed = 0;
1453
2031
  exportFailedMs = 0;
1454
2032
  readonly exportFailures = new Map<string, number>();
1455
- packFailed = 0;
1456
- /** Of `packFailed`, the results that didn't fit in this checker's logs. */
2033
+ /** TSC_TS_SHARE_VERIFY=2: records whose translation created types before an earlier record's. */
2034
+ orderViolations = 0;
2035
+ storeFailed = 0;
2036
+ /** Of `storeFailed`, the results that didn't fit in this checker's logs. */
1457
2037
  logFull = 0;
1458
2038
  computedPrivately = 0;
2039
+ /** Results this checker computed and kept because the main thread stopped sharing (control 1 or 2). */
2040
+ stopped = 0;
1459
2041
  /** How often a posted result couldn't be taken here; and results for declarations of the checker's own files. */
1460
2042
  sawDoneUnusable = 0;
1461
2043
  owned = 0;
1462
- unpackMs = 0;
1463
2044
  /** The program's nodes results named, by id. */
1464
2045
  readonly nodesById = new Map<number, Node>();
1465
2046
  readonly origins = new MergedOrigins();
@@ -1487,15 +2068,15 @@ export class ThreadSharing {
1487
2068
  readonly typeFiles = new FilesByKey();
1488
2069
  readonly returnFiles = new FilesByKey();
1489
2070
  /** The files of the computations in progress, innermost last. */
1490
- readonly fileStack: (Set<string> | undefined)[] = [];
2071
+ readonly fileStack: (Set<number> | undefined)[] = [];
1491
2072
 
1492
2073
  /** Adds `files` to those of the computation in progress. */
1493
- noteFiles(files: readonly string[] | undefined): void {
2074
+ noteFiles(files: readonly number[] | undefined): void {
1494
2075
  const stack = this.fileStack;
1495
2076
  if (files === undefined || stack.length === 0) return;
1496
2077
  let top = stack[stack.length - 1];
1497
2078
  if (top === undefined) {
1498
- top = new Set<string>();
2079
+ top = new Set<number>();
1499
2080
  stack[stack.length - 1] = top;
1500
2081
  }
1501
2082
  for (const f of files) top.add(f);
@@ -1531,18 +2112,18 @@ export class ThreadSharing {
1531
2112
  } finally {
1532
2113
  let own = this.fileStack.pop();
1533
2114
  for (let i = diagnosticsBefore; i < diagnostics.length; i++) {
1534
- if (own === undefined) own = new Set<string>();
2115
+ if (own === undefined) own = new Set<number>();
1535
2116
  const file = diagnostics[i]!.file;
1536
- own.add(file === undefined ? '' : file.fileName);
2117
+ own.add(file === undefined ? -1 : this.fileIndexOfName(file.fileName));
1537
2118
  }
1538
2119
  for (let i = suggestionsBefore; i < suggestions.length; i++) {
1539
- if (own === undefined) own = new Set<string>();
2120
+ if (own === undefined) own = new Set<number>();
1540
2121
  const file = suggestions[i]!.file;
1541
- own.add(file === undefined ? '' : file.fileName);
2122
+ own.add(file === undefined ? -1 : this.fileIndexOfName(file.fileName));
1542
2123
  }
1543
2124
  // A deferred diagnostic is reported where its file is checked, as the diagnostics it reported are.
1544
2125
  for (let i = deferredBefore; i < deferred.length; i++) {
1545
- if (own === undefined) own = new Set<string>();
2126
+ if (own === undefined) own = new Set<number>();
1546
2127
  own.add(deferred[i]!);
1547
2128
  }
1548
2129
  if (own !== undefined) {
@@ -1553,6 +2134,23 @@ export class ThreadSharing {
1553
2134
  }
1554
2135
  }
1555
2136
 
2137
+ /**
2138
+ * The index of the program file named `fileName`, or -1: a diagnostic in a file this checker hasn't is one every
2139
+ * checker would report, like a diagnostic without a file.
2140
+ */
2141
+ fileIndexOfName(fileName: string): number {
2142
+ let files = this.filesByIndex;
2143
+ if (files === undefined) {
2144
+ files = new Map<string, number>();
2145
+ for (const [file, index] of this.c.fileIndex) files.set(file.fileName, index);
2146
+ this.filesByIndex = files;
2147
+ }
2148
+ return files.get(fileName) ?? -1;
2149
+ }
2150
+
2151
+ /** The index of each file by name, built on first use. */
2152
+ private filesByIndex: Map<string, number> | undefined = undefined;
2153
+
1556
2154
  constructor(c: Checker, data: ThreadSharingData) {
1557
2155
  this.c = c;
1558
2156
  // `tracked` reads the diagnostics a computation added, and deferred, from these lists.
@@ -1560,16 +2158,26 @@ export class ThreadSharing {
1560
2158
  c.suggestionDiagnostics.added = [];
1561
2159
  c.deferredDiagnosticFiles = [];
1562
2160
  this.data = data;
2161
+ this.bounded = data.bounded;
2162
+ this.rule = new CostBoundedRule(this.bounded);
1563
2163
  this.used = new Int32Array(data.used);
1564
2164
  this.takers = new Int32Array(data.takers);
1565
2165
  this.forbidden = new Int32Array(data.forbidden);
2166
+ this.wanted = new Int32Array(data.wanted);
2167
+ this.control = new Int32Array(data.control);
2168
+ this.stats = new Float64Array(data.stats);
2169
+ this.row = data.index * shareStatColumns;
1566
2170
  this.types = new ResultTable(data.typeTable);
1567
2171
  this.returns = new ResultTable(data.returnTable);
1568
- // A posted result pays when enough checkers need it, so the threshold falls as checkers are added: 8 ms with 4
1569
- // checkers, 2 ms with 16.
1570
- this.minMs = shareMinValue === undefined ? 32 / data.count : Number(shareMinValue);
2172
+ // The floor, lowered with the rest of the 2-8 ms threshold band when posting is cost-bounded: a posted result pays
2173
+ // when enough checkers need it, so it falls as checkers are added, to 0.25 ms with 16. Below the bounded count the
2174
+ // run keeps the 2-8 ms band, where the posts the extra floor would add are what the c4 gate measured as a cost.
2175
+ this.minMs =
2176
+ shareMinValue === undefined
2177
+ ? (this.bounded ? shareFloorTotalMs : shareFloorTotalMsPhase1) / data.count
2178
+ : Number(shareMinValue);
1571
2179
  this.initial = initialTypes(c);
1572
- for (let i = 0; i < this.initial.length; i++) this.initialIndexes.set(this.initial[i]!, i);
2180
+ for (let i = 0; i < this.initial.length; i++) this.initialIndexes.set(this.initial[i]!.id, i);
1573
2181
  for (let k = 0; k < data.count; k++) {
1574
2182
  this.numberLogs.push(new Int32Array(data.numberLogs[k]!));
1575
2183
  this.recordTables.push(new Int32Array(data.recordTables[k]!));
@@ -1580,9 +2188,16 @@ export class ThreadSharing {
1580
2188
  }
1581
2189
 
1582
2190
  summary(): string {
2191
+ // The stores are written through a local cursor; the shared counters are only for this summary.
2192
+ const writer = this.storeExporter?.writer;
2193
+ const me = this.data.index;
2194
+ if (writer !== undefined) {
2195
+ this.used[usedStride * me] = writer.at;
2196
+ this.used[usedStride * me + 1] = writer.unitAt;
2197
+ }
1583
2198
  return (
1584
2199
  'checker ' +
1585
- this.data.index +
2200
+ me +
1586
2201
  ': outermost ms owned/computed here/translated/other ' +
1587
2202
  this.kindMs.map((v) => v.toFixed(0)).join('/') +
1588
2203
  '; posted ' +
@@ -1593,15 +2208,15 @@ export class ThreadSharing {
1593
2208
  this.translated +
1594
2209
  ' (' +
1595
2210
  this.translationMs.toFixed(0) +
1596
- ' ms, unpacking ' +
1597
- this.unpackMs.toFixed(0) +
1598
2211
  ' ms), export ' +
1599
2212
  this.exportMs.toFixed(0) +
1600
2213
  ' ms, computed here ' +
1601
2214
  this.computedPrivately +
1602
2215
  ' (' +
1603
2216
  this.sawDoneUnusable +
1604
- ' posted but not taken), owned ' +
2217
+ ' posted but not taken, ' +
2218
+ this.stopped +
2219
+ ' after the stop switch), owned ' +
1605
2220
  this.owned +
1606
2221
  '; not posted: cycles ' +
1607
2222
  this.notShareable +
@@ -1611,7 +2226,15 @@ export class ThreadSharing {
1611
2226
  this.belowMin +
1612
2227
  ' (' +
1613
2228
  this.belowMinMs.toFixed(0) +
1614
- ' ms), not exported ' +
2229
+ ' ms), not demanded ' +
2230
+ this.notDemanded +
2231
+ ', over the export budget ' +
2232
+ this.overBudget +
2233
+ ' (' +
2234
+ this.overBudgetMs.toFixed(0) +
2235
+ ' ms), takes stopped at their budget ' +
2236
+ this.stoppedTranslations +
2237
+ ', not exported ' +
1615
2238
  this.exportFailed +
1616
2239
  ' (' +
1617
2240
  this.exportFailedMs.toFixed(0) +
@@ -1621,16 +2244,21 @@ export class ThreadSharing {
1621
2244
  .slice(0, 4)
1622
2245
  .map(([k, v]) => k + ' ' + v.toFixed(0))
1623
2246
  .join('; ') +
1624
- '), not packed ' +
1625
- this.packFailed +
2247
+ '), in an export ' +
2248
+ this.nested +
2249
+ ', not stored ' +
2250
+ this.storeFailed +
1626
2251
  ' (logs full ' +
1627
2252
  this.logFull +
1628
2253
  ', used ' +
1629
- this.used[2 * this.data.index] +
2254
+ this.used[usedStride * me] +
1630
2255
  ' numbers, ' +
1631
- this.used[2 * this.data.index + 1] +
2256
+ this.used[usedStride * me + 1] +
1632
2257
  ' code units)' +
1633
- (shareVerifyValue === '1' ? '; verified ' + this.verified + ' (' + this.verifiedIdentity + ' equal as text)' : '')
2258
+ (shareVerifyValue === '1'
2259
+ ? '; verified ' + this.verified + ' (' + this.verifiedIdentity + ' equal as text)'
2260
+ : '') +
2261
+ (shareVerifyValue === '2' ? '; order violations ' + this.orderViolations : '')
1634
2262
  );
1635
2263
  }
1636
2264
 
@@ -1641,16 +2269,57 @@ export class ThreadSharing {
1641
2269
  */
1642
2270
  owns(file: SourceFile): boolean {
1643
2271
  const index = this.c.fileIndex.get(file);
1644
- if (index === undefined) return true;
2272
+ return index === undefined || this.ownsIndex(index);
2273
+ }
2274
+
2275
+ /** Whether this checker checks file `index`, or may; -1 names a diagnostic without a file, which it has. */
2276
+ ownsIndex(index: number): boolean {
2277
+ if (index < 0) return true;
1645
2278
  const taker = Atomics.load(this.takers, index);
1646
2279
  if (taker === 0) return false;
1647
2280
  return taker !== -2 && (taker < 0 || taker === this.data.index + 1);
1648
2281
  }
1649
2282
 
2283
+ /**
2284
+ * Notes that this checker looked for a result for a declaration in `file` and found none: a checker posts results
2285
+ * for that file only then (`demanded`). One `Atomics.or` per file and checker.
2286
+ */
2287
+ want(file: SourceFile): void {
2288
+ if (!this.bounded) return;
2289
+ const index = this.c.fileIndex.get(file);
2290
+ if (index === undefined || this.wantedFiles.has(index)) return;
2291
+ this.wantedFiles.add(index);
2292
+ const me = this.data.index;
2293
+ Atomics.or(this.wanted, index * forbiddenWords(this.data.count) + (me >>> 5), 1 << (me & 31));
2294
+ }
2295
+
2296
+ /**
2297
+ * Whether a checker other than this one and the file's taker missed on a key in file `index`: someone other than
2298
+ * the checkers that already compute its declarations would take a result for one of them.
2299
+ */
2300
+ demanded(index: number): boolean {
2301
+ const count = this.data.count;
2302
+ const me = this.data.index;
2303
+ const words = forbiddenWords(count);
2304
+ const taker = Atomics.load(this.takers, index);
2305
+ const base = index * words;
2306
+ for (let w = 0; w < words; w++) {
2307
+ let bits = Atomics.load(this.wanted, base + w);
2308
+ if (w === me >>> 5) bits &= ~(1 << (me & 31));
2309
+ if (taker > 0 && w === (taker - 1) >>> 5) bits &= ~(1 << ((taker - 1) & 31));
2310
+ if (bits !== 0) return true;
2311
+ }
2312
+ return false;
2313
+ }
2314
+
1650
2315
  /** After taking a result for a declaration in `file`: if no checker took the file yet, this one never will. */
1651
2316
  forbid(file: SourceFile): void {
1652
2317
  const index = this.c.fileIndex.get(file);
1653
- if (index === undefined) return;
2318
+ if (index !== undefined) this.forbidIndex(index);
2319
+ }
2320
+
2321
+ forbidIndex(index: number): void {
2322
+ if (index < 0) return;
1654
2323
  const me = this.data.index;
1655
2324
  if (Atomics.load(this.takers, index) === 0)
1656
2325
  Atomics.or(this.forbidden, index * forbiddenWords(this.data.count) + (me >>> 5), 1 << (me & 31));
@@ -1675,42 +2344,15 @@ export class ThreadSharing {
1675
2344
  return true;
1676
2345
  }
1677
2346
 
1678
- forbidsLastNamed(fileName: string): boolean {
1679
- this.ownsFileNamed(fileName);
1680
- const file = this.filesByName!.get(fileName);
1681
- const index = file === undefined ? undefined : this.c.fileIndex.get(file);
1682
- return index !== undefined && this.forbidsLast(index);
1683
- }
1684
-
1685
- forbidNamed(fileName: string): void {
1686
- this.ownsFileNamed(fileName);
1687
- const file = this.filesByName!.get(fileName);
1688
- if (file !== undefined) this.forbid(file);
1689
- }
1690
-
1691
- filesByName: Map<string, SourceFile> | undefined = undefined;
1692
-
1693
- ownsFileNamed(fileName: string): boolean {
1694
- let files = this.filesByName;
1695
- if (files === undefined) {
1696
- files = new Map<string, SourceFile>();
1697
- for (const file of this.c.files) files.set(file.fileName, file);
1698
- this.filesByName = files;
1699
- }
1700
- const file = files.get(fileName);
1701
- return file === undefined || this.owns(file);
1702
- }
1703
-
1704
- /** The result checker `k` posted at `offset` in its number log. */
2347
+ /** The result checker `k` posted at `offset` in its number log: the record of its type, and the files it named. */
1705
2348
  result(k: number, offset: number): StoredResult {
1706
2349
  const numbers = this.numberLogs[k]!;
1707
- const strings = this.stringLogs[k]!;
1708
2350
  const root = numbers[offset]!;
1709
- const count = numbers[offset + 1]!;
1710
- const files: string[] = [];
1711
- for (let i = 0; i < count; i++)
1712
- files.push(readText(strings, numbers[offset + 2 + 2 * i]!, numbers[offset + 3 + 2 * i]!));
1713
- return new StoredResult(root, files);
2351
+ const computeMs = numbers[offset + 1]! / 1000;
2352
+ const count = numbers[offset + 2]!;
2353
+ const files: number[] = [];
2354
+ for (let i = 0; i < count; i++) files.push(numbers[offset + 3 + i]!);
2355
+ return new StoredResult(root, files, computeMs);
1714
2356
  }
1715
2357
 
1716
2358
  /** The exporter of this checker's store: it exports each type once. */
@@ -1721,66 +2363,66 @@ export class ThreadSharing {
1721
2363
  * tables, -1 if it doesn't fit in the logs or a node or symbol in it can't be named across threads, or why it can't
1722
2364
  * be exported.
1723
2365
  */
1724
- post(t: Type, diagnosticFiles: readonly string[]): number | string {
2366
+ post(t: Type, diagnosticFiles: readonly number[], computeMs: number): number | string {
1725
2367
  const c = this.c;
2368
+ const me = this.data.index;
1726
2369
  let exporter = this.storeExporter;
1727
2370
  if (exporter === undefined) {
1728
- exporter = new Exporter(c, this.initialIndexes);
2371
+ const writer = new StoreWriter(this.numberLogs[me]!, this.stringLogs[me]!, this.recordTables[me]!, c.fileIndex);
2372
+ exporter = new Exporter(c, this.initialIndexes, writer);
1729
2373
  exporter.nodeLimit = this.data.nodeIds;
1730
2374
  exporter.symbolLimit = this.data.symbolIds;
1731
2375
  exporter.originsCache = this.origins;
1732
2376
  this.storeExporter = exporter;
1733
2377
  }
2378
+ // The export budget: a post appends at most `(kappa*T - p0)/e` records, so it costs at most `kappa*T` even when
2379
+ // the result is over it, and an over-budget result is not posted.
2380
+ if (this.bounded) {
2381
+ const maxRecords = Math.floor((shareKappa() * computeMs * 1000 - sharePostUs()) / shareExportRecordUs());
2382
+ if (maxRecords < 0) return budgetFailure;
2383
+ exporter.limit = exporter.base + maxRecords;
2384
+ } else exporter.limit = Number.POSITIVE_INFINITY;
2385
+ const writer = exporter.writer;
1734
2386
  exporter.failure = undefined;
1735
2387
  exporter.mergedOrigins = undefined;
1736
- const root = exporter.type(t);
1737
- if (exporter.failure !== undefined) {
1738
- const failure = exporter.failure;
1739
- forgetExport(exporter);
1740
- return failure;
1741
- }
1742
- const me = this.data.index;
1743
- const numbers = this.numberLogs[me]!;
1744
- const strings = this.stringLogs[me]!;
1745
- const table = this.recordTables[me]!;
1746
- const offset = this.used[2 * me]!;
1747
- const p = new RecordPacker(c.fileIndex, strings, this.used[2 * me + 1]!);
1748
- const starts: number[] = [];
1749
- for (const r of exporter.records) {
1750
- starts.push(p.f.length);
1751
- if (!packRecord(p, r)) {
1752
- forgetExport(exporter);
2388
+ exporter.base = this.storedRecords;
2389
+ exporter.count = 0;
2390
+ const at = writer.at;
2391
+ const unitAt = writer.unitAt;
2392
+ this.exporting = true;
2393
+ try {
2394
+ const root = exporter.type(t);
2395
+ if (exporter.failure !== undefined) {
2396
+ const failure = exporter.failure;
2397
+ forgetExport(exporter, at, unitAt);
2398
+ return failure;
2399
+ }
2400
+ // The result's own words, at the cursor after the records it added: its root record, how long the result
2401
+ // took to compute (microseconds: a checker that translates it saved that much), and the files its
2402
+ // diagnostics are in.
2403
+ const header = writer.at;
2404
+ writer.word(root);
2405
+ writer.word(Math.round(computeMs * 1000));
2406
+ writer.word(diagnosticFiles.length);
2407
+ for (const index of diagnosticFiles) writer.word(index);
2408
+ if (writer.full) {
2409
+ this.logFull++;
2410
+ forgetExport(exporter, at, unitAt);
1753
2411
  return -1;
1754
2412
  }
2413
+ const count = exporter.count;
2414
+ exporter.base += count;
2415
+ exporter.count = 0;
2416
+ exporter.addedTypes.length = 0;
2417
+ exporter.addedProperties.length = 0;
2418
+ this.posted++;
2419
+ this.storedRecords += count;
2420
+ this.writeStats();
2421
+ // Only this checker writes its logs and store; the result table's state publishes what it wrote.
2422
+ return postedState(this.data.count, me, header);
2423
+ } finally {
2424
+ this.exporting = false;
1755
2425
  }
1756
- const header = p.f.length;
1757
- p.f.push(root, diagnosticFiles.length);
1758
- for (const fileName of diagnosticFiles) p.text(fileName);
1759
- const count = exporter.records.length;
1760
- let fits = !p.full && offset + p.f.length <= numbers.length && exporter.base + count <= recordTableSize;
1761
- for (const v of p.f) if ((v | 0) !== v) fits = false;
1762
- if (!fits) {
1763
- this.logFull++;
1764
- forgetExport(exporter);
1765
- return -1;
1766
- }
1767
- for (let i = 0; i < p.f.length; i++) numbers[offset + i] = p.f[i]!;
1768
- for (let i = 0; i < count; i++) {
1769
- const index = exporter.base + i;
1770
- table[2 * index] = offset + starts[i]! + 1;
1771
- const r = exporter.records[i]!;
1772
- if (r.kind === RecordKind.CompleteObject) table[2 * r.num + 1] = index + 1;
1773
- }
1774
- // Only this checker writes its logs and store; the result table's state publishes what it wrote.
1775
- this.used[2 * me] = offset + p.f.length;
1776
- this.used[2 * me + 1] = p.unitAt;
1777
- exporter.base += count;
1778
- exporter.records = [];
1779
- exporter.addedTypes.length = 0;
1780
- exporter.addedProperties.length = 0;
1781
- this.posted++;
1782
- this.storedRecords += count;
1783
- return postedState(this.data.count, me, offset + header);
1784
2426
  }
1785
2427
 
1786
2428
  /** The records this checker stored. */
@@ -1797,8 +2439,7 @@ export class ThreadSharing {
1797
2439
  key: number,
1798
2440
  declarationFile: SourceFile,
1799
2441
  ): Type | undefined {
1800
- for (const fileName of posted.files)
1801
- if (fileName === '' || this.ownsFileNamed(fileName) || this.forbidsLastNamed(fileName)) return undefined;
2442
+ for (const index of posted.files) if (this.ownsIndex(index) || this.forbidsLast(index)) return undefined;
1802
2443
  const declarationIndex = this.c.fileIndex.get(declarationFile);
1803
2444
  if (declarationIndex !== undefined && this.forbidsLast(declarationIndex)) return undefined;
1804
2445
  const start = performance.now();
@@ -1807,16 +2448,23 @@ export class ThreadSharing {
1807
2448
  reader = new StoreReader(this, producer);
1808
2449
  this.readers[producer] = reader;
1809
2450
  }
1810
- const t = reader.translate(posted.root);
2451
+ // The translation budget: building more records than the rule allows costs more than computing the result again,
2452
+ // where `T` is the time the producer's computation took (`StoredResult.computeMs`). The walk stops at the bound
2453
+ // before anything is built.
2454
+ const t = reader.translate(posted.root, this.rule.buildBudget(posted.computeMs));
2455
+ if (t === undefined && reader.stopped) this.stoppedTranslations++;
1811
2456
  this.translationMs += performance.now() - start;
1812
2457
  if (t !== undefined) {
1813
2458
  this.translated++;
1814
- for (const fileName of posted.files) this.forbidNamed(fileName);
2459
+ // Translating the result saved the time its producer's computation took (`post`'s header).
2460
+ this.addSaved(posted.computeMs);
2461
+ for (const index of posted.files) this.forbidIndex(index);
1815
2462
  if (posted.files.length > 0) {
1816
2463
  files.set(key, posted.files);
1817
2464
  this.noteFiles(posted.files);
1818
2465
  }
1819
2466
  }
2467
+ this.writeStats();
1820
2468
  return t;
1821
2469
  }
1822
2470
  }
@@ -1824,11 +2472,14 @@ export class ThreadSharing {
1824
2472
  /** A result in a checker's store: the record of its type, and the files of its diagnostics. */
1825
2473
  export class StoredResult {
1826
2474
  readonly root: number;
1827
- readonly files: string[];
2475
+ readonly files: number[];
2476
+ /** How long computing the result took its producer, in milliseconds. */
2477
+ readonly computeMs: number;
1828
2478
 
1829
- constructor(root: number, files: string[]) {
2479
+ constructor(root: number, files: number[], computeMs: number = 0) {
1830
2480
  this.root = root;
1831
2481
  this.files = files;
2482
+ this.computeMs = computeMs;
1832
2483
  }
1833
2484
  }
1834
2485
 
@@ -1838,14 +2489,16 @@ function readText(strings: Uint16Array, start: number, length: number): string {
1838
2489
  return text;
1839
2490
  }
1840
2491
 
1841
- /** Forgets the types and properties a failed export of a store gave indexes. */
1842
- function forgetExport(exporter: Exporter): void {
2492
+ /** Forgets the types and properties a failed export of a store gave indexes, and its words. */
2493
+ function forgetExport(exporter: Exporter, at: number, unitAt: number): void {
1843
2494
  for (const t of exporter.addedTypes) exporter.indexes.delete(t);
1844
2495
  for (const prop of exporter.addedProperties) exporter.properties.delete(prop);
1845
2496
  exporter.addedTypes.length = 0;
1846
2497
  exporter.addedProperties.length = 0;
1847
- exporter.records = [];
2498
+ exporter.count = 0;
1848
2499
  exporter.failure = undefined;
2500
+ exporter.writer.at = at;
2501
+ exporter.writer.unitAt = unitAt;
1849
2502
  }
1850
2503
 
1851
2504
  /**
@@ -1860,6 +2513,7 @@ function computeAndPost(
1860
2513
  key: number,
1861
2514
  symbol: Symbol | undefined,
1862
2515
  sig: Signature | undefined,
2516
+ declarationFile: SourceFile,
1863
2517
  ): Type {
1864
2518
  const t = sharing.computeShared(files, key, symbol, sig);
1865
2519
  const ms = sharing.lastMs;
@@ -1868,24 +2522,64 @@ function computeAndPost(
1868
2522
  sharing.notShareableMs += ms;
1869
2523
  return t;
1870
2524
  }
1871
- // A quick computation is cheaper to repeat than to export, post and translate.
1872
- if (ms < sharing.minMs) {
1873
- sharing.belowMin++;
1874
- sharing.belowMinMs += ms;
2525
+ let diagnosticFiles: readonly number[] | undefined;
2526
+ if (sharing.bounded) {
2527
+ // The main thread stopped sharing at its window (control 1 or 2): this result is the checker's own,
2528
+ // and the others compute theirs, as they do when they find none posted.
2529
+ if (Atomics.load(sharing.control, 0) !== 0) {
2530
+ sharing.stopped++;
2531
+ return t;
2532
+ }
2533
+ const index = sharing.c.fileIndex.get(declarationFile);
2534
+ if (index === undefined) return t;
2535
+ // The rule priced this result: a quick computation is cheaper to repeat than to export, post and translate.
2536
+ diagnosticFiles = files.get(key) ?? [];
2537
+ if (!sharing.rule.posts(sharing, key, ms, diagnosticFiles, index)) {
2538
+ sharing.belowMin++;
2539
+ sharing.belowMinMs += ms;
2540
+ return t;
2541
+ }
2542
+ // A result is posted only for a declaration whose file another checker that may take it missed on: a file only
2543
+ // its own checkers read has nobody to take it.
2544
+ if (!sharing.demanded(index)) {
2545
+ sharing.notDemanded++;
2546
+ return t;
2547
+ }
2548
+ } else {
2549
+ // Below the bounded count the run is Phase 1's, and takes the same path: the floor first, then the stop switch,
2550
+ // and none of the bounded path's lookups (the file index and the diagnostics) is made.
2551
+ if (ms < sharing.minMs) {
2552
+ sharing.belowMin++;
2553
+ sharing.belowMinMs += ms;
2554
+ return t;
2555
+ }
2556
+ if (Atomics.load(sharing.control, 0) !== 0) {
2557
+ sharing.stopped++;
2558
+ return t;
2559
+ }
2560
+ }
2561
+ // A result computed inside this checker's export walk doesn't have the store to itself: the walk would write over
2562
+ // its records, so it stays with the checkers that compute it.
2563
+ if (sharing.exporting) {
2564
+ sharing.nested++;
1875
2565
  return t;
1876
2566
  }
1877
2567
  const slot = table.slot(key);
1878
2568
  if (slot < 0 || table.state(slot) !== 0) return t;
1879
2569
  const start = performance.now();
1880
2570
  // Diagnostics stay with this checker; the others only learn which files they are in.
1881
- const state = sharing.post(t, files.get(key) ?? []);
1882
- if (typeof state === 'string') {
2571
+ const state = sharing.post(t, diagnosticFiles ?? files.get(key) ?? [], ms);
2572
+ if (state === budgetFailure) {
2573
+ sharing.overBudget++;
2574
+ sharing.overBudgetMs += ms;
2575
+ } else if (typeof state === 'string') {
1883
2576
  sharing.exportFailed++;
1884
2577
  sharing.exportFailedMs += ms;
1885
2578
  sharing.exportFailures.set(state, (sharing.exportFailures.get(state) ?? 0) + ms);
1886
- } else if (state === -1) sharing.packFailed++;
2579
+ } else if (state === -1) sharing.storeFailed++;
1887
2580
  else table.publish(slot, state);
1888
2581
  sharing.exportMs += performance.now() - start;
2582
+ sharing.writeStats();
1889
2583
  return t;
1890
2584
  }
1891
2585
 
@@ -1915,24 +2609,31 @@ function threadTypeOfSymbolWorker(c: Checker, symbol: Symbol): Type {
1915
2609
  return cached;
1916
2610
  }
1917
2611
  if (id > sharing.data.symbolIds) return sharing.tracked(sharing.typeFiles, id, symbol, undefined);
2612
+ const declaration = symbol.valueDeclaration!;
2613
+ const file = getSourceFileOfNode(declaration);
2614
+ // A declaration with a type annotation: computing its type resolves the annotation, and translating the records
2615
+ // does too (`NodeType` resolves the node), so sharing saves nothing on it and costs a lookup. Below the bounded
2616
+ // count the exclusion is off, as it is in Phase 1.
2617
+ if (sharing.bounded && getType(declaration) !== undefined)
2618
+ return sharing.tracked(sharing.typeFiles, id, symbol, undefined);
1918
2619
  const table = sharing.types;
1919
- if (!sharing.owns(getSourceFileOfNode(symbol.valueDeclaration!))) {
1920
- const slot = table.find(id);
2620
+ if (!sharing.owns(file)) {
2621
+ // The stop switch is read before the table: a checker that stops translating computes the result itself.
2622
+ const slot = sharing.stoppedTranslating() ? -1 : table.find(id);
1921
2623
  const state = slot < 0 ? 0 : table.state(slot);
1922
2624
  if (
1923
2625
  state <= -2 &&
1924
2626
  findResolutionCycleStartIndex(c, symbol, TypeSystemPropertyName.Type) < 0 &&
1925
- !translationBlocked(symbol.valueDeclaration!)
2627
+ !translationBlocked(declaration)
1926
2628
  ) {
1927
2629
  const producer = postedProducer(sharing.data.count, state);
1928
2630
  const posted = sharing.result(producer, postedOffset(sharing.data.count, state));
1929
- const file = getSourceFileOfNode(symbol.valueDeclaration!);
1930
2631
  if (shareVerifyValue === '1') {
1931
2632
  const own = sharing.tracked(sharing.typeFiles, id, symbol, undefined);
1932
2633
  verifyTranslation(
1933
2634
  c,
1934
2635
  'types',
1935
- symbol.valueDeclaration!,
2636
+ declaration,
1936
2637
  own,
1937
2638
  sharing.translatePosted(producer, posted, sharing.typeFiles, id, file),
1938
2639
  );
@@ -1940,9 +2641,9 @@ function threadTypeOfSymbolWorker(c: Checker, symbol: Symbol): Type {
1940
2641
  }
1941
2642
  const t = sharing.translatePosted(producer, posted, sharing.typeFiles, id, file);
1942
2643
  if (t !== undefined) {
1943
- traceTranslation(c, 'types', symbol.valueDeclaration!, producer);
2644
+ traceTranslation(c, 'types', declaration, producer);
1944
2645
  if (sharing.depth === 1) sharing.kind = 2;
1945
- sharing.forbid(getSourceFileOfNode(symbol.valueDeclaration!));
2646
+ sharing.forbid(file);
1946
2647
  const again = c.symbolTypes.get(id);
1947
2648
  if (again !== undefined) return again;
1948
2649
  c.symbolTypes.set(id, t);
@@ -1950,13 +2651,16 @@ function threadTypeOfSymbolWorker(c: Checker, symbol: Symbol): Type {
1950
2651
  }
1951
2652
  sharing.sawDoneUnusable++;
1952
2653
  }
2654
+ // This checker needed a result for one of the file's declarations and has none: the others post its results only
2655
+ // then.
2656
+ sharing.want(file);
1953
2657
  sharing.computedPrivately++;
1954
2658
  if (sharing.depth === 1) sharing.kind = 1;
1955
2659
  } else {
1956
2660
  sharing.owned++;
1957
2661
  if (sharing.depth === 1) sharing.kind = 0;
1958
2662
  }
1959
- return computeAndPost(sharing, table, sharing.typeFiles, id, symbol, undefined);
2663
+ return computeAndPost(sharing, table, sharing.typeFiles, id, symbol, undefined, file);
1960
2664
  }
1961
2665
 
1962
2666
  /** Notes that the computation in progress used the cached return type of `sig` (see `ThreadSharing.typeFiles`). */
@@ -1985,9 +2689,15 @@ function threadReturnTypeWorker(c: Checker, sig: Signature): Type {
1985
2689
  const declaration = sig.declaration!;
1986
2690
  const key = declaration.id;
1987
2691
  if (key > sharing.data.nodeIds) return sharing.tracked(sharing.returnFiles, key, undefined, sig);
2692
+ const file = getSourceFileOfNode(declaration);
2693
+ // A declaration with a return type annotation: computing the return type resolves the annotation, and translating
2694
+ // the records does too, so sharing saves nothing on it and costs a lookup. Below the bounded count the exclusion is
2695
+ // off, as it is in Phase 1.
2696
+ if (sharing.bounded && getType(declaration) !== undefined)
2697
+ return sharing.tracked(sharing.returnFiles, key, undefined, sig);
1988
2698
  const table = sharing.returns;
1989
- if (!sharing.owns(getSourceFileOfNode(declaration))) {
1990
- const slot = table.find(key);
2699
+ if (!sharing.owns(file)) {
2700
+ const slot = sharing.stoppedTranslating() ? -1 : table.find(key);
1991
2701
  const state = slot < 0 ? 0 : table.state(slot);
1992
2702
  if (
1993
2703
  state <= -2 &&
@@ -1996,7 +2706,6 @@ function threadReturnTypeWorker(c: Checker, sig: Signature): Type {
1996
2706
  ) {
1997
2707
  const producer = postedProducer(sharing.data.count, state);
1998
2708
  const posted = sharing.result(producer, postedOffset(sharing.data.count, state));
1999
- const file = getSourceFileOfNode(declaration);
2000
2709
  if (shareVerifyValue === '1') {
2001
2710
  const own = sharing.tracked(sharing.returnFiles, key, undefined, sig);
2002
2711
  verifyTranslation(
@@ -2012,18 +2721,19 @@ function threadReturnTypeWorker(c: Checker, sig: Signature): Type {
2012
2721
  if (t !== undefined) {
2013
2722
  traceTranslation(c, 'returns', declaration, producer);
2014
2723
  if (sharing.depth === 1) sharing.kind = 2;
2015
- sharing.forbid(getSourceFileOfNode(declaration));
2724
+ sharing.forbid(file);
2016
2725
  return t;
2017
2726
  }
2018
2727
  sharing.sawDoneUnusable++;
2019
2728
  }
2729
+ sharing.want(file);
2020
2730
  sharing.computedPrivately++;
2021
2731
  if (sharing.depth === 1) sharing.kind = 1;
2022
2732
  } else {
2023
2733
  sharing.owned++;
2024
2734
  if (sharing.depth === 1) sharing.kind = 0;
2025
2735
  }
2026
- return computeAndPost(sharing, table, sharing.returnFiles, key, undefined, sig);
2736
+ return computeAndPost(sharing, table, sharing.returnFiles, key, undefined, sig, file);
2027
2737
  }
2028
2738
 
2029
2739
  // ---- Records in a checker's store
@@ -2050,331 +2760,313 @@ function doubleWords(): DoubleWords {
2050
2760
  return doubleWordsValue;
2051
2761
  }
2052
2762
 
2053
- /** Packs records into a checker's store: their numbers into `f`, their strings into its string log from `unitAt`. */
2054
- class RecordPacker {
2055
- readonly f: number[] = [];
2056
- readonly fileIndex: Map<SourceFile, number>;
2057
- readonly strings: Uint16Array;
2058
- unitAt: number;
2059
- /** Set when the string log is full. */
2060
- full = false;
2061
-
2062
- constructor(fileIndex: Map<SourceFile, number>, strings: Uint16Array, unitAt: number) {
2063
- this.fileIndex = fileIndex;
2064
- this.strings = strings;
2065
- this.unitAt = unitAt;
2066
- }
2067
-
2068
- text(t: string): void {
2069
- if (this.unitAt + t.length > this.strings.length) {
2070
- this.full = true;
2071
- this.f.push(0, 0);
2072
- return;
2073
- }
2074
- this.f.push(this.unitAt, t.length);
2075
- for (let j = 0; j < t.length; j++) this.strings[this.unitAt++] = t.charCodeAt(j);
2076
- }
2077
-
2078
- list(values: readonly number[]): void {
2079
- this.f.push(values.length);
2080
- for (const v of values) this.f.push(v);
2081
- }
2763
+ /** Collects the children of a node (`forEachChild`). */
2764
+ class ChildVisitor extends NodeVisitor {
2765
+ readonly children: Node[] = [];
2082
2766
 
2083
- /** A double as the two int32 halves of its bits. */
2084
- double(v: number): void {
2085
- const words = doubleWords();
2086
- words.f[0] = v;
2087
- this.f.push(words.i[0]!, words.i[1]!);
2767
+ visitNode(node: Node): boolean {
2768
+ this.children.push(node);
2769
+ return false;
2088
2770
  }
2771
+ }
2089
2772
 
2090
- node(node: Node | undefined): boolean {
2091
- if (node === undefined) {
2092
- this.f.push(-1);
2093
- return true;
2094
- }
2095
- const file = getSourceFileOfNode(node);
2096
- const index = this.fileIndex.get(file);
2097
- if (index === undefined) return false;
2098
- this.f.push(index, node.pos, node.end, node.kind, node.id);
2099
- return true;
2100
- }
2773
+ /** The nodes of the program records name, by id, found by descending from their file through the nodes that contain them. */
2774
+ export class NodeFinder {
2775
+ readonly files: readonly SourceFile[];
2776
+ readonly nodesById: Map<number, Node>;
2777
+ private visitor: ChildVisitor | undefined = undefined;
2101
2778
 
2102
- nodes(list: readonly (Node | undefined)[]): boolean {
2103
- this.f.push(list.length);
2104
- for (const node of list) if (!this.node(node)) return false;
2105
- return true;
2779
+ constructor(files: readonly SourceFile[], nodesById: Map<number, Node>) {
2780
+ this.files = files;
2781
+ this.nodesById = nodesById;
2106
2782
  }
2107
2783
 
2108
- /** A symbol of the program (by the node it is the symbol of, or the export symbol of), or none. */
2109
- symbol(symbol: Symbol | undefined): boolean {
2110
- if (symbol === undefined) {
2111
- this.f.push(-1);
2112
- return true;
2784
+ /** The node of file `fileIndex` with the range `pos`-`end` and id `id`; undefined if the program hasn't it. */
2785
+ find(fileIndex: number, pos: number, end: number, id: number): Node | undefined {
2786
+ const known = this.nodesById.get(id);
2787
+ if (known !== undefined) return known;
2788
+ const file = this.files[fileIndex];
2789
+ if (file === undefined) return undefined;
2790
+ let visitor = this.visitor;
2791
+ if (visitor === undefined) visitor = this.visitor = new ChildVisitor();
2792
+ let node: Node = file;
2793
+ for (;;) {
2794
+ if (node.id === id) {
2795
+ if (node.pos !== pos || node.end !== end) return undefined;
2796
+ this.nodesById.set(id, node);
2797
+ return node;
2798
+ }
2799
+ visitor.children.length = 0;
2800
+ node.forEachChild(visitor);
2801
+ let next: Node | undefined = undefined;
2802
+ for (const child of visitor.children)
2803
+ if (child.pos <= pos && end <= child.end) {
2804
+ // The innermost of nested nodes with one range is found by its id further down.
2805
+ if (child.id === id) {
2806
+ if (child.pos !== pos || child.end !== end) return undefined;
2807
+ this.nodesById.set(id, child);
2808
+ return child;
2809
+ }
2810
+ if (next === undefined) next = child;
2811
+ }
2812
+ if (next === undefined) return undefined;
2813
+ node = next;
2113
2814
  }
2114
- const declaration = symbol.declaration;
2115
- if (declaration === undefined) return false;
2116
- const local = getSymbol(declaration);
2117
- if (local === symbol) this.f.push(0);
2118
- else if (local !== undefined && local.exportSymbol === symbol) this.f.push(1);
2119
- else return false;
2120
- return this.node(declaration);
2121
2815
  }
2122
2816
  }
2123
2817
 
2124
- /** Writes `r`; false if a node or symbol in it can't be named across threads. */
2125
- function packRecord(p: RecordPacker, r: PortableRecord): boolean {
2126
- p.f.push(r.kind, r.id, r.flags);
2127
- // A number literal's value is any double, written as its two halves.
2128
- if (r.kind === RecordKind.Literal) p.double(r.num);
2129
- else p.f.push(r.num);
2130
- p.f.push(r.mergedId, r.aliasMergedId);
2131
- p.text(r.text);
2132
- if (!p.symbol(r.symbol) || !p.node(r.node) || !p.symbol(r.aliasSymbol)) return false;
2133
- p.list(r.refs);
2134
- p.list(r.nums);
2135
- if (!p.nodes(r.nodes)) return false;
2136
- p.f.push(r.texts.length);
2137
- for (const t of r.texts) p.text(t);
2138
- p.list(r.aliasRefs);
2139
- p.f.push(r.indexInfos.length);
2140
- for (const info of r.indexInfos) {
2141
- p.f.push(info.keyType, info.valueType, info.isReadonly ? 1 : 0);
2142
- if (!p.node(info.declaration) || !p.nodes(info.components)) return false;
2143
- }
2144
- return true;
2145
- }
2146
-
2147
- /** Reads records from a checker's store. */
2148
- class RecordUnpacker {
2149
- readonly c: Checker;
2150
- readonly f: Int32Array;
2818
+ /**
2819
+ * Reads the records of a checker's store where it wrote them. `begin` starts a record at its offset in the number log
2820
+ * and finds its parts without decoding its kind's fields: its refs, its alias, its symbol, its node, then the fields
2821
+ * at `fields`.
2822
+ */
2823
+ export class RecordReader {
2824
+ readonly numbers: Int32Array;
2151
2825
  readonly strings: Uint16Array;
2152
- at = 0;
2153
- failed = false;
2154
-
2155
- /** The nodes found so far, by id. */
2156
- readonly nodesById: Map<number, Node>;
2826
+ readonly table: Int32Array;
2827
+ readonly finder: NodeFinder;
2828
+ /** The record in progress: its header and kind, and where its refs are. */
2829
+ header = 0;
2830
+ kind = 0;
2831
+ refAt = 0;
2832
+ refCount = 0;
2833
+ /** Where its alias refs are, if it has an alias; -1 if none. */
2834
+ aliasRefAt = -1;
2835
+ aliasRefCount = 0;
2836
+ /** How its alias is named: `SymbolNaming` and a merged symbol id, or a node at `aliasNodeStart`. */
2837
+ aliasNaming = -1;
2838
+ aliasMergedId = -1;
2839
+ aliasNodeStart = -1;
2840
+ /** How its symbol is named: the kind of naming, a merged symbol id, or a node at `symbolNodeStart`. */
2841
+ symbolNaming = 0;
2842
+ symbolMergedId = -1;
2843
+ symbolNodeStart = -1;
2844
+ /** Where its node is, if it has one; -1 if none. */
2845
+ nodeStart = -1;
2846
+ /** The offset of its kind's own fields. */
2847
+ fields = 0;
2848
+ /** The records a record names, filled by `scan`. */
2849
+ readonly deps: number[] = [];
2157
2850
 
2158
- constructor(c: Checker, numbers: Int32Array, strings: Uint16Array, nodesById: Map<number, Node>) {
2159
- this.c = c;
2160
- this.f = numbers;
2851
+ constructor(
2852
+ numbers: Int32Array,
2853
+ strings: Uint16Array,
2854
+ table: Int32Array,
2855
+ files: readonly SourceFile[],
2856
+ nodesById: Map<number, Node>,
2857
+ ) {
2858
+ this.numbers = numbers;
2161
2859
  this.strings = strings;
2162
- this.nodesById = nodesById;
2163
- }
2164
-
2165
- next(): number {
2166
- return this.f[this.at++]!;
2860
+ this.table = table;
2861
+ this.finder = new NodeFinder(files, nodesById);
2862
+ }
2863
+
2864
+ /** Starts the record at `index`; false if the store hasn't it. */
2865
+ begin(index: number): boolean {
2866
+ if (index < 0 || index >= this.table.length) return false;
2867
+ const start = this.table[index]!;
2868
+ if (start <= 0) return false;
2869
+ const numbers = this.numbers;
2870
+ const at = start - 1;
2871
+ const header = numbers[at]!;
2872
+ this.header = header;
2873
+ this.kind = header & RecordHeader.KindMask;
2874
+ let count = (header >>> 16) & RefCountMask;
2875
+ this.refAt = at + 1;
2876
+ if (count === RecordHeader.RefEscape) {
2877
+ count = numbers[this.refAt]!;
2878
+ this.refAt++;
2879
+ }
2880
+ this.refCount = count;
2881
+ this.locate();
2882
+ return true;
2167
2883
  }
2168
2884
 
2169
- double(): number {
2170
- const words = doubleWords();
2171
- words.i[0] = this.next();
2172
- words.i[1] = this.next();
2173
- return words.f[0]!;
2885
+ /** Finds the record's alias refs, its symbol and its node, and the fields that follow them. */
2886
+ private locate(): void {
2887
+ const numbers = this.numbers;
2888
+ const header = this.header;
2889
+ let p = this.refAt + this.refCount;
2890
+ this.aliasNaming = -1;
2891
+ this.aliasMergedId = -1;
2892
+ this.aliasNodeStart = -1;
2893
+ this.aliasRefAt = -1;
2894
+ this.aliasRefCount = 0;
2895
+ if ((header & RecordHeader.HasAlias) !== 0) {
2896
+ if ((header & RecordHeader.AliasMerged) !== 0) {
2897
+ this.aliasNaming = SymbolNaming.Merged;
2898
+ this.aliasMergedId = numbers[p++]!;
2899
+ } else {
2900
+ this.aliasNaming = numbers[p]! === 1 ? SymbolNaming.Export : SymbolNaming.Node;
2901
+ this.aliasNodeStart = p + 1;
2902
+ p = this.aliasNodeStart + this.nodeWords(this.aliasNodeStart);
2903
+ }
2904
+ this.aliasRefCount = numbers[p]!;
2905
+ this.aliasRefAt = p + 1;
2906
+ p = this.aliasRefAt + this.aliasRefCount;
2907
+ }
2908
+ const naming = (header & RecordHeader.NamingMask) >>> RecordHeader.NamingShift;
2909
+ this.symbolNaming = naming;
2910
+ this.symbolMergedId = -1;
2911
+ this.symbolNodeStart = -1;
2912
+ if (naming === SymbolNaming.Merged) this.symbolMergedId = numbers[p++]!;
2913
+ else if (naming !== SymbolNaming.None) {
2914
+ this.symbolNodeStart = p;
2915
+ p += this.nodeWords(p);
2916
+ }
2917
+ this.nodeStart = -1;
2918
+ if ((header & RecordHeader.HasNode) !== 0) {
2919
+ this.nodeStart = p;
2920
+ p += this.nodeWords(p);
2921
+ }
2922
+ this.fields = p;
2174
2923
  }
2175
2924
 
2176
- text(): string {
2177
- const start = this.next();
2178
- const length = this.next();
2179
- return readText(this.strings, start, length);
2925
+ /** The words a node at `p` takes: its file index, range and id, or one word for no node. */
2926
+ nodeWords(p: number): number {
2927
+ return this.numbers[p]! < 0 ? 1 : 4;
2180
2928
  }
2181
2929
 
2182
- list(): number[] {
2183
- const n = this.next();
2184
- const values: number[] = [];
2185
- for (let i = 0; i < n; i++) values.push(this.next());
2186
- return values;
2930
+ /** The node at `p`; undefined if it is none or the program hasn't it. */
2931
+ readNode(p: number): Node | undefined {
2932
+ if (p < 0 || this.numbers[p]! < 0) return undefined;
2933
+ const numbers = this.numbers;
2934
+ return this.finder.find(numbers[p]!, numbers[p + 1]!, numbers[p + 2]!, numbers[p + 3]!);
2187
2935
  }
2188
2936
 
2937
+ /** The record's own node; undefined if it has none or the program hasn't it. */
2189
2938
  node(): Node | undefined {
2190
- const fileIndex = this.next();
2191
- if (fileIndex < 0) return undefined;
2192
- const pos = this.next();
2193
- const end = this.next();
2194
- const kind = this.next();
2195
- const id = this.next();
2196
- let node = this.nodesById.get(id);
2197
- if (node === undefined) {
2198
- const file = this.c.files[fileIndex];
2199
- node = file === undefined ? undefined : findNode(file, pos, end, kind, id);
2200
- if (node === undefined) {
2201
- this.failed = true;
2202
- return undefined;
2203
- }
2204
- this.nodesById.set(id, node);
2205
- }
2206
- return node;
2939
+ return this.readNode(this.nodeStart);
2207
2940
  }
2208
2941
 
2209
- nodes(): (Node | undefined)[] {
2210
- const n = this.next();
2211
- const list: (Node | undefined)[] = [];
2212
- for (let i = 0; i < n; i++) list.push(this.node());
2213
- return list;
2214
- }
2215
-
2216
- symbol(): Symbol | undefined {
2217
- const how = this.next();
2218
- if (how < 0) return undefined;
2219
- const node = this.node();
2220
- if (node === undefined) return undefined;
2221
- const local = getSymbol(node);
2222
- const symbol = how === 0 ? local : local?.exportSymbol;
2223
- if (symbol === undefined) this.failed = true;
2224
- return symbol;
2942
+ doubleAt(p: number): number {
2943
+ const words = doubleWords();
2944
+ words.i[0] = this.numbers[p]!;
2945
+ words.i[1] = this.numbers[p + 1]!;
2946
+ return words.f[0]!;
2225
2947
  }
2226
- }
2227
-
2228
- /** Reads a record; undefined if a node or symbol in it isn't in `u`'s program. */
2229
- function unpackRecord(u: RecordUnpacker): PortableRecord | undefined {
2230
- const kind = u.next() as RecordKind;
2231
- const id = u.next();
2232
- const flags = u.next();
2233
- const num = kind === RecordKind.Literal ? u.double() : u.next();
2234
- const mergedId = u.next();
2235
- const aliasMergedId = u.next();
2236
- const text = u.text();
2237
- const symbol = u.symbol();
2238
- const node = u.node();
2239
- const aliasSymbol = u.symbol();
2240
- const refs = u.list();
2241
- const record = new PortableRecord(kind, id, flags, num, text, symbol, node, refs);
2242
- record.mergedId = mergedId;
2243
- record.aliasMergedId = aliasMergedId;
2244
- record.aliasSymbol = aliasSymbol;
2245
- record.nums = u.list();
2246
- record.nodes = u.nodes();
2247
- const textCount = u.next();
2248
- const texts: string[] = [];
2249
- for (let k = 0; k < textCount; k++) texts.push(u.text());
2250
- record.texts = texts;
2251
- record.aliasRefs = u.list();
2252
- const infoCount = u.next();
2253
- const infos: PortableIndexInfo[] = [];
2254
- for (let k = 0; k < infoCount; k++) {
2255
- const keyType = u.next();
2256
- const valueType = u.next();
2257
- const isReadonly = u.next() === 1;
2258
- const declaration = u.node();
2259
- const components = u.nodes() as Node[];
2260
- infos.push(new PortableIndexInfo(keyType, valueType, isReadonly, declaration, components));
2261
- }
2262
- record.indexInfos = infos;
2263
- return u.failed ? undefined : record;
2264
- }
2265
-
2266
- /** Collects the children of a node (`forEachChild`). */
2267
- class ChildCollector extends NodeVisitor {
2268
- readonly children: Node[] = [];
2269
2948
 
2270
- visitNode(node: Node): boolean {
2271
- this.children.push(node);
2272
- return false;
2949
+ textAt(p: number): string {
2950
+ return readText(this.strings, this.numbers[p]!, this.numbers[p + 1]!);
2273
2951
  }
2274
- }
2275
2952
 
2276
- /** The node of `file` with the range, kind and id given, found by descending through the nodes that contain it. */
2277
- function findNode(file: SourceFile, pos: number, end: number, kind: number, id: number): Node | undefined {
2278
- let node: Node = file;
2279
- for (;;) {
2280
- if (node.id === id) return node.kind === kind && node.pos === pos && node.end === end ? node : undefined;
2281
- const collector = new ChildCollector();
2282
- node.forEachChild(collector);
2283
- let next: Node | undefined = undefined;
2284
- for (const child of collector.children)
2285
- if (child.pos <= pos && end <= child.end) {
2286
- // The innermost of nested nodes with one range is found by its id further down.
2287
- if (child.id === id) return child.kind === kind && child.pos === pos && child.end === end ? child : undefined;
2288
- if (next === undefined) next = child;
2953
+ /**
2954
+ * Collects in `deps` the indexes of the records the record in progress needs: its refs, its alias refs, and the
2955
+ * types of its own fields that are records (a union's origin, an object literal's completion, a completed object's
2956
+ * members' types). A record is written before the records it needs.
2957
+ */
2958
+ scan(): void {
2959
+ const numbers = this.numbers;
2960
+ const deps = this.deps;
2961
+ deps.length = 0;
2962
+ for (let i = 0; i < this.refCount; i++) deps.push(numbers[this.refAt + i]!);
2963
+ for (let i = 0; i < this.aliasRefCount; i++) deps.push(numbers[this.aliasRefAt + i]!);
2964
+ let p = this.fields;
2965
+ switch (this.kind) {
2966
+ case RecordKind.Union:
2967
+ case RecordKind.AnonymousObject:
2968
+ // The origin of a union, and the completion of an object literal.
2969
+ deps.push(numbers[p + 1]!);
2970
+ break;
2971
+ case RecordKind.CompleteObject: {
2972
+ // The object literal completed, then each index signature's key and value types.
2973
+ deps.push(numbers[p]!);
2974
+ const count = numbers[p + 1]!;
2975
+ let q = p + 2;
2976
+ for (let i = 0; i < count; i++) {
2977
+ deps.push(numbers[q]!, numbers[q + 1]!);
2978
+ q += 3;
2979
+ q += this.nodeWords(q);
2980
+ const components = numbers[q++]!;
2981
+ for (let j = 0; j < components; j++) q += this.nodeWords(q);
2982
+ }
2983
+ break;
2289
2984
  }
2290
- if (next === undefined) return undefined;
2291
- node = next;
2985
+ }
2292
2986
  }
2293
2987
  }
2294
2988
 
2989
+ /** The bits of a record header that hold the number of refs, or `RefEscape` if the number is a word of its own. */
2990
+ const RefCountMask = 0xffff;
2991
+
2295
2992
  /**
2296
- * A consumer's translations of another checker's store: each record is read and translated once, when a result
2297
- * first needs it, in the order of the store, which is the order the producer exported them in.
2993
+ * A consumer's translations of another checker's store: each record is read where it is, and translated once, when a
2994
+ * result first needs it, in the order of the store, which is the order the producer exported them in.
2298
2995
  */
2299
2996
  class StoreReader {
2300
2997
  readonly sharing: ThreadSharing;
2301
2998
  readonly producer: number;
2302
2999
  readonly translator: Translator;
2303
- readonly table: Int32Array;
2304
- readonly unpacker: RecordUnpacker;
2305
- /** By record: the record, its translation and whether it is translated. */
2306
- readonly records: (PortableRecord | undefined)[] = [];
2307
- readonly types: Type[] = [];
2308
- readonly symbols: (Symbol | undefined)[] = [];
2309
- readonly built: number[] = [];
3000
+ readonly reader: RecordReader;
3001
+ /** Whether the last `translate` stopped at its budget, having built nothing. */
3002
+ stopped = false;
3003
+ /** By record: the type it translated to, the symbol a property record makes, and whether a completion is done. */
3004
+ readonly types = new LinkTable<Type>();
3005
+ readonly symbols = new LinkTable<Symbol>();
3006
+ readonly completed = new LinkTable<boolean>();
2310
3007
 
2311
3008
  constructor(sharing: ThreadSharing, producer: number) {
2312
3009
  this.sharing = sharing;
2313
3010
  this.producer = producer;
2314
- this.translator = new Translator(sharing.c);
2315
- this.table = sharing.recordTables[producer]!;
2316
- this.unpacker = new RecordUnpacker(
2317
- sharing.c,
3011
+ this.translator = new Translator(sharing.c, this);
3012
+ this.reader = new RecordReader(
2318
3013
  sharing.numberLogs[producer]!,
2319
3014
  sharing.stringLogs[producer]!,
3015
+ sharing.recordTables[producer]!,
3016
+ sharing.c.files,
2320
3017
  sharing.nodesById,
2321
3018
  );
2322
3019
  }
2323
3020
 
2324
- private ensure(index: number): void {
2325
- const placeholder = this.sharing.c.errorType;
2326
- while (this.types.length <= index) {
2327
- this.types.push(placeholder);
2328
- this.symbols.push(undefined);
2329
- this.records.push(undefined);
2330
- this.built.push(0);
2331
- }
3021
+ /**
3022
+ * Whether record `index` is translated: a property record made its symbol, an object literal got its members, a
3023
+ * shell whose members come later only counts with them. A translation that fails after it built a shell leaves
3024
+ * the empty type behind, and a later translation of the shell alone would take it for the object.
3025
+ */
3026
+ private isBuilt(index: number): boolean {
3027
+ if (!this.reader.begin(index)) return false;
3028
+ const kind = this.reader.kind;
3029
+ if (kind === RecordKind.Property) return this.symbols.get(index) !== undefined;
3030
+ if (kind === RecordKind.CompleteObject) return this.completed.get(index) !== undefined;
3031
+ if (this.types.get(index) === undefined) return false;
3032
+ if (kind !== RecordKind.AnonymousObject) return true;
3033
+ const completion = this.reader.numbers[this.reader.fields + 1]!;
3034
+ return completion < 0 || this.completed.get(completion) !== undefined;
2332
3035
  }
2333
3036
 
2334
- /** Record `index`, read on first use; undefined if a node or symbol in it isn't in this program. */
2335
- private record(index: number): PortableRecord | undefined {
2336
- this.ensure(index);
2337
- const known = this.records[index];
2338
- if (known !== undefined) return known;
2339
- const u = this.unpacker;
2340
- u.at = this.table[2 * index]! - 1;
2341
- u.failed = false;
2342
- const start = performance.now();
2343
- const record = unpackRecord(u);
2344
- this.sharing.unpackMs += performance.now() - start;
2345
- if (record !== undefined) this.records[index] = record;
2346
- return record;
3037
+ /** Whether the record in progress names only merged symbols this program has. */
3038
+ private present(reader: RecordReader): boolean {
3039
+ const merged = this.sharing.c.mergedSymbols;
3040
+ if (reader.aliasNaming === SymbolNaming.Merged && !merged.has(reader.aliasMergedId)) return false;
3041
+ return reader.symbolNaming !== SymbolNaming.Merged || merged.has(reader.symbolMergedId);
2347
3042
  }
2348
3043
 
2349
- /** The translation of record `root` and the records it needs; undefined if one can't be translated here. */
2350
- translate(root: number): Type | undefined {
3044
+ /**
3045
+ * The translation of record `root` and the records it needs; undefined if one can't be translated here, or if the
3046
+ * records it would build pass `budget`.
3047
+ */
3048
+ translate(root: number, budget: number = Number.POSITIVE_INFINITY): Type | undefined {
2351
3049
  const c = this.sharing.c;
2352
3050
  const translator = this.translator;
3051
+ const reader = this.reader;
3052
+ translator.failed = false;
2353
3053
  const needed = new Set<number>();
2354
3054
  const stack = [root];
3055
+ this.stopped = false;
2355
3056
  while (stack.length > 0) {
2356
3057
  const i = stack.pop()!;
2357
- if (needed.has(i)) continue;
2358
- this.ensure(i);
2359
- if (this.built[i] === 1) continue;
2360
- const r = this.record(i);
2361
- if (r === undefined) return undefined;
2362
- if (
2363
- (r.mergedId >= 0 && !c.mergedSymbols.has(r.mergedId)) ||
2364
- (r.aliasMergedId >= 0 && !c.mergedSymbols.has(r.aliasMergedId))
2365
- )
3058
+ if (needed.has(i) || this.isBuilt(i)) continue;
3059
+ if (needed.size >= budget) {
3060
+ this.stopped = true;
2366
3061
  return undefined;
2367
- needed.add(i);
2368
- for (const j of r.refs) if (j >= 0) stack.push(j);
2369
- for (const j of r.aliasRefs) stack.push(j);
2370
- if (r.kind === RecordKind.CompleteObject || (r.kind === RecordKind.Union && r.num >= 0)) stack.push(r.num);
2371
- for (const info of r.indexInfos) {
2372
- stack.push(info.keyType);
2373
- stack.push(info.valueType);
2374
3062
  }
2375
- if (r.kind === RecordKind.AnonymousObject) {
2376
- const completion = this.table[2 * i + 1]! - 1;
2377
- if (completion >= 0) stack.push(completion);
3063
+ if (!reader.begin(i) || !this.present(reader)) return undefined;
3064
+ needed.add(i);
3065
+ reader.scan();
3066
+ const deps = reader.deps;
3067
+ for (let k = 0; k < deps.length; k++) {
3068
+ const j = deps[k]!;
3069
+ if (j >= 0) stack.push(j);
2378
3070
  }
2379
3071
  }
2380
3072
  const order = [...needed].sort((a, b) => a - b);
@@ -2382,33 +3074,71 @@ class StoreReader {
2382
3074
  // record is translated. A translation can start translations of this store again (resolving a type alias, or
2383
3075
  // a type of a symbol), which translate some of these records first.
2384
3076
  for (const i of order) {
2385
- if (this.built[i] === 1) continue;
2386
- const r = this.records[i]!;
2387
- if (
2388
- r.kind === RecordKind.NodeType ||
2389
- r.kind === RecordKind.Conditional ||
2390
- r.kind === RecordKind.DistributedConditional
2391
- )
2392
- translator.aliasOfNode(r.node!);
3077
+ if (this.isBuilt(i)) continue;
3078
+ reader.begin(i);
3079
+ const kind = reader.kind;
3080
+ if (kind !== RecordKind.NodeType && kind !== RecordKind.Conditional && kind !== RecordKind.DistributedConditional)
3081
+ continue;
3082
+ const node = reader.node();
3083
+ if (node === undefined) return undefined;
3084
+ translator.aliasOfNode(node);
2393
3085
  }
2394
3086
  if (translator.failed) {
2395
3087
  translator.failed = false;
2396
3088
  return undefined;
2397
3089
  }
3090
+ const verifyOrder = shareVerifyValue === '2';
3091
+ let lastNew = -1;
2398
3092
  for (const i of order) {
2399
- if (this.built[i] === 1) continue;
2400
- const r = this.records[i]!;
2401
- if (r.kind === RecordKind.Property) this.symbols[i] = translator.property(r, this.types, this.symbols);
2402
- else if (r.kind === RecordKind.CompleteObject) translator.complete(r, this.types, this.symbols);
2403
- else this.types[i] = translator.record(r, this.types);
2404
- if (translator.failed) {
2405
- translator.failed = false;
2406
- return undefined;
3093
+ if (this.isBuilt(i)) continue;
3094
+ reader.begin(i);
3095
+ const before = verifyOrder ? c.typeCount : 0;
3096
+ switch (reader.kind) {
3097
+ case RecordKind.Property: {
3098
+ const symbol = translator.property(i);
3099
+ if (symbol === undefined) {
3100
+ translator.failed = false;
3101
+ return undefined;
3102
+ }
3103
+ this.symbols.set(i, symbol);
3104
+ continue;
3105
+ }
3106
+ case RecordKind.CompleteObject:
3107
+ if (!translator.complete(i)) {
3108
+ translator.failed = false;
3109
+ return undefined;
3110
+ }
3111
+ this.completed.set(i, true);
3112
+ continue;
3113
+ default: {
3114
+ const t = translator.record(i);
3115
+ if (t === undefined) {
3116
+ translator.failed = false;
3117
+ return undefined;
3118
+ }
3119
+ this.types.set(i, t);
3120
+ // The types a translation creates, record by record, are created in the order the producer created them.
3121
+ if (verifyOrder && c.typeCount > before) {
3122
+ if (before < lastNew) {
3123
+ const violations = ++this.sharing.orderViolations;
3124
+ if (violations <= 20)
3125
+ process.stderr.write(
3126
+ 'share-verify: checker ' +
3127
+ this.sharing.data.index +
3128
+ ' record ' +
3129
+ i +
3130
+ ' created type ' +
3131
+ c.typeCount +
3132
+ ' after ' +
3133
+ lastNew +
3134
+ '\n',
3135
+ );
3136
+ }
3137
+ lastNew = c.typeCount;
3138
+ }
3139
+ }
2407
3140
  }
2408
- this.built[i] = 1;
2409
- // A translated record is not read again.
2410
- this.records[i] = undefined;
2411
3141
  }
2412
- return this.types[root]!;
3142
+ return this.types.get(root);
2413
3143
  }
2414
3144
  }