tsc-ts 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +1 -1
  2. package/dist/api/compiler/ast.d.ts +9 -3
  3. package/dist/api/compiler/ast.js +17 -12
  4. package/dist/api/compiler/checker-classes.js +2 -1
  5. package/dist/api/compiler/checker-contextual.d.ts +20 -0
  6. package/dist/api/compiler/checker-contextual.js +111 -1
  7. package/dist/api/compiler/checker-expressions.js +14 -1
  8. package/dist/api/compiler/checker-jsx.js +2 -1
  9. package/dist/api/compiler/checker-type-factory.js +53 -12
  10. package/dist/api/compiler/checker.d.ts +5 -1
  11. package/dist/api/compiler/checker.js +10 -4
  12. package/dist/api/compiler/compile-program.d.ts +5 -0
  13. package/dist/api/compiler/compile-program.js +8 -0
  14. package/dist/api/compiler/expr-record.d.ts +11 -0
  15. package/dist/api/compiler/expr-record.js +53 -0
  16. package/dist/api/compiler/identifier-name.d.ts +9 -0
  17. package/dist/api/compiler/identifier-name.js +10 -0
  18. package/dist/api/compiler/jsdoc.js +4 -2
  19. package/dist/api/compiler/parser.d.ts +2 -1
  20. package/dist/api/compiler/parser.js +10 -8
  21. package/dist/api/compiler/printer/factory.js +3 -2
  22. package/dist/api/compiler/printer/printer.js +3 -2
  23. package/dist/api/compiler/reparser.js +4 -3
  24. package/dist/api/compiler/scanner.d.ts +8 -0
  25. package/dist/api/compiler/scanner.js +58 -42
  26. package/dist/api/compiler/shared-results.d.ts +376 -143
  27. package/dist/api/compiler/shared-results.js +1550 -773
  28. package/dist/api/compiler/transformers/declarations.js +2 -1
  29. package/package.json +9 -9
  30. package/src/compiler/ast.ts +19 -12
  31. package/src/compiler/cgroup.ts +157 -0
  32. package/src/compiler/checker-classes.ts +2 -1
  33. package/src/compiler/checker-contextual.ts +104 -0
  34. package/src/compiler/checker-expressions.ts +16 -1
  35. package/src/compiler/checker-jsx.ts +2 -1
  36. package/src/compiler/checker-type-factory.ts +53 -14
  37. package/src/compiler/checker.ts +11 -4
  38. package/src/compiler/compile-program.ts +8 -0
  39. package/src/compiler/decoder.ts +2 -1
  40. package/src/compiler/doctor-report.ts +133 -0
  41. package/src/compiler/expr-record.ts +59 -0
  42. package/src/compiler/identifier-name.ts +13 -0
  43. package/src/compiler/jsdoc.ts +4 -2
  44. package/src/compiler/parallel-check.ts +353 -146
  45. package/src/compiler/parallel-emit.ts +3 -3
  46. package/src/compiler/parser.ts +10 -8
  47. package/src/compiler/printer/factory.ts +3 -2
  48. package/src/compiler/printer/printer.ts +4 -2
  49. package/src/compiler/reparser.ts +7 -3
  50. package/src/compiler/run-config.ts +1383 -0
  51. package/src/compiler/run-record-read.ts +87 -0
  52. package/src/compiler/run-records.ts +575 -0
  53. package/src/compiler/scanner.ts +57 -41
  54. package/src/compiler/shared-results.ts +1626 -896
  55. package/src/compiler/thread-entry.ts +3 -1
  56. package/src/compiler/transformers/declarations.ts +2 -1
  57. package/src/compiler/tsc-build.ts +64 -6
  58. package/src/compiler/tsc-cli.ts +33 -4
  59. package/src/compiler/tsc-compile.ts +213 -8
  60. package/src/compiler/tsc-watch.ts +25 -4
@@ -14,6 +14,7 @@ import { DiagnosticContext, canProduceDiagnostics, createDiagnosticForNode, decl
14
14
  import { DeclarationTracker } from './declarations-tracker.js';
15
15
  import { Transformer } from './transformer.js';
16
16
  import { declarationCount, getDeclarations } from '../symbol.js';
17
+ import { IdentifierName } from '../identifier-name.js';
17
18
  export class DeclarationTransformer extends Transformer {
18
19
  host;
19
20
  options;
@@ -1357,7 +1358,7 @@ export class DeclarationTransformer extends Transformer {
1357
1358
  keyword === 79 /* SyntaxKind.Identifier */ ||
1358
1359
  keyword === 89 /* SyntaxKind.DefaultKeyword */) {
1359
1360
  // A parse tree node, so names it uses are resolved where it's written.
1360
- const identifier = new Identifier(text);
1361
+ const identifier = new Identifier(new IdentifierName(text));
1361
1362
  identifier.parent = name.parent;
1362
1363
  return identifier;
1363
1364
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsc-ts",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "The TypeScript 7 compiler, ported to TypeScript and compiled to a native executable.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -29,18 +29,18 @@
29
29
  "./package.json": "./package.json"
30
30
  },
31
31
  "dependencies": {
32
- "@scriptc/threads": "0.4.7"
32
+ "@scriptc/threads": "0.4.8"
33
33
  },
34
34
  "engines": {
35
35
  "node": ">=20"
36
36
  },
37
37
  "optionalDependencies": {
38
- "@tsc-ts/darwin-arm64": "0.1.0",
39
- "@tsc-ts/darwin-x64": "0.1.0",
40
- "@tsc-ts/linux-arm64-gnu": "0.1.0",
41
- "@tsc-ts/linux-arm64-musl": "0.1.0",
42
- "@tsc-ts/linux-x64-gnu": "0.1.0",
43
- "@tsc-ts/linux-x64-musl": "0.1.0",
44
- "@tsc-ts/win32-x64-msvc": "0.1.0"
38
+ "@tsc-ts/darwin-arm64": "0.1.1",
39
+ "@tsc-ts/darwin-x64": "0.1.1",
40
+ "@tsc-ts/linux-arm64-gnu": "0.1.1",
41
+ "@tsc-ts/linux-arm64-musl": "0.1.1",
42
+ "@tsc-ts/linux-x64-gnu": "0.1.1",
43
+ "@tsc-ts/linux-x64-musl": "0.1.1",
44
+ "@tsc-ts/win32-x64-msvc": "0.1.1"
45
45
  }
46
46
  }
@@ -3,6 +3,7 @@
3
3
  // Every node extends `Node`. Child fields are typed by category (`Expression`, `TypeNode`, …),
4
4
  // which are aliases of `Node`; narrow with the `is*` guards in ast-utilities.ts or `instanceof`.
5
5
  import type { ProgramLifetime } from '@scriptc/types';
6
+ import { IdentifierName } from './identifier-name.js';
6
7
  import { SyntaxKind } from './syntax-kind.js';
7
8
  import { type CommentDirective, type CommentRange, type LanguageVariant, TokenFlags } from './types.js';
8
9
  import { type Diagnostic, DiagnosticFile } from './diagnostics.js';
@@ -449,11 +450,15 @@ export class Token extends Node {
449
450
  }
450
451
 
451
452
  export class Identifier extends Node {
452
- text: string;
453
+ readonly identifierName: IdentifierName;
453
454
 
454
- constructor(text: string) {
455
+ constructor(name: IdentifierName) {
455
456
  super(SyntaxKind.Identifier);
456
- this.text = text;
457
+ this.identifierName = name;
458
+ }
459
+
460
+ get text(): string {
461
+ return this.identifierName.text;
457
462
  }
458
463
 
459
464
  forEachChild(_v: NodeVisitor): boolean {
@@ -467,7 +472,7 @@ export class Identifier extends Node {
467
472
  }
468
473
 
469
474
  copy(): Identifier {
470
- const node = new Identifier(this.text);
475
+ const node = new Identifier(this.identifierName);
471
476
  node.flags = this.flags;
472
477
  node.pos = this.pos;
473
478
  node.end = this.end;
@@ -484,13 +489,13 @@ export class ReferenceIdentifier extends Identifier {
484
489
  // Set by the binder.
485
490
  flowNode: FlowNode | undefined;
486
491
 
487
- constructor(text: string) {
488
- super(text);
492
+ constructor(name: IdentifierName) {
493
+ super(name);
489
494
  this.flowNode = undefined;
490
495
  }
491
496
 
492
497
  copy(): Identifier {
493
- const node = new ReferenceIdentifier(this.text);
498
+ const node = new ReferenceIdentifier(this.identifierName);
494
499
  node.flags = this.flags;
495
500
  node.pos = this.pos;
496
501
  node.end = this.end;
@@ -8417,20 +8422,22 @@ export class PatternAmbientModule {
8417
8422
  readonly text: string;
8418
8423
  readonly starIndex: number;
8419
8424
  readonly symbol: Symbol;
8425
+ /** The part of `text` before `starIndex`, kept so a match does not copy it. */
8426
+ readonly prefix: string;
8427
+ /** The part of `text` after `starIndex`, kept so a match does not copy it. */
8428
+ readonly suffix: string;
8420
8429
 
8421
8430
  constructor(text: string, starIndex: number, symbol: Symbol) {
8422
8431
  this.text = text;
8423
8432
  this.starIndex = starIndex;
8424
8433
  this.symbol = symbol;
8434
+ this.prefix = text.substring(0, starIndex);
8435
+ this.suffix = text.substring(starIndex + 1);
8425
8436
  }
8426
8437
 
8427
8438
  matches(candidate: string): boolean {
8428
- const text = this.text;
8429
- const star = this.starIndex;
8430
8439
  return (
8431
- candidate.length >= text.length - 1 &&
8432
- candidate.startsWith(text.substring(0, star)) &&
8433
- candidate.endsWith(text.substring(star + 1))
8440
+ candidate.length >= this.text.length - 1 && candidate.startsWith(this.prefix) && candidate.endsWith(this.suffix)
8434
8441
  );
8435
8442
  }
8436
8443
  }
@@ -0,0 +1,157 @@
1
+ // The limits the process's cgroup puts on the machine a run sees: the whole CPUs its CPU quota allows
2
+ // and the smallest memory limit of its cgroup and that cgroup's ancestors. `os.availableParallelism()`
3
+ // already lowers its count to a quota of one CPU or more; the native runtime's count ignores a quota
4
+ // below one, which truncates to no CPUs there and falls back to the affinity mask, so the quota is read
5
+ // here too. The memory limit only sometimes reaches `process.constrainedMemory()` (libuv reads the
6
+ // process's own cgroup, and scriptc's runtime only the root of the cgroup mount, which is the process's
7
+ // own cgroup only inside a cgroup namespace).
8
+ import { readFileSync } from 'node:fs';
9
+ import { availableParallelism } from 'node:os';
10
+
11
+ /**
12
+ * The CPUs this process may run on: `os.availableParallelism()`, lowered to the whole CPUs its cgroup
13
+ * quota allows (`cgroupCpuQuota`). Where the runtime already applies the quota this returns the same
14
+ * count; a quota below one CPU, which the native runtime's count ignores, lowers it to one. The text and
15
+ * root parameters are for the tests.
16
+ */
17
+ export function availableCpus(membershipText: string = readMembership(), root: string = '/sys/fs/cgroup'): number {
18
+ const cpus = Math.max(1, availableParallelism());
19
+ const quota = cgroupCpuQuota(membershipText, root);
20
+ return quota > 0 && quota < cpus ? quota : cpus;
21
+ }
22
+
23
+ /**
24
+ * The whole CPUs this process's cgroup and its ancestors allow, or 0 where there is none or it is
25
+ * unknown: `cpu.max` under cgroup v2, `cpu.cfs_quota_us` and `cpu.cfs_period_us` under v1. A quota
26
+ * below one CPU counts as one, the least a run can use. The text and root parameters are for the tests.
27
+ */
28
+ export function cgroupCpuQuota(membershipText: string = readMembership(), root: string = '/sys/fs/cgroup'): number {
29
+ let quota = 0;
30
+ const lower = (value: number): void => {
31
+ if (value > 0 && (quota === 0 || value < quota)) quota = value;
32
+ };
33
+ forEachCgroupDirectory(membershipText, root, '', (directory) => {
34
+ lower(cpuMaxValue(directory + '/cpu.max'));
35
+ });
36
+ forEachCgroupDirectory(membershipText, root, 'cpu', (directory) => {
37
+ // The kernel's default period, for a mount that lost the file but kept its quota.
38
+ const period = numberIn(directory + '/cpu.cfs_period_us') || 100_000;
39
+ lower(wholeCpus(numberIn(directory + '/cpu.cfs_quota_us'), period));
40
+ });
41
+ return quota;
42
+ }
43
+
44
+ /**
45
+ * The smallest memory limit of this process's cgroup and its ancestors (Linux), or 0 where there is
46
+ * none or it is unknown: `memory.max` and `memory.high` under cgroup v2, `memory.limit_in_bytes` under
47
+ * v1. The text and root parameters are for the tests.
48
+ */
49
+ export function cgroupMemoryLimit(membershipText: string = readMembership(), root: string = '/sys/fs/cgroup'): number {
50
+ let limit = 0;
51
+ const lower = (value: number): void => {
52
+ if (value > 0 && (limit === 0 || value < limit)) limit = value;
53
+ };
54
+ forEachCgroupDirectory(membershipText, root, '', (directory) => {
55
+ lower(memoryLimitValue(directory + '/memory.max'));
56
+ lower(memoryLimitValue(directory + '/memory.high'));
57
+ });
58
+ forEachCgroupDirectory(membershipText, root, 'memory', (directory) => {
59
+ lower(memoryLimitValue(directory + '/memory.limit_in_bytes'));
60
+ });
61
+ return limit;
62
+ }
63
+
64
+ /**
65
+ * Calls `visit` with the directory of this process's cgroup for `controller` and of each of its
66
+ * ancestors, deepest first: the unified hierarchy for `''` (cgroup v2, whose line lists no
67
+ * controllers), or the controller's own mount for a v1 line that lists it.
68
+ */
69
+ function forEachCgroupDirectory(
70
+ membershipText: string,
71
+ root: string,
72
+ controller: string,
73
+ visit: (directory: string) => void,
74
+ ): void {
75
+ for (const line of membershipText.split('\n')) {
76
+ // "0::/path" (v2), "N:cpu:/path" or "N:cpu,cpuacct:/path" (v1).
77
+ const first = line.indexOf(':');
78
+ const second = line.indexOf(':', first + 1);
79
+ if (first < 0 || second < 0) continue;
80
+ const controllers = line.substring(first + 1, second);
81
+ const path = line.substring(second + 1);
82
+ if (controller === '') {
83
+ if (controllers !== '') continue;
84
+ forEachAncestor(path, (directory) => visit(root + directory));
85
+ } else if (controllers.split(',').indexOf(controller) >= 0) {
86
+ forEachAncestor(path, (directory) => visit(root + '/' + controller + directory));
87
+ }
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Calls `visit` with `path` and each of its ancestors, deepest first ("/a/b/c", "/a/b", "/a", ""): a
93
+ * cgroup's limits apply to every process below it, in cgroup v1 as in v2.
94
+ */
95
+ function forEachAncestor(path: string, visit: (directory: string) => void): void {
96
+ for (let directory = path; ; directory = directory.substring(0, directory.lastIndexOf('/'))) {
97
+ visit(directory === '/' ? '' : directory);
98
+ if (directory === '' || directory.lastIndexOf('/') < 0) break;
99
+ }
100
+ }
101
+
102
+ /** `/proc/self/cgroup`, or an empty text where it cannot be read (no cgroup limits). */
103
+ function readMembership(): string {
104
+ try {
105
+ return readFileSync('/proc/self/cgroup', 'utf8');
106
+ } catch {
107
+ return '';
108
+ }
109
+ }
110
+
111
+ /**
112
+ * The whole CPUs a cgroup v2 quota file allows, or 0 for `max`, an unreadable file or no number:
113
+ * `cpu.max` holds "MAX PERIOD" in microseconds.
114
+ */
115
+ function cpuMaxValue(file: string): number {
116
+ let text: string;
117
+ try {
118
+ text = readFileSync(file, 'utf8').trim();
119
+ } catch {
120
+ return 0;
121
+ }
122
+ if (text === '' || text === 'max') return 0;
123
+ const parts = text.split(/\s+/).map(Number);
124
+ return wholeCpus(parts[0]!, parts[1] ?? 100_000);
125
+ }
126
+
127
+ /** A cgroup memory limit file's value in bytes, or 0 for `max`, an unreadable file or no number. */
128
+ function memoryLimitValue(file: string): number {
129
+ let text: string;
130
+ try {
131
+ text = readFileSync(file, 'utf8').trim();
132
+ } catch {
133
+ return 0;
134
+ }
135
+ if (text === '' || text === 'max') return 0;
136
+ const value = Number(text);
137
+ // cgroup v1 reports no limit as a page-aligned value near 2^63.
138
+ return Number.isFinite(value) && value > 0 && value < 2 ** 62 ? value : 0;
139
+ }
140
+
141
+ /** A file's number, or 0 where it cannot be read or holds none (a v1 quota of -1 is no quota). */
142
+ function numberIn(file: string): number {
143
+ let text: string;
144
+ try {
145
+ text = readFileSync(file, 'utf8').trim();
146
+ } catch {
147
+ return 0;
148
+ }
149
+ const value = Number(text);
150
+ return Number.isFinite(value) && value > 0 ? value : 0;
151
+ }
152
+
153
+ /** The whole CPUs `quota` microseconds per `period` microseconds allow, at least one where there is one. */
154
+ function wholeCpus(quota: number, period: number): number {
155
+ if (!Number.isFinite(quota) || !Number.isFinite(period) || quota <= 0 || period <= 0) return 0;
156
+ return Math.max(1, Math.floor(quota / period));
157
+ }
@@ -107,6 +107,7 @@ import {
107
107
  } from './checker-heritage.js';
108
108
  import { checkDecorators, getFirstTransformableStaticClassElement } from './checker-decorators.js';
109
109
  import { getTypeOfNode } from './checker-services.js';
110
+ import { IdentifierName } from './identifier-name.js';
110
111
 
111
112
  export function checkClassDeclaration(c: Checker, node: Node): void {
112
113
  checkClassLikeDeclaration(c, node);
@@ -588,7 +589,7 @@ function newThisPropertyReference(symbol: Symbol, container: Node, flowNode: Flo
588
589
  const name = symbol.name;
589
590
  let accessName: Node;
590
591
  if (name.startsWith('\ufffe#')) accessName = new PrivateIdentifier(name.substring(name.indexOf('@') + 1));
591
- else accessName = new Identifier(name);
592
+ else accessName = new Identifier(new IdentifierName(name));
592
593
  const thisExpression = new KeywordExpression(SyntaxKind.ThisKeyword);
593
594
  const reference = new PropertyAccessExpression(thisExpression, undefined, accessName, NodeFlags.None);
594
595
  thisExpression.parent = reference;
@@ -7,6 +7,7 @@ import {
7
7
  type Node,
8
8
  type ParameterDeclaration,
9
9
  type PropertyAssignment,
10
+ getArguments,
10
11
  getInitializer,
11
12
  getName,
12
13
  getParameters,
@@ -224,6 +225,109 @@ export function findContextualNode(c: Checker, node: Node, includeCaches: boolea
224
225
  return -1;
225
226
  }
226
227
 
228
+ /**
229
+ * The id of the contextual type in force for `node`, or -1 if the node's contextual type is not on
230
+ * the stack. A key built from -1 can't tell a check under a contextual type that was not pushed from
231
+ * a check with no contextual type, so callers record nothing under -1.
232
+ */
233
+ export function getContextualTypeId(c: Checker, node: Node): number {
234
+ const index = findContextualNode(c, node, true);
235
+ if (index < 0) return -1;
236
+ const t = c.inferenceCaches.contextualTypes[index];
237
+ return t === undefined ? -1 : t.id;
238
+ }
239
+
240
+ /**
241
+ * Whether a check of `node` has no contextual type and no inference context, so its type is a
242
+ * function of the node and the check mode alone: the node is not on the contextual stack, no
243
+ * inference context contains it, and its parent gives it no contextual type. The parents answered
244
+ * here are the ones `getContextualTypeEx` returns `undefined` for without computing anything; a
245
+ * parent it computes a type for is not answered, so the result is never "yes" for a check that
246
+ * could have a contextual type.
247
+ *
248
+ * Types computed inside a flow loop are provisional (the checker does not cache them either), so a
249
+ * check in a loop is not context-free. The same holds while a reduce label is in force, as when
250
+ * flow analysis revisits a `finally` block with the antecedents of one outgoing path: the flow
251
+ * state differs from the one the earlier check of the block saw.
252
+ */
253
+ export function isCheckedWithoutContextualType(c: Checker, node: Node): boolean {
254
+ if (c.flowLoopStack.length !== 0) return false;
255
+ if (c.flowReduceLabels.length !== 0) return false;
256
+ if (findContextualNode(c, node, true) >= 0) return false;
257
+ if (getInferenceContext(c, node) !== undefined) return false;
258
+ const parent = node.parent;
259
+ if (parent === undefined) return true;
260
+ switch (parent.kind) {
261
+ case SyntaxKind.VariableDeclaration:
262
+ case SyntaxKind.Parameter:
263
+ case SyntaxKind.PropertyDeclaration:
264
+ case SyntaxKind.PropertySignature:
265
+ case SyntaxKind.BindingElement:
266
+ // Only the initializer is contextually typed by the declaration.
267
+ return getInitializer(parent) !== node;
268
+ case SyntaxKind.CallExpression:
269
+ case SyntaxKind.NewExpression: {
270
+ // Only the arguments are contextually typed by the parameters; the callee is not.
271
+ const args = getArguments(parent);
272
+ if (args !== undefined) {
273
+ for (const arg of args.nodes) if (arg === node) return false;
274
+ }
275
+ return true;
276
+ }
277
+ case SyntaxKind.ObjectLiteralExpression:
278
+ // Only a method takes the property's contextual type.
279
+ return node.kind !== SyntaxKind.MethodDeclaration;
280
+ case SyntaxKind.BinaryExpression: {
281
+ const binary = parent as BinaryExpression;
282
+ if (binary.type !== undefined) return false;
283
+ switch (binary.operatorToken.kind) {
284
+ case SyntaxKind.EqualsToken:
285
+ case SyntaxKind.AmpersandAmpersandEqualsToken:
286
+ case SyntaxKind.BarBarEqualsToken:
287
+ case SyntaxKind.QuestionQuestionEqualsToken:
288
+ return node !== binary.right;
289
+ case SyntaxKind.BarBarToken:
290
+ case SyntaxKind.QuestionQuestionToken:
291
+ case SyntaxKind.AmpersandAmpersandToken:
292
+ case SyntaxKind.CommaToken:
293
+ return false;
294
+ }
295
+ return true;
296
+ }
297
+ case SyntaxKind.ConditionalExpression: {
298
+ const conditional = parent as ConditionalExpression;
299
+ return node !== conditional.whenTrue && node !== conditional.whenFalse;
300
+ }
301
+ case SyntaxKind.TemplateSpan: {
302
+ // Only a tagged template types its spans.
303
+ const template = parent.parent!;
304
+ return template.parent === undefined || template.parent.kind !== SyntaxKind.TaggedTemplateExpression;
305
+ }
306
+ case SyntaxKind.ArrowFunction:
307
+ case SyntaxKind.ReturnStatement:
308
+ case SyntaxKind.YieldExpression:
309
+ case SyntaxKind.AwaitExpression:
310
+ case SyntaxKind.Decorator:
311
+ case SyntaxKind.AsExpression:
312
+ case SyntaxKind.TypeAssertionExpression:
313
+ case SyntaxKind.SatisfiesExpression:
314
+ case SyntaxKind.ExportAssignment:
315
+ case SyntaxKind.PropertyAssignment:
316
+ case SyntaxKind.ShorthandPropertyAssignment:
317
+ case SyntaxKind.SpreadAssignment:
318
+ case SyntaxKind.ArrayLiteralExpression:
319
+ case SyntaxKind.ParenthesizedExpression:
320
+ case SyntaxKind.NonNullExpression:
321
+ case SyntaxKind.JsxExpression:
322
+ case SyntaxKind.JsxAttribute:
323
+ case SyntaxKind.JsxSpreadAttribute:
324
+ case SyntaxKind.JsxOpeningElement:
325
+ case SyntaxKind.JsxSelfClosingElement:
326
+ return false;
327
+ }
328
+ return true;
329
+ }
330
+
227
331
  export function pushInferenceContext(c: Checker, node: Node, context: InferenceContext | undefined): void {
228
332
  const caches = c.inferenceCaches;
229
333
  const depth = caches.inferenceDepth;
@@ -238,7 +238,11 @@ import {
238
238
  getNarrowedTypeOfSymbol,
239
239
  } from './checker-narrowing.js';
240
240
  import { getFlowTypeOfAutoTypedProperty, getThisTypeOfAssignedFunction } from './checker-javascript.js';
241
- import { checkExpressionWithContextualType, getThisTypeOfObjectLiteralMember } from './checker-contextual.js';
241
+ import {
242
+ checkExpressionWithContextualType,
243
+ getThisTypeOfObjectLiteralMember,
244
+ isCheckedWithoutContextualType,
245
+ } from './checker-contextual.js';
242
246
 
243
247
  import {
244
248
  ContextFlags,
@@ -264,6 +268,16 @@ export function checkExpression(c: Checker, node: Node): Type {
264
268
  }
265
269
 
266
270
  export function checkExpressionEx(c: Checker, node: Node, checkMode: CheckMode): Type {
271
+ // A completed check of this node under this mode answers this one when the check has no
272
+ // contextual type and no inference context: the checker's state only grew since, so this check
273
+ // would produce the type the completed one produced, report the diagnostics it reported and
274
+ // defer the nodes it deferred. Those diagnostics are equal to ones already reported and a node
275
+ // is deferred at most once, so serving the completed type changes neither.
276
+ const recordable = isCheckedWithoutContextualType(c, node);
277
+ if (recordable) {
278
+ const recorded = c.exprCheckRecord.get(node.id, checkMode);
279
+ if (recorded !== undefined) return recorded;
280
+ }
267
281
  const saveCurrentNode = c.currentNode;
268
282
  c.currentNode = node;
269
283
  c.instantiationCount = 0;
@@ -275,6 +289,7 @@ export function checkExpressionEx(c: Checker, node: Node, checkMode: CheckMode):
275
289
  );
276
290
  if (isConstEnumObjectTypeLocal(t)) checkConstEnumAccess(c, node, t);
277
291
  c.currentNode = saveCurrentNode;
292
+ if (recordable) c.exprCheckRecord.set(node.id, checkMode, t);
278
293
  return t;
279
294
  }
280
295
 
@@ -171,6 +171,7 @@ import {
171
171
  SymbolFlags,
172
172
  type SymbolTable,
173
173
  } from './symbol.js';
174
+ import { IdentifierName } from './identifier-name.js';
174
175
 
175
176
  const enum JsxFlags {
176
177
  None = 0,
@@ -1067,7 +1068,7 @@ export function createJsxAttributesTypeFromAttributesProperty(
1067
1068
  // checked as an excess property like the attributes.
1068
1069
  const childrenDeclaration = new PropertySignatureDeclaration(
1069
1070
  undefined,
1070
- new Identifier(jsxChildrenPropertyName),
1071
+ new Identifier(new IdentifierName(jsxChildrenPropertyName)),
1071
1072
  undefined,
1072
1073
  undefined,
1073
1074
  undefined,
@@ -1196,40 +1196,79 @@ export function getUnionTypeEx(
1196
1196
  ): Type {
1197
1197
  if (types.length === 0) return c.neverType;
1198
1198
  if (types.length === 1) return types[0]!;
1199
- // Optimize the common case of unioning a union with some other type, like `undefined`.
1200
- if (
1201
- types.length === 2 &&
1202
- origin === undefined &&
1203
- ((types[0]!.flags & TypeFlags.Union) !== 0 || (types[1]!.flags & TypeFlags.Union) !== 0)
1204
- ) {
1205
- let id1 = types[0]!.id;
1206
- let id2 = types[1]!.id;
1199
+ // Unioning two types is the common case, both a union with some other type, like `undefined`, and
1200
+ // two constituents of a list, and the same pairs recur. The union is a function of the two types,
1201
+ // the reduction and the alias, so the result is cached by the pair. A union that exceeded the
1202
+ // complexity limit was reported at the expression that needed it and comes back as the error
1203
+ // type: caching that would silence the report at the next expression with the same input list, so
1204
+ // an error result is not cached.
1205
+ if (types.length === 2 && origin === undefined) {
1206
+ const t0 = types[0]!;
1207
+ const t1 = types[1]!;
1208
+ let id1 = t0.id;
1209
+ let id2 = t1.id;
1207
1210
  if (id1 > id2) {
1208
1211
  const t = id1;
1209
1212
  id1 = id2;
1210
1213
  id2 = t;
1211
1214
  }
1212
- // Without an alias, both ids and the reduction pack into one exact number.
1213
- if (alias === undefined && id2 < UnionPairIdLimit) {
1215
+ // Without an alias or a constrained intersection, both ids and the reduction pack into one
1216
+ // exact number. A constrained intersection's reduction resolves a type variable's constraint
1217
+ // (`addTypeToUnion`), so those pairs take the general path.
1218
+ if (
1219
+ alias === undefined &&
1220
+ id2 < UnionPairIdLimit &&
1221
+ !isConstrainedIntersection(t0) &&
1222
+ !isConstrainedIntersection(t1)
1223
+ ) {
1214
1224
  const id = (id1 * UnionPairIdLimit + id2) * 4 + unionReduction;
1215
1225
  let result = c.unionOfUnionTypesById.get(id);
1216
1226
  if (result === undefined) {
1217
1227
  result = getUnionTypeWorker(c, types, unionReduction, alias, undefined);
1218
- c.unionOfUnionTypesById.set(id, result);
1228
+ if (result !== c.errorType) c.unionOfUnionTypesById.set(id, result);
1229
+ }
1230
+ return result;
1231
+ }
1232
+ if ((t0.flags & TypeFlags.Union) !== 0 || (t1.flags & TypeFlags.Union) !== 0) {
1233
+ const key = id1 + ',' + id2 + ',' + unionReduction + aliasKey(alias);
1234
+ let result = c.unionOfUnionTypes.get(key);
1235
+ if (result === undefined) {
1236
+ result = getUnionTypeWorker(c, types, unionReduction, alias, undefined);
1237
+ if (result !== c.errorType) c.unionOfUnionTypes.set(key, result);
1219
1238
  }
1220
1239
  return result;
1221
1240
  }
1222
- const key = id1 + ',' + id2 + ',' + unionReduction + aliasKey(alias);
1223
- let result = c.unionOfUnionTypes.get(key);
1241
+ }
1242
+ // Longer lists recur too, from nested unions and from constituents mapped in the same order.
1243
+ // Normalizing a list costs a sort of its constituents and the reductions, and the union is a
1244
+ // function of the list as given, the reduction and the alias, so the result is cached by the list.
1245
+ if (origin === undefined) {
1246
+ const hash = typeListHash(types, alias, unionReduction);
1247
+ let result = c.unionTypesByInputList.get(hash, types, alias, unionReduction);
1224
1248
  if (result === undefined) {
1225
1249
  result = getUnionTypeWorker(c, types, unionReduction, alias, undefined);
1226
- c.unionOfUnionTypes.set(key, result);
1250
+ if (result !== c.errorType && !hasConstrainedIntersection(types))
1251
+ c.unionTypesByInputList.set(hash, types.slice(), alias, unionReduction, result);
1227
1252
  }
1228
1253
  return result;
1229
1254
  }
1230
1255
  return getUnionTypeWorker(c, types, unionReduction, alias, origin);
1231
1256
  }
1232
1257
 
1258
+ /** An intersection that `addTypeToUnion` reduces by resolving a type variable's constraint. */
1259
+ function isConstrainedIntersection(t: Type): boolean {
1260
+ return (t.flags & TypeFlags.Intersection) !== 0 && (t.objectFlags & ObjectFlags.IsConstrainedTypeVariable) !== 0;
1261
+ }
1262
+
1263
+ /** Whether `addTypesToUnion` would set `IncludesConstrainedTypeVariable` for `types`. */
1264
+ function hasConstrainedIntersection(types: readonly Type[]): boolean {
1265
+ for (const t of types) {
1266
+ if (isConstrainedIntersection(t)) return true;
1267
+ if ((t.flags & TypeFlags.Union) !== 0 && hasConstrainedIntersection((t as UnionType).types)) return true;
1268
+ }
1269
+ return false;
1270
+ }
1271
+
1233
1272
  class TypeSet {
1234
1273
  types: Type[] = [];
1235
1274
  includes = 0;
@@ -41,6 +41,7 @@ import {
41
41
  PagedIdSet,
42
42
  SparseLinkTable,
43
43
  } from './link-tables.js';
44
+ import { ExprCheckRecord } from './expr-record.js';
44
45
  import { getErrorRangeForNode, getSourceFileOfNode } from './binder-utilities.js';
45
46
  import {
46
47
  Diagnostic,
@@ -138,7 +139,7 @@ import type { GrammarChecker } from './checker-grammar.js';
138
139
  import type { ResolvedModule } from './module-resolution-types.js';
139
140
  import type { ModuleSpecifierHost } from './module-specifiers.js';
140
141
  import type { PropertyListIndex } from './checker-members.js';
141
- import { shareThreads, startThreadSharing, type ThreadSharing } from './shared-results.js';
142
+ import { startThreadSharing, type ThreadSharing } from './shared-results.js';
142
143
 
143
144
  /** What the checker needs from a program. */
144
145
  export abstract class CheckerHost {
@@ -489,6 +490,8 @@ export class Checker implements TypeChecking {
489
490
  readonly resolvedSymbols = new CompactLinkTable<Symbol>();
490
491
  /** The resolved type of a type node or expression. */
491
492
  readonly resolvedTypes = new CompactLinkTable<Type>();
493
+ /** The type the last completed check of each expression key produced (expr-record.ts). */
494
+ readonly exprCheckRecord = new ExprCheckRecord();
492
495
  /** The resolved signature of a call-like node or a signature declaration. */
493
496
  readonly resolvedSignatures = new CompactLinkTable<Signature>();
494
497
  /** Merged symbols, indexed by the id of the symbol merged into them. */
@@ -542,6 +545,7 @@ export class Checker implements TypeChecking {
542
545
  readonly unionOfUnionTypes = new Map<string, Type>();
543
546
  /** `unionOfUnionTypes` without an alias, by both ids and the reduction packed into one number. */
544
547
  readonly unionOfUnionTypesById = new Map<number, Type>();
548
+ readonly unionTypesByInputList = new TypeListCache<Type>();
545
549
  readonly intersectionTypes = new TypeListCache<Type>();
546
550
  readonly tupleTypes = new Map<string, Type>();
547
551
  readonly indexedAccessTypes = new Map<string, Type>();
@@ -717,7 +721,7 @@ export class Checker implements TypeChecking {
717
721
  deferDiagnostic(node: Node, report: () => void): void {
718
722
  this.deferredDiagnostics.push(report);
719
723
  if (this.deferredDiagnosticFiles !== undefined)
720
- this.deferredDiagnosticFiles.push(getSourceFileOfNode(node).fileName);
724
+ this.deferredDiagnosticFiles.push(this.fileIndex.get(getSourceFileOfNode(node)) ?? -1);
721
725
  }
722
726
 
723
727
  /**
@@ -843,7 +847,7 @@ export class Checker implements TypeChecking {
843
847
  /** The access flags of the key `getIndexedAccessTypeEx` keeps each indexed access type by. */
844
848
  indexedAccessKeyFlags: Map<Type, number> | undefined = undefined;
845
849
  /** Where results are shared between checker threads (shared-results.ts): the file of each diagnostic deferred. */
846
- deferredDiagnosticFiles: string[] | undefined = undefined;
850
+ deferredDiagnosticFiles: number[] | undefined = undefined;
847
851
 
848
852
  constructor(host: CheckerHost) {
849
853
  this.host = host;
@@ -1056,7 +1060,9 @@ export class Checker implements TypeChecking {
1056
1060
  mergeAmbientModulesAndAugmentations(this);
1057
1061
  this.modulesMerged = true;
1058
1062
  this.mergeDiagnostics.push(this.diagnostics.takeLog(), this.suggestionDiagnostics.takeLog());
1059
- if (shareThreads) startThreadSharing(this);
1063
+ // Returns unless this thread was given sharing data (`ThreadSharingData` in its worker data): the
1064
+ // main thread, and every thread of a run that didn't share.
1065
+ startThreadSharing(this);
1060
1066
  if (this.threadSharing !== undefined) {
1061
1067
  this.distributedConditionals = new Map<Type, DistributedConditional>();
1062
1068
  this.distributedByKey = new Map<string, Type>();
@@ -1271,6 +1277,7 @@ export function disposeChecker(c: Checker): void {
1271
1277
  c.unionTypes.clear();
1272
1278
  c.unionOfUnionTypes.clear();
1273
1279
  c.unionOfUnionTypesById.clear();
1280
+ c.unionTypesByInputList.clear();
1274
1281
  c.intersectionTypes.clear();
1275
1282
  c.tupleTypes.clear();
1276
1283
  c.indexedAccessTypes.clear();
@@ -375,6 +375,12 @@ export abstract class CompilerProgram {
375
375
  this.emitter = new ProgramEmitter(checked);
376
376
  }
377
377
 
378
+ /**
379
+ * Whether this run checked the program, which `getDiagnosticsOfProgram` sets when it checks it. A run
380
+ * stopped before checking, by syntax errors or `--noCheck`, keeps no record (`run-records.ts`).
381
+ */
382
+ checkedTheProgram = false;
383
+
378
384
  /**
379
385
  * The time spent in declaration emit while checking, to count as emit time rather than check time, since
380
386
  * the last call. Only an incremental program emits while checking, to compute signatures.
@@ -530,6 +536,8 @@ export function getDiagnosticsOfProgram(
530
536
  // Those found by checking come after the semantic diagnostics, as with a single checker.
531
537
  for (const d of program.getGlobalDiagnostics(false)) result.push(d);
532
538
  if (result.length === configCount) {
539
+ // With `--noCheck` the checker skips every file: the run checked nothing to sample (`run-records.ts`).
540
+ if (options.noCheck !== true) program.checkedTheProgram = true;
533
541
  const checkStart = performance.now();
534
542
  const semantic = program.getSemanticDiagnostics();
535
543
  // Declaration emit that computed signatures while checking counts as emit time, as in TypeScript 7.1.