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.
Files changed (81) hide show
  1. package/README.md +7 -1
  2. package/dist/api/compiler/ast.d.ts +15 -3
  3. package/dist/api/compiler/ast.js +36 -15
  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-grammar.d.ts +39 -4
  9. package/dist/api/compiler/checker-grammar.js +239 -75
  10. package/dist/api/compiler/checker-heritage.js +20 -14
  11. package/dist/api/compiler/checker-jsx.js +2 -1
  12. package/dist/api/compiler/checker-modules.js +4 -2
  13. package/dist/api/compiler/checker-source-file.js +1 -1
  14. package/dist/api/compiler/checker-type-factory.js +70 -19
  15. package/dist/api/compiler/checker-type-nodes.d.ts +1 -1
  16. package/dist/api/compiler/checker-type-nodes.js +5 -5
  17. package/dist/api/compiler/checker-unused.js +15 -10
  18. package/dist/api/compiler/checker.d.ts +7 -1
  19. package/dist/api/compiler/checker.js +14 -4
  20. package/dist/api/compiler/compile-program.d.ts +5 -0
  21. package/dist/api/compiler/compile-program.js +8 -0
  22. package/dist/api/compiler/expr-record.d.ts +11 -0
  23. package/dist/api/compiler/expr-record.js +53 -0
  24. package/dist/api/compiler/file-loader.d.ts +5 -0
  25. package/dist/api/compiler/file-loader.js +7 -0
  26. package/dist/api/compiler/identifier-name.d.ts +9 -0
  27. package/dist/api/compiler/identifier-name.js +10 -0
  28. package/dist/api/compiler/jsdoc.js +4 -2
  29. package/dist/api/compiler/parser.d.ts +2 -1
  30. package/dist/api/compiler/parser.js +10 -8
  31. package/dist/api/compiler/printer/factory.js +3 -2
  32. package/dist/api/compiler/printer/printer.js +3 -2
  33. package/dist/api/compiler/program-checker.d.ts +18 -0
  34. package/dist/api/compiler/program-checker.js +27 -0
  35. package/dist/api/compiler/reparser.js +4 -3
  36. package/dist/api/compiler/scanner.d.ts +8 -0
  37. package/dist/api/compiler/scanner.js +58 -42
  38. package/dist/api/compiler/shared-results.d.ts +376 -143
  39. package/dist/api/compiler/shared-results.js +1550 -773
  40. package/dist/api/compiler/transformers/declarations.js +2 -1
  41. package/package.json +15 -10
  42. package/src/compiler/ast.ts +39 -15
  43. package/src/compiler/cgroup.ts +157 -0
  44. package/src/compiler/checker-classes.ts +2 -1
  45. package/src/compiler/checker-contextual.ts +104 -0
  46. package/src/compiler/checker-expressions.ts +16 -1
  47. package/src/compiler/checker-grammar.ts +247 -88
  48. package/src/compiler/checker-heritage.ts +31 -17
  49. package/src/compiler/checker-jsx.ts +2 -1
  50. package/src/compiler/checker-modules.ts +4 -2
  51. package/src/compiler/checker-source-file.ts +1 -1
  52. package/src/compiler/checker-type-factory.ts +67 -21
  53. package/src/compiler/checker-type-nodes.ts +5 -5
  54. package/src/compiler/checker-unused.ts +13 -8
  55. package/src/compiler/checker.ts +15 -4
  56. package/src/compiler/compile-program.ts +8 -0
  57. package/src/compiler/decoder.ts +2 -1
  58. package/src/compiler/doctor-report.ts +133 -0
  59. package/src/compiler/expr-record.ts +59 -0
  60. package/src/compiler/file-loader.ts +8 -0
  61. package/src/compiler/identifier-name.ts +13 -0
  62. package/src/compiler/jsdoc.ts +4 -2
  63. package/src/compiler/parallel-check.ts +364 -146
  64. package/src/compiler/parallel-emit.ts +3 -3
  65. package/src/compiler/parse-thread.ts +24 -2
  66. package/src/compiler/parser.ts +10 -8
  67. package/src/compiler/printer/factory.ts +3 -2
  68. package/src/compiler/printer/printer.ts +4 -2
  69. package/src/compiler/program-checker.ts +28 -0
  70. package/src/compiler/reparser.ts +7 -3
  71. package/src/compiler/run-config.ts +1383 -0
  72. package/src/compiler/run-record-read.ts +87 -0
  73. package/src/compiler/run-records.ts +575 -0
  74. package/src/compiler/scanner.ts +57 -41
  75. package/src/compiler/shared-results.ts +1626 -896
  76. package/src/compiler/thread-entry.ts +3 -1
  77. package/src/compiler/transformers/declarations.ts +2 -1
  78. package/src/compiler/tsc-build.ts +64 -6
  79. package/src/compiler/tsc-cli.ts +33 -4
  80. package/src/compiler/tsc-compile.ts +223 -9
  81. 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.2",
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": {
@@ -28,19 +28,24 @@
28
28
  },
29
29
  "./package.json": "./package.json"
30
30
  },
31
- "dependencies": {
32
- "@scriptc/threads": "0.4.7"
31
+ "peerDependencies": {
32
+ "@scriptc/threads": "0.4.8"
33
+ },
34
+ "peerDependenciesMeta": {
35
+ "@scriptc/threads": {
36
+ "optional": true
37
+ }
33
38
  },
34
39
  "engines": {
35
40
  "node": ">=20"
36
41
  },
37
42
  "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"
43
+ "@tsc-ts/darwin-arm64": "0.1.2",
44
+ "@tsc-ts/darwin-x64": "0.1.2",
45
+ "@tsc-ts/linux-arm64-gnu": "0.1.2",
46
+ "@tsc-ts/linux-arm64-musl": "0.1.2",
47
+ "@tsc-ts/linux-x64-gnu": "0.1.2",
48
+ "@tsc-ts/linux-x64-musl": "0.1.2",
49
+ "@tsc-ts/win32-x64-msvc": "0.1.2"
45
50
  }
46
51
  }
@@ -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';
@@ -208,9 +209,14 @@ export abstract class NodeVisitor {
208
209
  }
209
210
  }
210
211
 
211
- // Native builds store `pos`, `end` and `id` as 32-bit integers when every write provably stores an
212
- // int32, so writes of values the compiler can't bound (parameters, the id counter) are coerced
213
- // with `| 0`; the values are always int32.
212
+ // Native builds store `pos`, `end` and `id` as 32-bit integers when every write provably stores
213
+ // an int32, so writes of values the compiler can't bound (parameters, the id counter) are
214
+ // coerced with `| 0`; the values are always int32.
215
+ //
216
+ // The fields of a node that the public API's node types declare are stored fields, not values
217
+ // computed from or packed into another field: a native build copies the stored fields of a node
218
+ // into a record when API code narrows one (`node as StringLiteralLike`), and a field behind an
219
+ // accessor is not copied.
214
220
  export abstract class Node implements ProgramLifetime {
215
221
  readonly kind: SyntaxKind;
216
222
  flags: NodeFlags;
@@ -449,11 +455,15 @@ export class Token extends Node {
449
455
  }
450
456
 
451
457
  export class Identifier extends Node {
452
- text: string;
458
+ readonly identifierName: IdentifierName;
453
459
 
454
- constructor(text: string) {
460
+ constructor(name: IdentifierName) {
455
461
  super(SyntaxKind.Identifier);
456
- this.text = text;
462
+ this.identifierName = name;
463
+ }
464
+
465
+ get text(): string {
466
+ return this.identifierName.text;
457
467
  }
458
468
 
459
469
  forEachChild(_v: NodeVisitor): boolean {
@@ -467,7 +477,7 @@ export class Identifier extends Node {
467
477
  }
468
478
 
469
479
  copy(): Identifier {
470
- const node = new Identifier(this.text);
480
+ const node = new Identifier(this.identifierName);
471
481
  node.flags = this.flags;
472
482
  node.pos = this.pos;
473
483
  node.end = this.end;
@@ -484,13 +494,13 @@ export class ReferenceIdentifier extends Identifier {
484
494
  // Set by the binder.
485
495
  flowNode: FlowNode | undefined;
486
496
 
487
- constructor(text: string) {
488
- super(text);
497
+ constructor(name: IdentifierName) {
498
+ super(name);
489
499
  this.flowNode = undefined;
490
500
  }
491
501
 
492
502
  copy(): Identifier {
493
- const node = new ReferenceIdentifier(this.text);
503
+ const node = new ReferenceIdentifier(this.identifierName);
494
504
  node.flags = this.flags;
495
505
  node.pos = this.pos;
496
506
  node.end = this.end;
@@ -8239,6 +8249,7 @@ export const emptyCommentDirectives: readonly CommentDirective[] = [];
8239
8249
  export const emptyPragmas: readonly Pragma[] = [];
8240
8250
  export const emptyFileReferences: readonly FileReference[] = [];
8241
8251
  export const emptyPatternAmbientModules: readonly PatternAmbientModule[] = [];
8252
+ const emptyNumbers: readonly number[] = [];
8242
8253
 
8243
8254
  /** `list` with `item` appended: a new array when `list` is empty (it may be shared), else `list` itself. */
8244
8255
  export function appendToList<T>(list: readonly T[], item: T): readonly T[] {
@@ -8315,6 +8326,14 @@ export class SourceFile extends Node {
8315
8326
  /** UMD exports from `export as namespace N`. */
8316
8327
  globalExports: SymbolTable | undefined;
8317
8328
 
8329
+ // Set by the grammar checks of a parse thread (checker-grammar.ts).
8330
+ /** The diagnostics the parse thread's grammar walk reported for this file. */
8331
+ grammarDiagnostics: Diagnostic[] | undefined;
8332
+ /** The nodes of this file whose grammar check needs the checker, in tree order. */
8333
+ grammarResidualNodes: Node[] | undefined;
8334
+ /** The blocks and statements that reported a statement in an ambient context. */
8335
+ grammarAmbientReports: readonly number[];
8336
+
8318
8337
  constructor(
8319
8338
  fileName: string,
8320
8339
  path: string,
@@ -8368,6 +8387,9 @@ export class SourceFile extends Node {
8368
8387
  this.symbolCount = 0;
8369
8388
  this.patternAmbientModules = emptyPatternAmbientModules;
8370
8389
  this.globalExports = undefined;
8390
+ this.grammarDiagnostics = undefined;
8391
+ this.grammarResidualNodes = undefined;
8392
+ this.grammarAmbientReports = emptyNumbers;
8371
8393
  }
8372
8394
 
8373
8395
  forEachChild(v: NodeVisitor): boolean {
@@ -8417,20 +8439,22 @@ export class PatternAmbientModule {
8417
8439
  readonly text: string;
8418
8440
  readonly starIndex: number;
8419
8441
  readonly symbol: Symbol;
8442
+ /** The part of `text` before `starIndex`, kept so a match does not copy it. */
8443
+ readonly prefix: string;
8444
+ /** The part of `text` after `starIndex`, kept so a match does not copy it. */
8445
+ readonly suffix: string;
8420
8446
 
8421
8447
  constructor(text: string, starIndex: number, symbol: Symbol) {
8422
8448
  this.text = text;
8423
8449
  this.starIndex = starIndex;
8424
8450
  this.symbol = symbol;
8451
+ this.prefix = text.substring(0, starIndex);
8452
+ this.suffix = text.substring(starIndex + 1);
8425
8453
  }
8426
8454
 
8427
8455
  matches(candidate: string): boolean {
8428
- const text = this.text;
8429
- const star = this.starIndex;
8430
8456
  return (
8431
- candidate.length >= text.length - 1 &&
8432
- candidate.startsWith(text.substring(0, star)) &&
8433
- candidate.endsWith(text.substring(star + 1))
8457
+ candidate.length >= this.text.length - 1 && candidate.startsWith(this.prefix) && candidate.endsWith(this.suffix)
8434
8458
  );
8435
8459
  }
8436
8460
  }
@@ -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