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.
- package/README.md +1 -1
- package/dist/api/compiler/ast.d.ts +9 -3
- package/dist/api/compiler/ast.js +17 -12
- 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-jsx.js +2 -1
- package/dist/api/compiler/checker-type-factory.js +53 -12
- package/dist/api/compiler/checker.d.ts +5 -1
- package/dist/api/compiler/checker.js +10 -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/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/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 +9 -9
- package/src/compiler/ast.ts +19 -12
- 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-jsx.ts +2 -1
- package/src/compiler/checker-type-factory.ts +53 -14
- package/src/compiler/checker.ts +11 -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/identifier-name.ts +13 -0
- package/src/compiler/jsdoc.ts +4 -2
- package/src/compiler/parallel-check.ts +353 -146
- package/src/compiler/parallel-emit.ts +3 -3
- 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/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 +213 -8
- 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.
|
|
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.
|
|
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.
|
|
39
|
-
"@tsc-ts/darwin-x64": "0.1.
|
|
40
|
-
"@tsc-ts/linux-arm64-gnu": "0.1.
|
|
41
|
-
"@tsc-ts/linux-arm64-musl": "0.1.
|
|
42
|
-
"@tsc-ts/linux-x64-gnu": "0.1.
|
|
43
|
-
"@tsc-ts/linux-x64-musl": "0.1.
|
|
44
|
-
"@tsc-ts/win32-x64-msvc": "0.1.
|
|
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
|
}
|
package/src/compiler/ast.ts
CHANGED
|
@@ -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
|
-
|
|
453
|
+
readonly identifierName: IdentifierName;
|
|
453
454
|
|
|
454
|
-
constructor(
|
|
455
|
+
constructor(name: IdentifierName) {
|
|
455
456
|
super(SyntaxKind.Identifier);
|
|
456
|
-
this.
|
|
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.
|
|
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(
|
|
488
|
-
super(
|
|
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.
|
|
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 {
|
|
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
|
-
//
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
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
|
|
1213
|
-
|
|
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
|
-
|
|
1223
|
-
|
|
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.
|
|
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;
|
package/src/compiler/checker.ts
CHANGED
|
@@ -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 {
|
|
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)
|
|
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:
|
|
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
|
-
|
|
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.
|