tsc-ts 0.1.0 → 0.1.2
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.
- package/README.md +7 -1
- package/dist/api/compiler/ast.d.ts +15 -3
- package/dist/api/compiler/ast.js +36 -15
- package/dist/api/compiler/checker-classes.js +2 -1
- package/dist/api/compiler/checker-contextual.d.ts +20 -0
- package/dist/api/compiler/checker-contextual.js +111 -1
- package/dist/api/compiler/checker-expressions.js +14 -1
- package/dist/api/compiler/checker-grammar.d.ts +39 -4
- package/dist/api/compiler/checker-grammar.js +239 -75
- package/dist/api/compiler/checker-heritage.js +20 -14
- package/dist/api/compiler/checker-jsx.js +2 -1
- package/dist/api/compiler/checker-modules.js +4 -2
- package/dist/api/compiler/checker-source-file.js +1 -1
- package/dist/api/compiler/checker-type-factory.js +70 -19
- package/dist/api/compiler/checker-type-nodes.d.ts +1 -1
- package/dist/api/compiler/checker-type-nodes.js +5 -5
- package/dist/api/compiler/checker-unused.js +15 -10
- package/dist/api/compiler/checker.d.ts +7 -1
- package/dist/api/compiler/checker.js +14 -4
- package/dist/api/compiler/compile-program.d.ts +5 -0
- package/dist/api/compiler/compile-program.js +8 -0
- package/dist/api/compiler/expr-record.d.ts +11 -0
- package/dist/api/compiler/expr-record.js +53 -0
- package/dist/api/compiler/file-loader.d.ts +5 -0
- package/dist/api/compiler/file-loader.js +7 -0
- package/dist/api/compiler/identifier-name.d.ts +9 -0
- package/dist/api/compiler/identifier-name.js +10 -0
- package/dist/api/compiler/jsdoc.js +4 -2
- package/dist/api/compiler/parser.d.ts +2 -1
- package/dist/api/compiler/parser.js +10 -8
- package/dist/api/compiler/printer/factory.js +3 -2
- package/dist/api/compiler/printer/printer.js +3 -2
- package/dist/api/compiler/program-checker.d.ts +18 -0
- package/dist/api/compiler/program-checker.js +27 -0
- package/dist/api/compiler/reparser.js +4 -3
- package/dist/api/compiler/scanner.d.ts +8 -0
- package/dist/api/compiler/scanner.js +58 -42
- package/dist/api/compiler/shared-results.d.ts +376 -143
- package/dist/api/compiler/shared-results.js +1550 -773
- package/dist/api/compiler/transformers/declarations.js +2 -1
- package/package.json +15 -10
- package/src/compiler/ast.ts +39 -15
- package/src/compiler/cgroup.ts +157 -0
- package/src/compiler/checker-classes.ts +2 -1
- package/src/compiler/checker-contextual.ts +104 -0
- package/src/compiler/checker-expressions.ts +16 -1
- package/src/compiler/checker-grammar.ts +247 -88
- package/src/compiler/checker-heritage.ts +31 -17
- package/src/compiler/checker-jsx.ts +2 -1
- package/src/compiler/checker-modules.ts +4 -2
- package/src/compiler/checker-source-file.ts +1 -1
- package/src/compiler/checker-type-factory.ts +67 -21
- package/src/compiler/checker-type-nodes.ts +5 -5
- package/src/compiler/checker-unused.ts +13 -8
- package/src/compiler/checker.ts +15 -4
- package/src/compiler/compile-program.ts +8 -0
- package/src/compiler/decoder.ts +2 -1
- package/src/compiler/doctor-report.ts +133 -0
- package/src/compiler/expr-record.ts +59 -0
- package/src/compiler/file-loader.ts +8 -0
- package/src/compiler/identifier-name.ts +13 -0
- package/src/compiler/jsdoc.ts +4 -2
- package/src/compiler/parallel-check.ts +364 -146
- package/src/compiler/parallel-emit.ts +3 -3
- package/src/compiler/parse-thread.ts +24 -2
- package/src/compiler/parser.ts +10 -8
- package/src/compiler/printer/factory.ts +3 -2
- package/src/compiler/printer/printer.ts +4 -2
- package/src/compiler/program-checker.ts +28 -0
- package/src/compiler/reparser.ts +7 -3
- package/src/compiler/run-config.ts +1383 -0
- package/src/compiler/run-record-read.ts +87 -0
- package/src/compiler/run-records.ts +575 -0
- package/src/compiler/scanner.ts +57 -41
- package/src/compiler/shared-results.ts +1626 -896
- package/src/compiler/thread-entry.ts +3 -1
- package/src/compiler/transformers/declarations.ts +2 -1
- package/src/compiler/tsc-build.ts +64 -6
- package/src/compiler/tsc-cli.ts +33 -4
- package/src/compiler/tsc-compile.ts +223 -9
- package/src/compiler/tsc-watch.ts +25 -4
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// Results one checker thread computed, as
|
|
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:
|
|
86
|
+
/** A type the checker creates when it starts: its position in `initialTypes`. */
|
|
86
87
|
Initial,
|
|
87
|
-
/** A regular literal type:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
117
|
+
/** `keyof refs[0]` with the index flags. */
|
|
117
118
|
Index,
|
|
118
|
-
/** `refs[0][refs[1]]` with the access flags
|
|
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
|
|
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
|
|
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
|
|
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)
|
|
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
|
|
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
|
|
145
|
-
*
|
|
146
|
-
*
|
|
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
|
-
/**
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
this.
|
|
212
|
-
this.
|
|
213
|
-
this.
|
|
214
|
-
this.
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
this.
|
|
220
|
-
this.
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
const
|
|
227
|
-
|
|
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
|
-
|
|
297
|
-
readonly indexes = new Map<Type, number>();
|
|
355
|
+
readonly writer: StoreWriter;
|
|
298
356
|
/**
|
|
299
|
-
* The index of
|
|
300
|
-
* (`ThreadSharing.post`):
|
|
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:
|
|
305
|
-
readonly addedProperties:
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
/**
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
|
339
|
-
* by the id of a symbol it merged.
|
|
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
|
-
|
|
342
|
-
let
|
|
343
|
-
let
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
const
|
|
352
|
-
if (
|
|
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
|
-
|
|
355
|
-
|
|
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
|
-
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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
|
-
|
|
373
|
-
|
|
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
|
-
|
|
376
|
-
|
|
489
|
+
this.naming = naming;
|
|
490
|
+
this.namingMerged = merged;
|
|
491
|
+
this.namingNode = node;
|
|
377
492
|
}
|
|
378
493
|
return true;
|
|
379
494
|
}
|
|
380
495
|
|
|
381
|
-
/**
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
482
|
-
|
|
483
|
-
|
|
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
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
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
|
|
508
|
-
|
|
509
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
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
|
-
|
|
561
|
-
|
|
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.
|
|
568
|
-
} else
|
|
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
|
|
572
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 =
|
|
633
|
-
|
|
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:
|
|
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
|
-
|
|
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 =
|
|
657
|
-
|
|
658
|
-
this.
|
|
659
|
-
|
|
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
|
|
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
|
|
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
|
|
704
|
-
const index = this.
|
|
705
|
-
this.properties.set(
|
|
706
|
-
this.addedProperties.push(
|
|
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
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
762
|
-
|
|
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 (
|
|
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
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
);
|
|
831
|
-
|
|
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
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
);
|
|
847
|
-
|
|
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
|
|
852
|
-
|
|
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
|
|
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
|
-
/**
|
|
882
|
-
|
|
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
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
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,
|
|
891
|
-
if (
|
|
892
|
-
|
|
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(
|
|
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
|
|
901
|
-
|
|
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 (
|
|
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
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
if (
|
|
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
|
-
|
|
927
|
-
|
|
928
|
-
return
|
|
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
|
-
|
|
949
|
-
|
|
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
|
|
967
|
-
const refs =
|
|
968
|
-
|
|
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[
|
|
971
|
-
case RecordKind.Literal:
|
|
972
|
-
|
|
973
|
-
if ((
|
|
974
|
-
|
|
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,
|
|
1321
|
+
return getDeclaredTypeOfSymbol(c, symbol!);
|
|
980
1322
|
case RecordKind.ThisType:
|
|
981
|
-
return (getDeclaredTypeOfSymbol(c,
|
|
1323
|
+
return (getDeclaredTypeOfSymbol(c, symbol!) as InterfaceType).thisType!;
|
|
982
1324
|
case RecordKind.UniqueSymbol:
|
|
983
|
-
return getESSymbolLikeTypeForNode(c,
|
|
1325
|
+
return getESSymbolLikeTypeForNode(c, symbol!.valueDeclaration!);
|
|
984
1326
|
case RecordKind.SymbolType:
|
|
985
|
-
return getTypeOfSymbol(c,
|
|
1327
|
+
return getTypeOfSymbol(c, symbol!);
|
|
986
1328
|
case RecordKind.NodeType: {
|
|
987
|
-
const
|
|
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
|
|
992
|
-
const t = getTypeFromTypeNode(c, node);
|
|
993
|
-
return c.resolvedTypes.get(node
|
|
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 <
|
|
1000
|
-
|
|
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,
|
|
1353
|
+
const typeParameters = outerTypeParametersOf(c, node!, target);
|
|
1005
1354
|
return instantiateTypeWithAlias(
|
|
1006
1355
|
c,
|
|
1007
1356
|
target,
|
|
1008
|
-
newTypeMapper(typeParameters, refs.slice(1, 1 +
|
|
1009
|
-
|
|
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
|
-
|
|
1014
|
-
const
|
|
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
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
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
|
-
|
|
1025
|
-
|
|
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
|
-
(
|
|
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]!,
|
|
1390
|
+
return getIndexTypeEx(c, refs[0]!, f[p]!);
|
|
1040
1391
|
case RecordKind.IndexedAccess:
|
|
1041
|
-
return getIndexedAccessTypeEx(
|
|
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
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
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
|
-
|
|
1052
|
-
|
|
1410
|
+
tag === 1,
|
|
1411
|
+
aliasSymbol === undefined ? undefined : typeAliasOf(c, aliasSymbol, aliasRefs),
|
|
1053
1412
|
);
|
|
1054
1413
|
}
|
|
1055
1414
|
case RecordKind.DistributedConditional: {
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
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 =
|
|
1062
|
-
getConditionalTypeInstantiation(c, declared, newTypeMapper(root.outerTypeParameters, args),
|
|
1063
|
-
const member = c.distributedByKey?.get(
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
|
1165
|
-
*
|
|
1166
|
-
*
|
|
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
|
|
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 (
|
|
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
|
|
1917
|
+
private readonly files = new Map<number, readonly number[]>();
|
|
1402
1918
|
private readonly keys = new IdBits();
|
|
1403
1919
|
|
|
1404
|
-
get(key: number): readonly
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1456
|
-
|
|
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<
|
|
2071
|
+
readonly fileStack: (Set<number> | undefined)[] = [];
|
|
1491
2072
|
|
|
1492
2073
|
/** Adds `files` to those of the computation in progress. */
|
|
1493
|
-
noteFiles(files: readonly
|
|
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<
|
|
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<
|
|
2115
|
+
if (own === undefined) own = new Set<number>();
|
|
1535
2116
|
const file = diagnostics[i]!.file;
|
|
1536
|
-
own.add(file === undefined ?
|
|
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<
|
|
2120
|
+
if (own === undefined) own = new Set<number>();
|
|
1540
2121
|
const file = suggestions[i]!.file;
|
|
1541
|
-
own.add(file === undefined ?
|
|
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<
|
|
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
|
-
//
|
|
1569
|
-
// checkers,
|
|
1570
|
-
|
|
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]
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
'),
|
|
1625
|
-
this.
|
|
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[
|
|
2254
|
+
this.used[usedStride * me] +
|
|
1630
2255
|
' numbers, ' +
|
|
1631
|
-
this.used[
|
|
2256
|
+
this.used[usedStride * me + 1] +
|
|
1632
2257
|
' code units)' +
|
|
1633
|
-
(shareVerifyValue === '1'
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
1710
|
-
const
|
|
1711
|
-
|
|
1712
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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.
|
|
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
|
-
|
|
1872
|
-
if (
|
|
1873
|
-
sharing
|
|
1874
|
-
|
|
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 (
|
|
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.
|
|
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(
|
|
1920
|
-
|
|
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(
|
|
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
|
-
|
|
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',
|
|
2644
|
+
traceTranslation(c, 'types', declaration, producer);
|
|
1944
2645
|
if (sharing.depth === 1) sharing.kind = 2;
|
|
1945
|
-
sharing.forbid(
|
|
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(
|
|
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(
|
|
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
|
-
/**
|
|
2054
|
-
class
|
|
2055
|
-
readonly
|
|
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
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
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
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
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
|
-
|
|
2103
|
-
this.
|
|
2104
|
-
|
|
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
|
-
/**
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
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
|
-
/**
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
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
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
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(
|
|
2159
|
-
|
|
2160
|
-
|
|
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.
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
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
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
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
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
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
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
2210
|
-
const
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
return
|
|
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
|
-
|
|
2271
|
-
this.
|
|
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
|
-
/**
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
readonly types
|
|
2308
|
-
readonly symbols
|
|
2309
|
-
readonly
|
|
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.
|
|
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
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
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
|
-
/**
|
|
2335
|
-
private
|
|
2336
|
-
this.
|
|
2337
|
-
|
|
2338
|
-
|
|
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
|
-
/**
|
|
2350
|
-
|
|
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
|
-
|
|
2359
|
-
|
|
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 (
|
|
2376
|
-
|
|
2377
|
-
|
|
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.
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
)
|
|
2392
|
-
|
|
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.
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
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
|
|
3142
|
+
return this.types.get(root);
|
|
2413
3143
|
}
|
|
2414
3144
|
}
|