tsc-ts 0.1.2 → 0.1.3
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/checker-functions.js +5 -1
- package/dist/api/compiler/checker-narrowing.d.ts +7 -0
- package/dist/api/compiler/checker-narrowing.js +31 -4
- package/dist/api/compiler/checker-source-file.js +15 -0
- package/dist/api/compiler/checker-statements.js +11 -2
- package/dist/api/compiler/checker.d.ts +2 -0
- package/dist/api/compiler/checker.js +2 -0
- package/package.json +8 -8
- package/src/compiler/checker-functions.ts +5 -0
- package/src/compiler/checker-narrowing.ts +30 -4
- package/src/compiler/checker-source-file.ts +15 -0
- package/src/compiler/checker-statements.ts +10 -2
- package/src/compiler/checker.ts +2 -0
- package/src/compiler/parallel-check.ts +2 -1
- package/src/compiler/run-records.ts +12 -5
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ npx tsc-ts --noEmit src/index.ts
|
|
|
33
33
|
|
|
34
34
|
`tsc-ts --api` serves the TypeScript API over stdin and stdout, like `tsc --api`. On Windows, `--api --pipe \\.\pipe\<name>` serves it over a named pipe it creates, as `tsc --api --pipe` does, for clients that connect to the pipe (such as scriptc under Node.js); only the native executable supports `--pipe`. On macOS and Linux, `--api --listen <path>` serves it on a Unix socket it creates, with mode 0600, to one client of the same user at a time, each until the client closes its end. Each client gets the answers a new `tsc-ts --api` process would give, except that symbol ids continue from earlier clients, and the server reads every file through the client again, but it keeps parsed files between clients and reuses those whose text is unchanged, along with the encoding `getSourceFile` sends for each, so a client that opens the same project again loads it faster. The server exits when no client connects for 60 seconds (`--idle-exit <seconds>` sets the time), or after a client when its resident memory is above 2.5 times what it held after its first client (`TSC_TS_LISTEN_MEMORY` sets the limit in megabytes). To stop, it removes the socket file, serves the clients that had connected, and exits. It replaces a socket file that no server answers on, such as one left by a server that was killed. Only the native executable supports `--listen`. With `--default-libraries-from-disk`, the server reads the files in its default library directory (`TSC_TS_LIB_DIR`, or the executable's directory) from the disk itself, even when the client handles file system calls (`--callbacks`), which saves a round trip to the client for each library file.
|
|
35
35
|
|
|
36
|
-
`--checkers N` checks the program on N threads. Unless `--checkers` or `--singleThreaded` is given, the native executable uses one checker per CPU it may run on (up to 16), and at least tsgo's four; it uses fewer than the CPUs, but never fewer than four, when the estimated peak memory of more checkers would not fit 85% of the machine's memory (`TSC_TS_CHECKERS=fixed` keeps tsgo's four), and checkers beyond the fourth stop taking files, one at a time, when the process's memory reaches that share; it shares one program between its threads and also parses and binds the program's files on them. Unless `--singleThreaded` is given, the program's files are parsed and bound on threads also with `--checkers 1` and in the first build of `--watch`, with or without build info, as tsgo does. Files that share costly declarations, such as a large generated schema and the few files importing it, are checked together on one checker. The checker threads share the results they compute where the run expects that to pay: at 12 or more checkers, when 40% of the program's files are declarations or come from `node_modules` and the checkers' ranges mostly resolve the same large libraries. `TSC_TS_SHARE=1` turns sharing on for any program and `TSC_TS_SHARE=0` keeps it off, and a run whose sharing stops paying stops sharing its results as it goes. A sharing run may also bound how many results it posts at a time, which costs a few percent on a project that does not gain by it and saves more on one that does: the learned choice among a project's runs, off until its record shows it faster, and `TSC_TS_SHARE_BOUNDED=1` turns it on for any program while `TSC_TS_SHARE_BOUNDED=0` keeps it off. `TSC_TS_AUTO=0` keeps the choices of a run before automatic selection (fewer checkers where the ranges resolve large libraries, no sharing), and `TSC_TS_AUTO=static` keeps the rules and the in-run switches but reads and writes no records, and `TSC_TS_AUTO=explore` adds the experimental exploration described below. Unless `--singleThreaded` is given, the files are parsed on the CPUs the process may run on, but one, whatever the checker count, and `TSC_TS_PARSE_THREADS` sets how many. `--extendedDiagnostics` prints the checker threads, the sharing and the parse threads after the statistics table, under `Suggestions:`, with where each value came from (`pinned: --checkers`, `default`, `automatic: ...`, `learned: check 14% faster over 3 runs`, `exploring`, `control`) and how to pin it; `--build` prints one section per project it builds. The section also names the run's record under `Records:`. Each run that checks a program writes what it measured to `~/.cache/tsc-ts/runs-v1/` (`TSC_TS_CACHE_DIR` sets the directory, `TSC_TS_CACHE=0` turns records off), for later runs of the same project on the same machine to read. A later run chooses from that record where the rules left a setting open: a configuration the samples show faster than the static choice — by 3% or twice the standard error of the gains each of its samples makes against the nearest run of the other arm, on three samples of each taken while the machine was not busy — is adopted, every sixteenth run (and the first after two weeks) re-measures the static choice as a control run, and an adopted configuration whose last three samples all fall behind the control runs reverts at once. A run never tries a configuration that could make itself slower: it adopts only what the samples already show faster, from `tsc-ts doctor`, from a pinned run or from a run that already used it. `TSC_TS_AUTO=explore` turns on an experimental alternative in which a run also tries a setting the signals leave uncertain: one run in eight tries three quarters of the count where the library text puts it near the cut, sharing at eight checkers near the rule's limits, or half the parse threads, and such a run is slower than the configuration it would have used. A program whose shape changed by more than 15% discards the record's samples. Watch mode, a `--build` of several projects at once, `--singleThreaded`, Node builds and `TSC_TS_AUTO=0` or `static` keep no record, and the section says why; a run with `CI` set reads and adopts but never explores. `tsc-ts doctor [-p <tsconfig>] [--runs N] [--budget SECONDS] [--emit] [--json] [--no-save]` measures these settings on this machine: it runs the project's check under the default configuration, with sharing on, with up to 16 checkers, with both, and with half the parse threads, compares the phase times by the same rule automatic selection uses, and prints what it recommends with the evidence for it. Each measured run is a child process, so it runs as a user's run would, and it writes what it measured to the file named by `TSC_TS_DOCTOR_REPORT`; runs without the variable are unaffected. Doctor stores its samples in the record with role `doctor`, unless `--no-save` is given. The command is experimental: the configurations it compares and what it recommends may change. Under Node.js, where every thread builds its own copy of the program, tsc-ts checks on one thread unless `--checkers` is given. `--incremental`, `--watch` and `--build` always check on one thread. The diagnostics are the same as with one checker.
|
|
36
|
+
`--checkers N` checks the program on N threads. Unless `--checkers` or `--singleThreaded` is given, the native executable uses one checker per CPU it may run on (up to 16), and at least tsgo's four; it uses fewer than the CPUs, but never fewer than four, when the estimated peak memory of more checkers would not fit 85% of the machine's memory (`TSC_TS_CHECKERS=fixed` keeps tsgo's four), and checkers beyond the fourth stop taking files, one at a time, when the process's memory reaches that share; it shares one program between its threads and also parses and binds the program's files on them. Unless `--singleThreaded` is given, the program's files are parsed and bound on threads also with `--checkers 1` and in the first build of `--watch`, with or without build info, as tsgo does. Files that share costly declarations, such as a large generated schema and the few files importing it, are checked together on one checker. The checker threads share the results they compute where the run expects that to pay: at 12 or more checkers, when 40% of the program's files are declarations or come from `node_modules` and the checkers' ranges mostly resolve the same large libraries. `TSC_TS_SHARE=1` turns sharing on for any program and `TSC_TS_SHARE=0` keeps it off, and a run whose sharing stops paying stops sharing its results as it goes. A sharing run may also bound how many results it posts at a time, which costs a few percent on a project that does not gain by it and saves more on one that does: the learned choice among a project's runs, off until its record shows it faster, and `TSC_TS_SHARE_BOUNDED=1` turns it on for any program while `TSC_TS_SHARE_BOUNDED=0` keeps it off. `TSC_TS_AUTO=0` keeps the choices of a run before automatic selection (fewer checkers where the ranges resolve large libraries, no sharing), and `TSC_TS_AUTO=static` keeps the rules and the in-run switches but reads and writes no records, and `TSC_TS_AUTO=explore` adds the experimental exploration described below. Unless `--singleThreaded` is given, the files are parsed on the CPUs the process may run on, but one, whatever the checker count, and `TSC_TS_PARSE_THREADS` sets how many. `--extendedDiagnostics` prints the checker threads, the sharing and the parse threads after the statistics table, under `Suggestions:`, with where each value came from (`pinned: --checkers`, `default`, `automatic: ...`, `learned: check 14% faster over 3 runs`, `exploring`, `control`) and how to pin it; `--build` prints one section per project it builds. The section also names the run's record under `Records:`. Each run that checks a program writes what it measured to `~/.cache/tsc-ts/runs-v1/` (`TSC_TS_CACHE_DIR` sets the directory, `TSC_TS_CACHE=0` turns records off), for later runs of the same project on the same machine to read. A later run chooses from that record where the rules left a setting open: a configuration the samples show faster than the static choice — by 3% or twice the standard error of the gains each of its samples makes against the nearest run of the other arm, on three samples of each taken while the machine was not busy — is adopted, every sixteenth run (and the first after two weeks) re-measures the static choice as a control run, and an adopted configuration whose last three samples all fall behind the control runs reverts at once. A run never tries a configuration that could make itself slower: it adopts only what the samples already show faster, from `tsc-ts doctor`, from a pinned run or from a run that already used it. `TSC_TS_AUTO=explore` turns on an experimental alternative in which a run also tries a setting the signals leave uncertain: one run in eight tries three quarters of the count where the library text puts it near the cut, sharing at eight checkers near the rule's limits, or half the parse threads, and such a run is slower than the configuration it would have used. A program whose shape changed by more than 15% discards the record's samples. Watch mode, a `--build` of several projects at once, `--singleThreaded`, Node builds and `TSC_TS_AUTO=0` or `static` keep no record, and the section says why; a run with `CI` set reads and adopts but never explores. `tsc-ts doctor [-p <tsconfig>] [--runs N] [--budget SECONDS] [--emit] [--json] [--no-save]` measures these settings on this machine: it runs the project's check under the default configuration, with sharing on, with up to 16 checkers, with both, and with half the parse threads, compares the phase times by the same rule automatic selection uses, and prints what it recommends with the evidence for it. Each measured run is a child process, so it runs as a user's run would, and it writes what it measured to the file named by `TSC_TS_DOCTOR_REPORT`; runs without the variable are unaffected. Doctor stores its samples in the record with role `doctor`, unless `--no-save` is given. The command is experimental: the configurations it compares and what it recommends may change. Under Node.js, where every thread builds its own copy of the program, tsc-ts checks on one thread unless `--checkers` is given. `--incremental`, `--watch` and `--build` always check on one thread. The diagnostics are the same as with one checker, except for the two a check reports when a type instantiation reaches the depth limit: TS5115 on the instantiations, and TS2344 on the constraint check that stops there. Whether a file reaches the limit depends on the declarations its checker has already resolved, and which files a checker has is decided while checking runs, so those two diagnostics are best effort with more than one checker: one run can report them in different files than another run, and a run with one checker can report them where a run with several does not.
|
|
37
37
|
|
|
38
38
|
The native executable never frees syntax trees, types, signatures or symbols: it keeps them until it exits, which saves the cost of tracking them during a build. A one-shot build is unaffected, but a long-running process grows with each rebuild or project update. Native `--watch` (also with `--build`) runs in a child process, and when a change arrives while that process holds more than 2.5 times the memory it held after its first build, a new process replaces it and builds the change, reporting what the old process would have. `TSC_TS_WATCH_MEMORY` sets that limit in megabytes, and `TSC_TS_WATCH_MEMORY=0` turns the replacement off. A `--api` process grows with each project update; restart it to reclaim the memory.
|
|
39
39
|
|
|
@@ -33,7 +33,7 @@ import { couldContainTypeVariables, instantiateSignature } from './instantiation
|
|
|
33
33
|
import { getContextualSignatureForFunctionLikeDeclaration, getInferenceContext, instantiateContextualType, } from './checker-contextual.js';
|
|
34
34
|
import { checkJSDocFullSignature, checkJSDocFullSignatureType, checkUnmatchedJSDocParameters, getFullSignatureTypeNode, } from './checker-jsdoc.js';
|
|
35
35
|
import { getEffectiveCheckNode, isFunctionLike, nodeIsMissing } from './ast-utilities.js';
|
|
36
|
-
import { getFlowTypeOfReferenceAt, isConstantReference, isReachableFlowNode, isSymbolAssigned, } from './checker-narrowing.js';
|
|
36
|
+
import { endFlowAnalysisStop, getFlowTypeOfReferenceAt, isConstantReference, isReachableFlowNode, isSymbolAssigned, } from './checker-narrowing.js';
|
|
37
37
|
import { FlowNode } from './flow.js';
|
|
38
38
|
import { checkAwaitedType, checkGeneratorInstantiationAssignabilityToReturnType, checkIteratedTypeOrElementType, createGeneratorType, createPromiseReturnType, createPromiseType, getAwaitedTypeEx, getAwaitedTypeNoAlias, getGlobalPromiseTypeChecked, getIterationTypeOfGeneratorFunctionReturnType, getIterationTypeOfIterable, getIterationTypesOfGeneratorFunctionReturnType, getIterationTypesOfIterable, getPromisedTypeOfPromise, isReferenceToType, unwrapAwaitedType, unwrapReturnType, } from './checker-iteration.js';
|
|
39
39
|
import { typeToString } from './type-printer.js';
|
|
@@ -274,6 +274,10 @@ export function checkFunctionLikeBody(c, node) {
|
|
|
274
274
|
if (returnType !== undefined)
|
|
275
275
|
checkReturnExpression(c, node, unwrapReturnType(c, returnType, functionFlags), body, body, exprType, false);
|
|
276
276
|
}
|
|
277
|
+
// A body too large for control flow analysis stops narrowing in the rest of that body only
|
|
278
|
+
// (flowAnalysisDisabled, checker-narrowing.ts); what follows the body starts over. Inferring the
|
|
279
|
+
// return type above checks the body's return expressions, which can reach the limit.
|
|
280
|
+
endFlowAnalysisStop(c, body);
|
|
277
281
|
}
|
|
278
282
|
/** Checks a generator's or async function's return type annotation. */
|
|
279
283
|
function checkSignatureReturnType(c, node, returnTypeNode, functionFlags) {
|
|
@@ -112,6 +112,13 @@ export declare function getEvolvingArrayType(c: Checker, elementType: Type): Typ
|
|
|
112
112
|
export declare function isEvolvingArrayOperationTarget(c: Checker, node: Node): boolean;
|
|
113
113
|
/** The array type an evolving array settles on; `any[]` while it has no elements. */
|
|
114
114
|
export declare function finalizeEvolvingArrayType(c: Checker, t: Type): Type;
|
|
115
|
+
/**
|
|
116
|
+
* Ends the narrowing stopped in `body`, whose checks have finished: what follows the body starts over.
|
|
117
|
+
* A walk that stopped in another body keeps its body's narrowing stopped.
|
|
118
|
+
*/
|
|
119
|
+
export declare function endFlowAnalysisStop(c: Checker, body: Node): void;
|
|
120
|
+
/** Whether `node` is the body of a function or a module, where flow analysis starts over (flowAnalysisDisabled). */
|
|
121
|
+
export declare function isFunctionOrModuleBlock(node: Node): boolean;
|
|
115
122
|
/**
|
|
116
123
|
* The type of `symbol` at `location`. A destructured variable of a const declaration or unassigned
|
|
117
124
|
* parameter is narrowed by checks of its siblings: in `const { kind, payload } = action`, testing
|
|
@@ -182,7 +182,7 @@ export function getFlowTypeOfReference(c, reference, declaredType, initialType,
|
|
|
182
182
|
}
|
|
183
183
|
/** `getFlowTypeOfReference` at `flowNode`, for pseudo-references such as binding patterns. */
|
|
184
184
|
export function getFlowTypeOfReferenceAt(c, reference, declaredType, initialType, flowContainer, flowNode) {
|
|
185
|
-
if (c.flowAnalysisDisabled)
|
|
185
|
+
if (c.flowAnalysisDisabled && isInFlowAnalysisStop(c, reference))
|
|
186
186
|
return c.errorType;
|
|
187
187
|
if (flowNode === undefined)
|
|
188
188
|
return declaredType;
|
|
@@ -211,7 +211,7 @@ export function getFlowTypeOfReferenceAt(c, reference, declaredType, initialType
|
|
|
211
211
|
f.steps >= minFlowWalkSteps &&
|
|
212
212
|
!f.incomplete &&
|
|
213
213
|
!f.positional &&
|
|
214
|
-
!c.flowAnalysisDisabled &&
|
|
214
|
+
!(c.flowAnalysisDisabled && isInFlowAnalysisStop(c, reference)) &&
|
|
215
215
|
c.resolutionCycleCount === cycles) {
|
|
216
216
|
const walk = f.walk;
|
|
217
217
|
if (walk !== undefined) {
|
|
@@ -248,6 +248,7 @@ function complete(f, t) {
|
|
|
248
248
|
export function getTypeAtFlowNode(c, f, start) {
|
|
249
249
|
if (f.depth === 2000) {
|
|
250
250
|
c.flowAnalysisDisabled = true;
|
|
251
|
+
c.flowAnalysisDisabledBody = getContainingFlowBody(f.reference);
|
|
251
252
|
reportFlowControlError(c, f.reference);
|
|
252
253
|
return complete(f, c.errorType);
|
|
253
254
|
}
|
|
@@ -2759,17 +2760,43 @@ function getContextFreeTypeOfExpression(c, node) {
|
|
|
2759
2760
|
links.contextFreeType = t;
|
|
2760
2761
|
return t;
|
|
2761
2762
|
}
|
|
2762
|
-
|
|
2763
|
+
/** The body whose walk reached the flow limit, or undefined when no body is stopped. */
|
|
2764
|
+
function getContainingFlowBody(node) {
|
|
2763
2765
|
let block = node;
|
|
2764
2766
|
while (block !== undefined && !isFunctionOrModuleBlock(block))
|
|
2765
2767
|
block = block.parent;
|
|
2768
|
+
return block;
|
|
2769
|
+
}
|
|
2770
|
+
/** Whether `node` is in the body whose walk reached the flow limit, or in a body it contains. */
|
|
2771
|
+
function isInFlowAnalysisStop(c, node) {
|
|
2772
|
+
const stop = c.flowAnalysisDisabledBody;
|
|
2773
|
+
if (stop === undefined)
|
|
2774
|
+
return false;
|
|
2775
|
+
for (let n = node; n !== undefined; n = n.parent)
|
|
2776
|
+
if (n === stop)
|
|
2777
|
+
return true;
|
|
2778
|
+
return false;
|
|
2779
|
+
}
|
|
2780
|
+
/**
|
|
2781
|
+
* Ends the narrowing stopped in `body`, whose checks have finished: what follows the body starts over.
|
|
2782
|
+
* A walk that stopped in another body keeps its body's narrowing stopped.
|
|
2783
|
+
*/
|
|
2784
|
+
export function endFlowAnalysisStop(c, body) {
|
|
2785
|
+
if (c.flowAnalysisDisabledBody !== body)
|
|
2786
|
+
return;
|
|
2787
|
+
c.flowAnalysisDisabled = false;
|
|
2788
|
+
c.flowAnalysisDisabledBody = undefined;
|
|
2789
|
+
}
|
|
2790
|
+
function reportFlowControlError(c, node) {
|
|
2791
|
+
const block = getContainingFlowBody(node);
|
|
2766
2792
|
if (block === undefined)
|
|
2767
2793
|
return;
|
|
2768
2794
|
const file = getSourceFileOfNode(node);
|
|
2769
2795
|
const span = getRangeOfTokenAtPosition(file, getStatements(block).pos);
|
|
2770
2796
|
c.addDiagnostic(new Diagnostic(file.diagnosticFile(), span.pos, span.end, 724 /* Diagnostics.The_containing_function_or_module_body_is_too_large_for_control_flow_analysis */, []));
|
|
2771
2797
|
}
|
|
2772
|
-
function
|
|
2798
|
+
/** Whether `node` is the body of a function or a module, where flow analysis starts over (flowAnalysisDisabled). */
|
|
2799
|
+
export function isFunctionOrModuleBlock(node) {
|
|
2773
2800
|
return (node.kind === 308 /* SyntaxKind.SourceFile */ ||
|
|
2774
2801
|
node.kind === 270 /* SyntaxKind.ModuleBlock */ ||
|
|
2775
2802
|
(node.kind === 243 /* SyntaxKind.Block */ && node.parent !== undefined && isFunctionLike(node.parent)));
|
|
@@ -43,6 +43,11 @@ import { checkContextualDeprecations, checkDeprecatedTypeReference } from './che
|
|
|
43
43
|
* identifiers, which need the whole file checked first.
|
|
44
44
|
*/
|
|
45
45
|
export function checkSourceFile(c, file, checkUnused = false) {
|
|
46
|
+
// A source file is a module body: narrowing stopped by a body too large for control flow analysis
|
|
47
|
+
// (flowAnalysisDisabled, checker-narrowing.ts) starts over in the next check. A checker checks many files
|
|
48
|
+
// (parallel-check.ts), so its diagnostics of a file must not depend on the files it checked before it.
|
|
49
|
+
c.flowAnalysisDisabled = false;
|
|
50
|
+
c.flowAnalysisDisabledBody = undefined;
|
|
46
51
|
const links = c.nodeLinks(file);
|
|
47
52
|
if ((links.flags & 1 /* NodeCheckFlags.TypeChecked */) === 0) {
|
|
48
53
|
links.flags |= 1 /* NodeCheckFlags.TypeChecked */;
|
|
@@ -52,6 +57,9 @@ export function checkSourceFile(c, file, checkUnused = false) {
|
|
|
52
57
|
links.flags |= 2 /* NodeCheckFlags.UnusedChecked */;
|
|
53
58
|
checkUnusedIdentifiers(c, file);
|
|
54
59
|
}
|
|
60
|
+
// Every body of the file has finished its checks; a stop recorded for one of them ends with the file.
|
|
61
|
+
c.flowAnalysisDisabled = false;
|
|
62
|
+
c.flowAnalysisDisabledBody = undefined;
|
|
55
63
|
}
|
|
56
64
|
function checkSourceFileWorker(c, file) {
|
|
57
65
|
c.fileBeingChecked = file;
|
|
@@ -89,6 +97,10 @@ function checkSourceFileWorker(c, file) {
|
|
|
89
97
|
*/
|
|
90
98
|
export function checkSourceFileRange(c, file, start, end) {
|
|
91
99
|
const statements = getStatements(file).nodes;
|
|
100
|
+
// A range of a file's statements is part of its module body: narrowing stopped by a body too large for
|
|
101
|
+
// control flow analysis (flowAnalysisDisabled, checker-narrowing.ts) starts over in the next range.
|
|
102
|
+
c.flowAnalysisDisabled = false;
|
|
103
|
+
c.flowAnalysisDisabledBody = undefined;
|
|
92
104
|
c.fileBeingChecked = file;
|
|
93
105
|
for (let i = start; i < end; i++)
|
|
94
106
|
checkSourceElement(c, statements[i]);
|
|
@@ -106,6 +118,9 @@ export function checkSourceFileRange(c, file, start, end) {
|
|
|
106
118
|
produceDeferredDiagnostics(c);
|
|
107
119
|
// Deferred nodes of a later range are queued again (checkNodeDeferred).
|
|
108
120
|
c.nodeLinks(file).deferredNodes = undefined;
|
|
121
|
+
// The range's bodies have finished their checks; a stop recorded for one of them ends with the range.
|
|
122
|
+
c.flowAnalysisDisabled = false;
|
|
123
|
+
c.flowAnalysisDisabledBody = undefined;
|
|
109
124
|
return c.diagnostics.forFile(file.fileName).length > 0;
|
|
110
125
|
}
|
|
111
126
|
/**
|
|
@@ -7,7 +7,7 @@ import { checkIteratedTypeOrElementType } from './checker-iteration.js';
|
|
|
7
7
|
import { checkDestructuringAssignment } from './checker-destructuring.js';
|
|
8
8
|
import { checkReferenceExpression, checkTestingKnownTruthyType, checkTruthinessExpression, isTypeEqualityComparableTo, } from './checker-operators.js';
|
|
9
9
|
import { checkTypeComparableTo, isTypeAssignableTo } from './relations.js';
|
|
10
|
-
import { isReachableFlowNode } from './checker-narrowing.js';
|
|
10
|
+
import { endFlowAnalysisStop, isFunctionOrModuleBlock, isReachableFlowNode } from './checker-narrowing.js';
|
|
11
11
|
import { getIndexType, isTypeAssignableToKindEx as isTypeAssignableToKind } from './checker-type-operators.js';
|
|
12
12
|
import { getExtractStringType } from './checker-symbol-types.js';
|
|
13
13
|
import { checkSourceElement } from './checker-source-file.js';
|
|
@@ -21,8 +21,17 @@ import { checkExternalEmitHelpers } from './checker-emit-helpers.js';
|
|
|
21
21
|
import { typeToString } from './type-printer.js';
|
|
22
22
|
import { getNonNullableType, hasTypeFacts } from './checker-flow.js';
|
|
23
23
|
export function checkBlock(c, node) {
|
|
24
|
-
|
|
24
|
+
const statements = getStatements(node).nodes;
|
|
25
|
+
if (!isFunctionOrModuleBlock(node)) {
|
|
26
|
+
for (const s of statements)
|
|
27
|
+
checkSourceElement(c, s);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
// A body too large for control flow analysis stops narrowing in the rest of that body only
|
|
31
|
+
// (flowAnalysisDisabled, checker-narrowing.ts); what follows the body starts over.
|
|
32
|
+
for (const s of statements)
|
|
25
33
|
checkSourceElement(c, s);
|
|
34
|
+
endFlowAnalysisStop(c, node);
|
|
26
35
|
}
|
|
27
36
|
export function checkExpressionStatement(c, node) {
|
|
28
37
|
checkExpression(c, getExpression(node));
|
|
@@ -466,6 +466,8 @@ export declare class Checker implements TypeChecking {
|
|
|
466
466
|
/** `{ meta: ImportMeta }`, the type of `import.meta` for symbol queries. */
|
|
467
467
|
importMetaExpressionType: Type | undefined;
|
|
468
468
|
flowAnalysisDisabled: boolean;
|
|
469
|
+
/** The body whose walk reached the flow limit (flowAnalysisDisabled), until its checks finish. */
|
|
470
|
+
flowAnalysisDisabledBody: Node | undefined;
|
|
469
471
|
inlineLevel: number;
|
|
470
472
|
/**
|
|
471
473
|
* Shared flow nodes cached by the walks in progress, with their types: the first
|
|
@@ -612,6 +612,8 @@ export class Checker {
|
|
|
612
612
|
importMetaExpressionType = undefined;
|
|
613
613
|
// Control flow analysis
|
|
614
614
|
flowAnalysisDisabled = false;
|
|
615
|
+
/** The body whose walk reached the flow limit (flowAnalysisDisabled), until its checks finish. */
|
|
616
|
+
flowAnalysisDisabledBody = undefined;
|
|
615
617
|
inlineLevel = 0;
|
|
616
618
|
/**
|
|
617
619
|
* Shared flow nodes cached by the walks in progress, with their types: the first
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tsc-ts",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
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": {
|
|
@@ -40,12 +40,12 @@
|
|
|
40
40
|
"node": ">=20"
|
|
41
41
|
},
|
|
42
42
|
"optionalDependencies": {
|
|
43
|
-
"@tsc-ts/darwin-arm64": "0.1.
|
|
44
|
-
"@tsc-ts/darwin-x64": "0.1.
|
|
45
|
-
"@tsc-ts/linux-arm64-gnu": "0.1.
|
|
46
|
-
"@tsc-ts/linux-arm64-musl": "0.1.
|
|
47
|
-
"@tsc-ts/linux-x64-gnu": "0.1.
|
|
48
|
-
"@tsc-ts/linux-x64-musl": "0.1.
|
|
49
|
-
"@tsc-ts/win32-x64-msvc": "0.1.
|
|
43
|
+
"@tsc-ts/darwin-arm64": "0.1.3",
|
|
44
|
+
"@tsc-ts/darwin-x64": "0.1.3",
|
|
45
|
+
"@tsc-ts/linux-arm64-gnu": "0.1.3",
|
|
46
|
+
"@tsc-ts/linux-arm64-musl": "0.1.3",
|
|
47
|
+
"@tsc-ts/linux-x64-gnu": "0.1.3",
|
|
48
|
+
"@tsc-ts/linux-x64-musl": "0.1.3",
|
|
49
|
+
"@tsc-ts/win32-x64-msvc": "0.1.3"
|
|
50
50
|
}
|
|
51
51
|
}
|
|
@@ -114,6 +114,7 @@ import {
|
|
|
114
114
|
} from './checker-jsdoc.js';
|
|
115
115
|
import { getEffectiveCheckNode, isFunctionLike, nodeIsMissing } from './ast-utilities.js';
|
|
116
116
|
import {
|
|
117
|
+
endFlowAnalysisStop,
|
|
117
118
|
getFlowTypeOfReferenceAt,
|
|
118
119
|
isConstantReference,
|
|
119
120
|
isReachableFlowNode,
|
|
@@ -437,6 +438,10 @@ export function checkFunctionLikeBody(c: Checker, node: Node): void {
|
|
|
437
438
|
if (returnType !== undefined)
|
|
438
439
|
checkReturnExpression(c, node, unwrapReturnType(c, returnType, functionFlags), body, body, exprType, false);
|
|
439
440
|
}
|
|
441
|
+
// A body too large for control flow analysis stops narrowing in the rest of that body only
|
|
442
|
+
// (flowAnalysisDisabled, checker-narrowing.ts); what follows the body starts over. Inferring the
|
|
443
|
+
// return type above checks the body's return expressions, which can reach the limit.
|
|
444
|
+
endFlowAnalysisStop(c, body);
|
|
440
445
|
}
|
|
441
446
|
|
|
442
447
|
/** Checks a generator's or async function's return type annotation. */
|
|
@@ -369,7 +369,7 @@ export function getFlowTypeOfReferenceAt(
|
|
|
369
369
|
flowContainer: Node | undefined,
|
|
370
370
|
flowNode: FlowNode | undefined,
|
|
371
371
|
): Type {
|
|
372
|
-
if (c.flowAnalysisDisabled) return c.errorType;
|
|
372
|
+
if (c.flowAnalysisDisabled && isInFlowAnalysisStop(c, reference)) return c.errorType;
|
|
373
373
|
if (flowNode === undefined) return declaredType;
|
|
374
374
|
let f: FlowState;
|
|
375
375
|
const stateDepth = c.flowStateDepth;
|
|
@@ -396,7 +396,7 @@ export function getFlowTypeOfReferenceAt(
|
|
|
396
396
|
f.steps >= minFlowWalkSteps &&
|
|
397
397
|
!f.incomplete &&
|
|
398
398
|
!f.positional &&
|
|
399
|
-
!c.flowAnalysisDisabled &&
|
|
399
|
+
!(c.flowAnalysisDisabled && isInFlowAnalysisStop(c, reference)) &&
|
|
400
400
|
c.resolutionCycleCount === cycles
|
|
401
401
|
) {
|
|
402
402
|
const walk = f.walk;
|
|
@@ -438,6 +438,7 @@ function complete(f: FlowState, t: Type): Type {
|
|
|
438
438
|
export function getTypeAtFlowNode(c: Checker, f: FlowState, start: FlowNode): Type {
|
|
439
439
|
if (f.depth === 2000) {
|
|
440
440
|
c.flowAnalysisDisabled = true;
|
|
441
|
+
c.flowAnalysisDisabledBody = getContainingFlowBody(f.reference);
|
|
441
442
|
reportFlowControlError(c, f.reference);
|
|
442
443
|
return complete(f, c.errorType);
|
|
443
444
|
}
|
|
@@ -3148,9 +3149,33 @@ function getContextFreeTypeOfExpression(c: Checker, node: Node): Type {
|
|
|
3148
3149
|
return t;
|
|
3149
3150
|
}
|
|
3150
3151
|
|
|
3151
|
-
|
|
3152
|
+
/** The body whose walk reached the flow limit, or undefined when no body is stopped. */
|
|
3153
|
+
function getContainingFlowBody(node: Node): Node | undefined {
|
|
3152
3154
|
let block: Node | undefined = node;
|
|
3153
3155
|
while (block !== undefined && !isFunctionOrModuleBlock(block)) block = block.parent;
|
|
3156
|
+
return block;
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
/** Whether `node` is in the body whose walk reached the flow limit, or in a body it contains. */
|
|
3160
|
+
function isInFlowAnalysisStop(c: Checker, node: Node): boolean {
|
|
3161
|
+
const stop = c.flowAnalysisDisabledBody;
|
|
3162
|
+
if (stop === undefined) return false;
|
|
3163
|
+
for (let n: Node | undefined = node; n !== undefined; n = n.parent) if (n === stop) return true;
|
|
3164
|
+
return false;
|
|
3165
|
+
}
|
|
3166
|
+
|
|
3167
|
+
/**
|
|
3168
|
+
* Ends the narrowing stopped in `body`, whose checks have finished: what follows the body starts over.
|
|
3169
|
+
* A walk that stopped in another body keeps its body's narrowing stopped.
|
|
3170
|
+
*/
|
|
3171
|
+
export function endFlowAnalysisStop(c: Checker, body: Node): void {
|
|
3172
|
+
if (c.flowAnalysisDisabledBody !== body) return;
|
|
3173
|
+
c.flowAnalysisDisabled = false;
|
|
3174
|
+
c.flowAnalysisDisabledBody = undefined;
|
|
3175
|
+
}
|
|
3176
|
+
|
|
3177
|
+
function reportFlowControlError(c: Checker, node: Node): void {
|
|
3178
|
+
const block = getContainingFlowBody(node);
|
|
3154
3179
|
if (block === undefined) return;
|
|
3155
3180
|
const file = getSourceFileOfNode(node);
|
|
3156
3181
|
const span = getRangeOfTokenAtPosition(file, getStatements(block)!.pos);
|
|
@@ -3165,7 +3190,8 @@ function reportFlowControlError(c: Checker, node: Node): void {
|
|
|
3165
3190
|
);
|
|
3166
3191
|
}
|
|
3167
3192
|
|
|
3168
|
-
function
|
|
3193
|
+
/** Whether `node` is the body of a function or a module, where flow analysis starts over (flowAnalysisDisabled). */
|
|
3194
|
+
export function isFunctionOrModuleBlock(node: Node): boolean {
|
|
3169
3195
|
return (
|
|
3170
3196
|
node.kind === SyntaxKind.SourceFile ||
|
|
3171
3197
|
node.kind === SyntaxKind.ModuleBlock ||
|
|
@@ -132,6 +132,11 @@ import { checkContextualDeprecations, checkDeprecatedTypeReference } from './che
|
|
|
132
132
|
* identifiers, which need the whole file checked first.
|
|
133
133
|
*/
|
|
134
134
|
export function checkSourceFile(c: Checker, file: SourceFile, checkUnused = false): void {
|
|
135
|
+
// A source file is a module body: narrowing stopped by a body too large for control flow analysis
|
|
136
|
+
// (flowAnalysisDisabled, checker-narrowing.ts) starts over in the next check. A checker checks many files
|
|
137
|
+
// (parallel-check.ts), so its diagnostics of a file must not depend on the files it checked before it.
|
|
138
|
+
c.flowAnalysisDisabled = false;
|
|
139
|
+
c.flowAnalysisDisabledBody = undefined;
|
|
135
140
|
const links = c.nodeLinks(file);
|
|
136
141
|
if ((links.flags & NodeCheckFlags.TypeChecked) === 0) {
|
|
137
142
|
links.flags |= NodeCheckFlags.TypeChecked;
|
|
@@ -141,6 +146,9 @@ export function checkSourceFile(c: Checker, file: SourceFile, checkUnused = fals
|
|
|
141
146
|
links.flags |= NodeCheckFlags.UnusedChecked;
|
|
142
147
|
checkUnusedIdentifiers(c, file);
|
|
143
148
|
}
|
|
149
|
+
// Every body of the file has finished its checks; a stop recorded for one of them ends with the file.
|
|
150
|
+
c.flowAnalysisDisabled = false;
|
|
151
|
+
c.flowAnalysisDisabledBody = undefined;
|
|
144
152
|
}
|
|
145
153
|
|
|
146
154
|
function checkSourceFileWorker(c: Checker, file: SourceFile): void {
|
|
@@ -176,6 +184,10 @@ function checkSourceFileWorker(c: Checker, file: SourceFile): void {
|
|
|
176
184
|
*/
|
|
177
185
|
export function checkSourceFileRange(c: Checker, file: SourceFile, start: number, end: number): boolean {
|
|
178
186
|
const statements = getStatements(file)!.nodes;
|
|
187
|
+
// A range of a file's statements is part of its module body: narrowing stopped by a body too large for
|
|
188
|
+
// control flow analysis (flowAnalysisDisabled, checker-narrowing.ts) starts over in the next range.
|
|
189
|
+
c.flowAnalysisDisabled = false;
|
|
190
|
+
c.flowAnalysisDisabledBody = undefined;
|
|
179
191
|
c.fileBeingChecked = file;
|
|
180
192
|
for (let i = start; i < end; i++) checkSourceElement(c, statements[i]!);
|
|
181
193
|
checkDeferredNodes(c, file);
|
|
@@ -191,6 +203,9 @@ export function checkSourceFileRange(c: Checker, file: SourceFile, start: number
|
|
|
191
203
|
produceDeferredDiagnostics(c);
|
|
192
204
|
// Deferred nodes of a later range are queued again (checkNodeDeferred).
|
|
193
205
|
c.nodeLinks(file).deferredNodes = undefined;
|
|
206
|
+
// The range's bodies have finished their checks; a stop recorded for one of them ends with the range.
|
|
207
|
+
c.flowAnalysisDisabled = false;
|
|
208
|
+
c.flowAnalysisDisabledBody = undefined;
|
|
194
209
|
return c.diagnostics.forFile(file.fileName).length > 0;
|
|
195
210
|
}
|
|
196
211
|
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
isTypeEqualityComparableTo,
|
|
43
43
|
} from './checker-operators.js';
|
|
44
44
|
import { checkTypeComparableTo, isTypeAssignableTo } from './relations.js';
|
|
45
|
-
import { isReachableFlowNode } from './checker-narrowing.js';
|
|
45
|
+
import { endFlowAnalysisStop, isFunctionOrModuleBlock, isReachableFlowNode } from './checker-narrowing.js';
|
|
46
46
|
import { getIndexType, isTypeAssignableToKindEx as isTypeAssignableToKind } from './checker-type-operators.js';
|
|
47
47
|
import { getExtractStringType } from './checker-symbol-types.js';
|
|
48
48
|
import { checkSourceElement } from './checker-source-file.js';
|
|
@@ -57,7 +57,15 @@ import { typeToString } from './type-printer.js';
|
|
|
57
57
|
import { TypeFacts, getNonNullableType, hasTypeFacts } from './checker-flow.js';
|
|
58
58
|
|
|
59
59
|
export function checkBlock(c: Checker, node: Node): void {
|
|
60
|
-
|
|
60
|
+
const statements = getStatements(node)!.nodes;
|
|
61
|
+
if (!isFunctionOrModuleBlock(node)) {
|
|
62
|
+
for (const s of statements) checkSourceElement(c, s);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
// A body too large for control flow analysis stops narrowing in the rest of that body only
|
|
66
|
+
// (flowAnalysisDisabled, checker-narrowing.ts); what follows the body starts over.
|
|
67
|
+
for (const s of statements) checkSourceElement(c, s);
|
|
68
|
+
endFlowAnalysisStop(c, node);
|
|
61
69
|
}
|
|
62
70
|
|
|
63
71
|
export function checkExpressionStatement(c: Checker, node: Node): void {
|
package/src/compiler/checker.ts
CHANGED
|
@@ -755,6 +755,8 @@ export class Checker implements TypeChecking {
|
|
|
755
755
|
|
|
756
756
|
// Control flow analysis
|
|
757
757
|
flowAnalysisDisabled = false;
|
|
758
|
+
/** The body whose walk reached the flow limit (flowAnalysisDisabled), until its checks finish. */
|
|
759
|
+
flowAnalysisDisabledBody: Node | undefined = undefined;
|
|
758
760
|
inlineLevel = 0;
|
|
759
761
|
/**
|
|
760
762
|
* Shared flow nodes cached by the walks in progress, with their types: the first
|
|
@@ -2161,7 +2161,8 @@ function checkSignals(
|
|
|
2161
2161
|
}
|
|
2162
2162
|
signals.library = library / ranges;
|
|
2163
2163
|
signals.own = own / ranges;
|
|
2164
|
-
|
|
2164
|
+
// Every range can be empty (all of a program's files in the shared queue), so `library + own` can be 0.
|
|
2165
|
+
signals.redundancy = library + own === 0 ? 0 : (library - unionChars) / (library + own);
|
|
2165
2166
|
signals.declarationShare = declarationFiles / files.length;
|
|
2166
2167
|
signals.nodeModulesShare = nodeModulesFiles / files.length;
|
|
2167
2168
|
if (config !== undefined) config.signals = signals;
|
|
@@ -282,10 +282,17 @@ export function readRecord(file: string, now: number = Math.floor(Date.now() / 1
|
|
|
282
282
|
return dropOldSamples(record, now);
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
+
/**
|
|
286
|
+
* A parsed value whose fields are read as `unknown` until they are checked. A native build checks a
|
|
287
|
+
* cast to a record type such as `Partial<RunRecord>` when it converts the value, and throws on a field
|
|
288
|
+
* of the wrong type, so a malformed record file would end the run instead of being ignored.
|
|
289
|
+
*/
|
|
290
|
+
type Unchecked<T> = { [K in keyof T]?: unknown };
|
|
291
|
+
|
|
285
292
|
/** `parsed` as a record, or `undefined` where it is not one this build writes. */
|
|
286
293
|
function recordOf(parsed: unknown): RunRecord | undefined {
|
|
287
294
|
if (typeof parsed !== 'object' || parsed === null) return undefined;
|
|
288
|
-
const r = parsed as
|
|
295
|
+
const r = parsed as Unchecked<RunRecord>;
|
|
289
296
|
if (r.format !== recordFormat || typeof r.runs !== 'number' || !isShape(r.shape) || !isChoice(r.chosen))
|
|
290
297
|
return undefined;
|
|
291
298
|
const samples = Array.isArray(r.samples) ? r.samples.filter(isSample) : [];
|
|
@@ -304,7 +311,7 @@ function recordOf(parsed: unknown): RunRecord | undefined {
|
|
|
304
311
|
|
|
305
312
|
function isShape(value: unknown): value is RecordShape {
|
|
306
313
|
if (typeof value !== 'object' || value === null) return false;
|
|
307
|
-
const s = value as
|
|
314
|
+
const s = value as Unchecked<RecordShape>;
|
|
308
315
|
return (
|
|
309
316
|
isNumber(s.files) &&
|
|
310
317
|
isNumber(s.chars) &&
|
|
@@ -318,7 +325,7 @@ function isShape(value: unknown): value is RecordShape {
|
|
|
318
325
|
|
|
319
326
|
function isChoice(value: unknown): value is RecordChoice {
|
|
320
327
|
if (typeof value !== 'object' || value === null) return false;
|
|
321
|
-
const c = value as
|
|
328
|
+
const c = value as Unchecked<RecordChoice>;
|
|
322
329
|
return (
|
|
323
330
|
isNumber(c.checkers) &&
|
|
324
331
|
typeof c.share === 'boolean' &&
|
|
@@ -331,7 +338,7 @@ function isChoice(value: unknown): value is RecordChoice {
|
|
|
331
338
|
|
|
332
339
|
function isRejection(value: unknown): value is RecordRejection {
|
|
333
340
|
if (typeof value !== 'object' || value === null) return false;
|
|
334
|
-
const r = value as
|
|
341
|
+
const r = value as Unchecked<RecordRejection>;
|
|
335
342
|
return (
|
|
336
343
|
isNumber(r.checkers) &&
|
|
337
344
|
typeof r.share === 'boolean' &&
|
|
@@ -342,7 +349,7 @@ function isRejection(value: unknown): value is RecordRejection {
|
|
|
342
349
|
|
|
343
350
|
function isSample(value: unknown): value is RecordSample {
|
|
344
351
|
if (typeof value !== 'object' || value === null) return false;
|
|
345
|
-
const s = value as
|
|
352
|
+
const s = value as Unchecked<RecordSample>;
|
|
346
353
|
return (
|
|
347
354
|
isNumber(s.run) &&
|
|
348
355
|
isNumber(s.at) &&
|