@scriptc/compiler 0.0.0 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/ambient/package.json +3 -0
- package/ambient/scriptc-node-fallback.d.ts +3001 -0
- package/ambient/scriptc-overrides.d.ts +95 -0
- package/ambient/scriptc.d.ts +74 -0
- package/dist/backend/cc.d.ts +169 -0
- package/dist/backend/cc.js +897 -0
- package/dist/backend/cc.js.map +1 -0
- package/dist/backend/emission/emit-async.d.ts +145 -0
- package/dist/backend/emission/emit-async.js +996 -0
- package/dist/backend/emission/emit-async.js.map +1 -0
- package/dist/backend/emission/emit-exprs.d.ts +3 -0
- package/dist/backend/emission/emit-exprs.js +5949 -0
- package/dist/backend/emission/emit-exprs.js.map +1 -0
- package/dist/backend/emission/emit-island.d.ts +45 -0
- package/dist/backend/emission/emit-island.js +271 -0
- package/dist/backend/emission/emit-island.js.map +1 -0
- package/dist/backend/emission/emit-shapes.d.ts +142 -0
- package/dist/backend/emission/emit-shapes.js +575 -0
- package/dist/backend/emission/emit-shapes.js.map +1 -0
- package/dist/backend/emission/emit-stmts.d.ts +78 -0
- package/dist/backend/emission/emit-stmts.js +960 -0
- package/dist/backend/emission/emit-stmts.js.map +1 -0
- package/dist/backend/emission/emit-types.d.ts +65 -0
- package/dist/backend/emission/emit-types.js +652 -0
- package/dist/backend/emission/emit-types.js.map +1 -0
- package/dist/backend/emission/emit-walkers.d.ts +154 -0
- package/dist/backend/emission/emit-walkers.js +1587 -0
- package/dist/backend/emission/emit-walkers.js.map +1 -0
- package/dist/backend/emission/emitter.d.ts +452 -0
- package/dist/backend/emission/emitter.js +1249 -0
- package/dist/backend/emission/emitter.js.map +1 -0
- package/dist/backend/emission/may-throw.d.ts +15 -0
- package/dist/backend/emission/may-throw.js +250 -0
- package/dist/backend/emission/may-throw.js.map +1 -0
- package/dist/backend/llvm/blocks.d.ts +21 -0
- package/dist/backend/llvm/blocks.js +68 -0
- package/dist/backend/llvm/blocks.js.map +1 -0
- package/dist/backend/llvm/classes.d.ts +84 -0
- package/dist/backend/llvm/classes.js +438 -0
- package/dist/backend/llvm/classes.js.map +1 -0
- package/dist/backend/llvm/dyn.d.ts +123 -0
- package/dist/backend/llvm/dyn.js +2624 -0
- package/dist/backend/llvm/dyn.js.map +1 -0
- package/dist/backend/llvm/emitter.d.ts +3 -0
- package/dist/backend/llvm/emitter.js +10102 -0
- package/dist/backend/llvm/emitter.js.map +1 -0
- package/dist/backend/llvm/shapes.d.ts +78 -0
- package/dist/backend/llvm/shapes.js +754 -0
- package/dist/backend/llvm/shapes.js.map +1 -0
- package/dist/backend/llvm/unsupported.d.ts +6 -0
- package/dist/backend/llvm/unsupported.js +12 -0
- package/dist/backend/llvm/unsupported.js.map +1 -0
- package/dist/backend/llvm/walkers.d.ts +58 -0
- package/dist/backend/llvm/walkers.js +793 -0
- package/dist/backend/llvm/walkers.js.map +1 -0
- package/dist/backend/mangle.d.ts +124 -0
- package/dist/backend/mangle.js +232 -0
- package/dist/backend/mangle.js.map +1 -0
- package/dist/coverage/report.d.ts +65 -0
- package/dist/coverage/report.js +238 -0
- package/dist/coverage/report.js.map +1 -0
- package/dist/diagnostics/diagnostic.d.ts +140 -0
- package/dist/diagnostics/diagnostic.js +458 -0
- package/dist/diagnostics/diagnostic.js.map +1 -0
- package/dist/diagnostics/render.d.ts +11 -0
- package/dist/diagnostics/render.js +58 -0
- package/dist/diagnostics/render.js.map +1 -0
- package/dist/frontend/cjs-lexer.d.ts +20 -0
- package/dist/frontend/cjs-lexer.js +813 -0
- package/dist/frontend/cjs-lexer.js.map +1 -0
- package/dist/frontend/lowering/http2-constants.d.ts +1 -0
- package/dist/frontend/lowering/http2-constants.js +251 -0
- package/dist/frontend/lowering/http2-constants.js.map +1 -0
- package/dist/frontend/lowering/lib-boundary.d.ts +6 -0
- package/dist/frontend/lowering/lib-boundary.js +143 -0
- package/dist/frontend/lowering/lib-boundary.js.map +1 -0
- package/dist/frontend/lowering/lower-assert.d.ts +18 -0
- package/dist/frontend/lowering/lower-assert.js +1269 -0
- package/dist/frontend/lowering/lower-assert.js.map +1 -0
- package/dist/frontend/lowering/lower-builtins.d.ts +511 -0
- package/dist/frontend/lowering/lower-builtins.js +5249 -0
- package/dist/frontend/lowering/lower-builtins.js.map +1 -0
- package/dist/frontend/lowering/lower-calls.d.ts +581 -0
- package/dist/frontend/lowering/lower-calls.js +6286 -0
- package/dist/frontend/lowering/lower-calls.js.map +1 -0
- package/dist/frontend/lowering/lower-classes.d.ts +727 -0
- package/dist/frontend/lowering/lower-classes.js +4701 -0
- package/dist/frontend/lowering/lower-classes.js.map +1 -0
- package/dist/frontend/lowering/lower-comptime.d.ts +53 -0
- package/dist/frontend/lowering/lower-comptime.js +244 -0
- package/dist/frontend/lowering/lower-comptime.js.map +1 -0
- package/dist/frontend/lowering/lower-containers.d.ts +486 -0
- package/dist/frontend/lowering/lower-containers.js +6345 -0
- package/dist/frontend/lowering/lower-containers.js.map +1 -0
- package/dist/frontend/lowering/lower-dgram.d.ts +20 -0
- package/dist/frontend/lowering/lower-dgram.js +319 -0
- package/dist/frontend/lowering/lower-dgram.js.map +1 -0
- package/dist/frontend/lowering/lower-emitter.d.ts +10 -0
- package/dist/frontend/lowering/lower-emitter.js +688 -0
- package/dist/frontend/lowering/lower-emitter.js.map +1 -0
- package/dist/frontend/lowering/lower-enums.d.ts +22 -0
- package/dist/frontend/lowering/lower-enums.js +235 -0
- package/dist/frontend/lowering/lower-enums.js.map +1 -0
- package/dist/frontend/lowering/lower-expando.d.ts +35 -0
- package/dist/frontend/lowering/lower-expando.js +276 -0
- package/dist/frontend/lowering/lower-expando.js.map +1 -0
- package/dist/frontend/lowering/lower-exprs.d.ts +524 -0
- package/dist/frontend/lowering/lower-exprs.js +8111 -0
- package/dist/frontend/lowering/lower-exprs.js.map +1 -0
- package/dist/frontend/lowering/lower-generators.d.ts +50 -0
- package/dist/frontend/lowering/lower-generators.js +421 -0
- package/dist/frontend/lowering/lower-generators.js.map +1 -0
- package/dist/frontend/lowering/lower-inspect.d.ts +20 -0
- package/dist/frontend/lowering/lower-inspect.js +1074 -0
- package/dist/frontend/lowering/lower-inspect.js.map +1 -0
- package/dist/frontend/lowering/lower-island.d.ts +108 -0
- package/dist/frontend/lowering/lower-island.js +650 -0
- package/dist/frontend/lowering/lower-island.js.map +1 -0
- package/dist/frontend/lowering/lower-mixins.d.ts +96 -0
- package/dist/frontend/lowering/lower-mixins.js +534 -0
- package/dist/frontend/lowering/lower-mixins.js.map +1 -0
- package/dist/frontend/lowering/lower-modules.d.ts +135 -0
- package/dist/frontend/lowering/lower-modules.js +1613 -0
- package/dist/frontend/lowering/lower-modules.js.map +1 -0
- package/dist/frontend/lowering/lower-namespaces.d.ts +168 -0
- package/dist/frontend/lowering/lower-namespaces.js +706 -0
- package/dist/frontend/lowering/lower-namespaces.js.map +1 -0
- package/dist/frontend/lowering/lower-server.d.ts +90 -0
- package/dist/frontend/lowering/lower-server.js +3471 -0
- package/dist/frontend/lowering/lower-server.js.map +1 -0
- package/dist/frontend/lowering/lower-stmts.d.ts +361 -0
- package/dist/frontend/lowering/lower-stmts.js +5341 -0
- package/dist/frontend/lowering/lower-stmts.js.map +1 -0
- package/dist/frontend/lowering/lower-stream.d.ts +88 -0
- package/dist/frontend/lowering/lower-stream.js +1533 -0
- package/dist/frontend/lowering/lower-stream.js.map +1 -0
- package/dist/frontend/lowering/lower-test.d.ts +27 -0
- package/dist/frontend/lowering/lower-test.js +353 -0
- package/dist/frontend/lowering/lower-test.js.map +1 -0
- package/dist/frontend/lowering/lowerer.d.ts +1748 -0
- package/dist/frontend/lowering/lowerer.js +6257 -0
- package/dist/frontend/lowering/lowerer.js.map +1 -0
- package/dist/frontend/lowering/surfaces.d.ts +272 -0
- package/dist/frontend/lowering/surfaces.js +1096 -0
- package/dist/frontend/lowering/surfaces.js.map +1 -0
- package/dist/frontend/npm-static.d.ts +55 -0
- package/dist/frontend/npm-static.js +300 -0
- package/dist/frontend/npm-static.js.map +1 -0
- package/dist/frontend/npm.d.ts +330 -0
- package/dist/frontend/npm.js +1259 -0
- package/dist/frontend/npm.js.map +1 -0
- package/dist/frontend/program.d.ts +186 -0
- package/dist/frontend/program.js +2255 -0
- package/dist/frontend/program.js.map +1 -0
- package/dist/frontend/provenance-registry.d.ts +48 -0
- package/dist/frontend/provenance-registry.js +87 -0
- package/dist/frontend/provenance-registry.js.map +1 -0
- package/dist/frontend/provenance.d.ts +6 -0
- package/dist/frontend/provenance.js +459 -0
- package/dist/frontend/provenance.js.map +1 -0
- package/dist/frontend/resolve.d.ts +56 -0
- package/dist/frontend/resolve.js +682 -0
- package/dist/frontend/resolve.js.map +1 -0
- package/dist/frontend/shared.d.ts +67 -0
- package/dist/frontend/shared.js +241 -0
- package/dist/frontend/shared.js.map +1 -0
- package/dist/frontend/ts7/adapter.d.ts +7 -0
- package/dist/frontend/ts7/adapter.js +54 -0
- package/dist/frontend/ts7/adapter.js.map +1 -0
- package/dist/frontend/ts7/ast.d.ts +50 -0
- package/dist/frontend/ts7/ast.js +211 -0
- package/dist/frontend/ts7/ast.js.map +1 -0
- package/dist/frontend/ts7/census-check.d.ts +1 -0
- package/dist/frontend/ts7/census-check.js +12 -0
- package/dist/frontend/ts7/census-check.js.map +1 -0
- package/dist/frontend/ts7/checker.d.ts +140 -0
- package/dist/frontend/ts7/checker.js +544 -0
- package/dist/frontend/ts7/checker.js.map +1 -0
- package/dist/frontend/ts7/enums.d.ts +18 -0
- package/dist/frontend/ts7/enums.js +43 -0
- package/dist/frontend/ts7/enums.js.map +1 -0
- package/dist/frontend/ts7/program.d.ts +99 -0
- package/dist/frontend/ts7/program.js +278 -0
- package/dist/frontend/ts7/program.js.map +1 -0
- package/dist/frontend/ts7/world-check.d.ts +3 -0
- package/dist/frontend/ts7/world-check.js +34 -0
- package/dist/frontend/ts7/world-check.js.map +1 -0
- package/dist/frontend/types.d.ts +205 -0
- package/dist/frontend/types.js +2485 -0
- package/dist/frontend/types.js.map +1 -0
- package/dist/index.d.ts +85 -0
- package/dist/index.js +429 -0
- package/dist/index.js.map +1 -0
- package/dist/ir/nodes.d.ts +4334 -0
- package/dist/ir/nodes.js +1906 -0
- package/dist/ir/nodes.js.map +1 -0
- package/dist/ir/serialize.d.ts +4 -0
- package/dist/ir/serialize.js +34 -0
- package/dist/ir/serialize.js.map +1 -0
- package/dist/ir/validate.d.ts +36 -0
- package/dist/ir/validate.js +4885 -0
- package/dist/ir/validate.js.map +1 -0
- package/package.json +30 -6
- package/README.md +0 -3
- package/index.js +0 -2
|
@@ -0,0 +1,1613 @@
|
|
|
1
|
+
/* Module-graph lowering: splitting each source file into its parts, the
|
|
2
|
+
* program-collection pass (signatures, classes, globals — reachability
|
|
3
|
+
* seeds), npm/JSON import collection, per-file %init functions, %main, and
|
|
4
|
+
* the module artifacts (globals, embedded npm tables) the IR module carries. */
|
|
5
|
+
import * as ts from "../ts7/adapter.js";
|
|
6
|
+
import { dirname as dirnamePath, resolve as resolvePath } from "node:path";
|
|
7
|
+
import { NpmGraphBuilder, packageNameOfPath, probeNodeImportRefusal } from "../npm.js";
|
|
8
|
+
import { isNpmStaticPackage } from "../npm-static.js";
|
|
9
|
+
import { isJsSourceFileName, isRelativeSpecifier } from "../shared.js";
|
|
10
|
+
import { cjsExportAssignmentOf, cjsExportDiscardReason, isCjsJsFile, isJsSourceFile, isRequireStatement, locOf, orderedImportsOf, resolveImport, resolveNpmImport } from "../program.js";
|
|
11
|
+
import { invalidJsonModuleDiag, npmEmbedFailedDiag, requiresDynamicImportDiag, unsupportedDiag } from "../../diagnostics/diagnostic.js";
|
|
12
|
+
import { BOOL, DYN, JSVAL, RUNTIME_ERROR_CLASSES, STRING, VOID, canConvertToDyn, isUnitType } from "../../ir/nodes.js";
|
|
13
|
+
import { ENTRY_NAME, PoisonError, boundIdentifiersOf, dynFallbackType, dynUndefinedExpr, importCallHandleType, newFnCtx, uncheckedOverloadHandleCall } from "./lowerer.js";
|
|
14
|
+
import { builtinMemberRequireDecl, builtinNamespaceDestructureModuleOf, isPromisifyCall } from "./lower-builtins.js";
|
|
15
|
+
import { bindingGenericFnInfoOf, bindingGenericFnNodeOf, implicitLocalFnInfoOf, implicitLocalFnNodeOf } from "./lower-calls.js";
|
|
16
|
+
import { isVarDeclared, provenanceElidedConstDecl } from "./lower-stmts.js";
|
|
17
|
+
import { streamClassAliasDecl } from "./lower-stream.js";
|
|
18
|
+
import { stdlibGlobalAliasDecl } from "./surfaces.js";
|
|
19
|
+
import { collectNamespaceStmt, nsPathPrefix } from "./lower-namespaces.js";
|
|
20
|
+
import { collectExpandoMembers } from "./lower-expando.js";
|
|
21
|
+
import { isUnitOnlyTsType, unitOnlyUnion } from "../types.js";
|
|
22
|
+
import { decoratorNodesOf, guaranteedDecorationThrow } from "./lower-classes.js";
|
|
23
|
+
import { isMixinFnBinding, mixinResultBindingClassOf } from "./lower-mixins.js";
|
|
24
|
+
/** Splits each file into function/class declarations and the top-level
|
|
25
|
+
* statements that form its init body. Node's evaluation order (depth-first
|
|
26
|
+
* postorder, entry last), computed by preflight. Single-file programs
|
|
27
|
+
* (tests, coverage on broken files) may arrive with an empty order — fall
|
|
28
|
+
* back to the entry alone. */
|
|
29
|
+
export function splitFiles(L) {
|
|
30
|
+
const files = L.moduleOrder.length > 0 ? L.moduleOrder : [L.entry];
|
|
31
|
+
return files.map((sf) => {
|
|
32
|
+
const fp = { sf, fnDecls: [], classDecls: [], topStmts: [] };
|
|
33
|
+
for (const stmt of sf.statements) {
|
|
34
|
+
if (ts.isFunctionDeclaration(stmt))
|
|
35
|
+
fp.fnDecls.push(stmt);
|
|
36
|
+
else if (ts.isClassDeclaration(stmt))
|
|
37
|
+
fp.classDecls.push(stmt);
|
|
38
|
+
// Namespaces: ambient/type-only ones are zero-runtime and skip;
|
|
39
|
+
// instantiated bodies FLATTEN into this file's parts (functions/
|
|
40
|
+
// classes hoist under namespace-qualified names, statements join
|
|
41
|
+
// the init body in source order) — lower-namespaces.ts.
|
|
42
|
+
else if (ts.isModuleDeclaration(stmt))
|
|
43
|
+
collectNamespaceStmt(L, stmt, fp);
|
|
44
|
+
else if (ts.isInterfaceDeclaration(stmt) ||
|
|
45
|
+
ts.isTypeAliasDeclaration(stmt) ||
|
|
46
|
+
ts.isImportDeclaration(stmt) ||
|
|
47
|
+
// Export lists and re-exports are pure alias plumbing (importers
|
|
48
|
+
// resolve through them to the original declarations); their
|
|
49
|
+
// module EDGES live in preflight/buildInitSteps. Nothing runs.
|
|
50
|
+
ts.isExportDeclaration(stmt)) {
|
|
51
|
+
continue; // type-only / already-resolved module plumbing
|
|
52
|
+
}
|
|
53
|
+
else
|
|
54
|
+
fp.topStmts.push(stmt);
|
|
55
|
+
}
|
|
56
|
+
return fp;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/** A source file dynamic `import("<relative>")` can host as a COMPILED
|
|
60
|
+
* module namespace (lowerOwnModuleImport): a non-declaration program file
|
|
61
|
+
* that is not JSON and not CommonJS-flavored (a CJS namespace is built
|
|
62
|
+
* from module.exports through Node's lexer — a different surface with no
|
|
63
|
+
* static story here). Null for everything else. */
|
|
64
|
+
export function dynamicImportProgramTargetOf(program, sf, spec) {
|
|
65
|
+
if (!isRelativeSpecifier(spec) && !spec.startsWith("/"))
|
|
66
|
+
return null;
|
|
67
|
+
const dep = resolveImport(program, sf, spec);
|
|
68
|
+
if (!dep || dep.isDeclarationFile)
|
|
69
|
+
return null;
|
|
70
|
+
if (dep.fileName.endsWith(".json") || dep.fileName.endsWith(".cts"))
|
|
71
|
+
return null;
|
|
72
|
+
if (isCjsJsFile(dep))
|
|
73
|
+
return null;
|
|
74
|
+
return dep;
|
|
75
|
+
}
|
|
76
|
+
/** Every distinct program module a file's `import("literal")` sites name
|
|
77
|
+
* (source order, deduped). */
|
|
78
|
+
function dynamicProgramImportsOf(program, sf) {
|
|
79
|
+
const out = [];
|
|
80
|
+
const seen = new Set();
|
|
81
|
+
ts.walkPreorder(sf, (node) => {
|
|
82
|
+
if (ts.isCallExpression(node) &&
|
|
83
|
+
node.expression.kind === ts.SyntaxKind.ImportKeyword &&
|
|
84
|
+
node.arguments[0] !== undefined &&
|
|
85
|
+
ts.isStringLiteralLike(node.arguments[0])) {
|
|
86
|
+
const dep = dynamicImportProgramTargetOf(program, sf, node.arguments[0].text);
|
|
87
|
+
if (dep && !seen.has(dep)) {
|
|
88
|
+
seen.add(dep);
|
|
89
|
+
out.push(dep);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
/** --dynamic builds: program modules reachable ONLY through dynamic
|
|
96
|
+
* `import()` join the compiled module graph — appended to `order` (before
|
|
97
|
+
* the entry, which stays last) in depth-first postorder over their own
|
|
98
|
+
* static import edges, exactly like preflight ordered the static graph.
|
|
99
|
+
* NOTHING calls their %init at startup (%main runs only the entry's init,
|
|
100
|
+
* and no static import header names these), so a dynamic-only module
|
|
101
|
+
* still evaluates exactly when Node evaluates it: the import() site's
|
|
102
|
+
* namespace builder calls the guarded init on the engine microtask.
|
|
103
|
+
* Fixpoint: an added module's own import() sites are scanned too. Static
|
|
104
|
+
* cycles inside the added subgraph get preflight's SC1016 via `onCycle`
|
|
105
|
+
* (preflight only walked the entry's static graph, so it never saw
|
|
106
|
+
* these). Idempotent — re-running on an already-extended order adds
|
|
107
|
+
* nothing (both lowering passes share one array). */
|
|
108
|
+
export function appendDynamicImportModules(program, order, onCycle) {
|
|
109
|
+
if (order.length === 0)
|
|
110
|
+
return; // no preflight order — sites keep their fences
|
|
111
|
+
const state = new Map();
|
|
112
|
+
for (const sf of order)
|
|
113
|
+
state.set(sf, "done");
|
|
114
|
+
const added = [];
|
|
115
|
+
const stack = [];
|
|
116
|
+
const visit = (sf) => {
|
|
117
|
+
const s = state.get(sf);
|
|
118
|
+
if (s === "done")
|
|
119
|
+
return;
|
|
120
|
+
if (s === "visiting") {
|
|
121
|
+
const cycleStart = stack.indexOf(sf.fileName);
|
|
122
|
+
onCycle([...stack.slice(cycleStart), sf.fileName].join(" → "));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
state.set(sf, "visiting");
|
|
126
|
+
stack.push(sf.fileName);
|
|
127
|
+
for (const { dep } of orderedImportsOf(program, sf))
|
|
128
|
+
if (dep && dep !== sf)
|
|
129
|
+
visit(dep);
|
|
130
|
+
stack.pop();
|
|
131
|
+
state.set(sf, "done");
|
|
132
|
+
added.push(sf);
|
|
133
|
+
};
|
|
134
|
+
const scanned = new Set();
|
|
135
|
+
for (;;) {
|
|
136
|
+
const scan = [...order, ...added].filter((sf) => !scanned.has(sf));
|
|
137
|
+
if (scan.length === 0)
|
|
138
|
+
break;
|
|
139
|
+
for (const sf of scan) {
|
|
140
|
+
scanned.add(sf);
|
|
141
|
+
for (const dep of dynamicProgramImportsOf(program, sf))
|
|
142
|
+
visit(dep);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (added.length > 0)
|
|
146
|
+
order.splice(order.length - 1, 0, ...added);
|
|
147
|
+
}
|
|
148
|
+
/** Collect pass over the WHOLE program: signatures, class shapes, and
|
|
149
|
+
* file-scope globals visible everywhere → hoisting, mutual recursion, and
|
|
150
|
+
* cross-module references fall out for free. Runs in both passes —
|
|
151
|
+
* signatures are cheap and calls resolve against them; only BODY lowering
|
|
152
|
+
* is reachability-gated. */
|
|
153
|
+
export function collectProgram(L, parts) {
|
|
154
|
+
L.collecting = true;
|
|
155
|
+
try {
|
|
156
|
+
for (const fp of parts)
|
|
157
|
+
for (const decl of fp.classDecls)
|
|
158
|
+
L.collectClassShape(decl);
|
|
159
|
+
for (const fp of parts)
|
|
160
|
+
for (const decl of fp.fnDecls)
|
|
161
|
+
L.collectSignature(decl);
|
|
162
|
+
}
|
|
163
|
+
finally {
|
|
164
|
+
L.collecting = false;
|
|
165
|
+
}
|
|
166
|
+
// Globals are top-level (always reachable), so their collection reports
|
|
167
|
+
// eagerly — including deferred-class flushes their types trigger. The
|
|
168
|
+
// coverage remainder still needs the registrations but the emit pass
|
|
169
|
+
// already reported the diagnostics; discard the duplicates.
|
|
170
|
+
if (L.remainder) {
|
|
171
|
+
L.diagSink = [];
|
|
172
|
+
try {
|
|
173
|
+
for (const fp of parts)
|
|
174
|
+
L.collectGlobals(fp.sf, fp.topStmts);
|
|
175
|
+
L.collectNpmImports(parts);
|
|
176
|
+
L.collectJsonImports(parts);
|
|
177
|
+
}
|
|
178
|
+
finally {
|
|
179
|
+
L.diagSink = null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
for (const fp of parts)
|
|
184
|
+
L.collectGlobals(fp.sf, fp.topStmts);
|
|
185
|
+
L.collectNpmImports(parts);
|
|
186
|
+
L.collectJsonImports(parts);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/** The npm-import chokepoint, run once per pass during collection (so
|
|
190
|
+
* bodies lowered in ANY pass resolve the bindings). In a static build
|
|
191
|
+
* every value-importing npm import reports the requires-dynamic
|
|
192
|
+
* diagnostic naming the package — the package's shipped JS has exactly
|
|
193
|
+
* one execution home, the embedded engine. Under --dynamic each value
|
|
194
|
+
* binding becomes a jsval GLOBAL, registered by ALIASED symbol (modules
|
|
195
|
+
* importing the same export share one global) and initialized at the
|
|
196
|
+
* top of the importing module's %init by island.import — the engine's
|
|
197
|
+
* module registry caches, so re-imports are lookups, not re-evaluations.
|
|
198
|
+
* The runtime graph (the package entry plus everything it reaches)
|
|
199
|
+
* builds here too; its failures are ordinary diagnostics at the import
|
|
200
|
+
* site. Type-only imports are free either way: the .d.ts is a type
|
|
201
|
+
* surface, not code. */
|
|
202
|
+
export function collectNpmImports(L, parts) {
|
|
203
|
+
const builder = L.dynamic ? new NpmGraphBuilder() : null;
|
|
204
|
+
for (const fp of parts) {
|
|
205
|
+
for (const stmt of fp.sf.statements) {
|
|
206
|
+
if (!ts.isImportDeclaration(stmt) || !ts.isStringLiteral(stmt.moduleSpecifier))
|
|
207
|
+
continue;
|
|
208
|
+
const clause = stmt.importClause;
|
|
209
|
+
if (clause?.phaseModifier === ts.SyntaxKind.TypeKeyword)
|
|
210
|
+
continue;
|
|
211
|
+
if (clause?.namedBindings &&
|
|
212
|
+
ts.isNamedImports(clause.namedBindings) &&
|
|
213
|
+
!clause.name &&
|
|
214
|
+
clause.namedBindings.elements.every((e) => e.isTypeOnly)) {
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
const spec = stmt.moduleSpecifier.text;
|
|
218
|
+
const npm = resolveNpmImport(fp.sf.fileName, spec);
|
|
219
|
+
// --npm-static: an opted-in package that made it through preflight
|
|
220
|
+
// is a PROGRAM-MODULE dependency — its entry sits in the module
|
|
221
|
+
// order, orderedImportsOf answers it for the %init header, and the
|
|
222
|
+
// checker's alias chains bind the imports to its own exports. The
|
|
223
|
+
// island owns nothing here, in static and --dynamic builds alike.
|
|
224
|
+
if (npm !== null &&
|
|
225
|
+
isNpmStaticPackage(npm.packageName) &&
|
|
226
|
+
isJsSourceFileName(npm.typesFile) &&
|
|
227
|
+
L.program.getSourceFile(npm.typesFile) !== undefined) {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
// A RELATIVE specifier resolving INTO node_modules (the e2e driver
|
|
231
|
+
// shape: `import "../node_modules/vercel/dist/index.js"`) is
|
|
232
|
+
// package code too — tsc pulls the FILE into the program under
|
|
233
|
+
// allowJs, but shipped JS has exactly one execution home, the
|
|
234
|
+
// embedded engine, so it takes the same island path as a bare
|
|
235
|
+
// specifier (previously it lowered to NOTHING: not an npm import,
|
|
236
|
+
// and node_modules files never lower as program modules — the
|
|
237
|
+
// import silently dropped).
|
|
238
|
+
const relPkg = npm === null && isRelativeSpecifier(spec)
|
|
239
|
+
? packageNameOfPath(resolvePath(dirnamePath(fp.sf.fileName), spec))
|
|
240
|
+
: null;
|
|
241
|
+
const relIsJs = relPkg !== null && !/\.(ts|tsx|mts|cts)$/.test(spec);
|
|
242
|
+
// A relative import INTO an opted-in --npm-static package is the
|
|
243
|
+
// program-module path too (preflight resolved the file edge).
|
|
244
|
+
if (relIsJs && isNpmStaticPackage(relPkg))
|
|
245
|
+
continue;
|
|
246
|
+
if (!npm && !relIsJs)
|
|
247
|
+
continue;
|
|
248
|
+
// An edge Node's RUNTIME resolution refuses at startup (types
|
|
249
|
+
// resolved, but the exports target ships no JS — the types-only
|
|
250
|
+
// package shape): preflight registered Node's startup crash for
|
|
251
|
+
// it, %main throws before any init runs, and the bindings never
|
|
252
|
+
// link — nothing to embed, no island requirement, in EITHER mode.
|
|
253
|
+
if (npm !== null && probeNodeImportRefusal(fp.sf.fileName, spec) !== null)
|
|
254
|
+
continue;
|
|
255
|
+
if (!builder) {
|
|
256
|
+
L.pushDiag(requiresDynamicImportDiag(npm?.packageName ?? relPkg, locOf(stmt)));
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
const before = builder.errors.length;
|
|
260
|
+
const entryKey = npm
|
|
261
|
+
? (builder.addImport(fp.sf.fileName, spec), builder.entryOf(fp.sf.fileName, spec))
|
|
262
|
+
: builder.addFileImport(fp.sf.fileName, spec);
|
|
263
|
+
for (const err of builder.errors.slice(before)) {
|
|
264
|
+
L.pushDiag(npmEmbedFailedDiag(err.message, locOf(stmt)));
|
|
265
|
+
}
|
|
266
|
+
if (entryKey === null)
|
|
267
|
+
continue; // resolution failed — reported above
|
|
268
|
+
const loc = locOf(stmt);
|
|
269
|
+
const byStmt = L.npmInitActions.get(fp.sf) ?? new Map();
|
|
270
|
+
L.npmInitActions.set(fp.sf, byStmt);
|
|
271
|
+
const actions = byStmt.get(stmt) ?? [];
|
|
272
|
+
byStmt.set(stmt, actions);
|
|
273
|
+
const importExpr = (exportName) => ({
|
|
274
|
+
kind: "libCall",
|
|
275
|
+
fn: "island.import",
|
|
276
|
+
args: [
|
|
277
|
+
{ kind: "strLit", value: entryKey, type: STRING, loc },
|
|
278
|
+
{ kind: "strLit", value: exportName, type: STRING, loc },
|
|
279
|
+
// The specifier as WRITTEN — Node's missing-export SyntaxError
|
|
280
|
+
// names it ("The requested module 'x' does not provide an
|
|
281
|
+
// export named 'y'"), and the runtime check reproduces that
|
|
282
|
+
// message exactly (scr_jsval_import).
|
|
283
|
+
{ kind: "strLit", value: spec, type: STRING, loc },
|
|
284
|
+
],
|
|
285
|
+
type: JSVAL,
|
|
286
|
+
loc,
|
|
287
|
+
});
|
|
288
|
+
const bind = (nameNode, exportName) => {
|
|
289
|
+
let symbol = L.checker.getSymbolAtLocation(nameNode);
|
|
290
|
+
if (symbol && symbol.flags & ts.SymbolFlags.Alias) {
|
|
291
|
+
symbol = L.checker.getAliasedSymbol(symbol);
|
|
292
|
+
}
|
|
293
|
+
if (!symbol)
|
|
294
|
+
return;
|
|
295
|
+
let g = L.globalsBySymbol.get(symbol);
|
|
296
|
+
if (!g) {
|
|
297
|
+
g = {
|
|
298
|
+
id: `%g.npm.${L.globalsList.length}`,
|
|
299
|
+
name: nameNode.text,
|
|
300
|
+
type: JSVAL,
|
|
301
|
+
mutable: false,
|
|
302
|
+
};
|
|
303
|
+
L.globalsBySymbol.set(symbol, g);
|
|
304
|
+
L.globalsList.push(g);
|
|
305
|
+
}
|
|
306
|
+
actions.push({ kind: "assign", localId: g.id, value: importExpr(exportName), loc });
|
|
307
|
+
};
|
|
308
|
+
if (!clause) {
|
|
309
|
+
// Side-effect import: load for its top-level effects, keep nothing.
|
|
310
|
+
actions.push({ kind: "exprStmt", expr: importExpr("*"), loc });
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
if (clause.name)
|
|
314
|
+
bind(clause.name, "default");
|
|
315
|
+
if (clause.namedBindings) {
|
|
316
|
+
if (ts.isNamespaceImport(clause.namedBindings)) {
|
|
317
|
+
bind(clause.namedBindings.name, "*");
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
for (const el of clause.namedBindings.elements) {
|
|
321
|
+
if (el.isTypeOnly)
|
|
322
|
+
continue;
|
|
323
|
+
bind(el.name, el.propertyName?.text ?? el.name.text);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (builder) {
|
|
330
|
+
for (const fp of parts)
|
|
331
|
+
collectDynamicImports(L, builder, fp.sf);
|
|
332
|
+
const graph = builder.finish();
|
|
333
|
+
if (graph.modules.length > 0) {
|
|
334
|
+
L.npmEmbedded = { modules: graph.modules, edges: graph.edges };
|
|
335
|
+
}
|
|
336
|
+
if (graph.builtins.length > 0)
|
|
337
|
+
L.npmBuiltins = graph.builtins;
|
|
338
|
+
if (graph.lazyTraps.length > 0)
|
|
339
|
+
L.npmLazyTraps = graph.lazyTraps;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
/** The dynamic-import half of the npm chokepoint (--dynamic only): every
|
|
343
|
+
* `import("literal")` in the file — whatever body it sits in — resolves
|
|
344
|
+
* and embeds AT COLLECTION time, so the per-site lowering later just
|
|
345
|
+
* looks its key up (the emitted tables are assembled once, here; a
|
|
346
|
+
* site lowered in any pass finds its module embedded). Resolution
|
|
347
|
+
* failures and unshimmed builtins are diagnostics HERE, at the import
|
|
348
|
+
* expression, exactly like static npm imports at their statements; the
|
|
349
|
+
* lowering poisons those sites without re-reporting. Non-literal
|
|
350
|
+
* specifiers are skipped — the lowering owns that fence (the module
|
|
351
|
+
* graph is a build-time artifact; there is nothing to embed for a
|
|
352
|
+
* runtime-computed name). */
|
|
353
|
+
function collectDynamicImports(L, builder, sf) {
|
|
354
|
+
const visit = (node) => {
|
|
355
|
+
if (ts.isCallExpression(node) &&
|
|
356
|
+
node.expression.kind === ts.SyntaxKind.ImportKeyword &&
|
|
357
|
+
node.arguments.length >= 1 &&
|
|
358
|
+
node.arguments[0] !== undefined &&
|
|
359
|
+
ts.isStringLiteralLike(node.arguments[0])) {
|
|
360
|
+
const spec = node.arguments[0].text;
|
|
361
|
+
const mapKey = `${sf.fileName}\u0000${spec}`;
|
|
362
|
+
if (!L.dynImports.has(mapKey)) {
|
|
363
|
+
// The program's OWN modules first, by the checker's resolution
|
|
364
|
+
// (tsc already resolved "./helper.js" to helper.ts): a compiled
|
|
365
|
+
// module has no runtime namespace object — the per-site fence
|
|
366
|
+
// says so. Only non-declaration source files count; a sibling
|
|
367
|
+
// .d.mts typing shipped JS is the EMBED case, not this one.
|
|
368
|
+
const modSym = L.checker.getSymbolAtLocation(node.arguments[0]);
|
|
369
|
+
const ownModule = modSym !== undefined && L.checker.declarationsOf(modSym).some((d) => ts.isSourceFile(d) && !d.isDeclarationFile);
|
|
370
|
+
const res = ownModule
|
|
371
|
+
? { kind: "program-module" }
|
|
372
|
+
: builder.addDynamicImport(sf.fileName, spec);
|
|
373
|
+
L.dynImports.set(mapKey, res);
|
|
374
|
+
if (res.kind === "unsupported-builtin") {
|
|
375
|
+
L.pushDiag(npmEmbedFailedDiag(`'${spec}' is a Node builtin the island does not provide a shim for`, locOf(node)));
|
|
376
|
+
}
|
|
377
|
+
else if (res.kind === "unresolved") {
|
|
378
|
+
L.pushDiag(npmEmbedFailedDiag(res.message, locOf(node)));
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
ts.forEachChild(node, visit);
|
|
383
|
+
};
|
|
384
|
+
visit(sf);
|
|
385
|
+
}
|
|
386
|
+
/** JSON module default imports (`import pkg from "../package.json"`):
|
|
387
|
+
* the document is DATA known at build time, so the binding bakes into a
|
|
388
|
+
* record global — the checker's structural type for the module (thanks
|
|
389
|
+
* to resolveJsonModule) maps to a record shape, the JSON text parses in
|
|
390
|
+
* the compiler, and comptimeValueToIr turns the value into literal IR
|
|
391
|
+
* assigned in the importing module's %init prelude (evaluation order:
|
|
392
|
+
* imports before the importer's body, like npm bindings). Two importers
|
|
393
|
+
* of the same document share one global (keyed by the ALIASED symbol).
|
|
394
|
+
* Shapes outside the bakeable surface (null-valued fields, mixed
|
|
395
|
+
* arrays, ...) report the standard unsupported-type diagnostic at the
|
|
396
|
+
* import site. Preflight already fenced named/namespace JSON imports
|
|
397
|
+
* and kept .json files out of the module order. */
|
|
398
|
+
export function collectJsonImports(L, parts) {
|
|
399
|
+
for (const fp of parts) {
|
|
400
|
+
for (const stmt of fp.sf.statements) {
|
|
401
|
+
if (!ts.isImportDeclaration(stmt) || !ts.isStringLiteral(stmt.moduleSpecifier))
|
|
402
|
+
continue;
|
|
403
|
+
const clause = stmt.importClause;
|
|
404
|
+
if (!clause?.name || clause.phaseModifier === ts.SyntaxKind.TypeKeyword)
|
|
405
|
+
continue;
|
|
406
|
+
const nameSym = L.checker.getSymbolAtLocation(clause.name);
|
|
407
|
+
if (!nameSym || !(nameSym.flags & ts.SymbolFlags.Alias))
|
|
408
|
+
continue;
|
|
409
|
+
const target = L.checker.getAliasedSymbol(nameSym);
|
|
410
|
+
const jsonSf = L.checker.declarationsOf(target)[0]?.getSourceFile();
|
|
411
|
+
if (!jsonSf || !jsonSf.fileName.endsWith(".json"))
|
|
412
|
+
continue;
|
|
413
|
+
try {
|
|
414
|
+
const tsType = L.typeOf(clause.name);
|
|
415
|
+
const mapped = L.mapTypeOf(tsType);
|
|
416
|
+
if (!mapped || !L.comptimeBakeable(mapped)) {
|
|
417
|
+
L.badType(clause.name, tsType);
|
|
418
|
+
}
|
|
419
|
+
// tsgo tolerates JSON shapes strict JSON.parse rejects (a leading
|
|
420
|
+
// `//` comment — importAttributes11), so no SC0001 guarantees a
|
|
421
|
+
// clean document: a failing parse gates at the import statement
|
|
422
|
+
// (Node refuses to import the module at runtime too).
|
|
423
|
+
let parsed;
|
|
424
|
+
try {
|
|
425
|
+
parsed = JSON.parse(jsonSf.text);
|
|
426
|
+
}
|
|
427
|
+
catch (e) {
|
|
428
|
+
L.pushDiag(invalidJsonModuleDiag(jsonSf.fileName, e instanceof Error ? e.message : String(e), locOf(stmt)));
|
|
429
|
+
throw new PoisonError();
|
|
430
|
+
}
|
|
431
|
+
const value = L.comptimeValueToIr(parsed, mapped, "$", clause.name);
|
|
432
|
+
let g = L.globalsBySymbol.get(target);
|
|
433
|
+
if (!g) {
|
|
434
|
+
g = {
|
|
435
|
+
id: `%g.json.${L.globalsList.length}`,
|
|
436
|
+
name: clause.name.text,
|
|
437
|
+
type: mapped,
|
|
438
|
+
mutable: false,
|
|
439
|
+
};
|
|
440
|
+
L.globalsBySymbol.set(target, g);
|
|
441
|
+
L.globalsList.push(g);
|
|
442
|
+
}
|
|
443
|
+
const actions = L.jsonInitActions.get(fp.sf) ?? [];
|
|
444
|
+
L.jsonInitActions.set(fp.sf, actions);
|
|
445
|
+
actions.push({ kind: "assign", localId: g.id, value, loc: locOf(stmt) });
|
|
446
|
+
}
|
|
447
|
+
catch (e) {
|
|
448
|
+
if (!(e instanceof PoisonError))
|
|
449
|
+
throw e;
|
|
450
|
+
// diagnostic already recorded; uses of the binding poison too
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
/** The classes, records, and unions the emitted module carries: exactly
|
|
456
|
+
* what the lowered functions and globals reference, closed transitively
|
|
457
|
+
* over record fields, union arms, class field types, base chains, and
|
|
458
|
+
* WHOLE hierarchies (hierarchy membership decides object layout:
|
|
459
|
+
* dropping an unused subclass must not turn its base into a standalone
|
|
460
|
+
* class at emit time). Registry entries interned while collecting
|
|
461
|
+
* signatures nothing reaches stay out — an unreached type leaves no
|
|
462
|
+
* trace. A referenced class with no registration had its collection
|
|
463
|
+
* deferred: the reference flushes those diagnostics. Methods whose
|
|
464
|
+
* bodies were not lowered leave the def's method list, so vtable slots
|
|
465
|
+
* exist exactly for emitted overrides. */
|
|
466
|
+
export function moduleArtifacts(L, functions) {
|
|
467
|
+
const classNames = new Set();
|
|
468
|
+
const shapeIds = new Set();
|
|
469
|
+
const unionIds = new Set();
|
|
470
|
+
const pendingClasses = [];
|
|
471
|
+
const pendingShapes = [];
|
|
472
|
+
const pendingUnions = [];
|
|
473
|
+
const visit = (node) => {
|
|
474
|
+
if (Array.isArray(node)) {
|
|
475
|
+
for (const item of node)
|
|
476
|
+
visit(item);
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
if (node === null || typeof node !== "object")
|
|
480
|
+
return;
|
|
481
|
+
const rec = node;
|
|
482
|
+
if (typeof rec["className"] === "string" && !classNames.has(rec["className"])) {
|
|
483
|
+
classNames.add(rec["className"]);
|
|
484
|
+
pendingClasses.push(rec["className"]);
|
|
485
|
+
}
|
|
486
|
+
if (typeof rec["shapeId"] === "string" && !shapeIds.has(rec["shapeId"])) {
|
|
487
|
+
shapeIds.add(rec["shapeId"]);
|
|
488
|
+
pendingShapes.push(rec["shapeId"]);
|
|
489
|
+
}
|
|
490
|
+
if (typeof rec["unionId"] === "string" && !unionIds.has(rec["unionId"])) {
|
|
491
|
+
unionIds.add(rec["unionId"]);
|
|
492
|
+
pendingUnions.push(rec["unionId"]);
|
|
493
|
+
}
|
|
494
|
+
for (const key of Object.keys(rec)) {
|
|
495
|
+
if (key !== "loc")
|
|
496
|
+
visit(rec[key]);
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
visit(functions);
|
|
500
|
+
visit(L.globalsList);
|
|
501
|
+
// The builtin error classes ride EVERY module: the runtime's own throws
|
|
502
|
+
// (JSON/dynCheck/regex failures) mint instances of them whether or not
|
|
503
|
+
// user code mentions Error, and the uncaught printer tells Error
|
|
504
|
+
// payloads apart by their preorder interval — which must therefore be
|
|
505
|
+
// part of this program's numbering (backends stamp it into the
|
|
506
|
+
// runtime's vtables at main).
|
|
507
|
+
for (const name of RUNTIME_ERROR_CLASSES.keys()) {
|
|
508
|
+
if (!classNames.has(name)) {
|
|
509
|
+
classNames.add(name);
|
|
510
|
+
pendingClasses.push(name);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
while (pendingClasses.length > 0 ||
|
|
514
|
+
pendingShapes.length > 0 ||
|
|
515
|
+
pendingUnions.length > 0) {
|
|
516
|
+
while (pendingShapes.length > 0) {
|
|
517
|
+
// Declared fields AND the index-signature value type: an
|
|
518
|
+
// overflow-valued shape (`{ [key: string]: {script?: string} }`)
|
|
519
|
+
// references its value shape through indexValue alone.
|
|
520
|
+
const shape = L.shapes.get(pendingShapes.pop());
|
|
521
|
+
visit(shape?.fields);
|
|
522
|
+
if (shape?.indexValue)
|
|
523
|
+
visit(shape.indexValue);
|
|
524
|
+
}
|
|
525
|
+
while (pendingUnions.length > 0)
|
|
526
|
+
visit(L.unions.get(pendingUnions.pop())?.arms);
|
|
527
|
+
while (pendingClasses.length > 0) {
|
|
528
|
+
const name = pendingClasses.pop();
|
|
529
|
+
const info = L.classes.get(name);
|
|
530
|
+
if (!info) {
|
|
531
|
+
// Referenced by an emitted type but never registered: collection
|
|
532
|
+
// deferred its diagnostics — a reached reference makes them count.
|
|
533
|
+
L.flushDeferredClass(name);
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
visit(info.def.fields);
|
|
537
|
+
if (info.base)
|
|
538
|
+
visit([{ className: info.base.def.name }]);
|
|
539
|
+
if (L.inHierarchy(info)) {
|
|
540
|
+
let root = info;
|
|
541
|
+
while (root.base)
|
|
542
|
+
root = root.base;
|
|
543
|
+
const wholeTree = (c) => {
|
|
544
|
+
visit([{ className: c.def.name }]);
|
|
545
|
+
for (const s of c.subclasses)
|
|
546
|
+
wholeTree(s);
|
|
547
|
+
};
|
|
548
|
+
wholeTree(root);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
const reachable = L.reachable;
|
|
553
|
+
return {
|
|
554
|
+
classes: [...L.classes.values()]
|
|
555
|
+
.map((c) => c.def)
|
|
556
|
+
.filter((def) => classNames.has(def.name))
|
|
557
|
+
.map((def) => {
|
|
558
|
+
// Generic-class instantiations bypass the reachability gate
|
|
559
|
+
// (demand-driven — every member of a demanded instantiation
|
|
560
|
+
// lowers; see lowerClassMembers), so their defs keep every
|
|
561
|
+
// method: the emitted functions all exist. ABSTRACT entries ride
|
|
562
|
+
// the same rule (noteVirtualEdge marks the abstract declarer, so
|
|
563
|
+
// a dispatched slot keeps its declaration; an unreferenced one
|
|
564
|
+
// drops and the root-most CONCRETE declaration owns the slot).
|
|
565
|
+
const methods = reachable === null || def.genericOf !== undefined
|
|
566
|
+
? (def.methods ?? [])
|
|
567
|
+
: (def.methods?.filter((m) => reachable.has(`%${def.name}.${m}`)) ?? []);
|
|
568
|
+
const abstractMethods = def.abstractMethods?.filter((m) => methods.includes(m)) ?? [];
|
|
569
|
+
const rest = { ...def };
|
|
570
|
+
delete rest.methods;
|
|
571
|
+
delete rest.abstractMethods;
|
|
572
|
+
return {
|
|
573
|
+
...rest,
|
|
574
|
+
...(methods.length > 0 ? { methods } : {}),
|
|
575
|
+
...(abstractMethods.length > 0 ? { abstractMethods } : {}),
|
|
576
|
+
};
|
|
577
|
+
}),
|
|
578
|
+
records: L.shapes.shapes.filter((r) => shapeIds.has(r.id)),
|
|
579
|
+
unions: L.unions.unions.filter((u) => unionIds.has(u.id)),
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
/** Registers every DIRECT top-level `const`/`let` of a file as a module
|
|
583
|
+
* global (stable storage: cross-module live bindings, and functions can
|
|
584
|
+
* reference them without capture). Block-scoped vars inside nested
|
|
585
|
+
* statements keep their different symbols and stay locals. */
|
|
586
|
+
/** JS collection failures defer to runtime fences: the registration's
|
|
587
|
+
* diagnostics move off the build (into L.runtimeFences) — the declaring
|
|
588
|
+
* statement re-fails during init lowering and compiles to the
|
|
589
|
+
* runtimeFence trap there, and every use site cascades to its own trap.
|
|
590
|
+
* TypeScript files keep eager collection reports. */
|
|
591
|
+
function deferJsCollectionDiags(L, sf, diagsBefore) {
|
|
592
|
+
if (!isJsSourceFile(sf))
|
|
593
|
+
return;
|
|
594
|
+
const captured = L.diags.splice(diagsBefore);
|
|
595
|
+
const ice = captured.filter((d) => d.code === "SC9001");
|
|
596
|
+
if (ice.length > 0)
|
|
597
|
+
L.diags.push(...ice);
|
|
598
|
+
L.runtimeFences.push(...captured.filter((d) => d.code !== "SC9001"));
|
|
599
|
+
}
|
|
600
|
+
/** A scalar-literal expression a single-value `module.exports =` can carry
|
|
601
|
+
* without any declaration to alias to: string/number/boolean literals,
|
|
602
|
+
* substitution-free templates, and negated numbers. */
|
|
603
|
+
function cjsScalarLiteral(e) {
|
|
604
|
+
if (ts.isNumericLiteral(e) || ts.isStringLiteral(e) || ts.isNoSubstitutionTemplateLiteral(e))
|
|
605
|
+
return true;
|
|
606
|
+
if (e.kind === ts.SyntaxKind.TrueKeyword || e.kind === ts.SyntaxKind.FalseKeyword)
|
|
607
|
+
return true;
|
|
608
|
+
return (ts.isPrefixUnaryExpression(e) &&
|
|
609
|
+
e.operator === ts.SyntaxKind.MinusToken &&
|
|
610
|
+
ts.isNumericLiteral(e.operand));
|
|
611
|
+
}
|
|
612
|
+
/** True when a JS file-scope declaration's initializer provably LOWERS to
|
|
613
|
+
* a checked-dynamic (or unit) value, so a DYN module global can hold it:
|
|
614
|
+
* no initializer, a unit literal, or a checker-`any` call/member/
|
|
615
|
+
* identifier read (any-typed operations lower through the DOM in JS).
|
|
616
|
+
* `new` expressions stay OUT even when checker-any (`new Anon.Sub()` over
|
|
617
|
+
* an expando class member constructs a TYPED instance — corpus 2032), as
|
|
618
|
+
* does everything else (object/array literals have their own rules;
|
|
619
|
+
* typed-but-unmappable initializers keep the %init-local adoption). */
|
|
620
|
+
function jsDynHoldableInitializer(L, init) {
|
|
621
|
+
if (init === undefined)
|
|
622
|
+
return true;
|
|
623
|
+
let e = init;
|
|
624
|
+
while (ts.isParenthesizedExpression(e))
|
|
625
|
+
e = e.expression;
|
|
626
|
+
if (e.kind === ts.SyntaxKind.NullKeyword)
|
|
627
|
+
return true;
|
|
628
|
+
if (ts.isIdentifier(e) && e.text === "undefined")
|
|
629
|
+
return true;
|
|
630
|
+
if ((ts.isCallExpression(e) || ts.isPropertyAccessExpression(e) || ts.isIdentifier(e)) &&
|
|
631
|
+
(L.typeOf(e).flags & ts.TypeFlags.Any) !== 0) {
|
|
632
|
+
return true;
|
|
633
|
+
}
|
|
634
|
+
// A CALL whose inferred type has no static mapping and whose residue
|
|
635
|
+
// falls to the checked-dynamic kind (`const exec = common.mustCall(() =>
|
|
636
|
+
// ... exec ...)` — the wrapper's rest-args type is not checker-`any`,
|
|
637
|
+
// but the VALUE lowers through the DOM the same way): registering the
|
|
638
|
+
// dyn global BEFORE the initializer lowers is what lets the callback
|
|
639
|
+
// capture the binding itself — the Node-suite self-referential-const
|
|
640
|
+
// idiom. Non-dyn residues (pure single-signature function types, typed
|
|
641
|
+
// arrays) keep their static stories.
|
|
642
|
+
if (ts.isCallExpression(e)) {
|
|
643
|
+
const t = L.typeOf(e);
|
|
644
|
+
if (L.mapTypeOf(t) === null && dynFallbackType(L, e, t)?.kind === "dyn")
|
|
645
|
+
return true;
|
|
646
|
+
}
|
|
647
|
+
return false;
|
|
648
|
+
}
|
|
649
|
+
export function collectGlobals(L, sf, topStmts) {
|
|
650
|
+
// Segment-tagged so mangled names can't collide across files (user
|
|
651
|
+
// identifiers cannot contain '.'): entry globals "e.<name>", others
|
|
652
|
+
// "m<i>.<name>".
|
|
653
|
+
const rawTag = L.fileTag.get(sf) ?? "";
|
|
654
|
+
const tag = rawTag === "" ? "e." : rawTag.replace(/^%/, "");
|
|
655
|
+
// Expando function members (`foo.bar = 12` anywhere in the file)
|
|
656
|
+
// register their module globals first — reads inside function bodies
|
|
657
|
+
// collected earlier in this pass must resolve them (lower-expando.ts).
|
|
658
|
+
collectExpandoMembers(L, sf);
|
|
659
|
+
for (const stmt of topStmts) {
|
|
660
|
+
// `export default <expr>`: the module's `default` binding is a const
|
|
661
|
+
// module global (registered under the checker's default-export
|
|
662
|
+
// symbol — the symbol a default IMPORT's alias chain terminates at,
|
|
663
|
+
// so importers share the storage). The init body assigns it at the
|
|
664
|
+
// statement's position (lowerDefaultExport) — which IS Node's
|
|
665
|
+
// semantics for expression defaults: the binding snapshots the value
|
|
666
|
+
// once, when the export statement evaluates.
|
|
667
|
+
//
|
|
668
|
+
// ENTITY-NAME defaults (`export default x`, `export default N.a`)
|
|
669
|
+
// are different: the checker binds the default symbol as an ALIAS to
|
|
670
|
+
// the target, and importers resolve straight through to the target's
|
|
671
|
+
// own storage — pure plumbing, like `export { x as default }`. That
|
|
672
|
+
// is honest exactly when the target cannot be reassigned (consts,
|
|
673
|
+
// function/class declarations — reassignment of those is fenced
|
|
674
|
+
// elsewhere): Node's default binding would NOT see a later write
|
|
675
|
+
// (snapshot), the alias-resolved read would (live). Mutable targets
|
|
676
|
+
// fence by name instead of diverging silently.
|
|
677
|
+
if (ts.isExportAssignment(stmt) && !stmt.isExportEquals) {
|
|
678
|
+
const symbol = defaultExportSymbolOf(L, sf);
|
|
679
|
+
if (!symbol)
|
|
680
|
+
continue;
|
|
681
|
+
if (symbol.flags & ts.SymbolFlags.Alias) {
|
|
682
|
+
const target = L.checker.getAliasedSymbol(symbol);
|
|
683
|
+
const vd = L.checker.valueDeclarationOf(target);
|
|
684
|
+
if (vd !== undefined &&
|
|
685
|
+
ts.isVariableDeclaration(vd) &&
|
|
686
|
+
!vd.getSourceFile().isDeclarationFile &&
|
|
687
|
+
(ts.getCombinedNodeFlags(vd) & ts.NodeFlags.Const) === 0) {
|
|
688
|
+
L.pushDiag(unsupportedDiag("SC1012", locOf(stmt), `default-exporting the mutable binding '${stmt.expression.getText()}'`, "Node exports a SNAPSHOT taken when the export statement runs, which is not modeled for let/var targets — export a const, or use a named export (export { x }) for a live binding"));
|
|
689
|
+
}
|
|
690
|
+
continue; // alias plumbing — importers resolve to the target's storage
|
|
691
|
+
}
|
|
692
|
+
// A default whose expression is a decorated class expression with
|
|
693
|
+
// a PROVABLY-THROWING decoration (the ambient-decorator shape):
|
|
694
|
+
// evaluating the export statement throws before the binding could
|
|
695
|
+
// snapshot anything, so no storage registers — the statement
|
|
696
|
+
// lowering emits the throw itself (lowerDefaultExport), and no
|
|
697
|
+
// importer's read can ever run (the module init unwound).
|
|
698
|
+
{
|
|
699
|
+
let inner = stmt.expression;
|
|
700
|
+
while (ts.isParenthesizedExpression(inner))
|
|
701
|
+
inner = inner.expression;
|
|
702
|
+
if (ts.isClassExpression(inner) && !isJsSourceFile(sf) &&
|
|
703
|
+
inner.typeParameters === undefined &&
|
|
704
|
+
(decoratorNodesOf(inner).length > 0 || inner.members.some((m) => decoratorNodesOf(m).length > 0)) &&
|
|
705
|
+
guaranteedDecorationThrow(L, inner) !== null) {
|
|
706
|
+
continue;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
try {
|
|
710
|
+
let tsType = L.typeOf(stmt.expression);
|
|
711
|
+
// tsgo answers `any` at some export-assignment EXPRESSION
|
|
712
|
+
// positions (a bare literal default) — the SYMBOL's type is the
|
|
713
|
+
// declared truth and serves as the fallback.
|
|
714
|
+
if (tsType.flags & ts.TypeFlags.Any) {
|
|
715
|
+
const symType = L.checker.getTypeOfSymbol(symbol);
|
|
716
|
+
if (!(symType.flags & ts.TypeFlags.Any))
|
|
717
|
+
tsType = symType;
|
|
718
|
+
}
|
|
719
|
+
let type = L.mapTypeOf(tsType) ?? dynFallbackType(L, stmt.expression, tsType) ?? L.badType(stmt.expression, tsType);
|
|
720
|
+
// `export default undefined` — the unit-only union, like any
|
|
721
|
+
// unit-only binding (`export default null` maps via mapType).
|
|
722
|
+
if (type.kind === "void" && isUnitOnlyTsType(tsType)) {
|
|
723
|
+
type = unitOnlyUnion(L.unions);
|
|
724
|
+
}
|
|
725
|
+
if (type.kind === "void")
|
|
726
|
+
L.badType(stmt.expression, tsType);
|
|
727
|
+
const g = { id: `%g.${tag}default`, name: "default", type, mutable: false };
|
|
728
|
+
L.globalsBySymbol.set(symbol, g);
|
|
729
|
+
L.globalsList.push(g);
|
|
730
|
+
}
|
|
731
|
+
catch (e) {
|
|
732
|
+
if (!(e instanceof PoisonError))
|
|
733
|
+
throw e;
|
|
734
|
+
// diagnostic already recorded; lowering the statement poisons too
|
|
735
|
+
}
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
// CommonJS EXPORT statements (JS files): `exports.f = <expr>` /
|
|
739
|
+
// `module.exports.f = <expr>` register a module global keyed by the
|
|
740
|
+
// export symbol (the symbol importer bindings alias to), assigned by
|
|
741
|
+
// the statement's lowering at its source position — the `export
|
|
742
|
+
// default` machinery's shape. `module.exports = { ... }` registers
|
|
743
|
+
// one for each EXPRESSION-valued property; identifier/shorthand
|
|
744
|
+
// properties are pure alias plumbing (resolveValueSymbol re-resolves
|
|
745
|
+
// importers to the referenced declarations — no storage needed).
|
|
746
|
+
if (isJsSourceFile(sf)) {
|
|
747
|
+
// Statements Node would DISCARD (a replaced export object) never
|
|
748
|
+
// register storage — importer reads must not bind to values Node's
|
|
749
|
+
// importers never see. The statement lowering owns the diagnostic.
|
|
750
|
+
const cjs = cjsExportDiscardReason(stmt) === null ? cjsExportAssignmentOf(stmt) : null;
|
|
751
|
+
const registerExport = (nameNode, name, typeNode) => {
|
|
752
|
+
const diagsBefore = L.diags.length;
|
|
753
|
+
try {
|
|
754
|
+
// tsgo answers no symbol at the attachment site's NAME (no
|
|
755
|
+
// expando synthesis); the module symbol's exports table still
|
|
756
|
+
// names the member — the same identity use sites resolve.
|
|
757
|
+
const symbol = L.checker.getSymbolAtLocation(nameNode) ?? L.cjsModuleExportSymbol(sf, name);
|
|
758
|
+
if (!symbol || L.globalsBySymbol.has(symbol))
|
|
759
|
+
return;
|
|
760
|
+
// JS exports whose strict type has no mapping take the
|
|
761
|
+
// checked-dynamic fallback (implicit-any function exports —
|
|
762
|
+
// common/tls's `exports.check = function (certs) {...}` —
|
|
763
|
+
// keep func-ness with dyn pieces; everything else is dyn).
|
|
764
|
+
const strict = L.checker.getTypeOfSymbol(symbol);
|
|
765
|
+
const t = L.mapTypeOf(strict) ?? dynFallbackType(L, nameNode, strict);
|
|
766
|
+
if (!t || t.kind === "void")
|
|
767
|
+
L.badType(typeNode, strict);
|
|
768
|
+
// `module.exports.Strings = Strings` naming a DOM-HOLDING
|
|
769
|
+
// const (the JS file-scope object-literal identity story): the
|
|
770
|
+
// export ALIASES the const's own dyn global — one storage, one
|
|
771
|
+
// owner, identity by construction (a record-typed second slot
|
|
772
|
+
// would either copy or mismatch the dyn value — that mismatch
|
|
773
|
+
// was an ICE; a second same-named global double-released at
|
|
774
|
+
// teardown). The statement's assign degenerates to a harmless
|
|
775
|
+
// self-assign. Mutable (`let`) sources keep the registration
|
|
776
|
+
// below: Node copies the VALUE at this statement, and separate
|
|
777
|
+
// storage is exactly that copy.
|
|
778
|
+
{
|
|
779
|
+
let rhs = typeNode;
|
|
780
|
+
while (ts.isParenthesizedExpression(rhs))
|
|
781
|
+
rhs = rhs.expression;
|
|
782
|
+
if (ts.isIdentifier(rhs)) {
|
|
783
|
+
const vSym = L.checker.getSymbolAtLocation(rhs);
|
|
784
|
+
const vG = vSym && L.globalsBySymbol.get(vSym);
|
|
785
|
+
if (vG?.type.kind === "dyn") {
|
|
786
|
+
if (!vG.mutable) {
|
|
787
|
+
L.globalsBySymbol.set(symbol, vG);
|
|
788
|
+
for (const d of L.checker.declarationsOf(symbol))
|
|
789
|
+
L.globalsByDeclNode.set(d, vG);
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
// A mutable (`let`) DOM source: separate DYN storage IS
|
|
793
|
+
// Node's copy-of-the-reference at this statement (later
|
|
794
|
+
// reassignments of the let stay invisible through the
|
|
795
|
+
// export). The id must not collide with the let's own
|
|
796
|
+
// `%g.<tag><name>` global.
|
|
797
|
+
const g = { id: `%g.${tag}%export.${name}`, name, type: DYN, mutable: false };
|
|
798
|
+
L.globalsBySymbol.set(symbol, g);
|
|
799
|
+
for (const d of L.checker.declarationsOf(symbol))
|
|
800
|
+
L.globalsByDeclNode.set(d, g);
|
|
801
|
+
L.globalsList.push(g);
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
const g = { id: `%g.${tag}${name}`, name, type: t, mutable: false };
|
|
807
|
+
L.globalsBySymbol.set(symbol, g);
|
|
808
|
+
// Importer aliases resolve to a DISTINCT late-bound symbol with
|
|
809
|
+
// the same declaration — key the node too (globalOf's fallback).
|
|
810
|
+
for (const d of L.checker.declarationsOf(symbol))
|
|
811
|
+
L.globalsByDeclNode.set(d, g);
|
|
812
|
+
L.globalsList.push(g);
|
|
813
|
+
}
|
|
814
|
+
catch (e) {
|
|
815
|
+
if (!(e instanceof PoisonError))
|
|
816
|
+
throw e;
|
|
817
|
+
// diagnostic recorded; the statement lowering poisons too —
|
|
818
|
+
// both defer to the statement's runtime fence in JS files.
|
|
819
|
+
deferJsCollectionDiags(L, sf, diagsBefore);
|
|
820
|
+
}
|
|
821
|
+
};
|
|
822
|
+
if (cjs?.kind === "member" && ts.isIdentifier(cjs.name)) {
|
|
823
|
+
// `exports.C = C` naming a CLASS declaration: alias plumbing —
|
|
824
|
+
// no storage (a class VALUE global would fence for
|
|
825
|
+
// builtin-derived classes); importers re-resolve to the class
|
|
826
|
+
// symbol (cjsMemberExportClassSymbol) and the class registry
|
|
827
|
+
// applies unchanged.
|
|
828
|
+
if (L.cjsMemberExportClassSymbol(cjs.expr) !== null)
|
|
829
|
+
continue;
|
|
830
|
+
registerExport(cjs.name, cjs.name.text, cjs.value);
|
|
831
|
+
continue;
|
|
832
|
+
}
|
|
833
|
+
// `module.exports = <scalar literal>` — a single-value replacement
|
|
834
|
+
// export with no declaration for requirer aliases to land on: the
|
|
835
|
+
// checker's `export=` symbol declares AT this statement, so the
|
|
836
|
+
// const export global registers keyed by the statement node
|
|
837
|
+
// (globalOf's declaration-node fallback routes requirer reads
|
|
838
|
+
// here). Identifier values register nothing — alias plumbing, like
|
|
839
|
+
// identifier table entries; other value shapes fence in the
|
|
840
|
+
// statement lowering.
|
|
841
|
+
if (cjs?.kind === "table" && !cjs.obj) {
|
|
842
|
+
let rhs = cjs.expr.right;
|
|
843
|
+
while (ts.isParenthesizedExpression(rhs))
|
|
844
|
+
rhs = rhs.expression;
|
|
845
|
+
// `module.exports = function (…) {…}` (ms's shape — the whole
|
|
846
|
+
// export IS an anonymous function): a FUNC-typed const export
|
|
847
|
+
// global keyed by the statement node, exactly the scalar
|
|
848
|
+
// plumbing — requirer/default-import aliases resolve to the
|
|
849
|
+
// checker's export= symbol, whose declaration IS this statement
|
|
850
|
+
// (globalOf's node fallback), and calls ride the ordinary
|
|
851
|
+
// func-value paths. The inference types the signature (JSDoc
|
|
852
|
+
// included); unmappable pieces take the JS checked-dynamic
|
|
853
|
+
// fallback like every JS binding.
|
|
854
|
+
const fnValued = ts.isFunctionExpression(rhs) || ts.isArrowFunction(rhs);
|
|
855
|
+
if (cjsScalarLiteral(rhs) || fnValued) {
|
|
856
|
+
const diagsBefore = L.diags.length;
|
|
857
|
+
try {
|
|
858
|
+
const strict = L.typeOf(rhs);
|
|
859
|
+
const t = L.mapTypeOf(strict) ?? (fnValued ? dynFallbackType(L, rhs, strict) : null);
|
|
860
|
+
if (t && t.kind !== "void") {
|
|
861
|
+
const g = { id: `%g.${tag}exports`, name: "exports", type: t, mutable: false };
|
|
862
|
+
L.globalsByDeclNode.set(cjs.expr, g);
|
|
863
|
+
L.globalsList.push(g);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
catch (e) {
|
|
867
|
+
if (!(e instanceof PoisonError))
|
|
868
|
+
throw e;
|
|
869
|
+
deferJsCollectionDiags(L, sf, diagsBefore);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
continue;
|
|
873
|
+
}
|
|
874
|
+
if (cjs?.kind === "table" && cjs.obj) {
|
|
875
|
+
for (const prop of cjs.obj.properties) {
|
|
876
|
+
if (ts.isShorthandPropertyAssignment(prop))
|
|
877
|
+
continue; // alias plumbing
|
|
878
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.initializer))
|
|
879
|
+
continue;
|
|
880
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
881
|
+
registerExport(prop.name, prop.name.text, prop.initializer);
|
|
882
|
+
}
|
|
883
|
+
// other property forms fence in the statement lowering
|
|
884
|
+
}
|
|
885
|
+
continue;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
// `import x = N.y` where the target is a MUTABLE binding: Node's
|
|
889
|
+
// transform emits `var x = N.y` — a SNAPSHOT taken when the alias
|
|
890
|
+
// statement runs — so the alias gets its own const storage, keyed by
|
|
891
|
+
// the alias's PRE-alias symbol (resolveValueSymbol stops at alias
|
|
892
|
+
// symbols with registered storage instead of chasing them to the
|
|
893
|
+
// live target). lowerImportEquals assigns it at the statement's
|
|
894
|
+
// source position. Immutable targets stay pure plumbing (no storage;
|
|
895
|
+
// the alias-resolved read IS the snapshot when nothing can
|
|
896
|
+
// reassign), and the require/type-only forms are handled at the
|
|
897
|
+
// statement lowering.
|
|
898
|
+
if (ts.isImportEqualsDeclaration(stmt)) {
|
|
899
|
+
if (ts.isExternalModuleReference(stmt.moduleReference) || stmt.isTypeOnly)
|
|
900
|
+
continue;
|
|
901
|
+
const aliasSym = L.checker.getSymbolAtLocation(stmt.name);
|
|
902
|
+
if (!aliasSym || !(aliasSym.flags & ts.SymbolFlags.Alias) || L.globalsBySymbol.has(aliasSym))
|
|
903
|
+
continue;
|
|
904
|
+
const target = L.checker.getAliasedSymbol(aliasSym);
|
|
905
|
+
const vd = L.checker.valueDeclarationOf(target);
|
|
906
|
+
const mutableVar = vd !== undefined &&
|
|
907
|
+
ts.isVariableDeclaration(vd) &&
|
|
908
|
+
!vd.getSourceFile().isDeclarationFile &&
|
|
909
|
+
(ts.getCombinedNodeFlags(vd) & ts.NodeFlags.Const) === 0;
|
|
910
|
+
if (!mutableVar)
|
|
911
|
+
continue;
|
|
912
|
+
const diagsBefore = L.diags.length;
|
|
913
|
+
try {
|
|
914
|
+
let type = L.irTypeOf(stmt.name);
|
|
915
|
+
if (type.kind === "void" && isUnitOnlyTsType(L.typeOf(stmt.name))) {
|
|
916
|
+
type = unitOnlyUnion(L.unions);
|
|
917
|
+
}
|
|
918
|
+
if (type.kind === "void")
|
|
919
|
+
L.badType(stmt.name, L.typeOf(stmt.name));
|
|
920
|
+
const g = {
|
|
921
|
+
id: `%g.${tag}${nsPathPrefix(stmt)}%alias.${stmt.name.text}%${stmt.getStart()}`,
|
|
922
|
+
name: stmt.name.text,
|
|
923
|
+
type,
|
|
924
|
+
mutable: false,
|
|
925
|
+
};
|
|
926
|
+
L.globalsBySymbol.set(aliasSym, g);
|
|
927
|
+
L.globalsList.push(g);
|
|
928
|
+
}
|
|
929
|
+
catch (e) {
|
|
930
|
+
if (!(e instanceof PoisonError))
|
|
931
|
+
throw e;
|
|
932
|
+
deferJsCollectionDiags(L, sf, diagsBefore);
|
|
933
|
+
}
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
936
|
+
// `var` at module scope hoists across the WHOLE module body, blocks
|
|
937
|
+
// included: `if (c) { var y = 1; }` at top level is a module-scoped
|
|
938
|
+
// binding other functions in the file read. Non-top-level var
|
|
939
|
+
// statements get their globals from the nested walk (top-level
|
|
940
|
+
// statements continue into the main registration below, which also
|
|
941
|
+
// owns the special forms — requires, aliases, promisify).
|
|
942
|
+
if (!ts.isVariableStatement(stmt)) {
|
|
943
|
+
collectNestedVarGlobals(L, sf, stmt, tag);
|
|
944
|
+
continue;
|
|
945
|
+
}
|
|
946
|
+
// CommonJS require declarations are alias plumbing (preflight owns
|
|
947
|
+
// the module edges; resolveValueSymbol routes reads through to the
|
|
948
|
+
// exporter's declarations): no storage, and the statement lowers to
|
|
949
|
+
// nothing. One registration DOES happen here: `const process =
|
|
950
|
+
// require('node:process')` aliases the global process
|
|
951
|
+
// (stdlibGlobalAliasDecl's require form) so reads through the
|
|
952
|
+
// binding lower via the process surface.
|
|
953
|
+
if (isRequireStatement(stmt)) {
|
|
954
|
+
if (ts.isVariableStatement(stmt) &&
|
|
955
|
+
(stmt.declarationList.flags & ts.NodeFlags.Const) !== 0) {
|
|
956
|
+
for (const decl of stmt.declarationList.declarations) {
|
|
957
|
+
stdlibGlobalAliasDecl(L, decl.name, decl.initializer);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
continue;
|
|
961
|
+
}
|
|
962
|
+
// `declare const __VERSION__: string` — AMBIENT: no storage exists
|
|
963
|
+
// (a bundler define in the real pipeline; nothing defines it here,
|
|
964
|
+
// exactly like running the source under Node). Reads lower to
|
|
965
|
+
// Node's ReferenceError at the access (lower-exprs), and the
|
|
966
|
+
// statement itself lowers to nothing.
|
|
967
|
+
if (ts.getCombinedModifierFlags(stmt.declarationList.declarations[0]) & ts.ModifierFlags.Ambient)
|
|
968
|
+
continue;
|
|
969
|
+
const list = stmt.declarationList;
|
|
970
|
+
if ((list.flags & ts.NodeFlags.Using) !== 0)
|
|
971
|
+
continue; // fenced at the statement lowering
|
|
972
|
+
const isConst = (list.flags & ts.NodeFlags.Const) !== 0;
|
|
973
|
+
// `var` registers a mutable module global exactly like `let` — the
|
|
974
|
+
// hoisting difference (binding exists from module entry, undefined
|
|
975
|
+
// until assigned) lives in lowerFileInit's entry inits.
|
|
976
|
+
const isLet = (list.flags & ts.NodeFlags.Let) !== 0 || (list.flags & ts.NodeFlags.BlockScoped) === 0;
|
|
977
|
+
// Namespace members carry their namespace path in the storage id
|
|
978
|
+
// (`%g.e.A%B.x`), so `namespace A { export const x }` and a
|
|
979
|
+
// top-level `const x` never collide; non-exported members add the
|
|
980
|
+
// declaring block's position (block-local bindings).
|
|
981
|
+
const nsPrefix = nsPathPrefix(stmt, list.declarations[0]);
|
|
982
|
+
for (const decl of list.declarations) {
|
|
983
|
+
// --provenance-sources: an elided pure-annotated dead const in a
|
|
984
|
+
// fetched source module registers no global (the statement
|
|
985
|
+
// lowering emits nothing by the same test — see
|
|
986
|
+
// provenanceElidedConstDecl). Checked FIRST: the mixin/promisify
|
|
987
|
+
// probes below would otherwise claim the call-initializer shape
|
|
988
|
+
// and put its fences on the build.
|
|
989
|
+
if (provenanceElidedConstDecl(L, decl))
|
|
990
|
+
continue;
|
|
991
|
+
// `const f = <T>(x: T) => x` at file scope: a generic function
|
|
992
|
+
// value binding — no global exists (the binding is never read;
|
|
993
|
+
// calls and pinned references monomorphize against the
|
|
994
|
+
// initializer like a generic function declaration, registered
|
|
995
|
+
// HERE so hoisted function bodies and cross-module references
|
|
996
|
+
// resolve it). Non-qualifying shapes (reassigned bindings) fence
|
|
997
|
+
// by name; the statement lowering skips (or re-fences — pushDiag
|
|
998
|
+
// dedupes) by the same test.
|
|
999
|
+
{
|
|
1000
|
+
const gfnNode = bindingGenericFnNodeOf(decl);
|
|
1001
|
+
if (gfnNode) {
|
|
1002
|
+
try {
|
|
1003
|
+
bindingGenericFnInfoOf(L, decl, gfnNode);
|
|
1004
|
+
}
|
|
1005
|
+
catch (e) {
|
|
1006
|
+
if (!(e instanceof PoisonError))
|
|
1007
|
+
throw e;
|
|
1008
|
+
}
|
|
1009
|
+
continue;
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
// `const f = (x) => ...` at file scope of an npm-static JS file
|
|
1013
|
+
// whose params are implicit-any: the implicit-monomorphization
|
|
1014
|
+
// twin of the generic binding above — no global exists; calls and
|
|
1015
|
+
// value references resolve through genericFnsBySymbol, and the
|
|
1016
|
+
// statement lowering skips by the same test.
|
|
1017
|
+
{
|
|
1018
|
+
const implicitNode = implicitLocalFnNodeOf(L, decl);
|
|
1019
|
+
if (implicitNode) {
|
|
1020
|
+
implicitLocalFnInfoOf(L, decl, implicitNode);
|
|
1021
|
+
continue;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
// `const M = (Base: Ctor) => class extends Base {…}` — a
|
|
1025
|
+
// NON-generic mixin function binding (generic ones took the
|
|
1026
|
+
// branch above): no global exists — calls instantiate the class
|
|
1027
|
+
// inside per site (lower-mixins.ts); the statement lowering skips
|
|
1028
|
+
// by the same test.
|
|
1029
|
+
if (isMixinFnBinding(L, decl))
|
|
1030
|
+
continue;
|
|
1031
|
+
// `const Thing1 = Tagged(Derived)` — a mixin RESULT binding: the
|
|
1032
|
+
// binding provably holds that one call's class object forever
|
|
1033
|
+
// (const), so it registers like a class declaration
|
|
1034
|
+
// (classBySymbol) HERE — before any body lowers — and every
|
|
1035
|
+
// downstream path (new, statics, extends, instanceof, reads)
|
|
1036
|
+
// answers directly. No global exists; the statement lowering
|
|
1037
|
+
// skips by the same test. A poisoned instantiation reported its
|
|
1038
|
+
// own diagnostics — the binding registers nothing and use sites
|
|
1039
|
+
// fence per reference.
|
|
1040
|
+
if (ts.isIdentifier(decl.name) && decl.initializer !== undefined && isConst) {
|
|
1041
|
+
let init = decl.initializer;
|
|
1042
|
+
while (ts.isParenthesizedExpression(init))
|
|
1043
|
+
init = init.expression;
|
|
1044
|
+
if (ts.isCallExpression(init)) {
|
|
1045
|
+
const bindSym = L.checker.getSymbolAtLocation(decl.name);
|
|
1046
|
+
try {
|
|
1047
|
+
if (mixinResultBindingClassOf(L, bindSym))
|
|
1048
|
+
continue;
|
|
1049
|
+
}
|
|
1050
|
+
catch (e) {
|
|
1051
|
+
if (!(e instanceof PoisonError))
|
|
1052
|
+
throw e;
|
|
1053
|
+
continue;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
// `const execFileAsync = promisify(execFile)` at file scope: no
|
|
1058
|
+
// global exists (the promisified function value never does) — the
|
|
1059
|
+
// symbol registers HERE, before any use site can lower, and the
|
|
1060
|
+
// statement lowering skips the declaration by the same check. A
|
|
1061
|
+
// bad promisify target reports here (recorded once; the statement
|
|
1062
|
+
// lowering poisons too, like every collectGlobals failure).
|
|
1063
|
+
if (ts.isIdentifier(decl.name) && decl.initializer && isPromisifyCall(L, decl.initializer)) {
|
|
1064
|
+
try {
|
|
1065
|
+
L.promisifiedExecFileDecl(decl.name, decl.initializer);
|
|
1066
|
+
}
|
|
1067
|
+
catch (e) {
|
|
1068
|
+
if (!(e instanceof PoisonError))
|
|
1069
|
+
throw e;
|
|
1070
|
+
}
|
|
1071
|
+
continue;
|
|
1072
|
+
}
|
|
1073
|
+
// `const inspect = require("util").inspect` at file scope: a
|
|
1074
|
+
// named import in const clothing (builtinImportOf resolves uses)
|
|
1075
|
+
// — no global storage; the statement lowering skips it by the
|
|
1076
|
+
// same test.
|
|
1077
|
+
if (builtinMemberRequireDecl(decl.name, decl.initializer))
|
|
1078
|
+
continue;
|
|
1079
|
+
// `const { createSign } = crypto` over a builtin NAMESPACE binding
|
|
1080
|
+
// at file scope: alias plumbing like the destructured-require form
|
|
1081
|
+
// — no storage (the statement lowering skips by the same test).
|
|
1082
|
+
if (builtinNamespaceDestructureModuleOf(L, decl) !== null)
|
|
1083
|
+
continue;
|
|
1084
|
+
// `const { NGHTTP2_CANCEL } = http2.constants` at file scope: a
|
|
1085
|
+
// destructure over the baked constants table — alias plumbing, no
|
|
1086
|
+
// global storage (the statement lowering skips it by the same
|
|
1087
|
+
// test).
|
|
1088
|
+
if (L.http2ConstantsDestructureDecl(decl.name, decl.initializer))
|
|
1089
|
+
continue;
|
|
1090
|
+
// `const Writable = stream.Writable` at file scope: a stream
|
|
1091
|
+
// class through the namespace binding — alias plumbing, no
|
|
1092
|
+
// global storage (the statement lowering skips it by the same
|
|
1093
|
+
// test).
|
|
1094
|
+
if (isConst && streamClassAliasDecl(L, decl.name, decl.initializer))
|
|
1095
|
+
continue;
|
|
1096
|
+
// `const process = globalThis.process` at file scope: a stdlib-
|
|
1097
|
+
// global snapshot — alias plumbing, no global storage (see
|
|
1098
|
+
// stdlibGlobalAliasDecl; the statement lowering skips it by the
|
|
1099
|
+
// same test).
|
|
1100
|
+
if (isConst && stdlibGlobalAliasDecl(L, decl.name, decl.initializer))
|
|
1101
|
+
continue;
|
|
1102
|
+
// Destructuring declarations register EVERY bound identifier (the
|
|
1103
|
+
// desugar in the init function assigns the pre-registered globals,
|
|
1104
|
+
// exactly like plain declarations).
|
|
1105
|
+
for (const nameNode of boundIdentifiersOf(decl.name)) {
|
|
1106
|
+
const diagsBefore = L.diags.length;
|
|
1107
|
+
try {
|
|
1108
|
+
// A JS file-scope evolving ARRAY (`const mustCallChecks = [];`
|
|
1109
|
+
// — test/common's exit-accounting ledger): the strict type
|
|
1110
|
+
// (any[]) has no mapping, but the VALUE is the DOM array the
|
|
1111
|
+
// literal builds (lowerArrayLiteral's JS fallback) — register
|
|
1112
|
+
// a checked-dynamic global so separately-declared function
|
|
1113
|
+
// bodies (runCallChecks, _mustCallInner) reach the SAME
|
|
1114
|
+
// storage instead of fencing per reference. Identifier
|
|
1115
|
+
// declarations only: a destructured array literal binds its
|
|
1116
|
+
// pieces, not the array.
|
|
1117
|
+
if (isJsSourceFile(sf) && !L.mapTypeOf(L.typeOf(nameNode)) &&
|
|
1118
|
+
ts.isIdentifier(decl.name) && nameNode === decl.name &&
|
|
1119
|
+
decl.initializer !== undefined && ts.isArrayLiteralExpression(decl.initializer)) {
|
|
1120
|
+
const symbol = L.checker.getSymbolAtLocation(nameNode);
|
|
1121
|
+
if (symbol) {
|
|
1122
|
+
const g = { id: `%g.${tag}${nameNode.text}`, name: nameNode.text, type: DYN, mutable: isLet };
|
|
1123
|
+
L.globalsBySymbol.set(symbol, g);
|
|
1124
|
+
L.globalsList.push(g);
|
|
1125
|
+
}
|
|
1126
|
+
continue;
|
|
1127
|
+
}
|
|
1128
|
+
// A JS file-scope OBJECT LITERAL (`const input = { foo: 'bar' }`
|
|
1129
|
+
// — the Node-suite trace-context idiom): JS object identity is
|
|
1130
|
+
// the literal's contract — the SAME object flows into untyped
|
|
1131
|
+
// callees, gets stamped by them, and is compared back against
|
|
1132
|
+
// the binding (assert.strictEqual(found, input)). A static
|
|
1133
|
+
// record global would re-box at every DOM crossing and lose
|
|
1134
|
+
// that identity, so the binding holds the DOM object itself.
|
|
1135
|
+
// Gated to literals that are PURE DATA WRITTEN INLINE — every
|
|
1136
|
+
// property a non-computed PropertyAssignment whose value is
|
|
1137
|
+
// dyn-representable (unmappable member types are the DOM
|
|
1138
|
+
// fallback's own case) — so shorthand aggregates (test/common's
|
|
1139
|
+
// export object: alias plumbing importers resolve THROUGH),
|
|
1140
|
+
// spreads, accessors, and literals holding unconvertible typed
|
|
1141
|
+
// values (a Map member) keep their static/alias stories.
|
|
1142
|
+
if (isJsSourceFile(sf) &&
|
|
1143
|
+
ts.isIdentifier(decl.name) && nameNode === decl.name &&
|
|
1144
|
+
decl.initializer !== undefined && ts.isObjectLiteralExpression(decl.initializer) &&
|
|
1145
|
+
decl.initializer.properties.every((p) => {
|
|
1146
|
+
if (!ts.isPropertyAssignment(p) || ts.isComputedPropertyName(p.name))
|
|
1147
|
+
return false;
|
|
1148
|
+
const mt = L.mapTypeOf(L.typeOf(p.initializer));
|
|
1149
|
+
return mt === null || mt.kind === "dyn" ||
|
|
1150
|
+
canConvertToDyn(mt, (id) => L.shapes.get(id), (id) => L.unions.get(id));
|
|
1151
|
+
})) {
|
|
1152
|
+
const symbol = L.checker.getSymbolAtLocation(nameNode);
|
|
1153
|
+
if (symbol && !L.globalsBySymbol.has(symbol)) {
|
|
1154
|
+
const g = { id: `%g.${tag}${nameNode.text}`, name: nameNode.text, type: DYN, mutable: isLet };
|
|
1155
|
+
L.globalsBySymbol.set(symbol, g);
|
|
1156
|
+
L.globalsList.push(g);
|
|
1157
|
+
}
|
|
1158
|
+
continue;
|
|
1159
|
+
}
|
|
1160
|
+
// A JS file-scope ANY-RESIDUE binding whose VALUE provably
|
|
1161
|
+
// lives in the DOM: no initializer (`let catchWarning;` —
|
|
1162
|
+
// test/common's warning ledger), a unit initializer (`let
|
|
1163
|
+
// localhostIPv4 = null;` — the lazy-cache idiom), or an
|
|
1164
|
+
// initializer whose own checker type is `any` (`const handler
|
|
1165
|
+
// = common.mustCall(cb)` — a dyn call result). Register a
|
|
1166
|
+
// checked-dynamic global (the evolving-array precedent above)
|
|
1167
|
+
// so separately-declared functions and getters reach the SAME
|
|
1168
|
+
// storage instead of fencing per reference. Typed-but-
|
|
1169
|
+
// unmappable initializers (`new Set(...)` — a real Set the
|
|
1170
|
+
// decl keeps as an %init local) stay OUT: forcing those into
|
|
1171
|
+
// the DOM would trade their working typed representation for
|
|
1172
|
+
// fences.
|
|
1173
|
+
if (isJsSourceFile(sf) && !L.mapTypeOf(L.typeOf(nameNode)) &&
|
|
1174
|
+
ts.isIdentifier(decl.name) && nameNode === decl.name &&
|
|
1175
|
+
jsDynHoldableInitializer(L, decl.initializer) &&
|
|
1176
|
+
dynFallbackType(L, nameNode, L.typeOf(nameNode))?.kind === "dyn") {
|
|
1177
|
+
const symbol = L.checker.getSymbolAtLocation(nameNode);
|
|
1178
|
+
if (symbol && !L.globalsBySymbol.has(symbol)) {
|
|
1179
|
+
const g = { id: `%g.${tag}${nsPrefix}${nameNode.text}`, name: nameNode.text, type: DYN, mutable: isLet };
|
|
1180
|
+
L.globalsBySymbol.set(symbol, g);
|
|
1181
|
+
L.globalsList.push(g);
|
|
1182
|
+
// Mutable dyn globals hold the DOM undefined from module
|
|
1183
|
+
// entry (the main path's rule below): a closure called
|
|
1184
|
+
// above the declaration reads undefined instead of
|
|
1185
|
+
// faulting on NULL.
|
|
1186
|
+
if (g.mutable)
|
|
1187
|
+
noteVarGlobalEntryInit(L, sf, g);
|
|
1188
|
+
}
|
|
1189
|
+
continue;
|
|
1190
|
+
}
|
|
1191
|
+
// JS declarations whose STRICT type has no mapping register no
|
|
1192
|
+
// global at all: the declaration lowers as an %init-body LOCAL
|
|
1193
|
+
// whose type adopts the initializer's (lowerVarDecl's JS
|
|
1194
|
+
// rule — `new Set([...tokens])` becomes a real Set<string>),
|
|
1195
|
+
// and closures created in the init body capture it normally.
|
|
1196
|
+
// References from separately-declared functions cascade to
|
|
1197
|
+
// their own per-site runtime fences.
|
|
1198
|
+
if (isJsSourceFile(sf) && !L.mapTypeOf(L.typeOf(nameNode)))
|
|
1199
|
+
continue;
|
|
1200
|
+
// `var p1 = import("./m")` at file scope: the global holds the
|
|
1201
|
+
// island promise/handle — the import expression's only
|
|
1202
|
+
// production — whatever the checker's namespace type mapped to
|
|
1203
|
+
// (lowerVarDecl's rule at module scope; the init body assigns
|
|
1204
|
+
// it).
|
|
1205
|
+
const handleT = L.dynamic && ts.isIdentifier(decl.name) && nameNode === decl.name
|
|
1206
|
+
? (importCallHandleType(decl.initializer) ??
|
|
1207
|
+
// An unchecked-overload call result stores the handle,
|
|
1208
|
+
// exactly the local rule (uncheckedOverloadHandleCall).
|
|
1209
|
+
(uncheckedOverloadHandleCall(L, decl.initializer) ? JSVAL : null))
|
|
1210
|
+
: null;
|
|
1211
|
+
let type = handleT ?? L.irTypeOf(nameNode);
|
|
1212
|
+
// A file-scope PATTERN over an ISLAND-bound source (`export
|
|
1213
|
+
// let { toString } = 1;` — the engine reads the wrapper's
|
|
1214
|
+
// prototype member): the bound value follows the island
|
|
1215
|
+
// property-read rule, so the global slot must too — declared
|
|
1216
|
+
// primitives exit eagerly to their static type, everything
|
|
1217
|
+
// else stores the HANDLE (a func-typed slot could never take
|
|
1218
|
+
// the engine value the desugar assigns).
|
|
1219
|
+
if (L.dynamic && !ts.isIdentifier(decl.name) && decl.initializer !== undefined) {
|
|
1220
|
+
const srcT = L.mapTypeOf(L.typeOf(decl.initializer));
|
|
1221
|
+
const island = srcT !== null &&
|
|
1222
|
+
(srcT.kind === "jsval" || srcT.kind === "f64" || srcT.kind === "bool" ||
|
|
1223
|
+
srcT.kind === "string" || isUnitType(srcT));
|
|
1224
|
+
if (island && type.kind !== "f64" && type.kind !== "bool" && type.kind !== "string") {
|
|
1225
|
+
type = JSVAL;
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
// File-scope unit-only bindings (`var x: undefined`, `const
|
|
1229
|
+
// y: void = undefined`) ride the unit-only union, exactly
|
|
1230
|
+
// like function-scope locals (lowerVarDecl's rule).
|
|
1231
|
+
if (type.kind === "void" && isUnitOnlyTsType(L.typeOf(nameNode))) {
|
|
1232
|
+
type = unitOnlyUnion(L.unions);
|
|
1233
|
+
}
|
|
1234
|
+
if (type.kind === "void")
|
|
1235
|
+
L.badType(nameNode, L.typeOf(nameNode));
|
|
1236
|
+
// A JS file-scope FUNCTION binding whose unannotated return
|
|
1237
|
+
// infers a record (`const wrapped = function () { return
|
|
1238
|
+
// expectedResult; }`): the closure VALUE lowers with a dyn
|
|
1239
|
+
// return (declaredReturnType's record twin — JS object
|
|
1240
|
+
// literals are DOM values, and a record return would copy
|
|
1241
|
+
// identity away), so the binding's slot must agree or an
|
|
1242
|
+
// adapter would re-copy at the assignment.
|
|
1243
|
+
if (isJsSourceFile(sf) &&
|
|
1244
|
+
type.kind === "func" &&
|
|
1245
|
+
type.ret.kind === "record" &&
|
|
1246
|
+
decl.initializer !== undefined &&
|
|
1247
|
+
(ts.isFunctionExpression(decl.initializer) || ts.isArrowFunction(decl.initializer)) &&
|
|
1248
|
+
decl.initializer.type === undefined &&
|
|
1249
|
+
type.params.every((pt) => pt.kind === "dyn")) {
|
|
1250
|
+
type = { ...type, ret: DYN };
|
|
1251
|
+
}
|
|
1252
|
+
// A file-scope JS `let x = {}`: the same checked-dynamic rule
|
|
1253
|
+
// lowerVarDecl applies to locals — TS's empty-object-literal
|
|
1254
|
+
// type admits ANY later non-nullish assignment, so the static
|
|
1255
|
+
// empty struct cannot hold the binding's future.
|
|
1256
|
+
if (isLet && type.kind === "record" && isJsSourceFile(sf)) {
|
|
1257
|
+
const shape = L.shapes.get(type.shapeId);
|
|
1258
|
+
if (shape && shape.fields.length === 0 && !shape.indexValue && !shape.tuple)
|
|
1259
|
+
type = DYN;
|
|
1260
|
+
}
|
|
1261
|
+
const symbol = L.checker.getSymbolAtLocation(nameNode);
|
|
1262
|
+
// Merged `var` redeclarations (`var y = 1; ...; var y = 2;` —
|
|
1263
|
+
// one symbol) register exactly one global; later declarations
|
|
1264
|
+
// are plain assignments.
|
|
1265
|
+
if (!symbol || L.globalsBySymbol.has(symbol))
|
|
1266
|
+
continue;
|
|
1267
|
+
const g = {
|
|
1268
|
+
id: `%g.${tag}${nsPrefix}${nameNode.text}`,
|
|
1269
|
+
name: nameNode.text,
|
|
1270
|
+
type,
|
|
1271
|
+
mutable: isLet,
|
|
1272
|
+
};
|
|
1273
|
+
L.globalsBySymbol.set(symbol, g);
|
|
1274
|
+
L.globalsList.push(g);
|
|
1275
|
+
// Mutable checked-dynamic LET globals ride the same entry
|
|
1276
|
+
// init: a closure called above the declaration statement
|
|
1277
|
+
// reads the DOM undefined instead of faulting on NULL — the
|
|
1278
|
+
// dyn face of let's documented pre-declaration window (Node
|
|
1279
|
+
// throws the TDZ ReferenceError there).
|
|
1280
|
+
if (isVarDeclared(decl) || (g.type.kind === "dyn" && g.mutable)) {
|
|
1281
|
+
noteVarGlobalEntryInit(L, sf, g);
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
catch (e) {
|
|
1285
|
+
if (!(e instanceof PoisonError))
|
|
1286
|
+
throw e;
|
|
1287
|
+
// diagnostic already recorded; lowering the statement poisons
|
|
1288
|
+
// too — and in a JS file BOTH defer to the statement's
|
|
1289
|
+
// runtime fence (deferJsCollectionDiags).
|
|
1290
|
+
deferJsCollectionDiags(L, sf, diagsBefore);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
/** Registers the entry-init note for a `var` module global: JS hoists
|
|
1297
|
+
* module vars to `undefined` at module entry, so an undefined-armed slot
|
|
1298
|
+
* must hold the interned undefined arm BEFORE the body runs (a function
|
|
1299
|
+
* called above the declaration statement may read it — lowerFileInit
|
|
1300
|
+
* emits the assigns right after the run-once guard). Checked-dynamic
|
|
1301
|
+
* slots hold the DOM undefined the same way (a NULL dyn is a trap, not
|
|
1302
|
+
* a value). Other types need nothing: tsc's flow analysis rejects their
|
|
1303
|
+
* direct early reads, and closure reads share let's documented
|
|
1304
|
+
* zero/NULL divergence window. */
|
|
1305
|
+
function noteVarGlobalEntryInit(L, sf, g) {
|
|
1306
|
+
// 'any' globals need the entry init exactly like undefined-armed
|
|
1307
|
+
// unions: tsc never guards `any` reads, so `var x: any;` is readable
|
|
1308
|
+
// before any assignment and its slot must hold its world's undefined
|
|
1309
|
+
// — the ENGINE's for jsval (--dynamic), the DOM's for dyn (static) —
|
|
1310
|
+
// rather than a C-level NULL (an op or validated exit on NULL is
|
|
1311
|
+
// memory-unsafe, not a TypeError).
|
|
1312
|
+
if (g.type.kind !== "union" && g.type.kind !== "jsval" && g.type.kind !== "dyn")
|
|
1313
|
+
return;
|
|
1314
|
+
const inits = L.varGlobalEntryInits.get(sf) ?? [];
|
|
1315
|
+
inits.push(g);
|
|
1316
|
+
L.varGlobalEntryInits.set(sf, inits);
|
|
1317
|
+
}
|
|
1318
|
+
/** The nested half of module-scope `var` hoisting: a `var` inside a
|
|
1319
|
+
* top-level BLOCK (an if arm, a loop body, a try) is module-scoped in
|
|
1320
|
+
* JS — other functions in the file read the same binding — so it must
|
|
1321
|
+
* be a module global like its top-level siblings. Walks a top-level
|
|
1322
|
+
* statement's insides for var-flagged declaration lists (statement and
|
|
1323
|
+
* for/for-in/for-of initializer positions), never descending into
|
|
1324
|
+
* function-likes, classes, or static blocks (each hoists to its own
|
|
1325
|
+
* scope). Registration mirrors the main path's identifier story:
|
|
1326
|
+
* JS-unmappable types register nothing (per-site fences own the
|
|
1327
|
+
* references), everything else gets a mutable global. */
|
|
1328
|
+
function collectNestedVarGlobals(L, sf, stmt, tag) {
|
|
1329
|
+
const lists = [];
|
|
1330
|
+
// Iterative walk (walkPreorder): this sweep sees every non-var top-level
|
|
1331
|
+
// statement whole — the binderBinaryExpressionStress bare ~6500-term
|
|
1332
|
+
// chain sat within ~50 terms of overflowing the recursive form.
|
|
1333
|
+
ts.walkPreorder(stmt, (node) => {
|
|
1334
|
+
if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node) ||
|
|
1335
|
+
ts.isMethodDeclaration(node) || ts.isConstructorDeclaration(node) ||
|
|
1336
|
+
ts.isGetAccessorDeclaration(node) || ts.isSetAccessorDeclaration(node) ||
|
|
1337
|
+
ts.isClassStaticBlockDeclaration(node) || ts.isClassDeclaration(node) || ts.isClassExpression(node) ||
|
|
1338
|
+
ts.isModuleDeclaration(node)) {
|
|
1339
|
+
return "skip";
|
|
1340
|
+
}
|
|
1341
|
+
if (ts.isVariableDeclarationList(node) && (node.flags & ts.NodeFlags.BlockScoped) === 0) {
|
|
1342
|
+
lists.push(node);
|
|
1343
|
+
return "skip"; // initializer expressions hold no further statements outside function bodies
|
|
1344
|
+
}
|
|
1345
|
+
return undefined;
|
|
1346
|
+
});
|
|
1347
|
+
for (const list of lists) {
|
|
1348
|
+
for (const decl of list.declarations) {
|
|
1349
|
+
for (const nameNode of boundIdentifiersOf(decl.name)) {
|
|
1350
|
+
const diagsBefore = L.diags.length;
|
|
1351
|
+
try {
|
|
1352
|
+
if (isJsSourceFile(sf) && !L.mapTypeOf(L.typeOf(nameNode)))
|
|
1353
|
+
continue;
|
|
1354
|
+
let type = L.irTypeOf(nameNode);
|
|
1355
|
+
// Nested-var unit-only bindings take the unit-only union too
|
|
1356
|
+
// (the top-level registration's rule).
|
|
1357
|
+
if (type.kind === "void" && isUnitOnlyTsType(L.typeOf(nameNode))) {
|
|
1358
|
+
type = unitOnlyUnion(L.unions);
|
|
1359
|
+
}
|
|
1360
|
+
if (type.kind === "void")
|
|
1361
|
+
L.badType(nameNode, L.typeOf(nameNode));
|
|
1362
|
+
if (type.kind === "record" && isJsSourceFile(sf)) {
|
|
1363
|
+
const shape = L.shapes.get(type.shapeId);
|
|
1364
|
+
if (shape && shape.fields.length === 0 && !shape.indexValue && !shape.tuple)
|
|
1365
|
+
type = DYN;
|
|
1366
|
+
}
|
|
1367
|
+
const symbol = L.checker.getSymbolAtLocation(nameNode);
|
|
1368
|
+
if (!symbol || L.globalsBySymbol.has(symbol))
|
|
1369
|
+
continue;
|
|
1370
|
+
const g = { id: `%g.${tag}${nameNode.text}`, name: nameNode.text, type, mutable: true };
|
|
1371
|
+
L.globalsBySymbol.set(symbol, g);
|
|
1372
|
+
L.globalsList.push(g);
|
|
1373
|
+
noteVarGlobalEntryInit(L, sf, g);
|
|
1374
|
+
}
|
|
1375
|
+
catch (e) {
|
|
1376
|
+
if (!(e instanceof PoisonError))
|
|
1377
|
+
throw e;
|
|
1378
|
+
deferJsCollectionDiags(L, sf, diagsBefore);
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
/** The checker's symbol for a module's default export — the symbol a
|
|
1385
|
+
* default import of this module aliases to (getAliasedSymbol), so keying
|
|
1386
|
+
* the default's global by it makes every importer resolve to the same
|
|
1387
|
+
* storage through the ordinary globalOf path. Null for modules without
|
|
1388
|
+
* a default export (or non-module files). */
|
|
1389
|
+
export function defaultExportSymbolOf(L, sf) {
|
|
1390
|
+
const moduleSym = L.checker.getSymbolAtLocation(sf);
|
|
1391
|
+
return moduleSym?.getExports().get(ts.InternalSymbolName.Default) ?? null;
|
|
1392
|
+
}
|
|
1393
|
+
/** The declaration symbol of a top-level function/class declaration,
|
|
1394
|
+
* covering the one legal NAMELESS form: `export default function () {}` /
|
|
1395
|
+
* `export default class {}`. A named declaration answers through its
|
|
1396
|
+
* name node; the anonymous default IS the module's `default` export
|
|
1397
|
+
* symbol (the binder declares it there), so collection, reachability,
|
|
1398
|
+
* and body lowering all key that one identity — which is also what a
|
|
1399
|
+
* default import's alias chain resolves to. */
|
|
1400
|
+
export function declSymbolOf(L, decl) {
|
|
1401
|
+
if (decl.name)
|
|
1402
|
+
return L.checker.getSymbolAtLocation(decl.name);
|
|
1403
|
+
const isDefault = ts.canHaveModifiers(decl) &&
|
|
1404
|
+
ts.getModifiers(decl)?.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword) === true;
|
|
1405
|
+
if (!isDefault)
|
|
1406
|
+
return undefined;
|
|
1407
|
+
const sym = defaultExportSymbolOf(L, decl.getSourceFile());
|
|
1408
|
+
return sym ?? undefined;
|
|
1409
|
+
}
|
|
1410
|
+
/** `export default <expr>` in a module's init body: the first (and only)
|
|
1411
|
+
* assignment of the pre-registered `default` global — evaluated at the
|
|
1412
|
+
* statement's source position, exactly where Node runs it (Node's
|
|
1413
|
+
* expression defaults snapshot exactly once, here). ENTITY-NAME defaults
|
|
1414
|
+
* are alias plumbing with no storage of their own: the statement lowers
|
|
1415
|
+
* to nothing — collection either accepted the alias (const/function/
|
|
1416
|
+
* class targets, where importers' alias-resolved reads equal the
|
|
1417
|
+
* snapshot) or already fenced it (mutable targets), and re-lowering
|
|
1418
|
+
* would double-report. A NON-alias default whose type had no
|
|
1419
|
+
* registration (collection reported the blocker) re-lowers the
|
|
1420
|
+
* expression so the expression's OWN diagnostic wins when it has one,
|
|
1421
|
+
* then poisons. */
|
|
1422
|
+
export function lowerDefaultExport(L, stmt) {
|
|
1423
|
+
const symbol = defaultExportSymbolOf(L, stmt.getSourceFile());
|
|
1424
|
+
if (symbol && symbol.flags & ts.SymbolFlags.Alias)
|
|
1425
|
+
return null;
|
|
1426
|
+
const g = symbol ? L.globalsBySymbol.get(symbol) : undefined;
|
|
1427
|
+
if (!g) {
|
|
1428
|
+
// A provably-throwing decorated class expression registered no
|
|
1429
|
+
// storage (the registration pass skipped it): the statement IS the
|
|
1430
|
+
// throw — lowerClassExpression answers the ambient undefRead.
|
|
1431
|
+
{
|
|
1432
|
+
let inner = stmt.expression;
|
|
1433
|
+
while (ts.isParenthesizedExpression(inner))
|
|
1434
|
+
inner = inner.expression;
|
|
1435
|
+
if (ts.isClassExpression(inner) && !isJsSourceFile(stmt.getSourceFile()) &&
|
|
1436
|
+
inner.typeParameters === undefined &&
|
|
1437
|
+
(decoratorNodesOf(inner).length > 0 || inner.members.some((m) => decoratorNodesOf(m).length > 0)) &&
|
|
1438
|
+
guaranteedDecorationThrow(L, inner) !== null) {
|
|
1439
|
+
return { kind: "exprStmt", expr: L.lowerExpr(stmt.expression), loc: locOf(stmt) };
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
L.lowerExpr(stmt.expression);
|
|
1443
|
+
L.badType(stmt.expression, L.typeOf(stmt.expression));
|
|
1444
|
+
}
|
|
1445
|
+
const value = L.lowerExprExpecting(stmt.expression, g.type);
|
|
1446
|
+
return { kind: "assign", localId: g.id, value, loc: locOf(stmt) };
|
|
1447
|
+
}
|
|
1448
|
+
/** One file's top-level statements as its init function — RUN-ONCE like a
|
|
1449
|
+
* Node module body: non-entry inits open with a guard flag test (already
|
|
1450
|
+
* ran → return) and set the flag before anything else, so every caller —
|
|
1451
|
+
* hoisted import headers, inline require statements, diamonds, cache-hit
|
|
1452
|
+
* re-requires — gets Node's module-cache semantics from the same call.
|
|
1453
|
+
* After the guard comes the file's HOISTED module-edge header: one
|
|
1454
|
+
* guarded %init call per imported user module and the island loads for
|
|
1455
|
+
* npm imports, in import order (Node evaluates every imported module
|
|
1456
|
+
* before the importer's body — depth-first postorder falls out of the
|
|
1457
|
+
* nesting). CommonJS requires are NOT in the header: Node runs them at
|
|
1458
|
+
* their statements, so the statement lowering emits their %init calls
|
|
1459
|
+
* inline in the body. JSON-import bindings (pure data) load next; then
|
|
1460
|
+
* the body. Top-level var statements assign the pre-registered globals
|
|
1461
|
+
* instead of declaring locals. */
|
|
1462
|
+
export function lowerFileInit(L, sf, stmts, name) {
|
|
1463
|
+
L.fnStack.push(newFnCtx(false, null, null, VOID));
|
|
1464
|
+
try {
|
|
1465
|
+
const loc0 = { file: sf.fileName, start: 0, end: 0 };
|
|
1466
|
+
const header = [];
|
|
1467
|
+
const guardId = L.moduleGuardOf.get(sf);
|
|
1468
|
+
if (guardId !== undefined) {
|
|
1469
|
+
header.push({
|
|
1470
|
+
kind: "if",
|
|
1471
|
+
cond: { kind: "varRef", localId: guardId, type: BOOL, loc: loc0 },
|
|
1472
|
+
then: [{ kind: "return", value: null, loc: loc0 }],
|
|
1473
|
+
else_: null,
|
|
1474
|
+
loc: loc0,
|
|
1475
|
+
});
|
|
1476
|
+
// The flag sets BEFORE the body runs — Node marks the cache entry
|
|
1477
|
+
// before evaluating too (moot while cycles are fenced, but the
|
|
1478
|
+
// honest order costs nothing).
|
|
1479
|
+
header.push({
|
|
1480
|
+
kind: "assign",
|
|
1481
|
+
localId: guardId,
|
|
1482
|
+
value: { kind: "boolLit", value: true, type: BOOL, loc: loc0 },
|
|
1483
|
+
loc: loc0,
|
|
1484
|
+
});
|
|
1485
|
+
}
|
|
1486
|
+
for (const { stmt, dep } of orderedImportsOf(L.program, sf)) {
|
|
1487
|
+
const npm = L.npmInitActions.get(sf)?.get(stmt);
|
|
1488
|
+
if (npm !== undefined && npm.length > 0) {
|
|
1489
|
+
header.push(...npm);
|
|
1490
|
+
}
|
|
1491
|
+
else if (dep !== null && dep !== sf) {
|
|
1492
|
+
// dep === sf is the self-import (the package self-name resolving
|
|
1493
|
+
// to the importing module): Node answers it from the module
|
|
1494
|
+
// cache mid-evaluation — no re-evaluation, nothing to call (and
|
|
1495
|
+
// the ENTRY has no run-once guard, so a self-call would recurse).
|
|
1496
|
+
const depInit = L.initNameOf.get(dep);
|
|
1497
|
+
if (depInit !== undefined) {
|
|
1498
|
+
const loc = locOf(stmt);
|
|
1499
|
+
header.push({
|
|
1500
|
+
kind: "exprStmt",
|
|
1501
|
+
expr: { kind: "call", callee: depInit, args: [], type: VOID, loc },
|
|
1502
|
+
loc,
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
// Module-scope `var` hoisting: undefined-armed var globals hold the
|
|
1508
|
+
// interned undefined from module entry (before any body statement —
|
|
1509
|
+
// a function called above the declaration reads `undefined`, exactly
|
|
1510
|
+
// Node); checked-dynamic var globals hold the DOM undefined the same
|
|
1511
|
+
// way. After the guard: a cache-hit revisit must not reset them.
|
|
1512
|
+
for (const g of L.varGlobalEntryInits.get(sf) ?? []) {
|
|
1513
|
+
const wrapped = g.type.kind === "dyn" ? dynUndefinedExpr(loc0) : L.unassignedSlotInit(g.type, loc0);
|
|
1514
|
+
if (wrapped) {
|
|
1515
|
+
header.push({ kind: "assign", localId: g.id, value: wrapped, loc: loc0 });
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
const prelude = L.jsonInitActions.get(sf) ?? [];
|
|
1519
|
+
// Class STATIC readonly fields, static BLOCKS, and class DECORATORS
|
|
1520
|
+
// run at their class statement's source position (splitFiles hoisted
|
|
1521
|
+
// the declarations out of `stmts`, so the statements merge back in
|
|
1522
|
+
// by position) — exactly when JS evaluates decorators, static
|
|
1523
|
+
// initializers, and blocks, so code reading an earlier module
|
|
1524
|
+
// binding sees its assigned value.
|
|
1525
|
+
const statics = [...L.classes.values()]
|
|
1526
|
+
.filter((c) =>
|
|
1527
|
+
// Class DECLARATIONS only: expression classes run their static
|
|
1528
|
+
// inits through pendingClassExprInits (below) at the statement
|
|
1529
|
+
// that evaluates them. MIXIN instantiations join by their CALL
|
|
1530
|
+
// SITE's position instead (below) — their inner class node may
|
|
1531
|
+
// even live in another file.
|
|
1532
|
+
c.decl && ts.isClassDeclaration(c.decl) && !c.mixinInstance && c.decl.getSourceFile() === sf &&
|
|
1533
|
+
(c.staticFields.length > 0 || (c.staticBlocks?.length ?? 0) > 0 || c.classDecorators !== undefined))
|
|
1534
|
+
.map((c) => ({ pos: c.decl.getStart(), info: c }));
|
|
1535
|
+
// Statics-bearing MIXIN instantiations whose call evaluates in THIS
|
|
1536
|
+
// file: their declaration-time code runs when the call does — the
|
|
1537
|
+
// top-level statement lower-mixins recorded. Insertion order is
|
|
1538
|
+
// demand order (a nested mixin's base instantiates first, a
|
|
1539
|
+
// heritage base before its derived class), so the stable sort keeps
|
|
1540
|
+
// JS's evaluation order at equal positions.
|
|
1541
|
+
for (const c of L.classes.values()) {
|
|
1542
|
+
const ms = c.mixinInstance?.statics;
|
|
1543
|
+
if (ms && ms.sf === sf)
|
|
1544
|
+
statics.push({ pos: ms.pos, info: c });
|
|
1545
|
+
}
|
|
1546
|
+
statics.sort((a, b) => a.pos - b.pos);
|
|
1547
|
+
const body = [...header, ...prelude];
|
|
1548
|
+
let at = 0;
|
|
1549
|
+
for (const stmt of stmts) {
|
|
1550
|
+
// `<=`: a mixin instantiation's position IS its statement's start
|
|
1551
|
+
// — its statics run mid-statement in JS (the call), before
|
|
1552
|
+
// anything the statement's lowering emits. Hoisted class
|
|
1553
|
+
// declarations never share a kept statement's start, so `<=`
|
|
1554
|
+
// changes nothing for them.
|
|
1555
|
+
while (at < statics.length && statics[at].pos <= stmt.getStart()) {
|
|
1556
|
+
body.push(...L.lowerStaticFieldInits(statics[at].info));
|
|
1557
|
+
at++;
|
|
1558
|
+
}
|
|
1559
|
+
const stmtIr = L.lowerStmts([stmt]);
|
|
1560
|
+
// Class EXPRESSIONS inside this statement queued their static
|
|
1561
|
+
// inits while it lowered: they land immediately before it — JS's
|
|
1562
|
+
// order for the supported whole-initializer positions.
|
|
1563
|
+
body.push(...L.pendingClassExprInits.splice(0), ...stmtIr);
|
|
1564
|
+
}
|
|
1565
|
+
while (at < statics.length) {
|
|
1566
|
+
body.push(...L.lowerStaticFieldInits(statics[at].info));
|
|
1567
|
+
at++;
|
|
1568
|
+
}
|
|
1569
|
+
const loc = { file: sf.fileName, start: 0, end: 0 };
|
|
1570
|
+
return { name, params: [], returnType: VOID, locals: L.ctx.locals, body, loc };
|
|
1571
|
+
}
|
|
1572
|
+
finally {
|
|
1573
|
+
L.fnStack.pop();
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
/** %main is just the entry's %init call: Node's whole module evaluation
|
|
1577
|
+
* order lives in the init functions themselves — each file's hoisted
|
|
1578
|
+
* import header runs its dependencies first (depth-first postorder by
|
|
1579
|
+
* nesting), CommonJS require statements call dependency inits inline in
|
|
1580
|
+
* their bodies, and the run-once guards turn every revisit (diamonds,
|
|
1581
|
+
* re-imports, re-requires) into a cache hit. */
|
|
1582
|
+
export function buildMain(L) {
|
|
1583
|
+
const loc = { file: L.entry.fileName, start: 0, end: 0 };
|
|
1584
|
+
const entryInit = L.initNameOf.get(L.entry);
|
|
1585
|
+
const body = entryInit !== undefined
|
|
1586
|
+
? [{ kind: "exprStmt", expr: { kind: "call", callee: entryInit, args: [], type: VOID, loc }, loc }]
|
|
1587
|
+
: [];
|
|
1588
|
+
// Node's startup refusal (a resolution the graph carries that Node
|
|
1589
|
+
// rejects — preflight's Node-order resolution walk — or the module-
|
|
1590
|
+
// LINK SyntaxError of a named import of a CommonJS export its lexer
|
|
1591
|
+
// cannot detect — cjsNamedImportLinkCheck): the graph is refused
|
|
1592
|
+
// before ANY module evaluates, so %main opens with exactly that throw
|
|
1593
|
+
// (message and error class both Node's) and the entry init below it
|
|
1594
|
+
// never runs. The init still lowers — the program must otherwise
|
|
1595
|
+
// compile, exactly like Node parses every module before its
|
|
1596
|
+
// fetch/instantiate phases refuse the graph.
|
|
1597
|
+
if (L.startupCrash !== null) {
|
|
1598
|
+
const crashLoc = L.startupCrash.loc;
|
|
1599
|
+
body.unshift({
|
|
1600
|
+
kind: "throw",
|
|
1601
|
+
value: {
|
|
1602
|
+
kind: "libCall",
|
|
1603
|
+
fn: "error.new",
|
|
1604
|
+
args: [{ kind: "strLit", value: L.startupCrash.message, type: STRING, loc: crashLoc }],
|
|
1605
|
+
type: { kind: "object", className: L.startupCrash.className },
|
|
1606
|
+
loc: crashLoc,
|
|
1607
|
+
},
|
|
1608
|
+
loc: crashLoc,
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
return { name: ENTRY_NAME, params: [], returnType: VOID, locals: [], body, loc };
|
|
1612
|
+
}
|
|
1613
|
+
//# sourceMappingURL=lower-modules.js.map
|