@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,650 @@
|
|
|
1
|
+
/* Island-boundary lowering: jsval marshaling into the island (jsvalIn and
|
|
2
|
+
* its boundary fences), island-expression detection, the island method-call
|
|
3
|
+
* surface (Math and number/string methods under --dynamic), and the npm
|
|
4
|
+
* package boundary fences for node_modules-declared symbols. */
|
|
5
|
+
import * as ts from "../ts7/adapter.js";
|
|
6
|
+
import { F64, JSVAL, MAX_ISLAND_CALLBACK_ARITY, STRING, VOID, canMarshalTypedFuncIntoIsland, isUnitType } from "../../ir/nodes.js";
|
|
7
|
+
import { ISLAND_SURFACE, STATIC_MATH_FNS, boundaryIntoIslandMsg } from "./surfaces.js";
|
|
8
|
+
import { requiresDynamicApiDiag, requiresDynamicPackageDiag } from "../../diagnostics/diagnostic.js";
|
|
9
|
+
import { isCjsJsFile, locOf, npmPackageNameOf } from "../program.js";
|
|
10
|
+
import { pureReemittable } from "./lower-exprs.js";
|
|
11
|
+
import { PoisonError, newFnCtx, own } from "./lowerer.js";
|
|
12
|
+
/** True iff the checker's type for this node maps to jsval ('any') —
|
|
13
|
+
* the island test in front of every engine-op lowering (receivers,
|
|
14
|
+
* callees, assignment targets). ALSO true for an identifier bound to an
|
|
15
|
+
* island-HANDLE local whose declared type says otherwise (`const {
|
|
16
|
+
* readFileSync } = await import("fs")` — the binding's declared
|
|
17
|
+
* function/Buffer type never held the value; the handle is the value's
|
|
18
|
+
* only story), so its uses dispatch to engine ops instead of
|
|
19
|
+
* re-reporting the declared type. The one exclusion is promise-mapped
|
|
20
|
+
* declared types: a package promise held as a handle keeps its existing
|
|
21
|
+
* checker-driven dispatch (the await/.catch bridge lowerings own it). */
|
|
22
|
+
export function isIslandExpr(L, node) {
|
|
23
|
+
const mapped = L.mapTypeOf(L.typeOf(node));
|
|
24
|
+
if (mapped?.kind === "jsval")
|
|
25
|
+
return true;
|
|
26
|
+
if (mapped?.kind !== "promise" && ts.isIdentifier(node)) {
|
|
27
|
+
const local = L.resolveLocal(node);
|
|
28
|
+
if (local?.type.kind === "jsval")
|
|
29
|
+
return true;
|
|
30
|
+
// File-scope handle bindings (a module global slotted jsval by the
|
|
31
|
+
// island-pattern or unchecked-overload rules) take the same rule as
|
|
32
|
+
// locals: the handle is the value's only story.
|
|
33
|
+
if (!local && L.globalOf(node)?.type.kind === "jsval")
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
/** Marshal a static value into the island (--dynamic): primitives by
|
|
39
|
+
* value, JSON-safe composites as a deep copy (the documented aliasing
|
|
40
|
+
* divergence). Values with no island representation — closures, class
|
|
41
|
+
* instances, promises, un-validated 'unknown' — are rejected with the
|
|
42
|
+
* boundary message. */
|
|
43
|
+
export function jsvalIn(L, e, node) {
|
|
44
|
+
if (e.type.kind === "jsval")
|
|
45
|
+
return e;
|
|
46
|
+
// Bare unit literals (`undefined` / `null` in an 'any' slot): the
|
|
47
|
+
// engine's own units — unit-typed expressions are literals (units have
|
|
48
|
+
// no other producers), so dropping the operand loses nothing.
|
|
49
|
+
if (isUnitType(e.type)) {
|
|
50
|
+
return { kind: "jsOp", op: e.type.kind === "undefinedT" ? "undefLit" : "nullLit", args: [], type: JSVAL, loc: e.loc };
|
|
51
|
+
}
|
|
52
|
+
if (e.type.kind === "dyn") {
|
|
53
|
+
L.unsupported("SC1100", node, "passing 'unknown' values into dynamically-executed ('any'-typed) code (validate with 'as <type>' first)");
|
|
54
|
+
}
|
|
55
|
+
// Closures cross INTO the island as host functions — THE package
|
|
56
|
+
// callback pattern (`.action((a, b) => ...)`). Unannotated params stay
|
|
57
|
+
// 'any' (contextual typing against package signatures) and pass through
|
|
58
|
+
// as handles; TYPED params convert at call time through the validated-
|
|
59
|
+
// exit machinery (strict primitives, JSON round-trip composites,
|
|
60
|
+
// `T | undefined` taking the undefined arm for an absent argument), so
|
|
61
|
+
// a conversion failure throws back into the island as a TypeError.
|
|
62
|
+
// Anything outside both shapes gets the specific fix, not the generic
|
|
63
|
+
// boundary recitation.
|
|
64
|
+
if (e.type.kind === "func" &&
|
|
65
|
+
!canMarshalTypedFuncIntoIsland(e.type, (id) => L.shapes.get(id), (id) => L.unions.get(id))) {
|
|
66
|
+
L.unsupported("SC1090", node, `functions with this signature crossing into dynamically-executed code ` +
|
|
67
|
+
`(a callback passed to a package/'any' API may take 'any' parameters — ` +
|
|
68
|
+
`leave them unannotated so contextual typing keeps them 'any' — or parameters ` +
|
|
69
|
+
`convertible at the boundary (number, string, boolean, JSON-safe ` +
|
|
70
|
+
`records/arrays/unions, 'T | undefined'), and return 'any', void, number, ` +
|
|
71
|
+
`string, boolean, a JSON-safe composite, or a Promise of the primitive kinds${e.type.kind === "func" && e.type.params.length > MAX_ISLAND_CALLBACK_ARITY
|
|
72
|
+
? `; at most ${MAX_ISLAND_CALLBACK_ARITY} parameters`
|
|
73
|
+
: ""})`);
|
|
74
|
+
}
|
|
75
|
+
if (e.type.kind !== "func" && !L.boundarySafe(e.type)) {
|
|
76
|
+
// A RegExp crossing INTO the island (`z.string().regex(/^a+$/)` —
|
|
77
|
+
// the validation-pattern argument): the engine compiles its own
|
|
78
|
+
// from source+flags. A fresh engine RegExp per marshal — identity
|
|
79
|
+
// and lastIndex state do not cross (SEMANTICS.md). Only literal
|
|
80
|
+
// and pure-read spellings lower (the rebuild reads the operand
|
|
81
|
+
// twice); computed regex values keep the boundary fence.
|
|
82
|
+
if (e.type.kind === "regex") {
|
|
83
|
+
const re = islandRegexpOf(e);
|
|
84
|
+
if (re)
|
|
85
|
+
return re;
|
|
86
|
+
}
|
|
87
|
+
// jsval-BEARING composites (an `any[]` value, a record holding one)
|
|
88
|
+
// have no JSON marshal but an honest per-field/per-element island
|
|
89
|
+
// construction — the same lift the implicit coercion path uses.
|
|
90
|
+
if (L.jsvalLiftable(e.type))
|
|
91
|
+
return L.jsvalLiftExpr(e, e.loc);
|
|
92
|
+
L.unsupported("SC1090", node, boundaryIntoIslandMsg(L.fmt(e.type)));
|
|
93
|
+
}
|
|
94
|
+
return { kind: "jsMarshal", value: e, type: JSVAL, loc: e.loc };
|
|
95
|
+
}
|
|
96
|
+
/** A static RegExp value as a fresh ENGINE RegExp — `new RegExp(source,
|
|
97
|
+
* flags)` in the island, the pattern TEXT crossing (both worlds compile
|
|
98
|
+
* the ES-spec grammar). Literals rebuild from their own pattern/flags;
|
|
99
|
+
* pure reads (a regex-typed binding) read the source/flags intrinsics —
|
|
100
|
+
* the rebuild emits the operand twice, so effectful producers return
|
|
101
|
+
* null into the caller's boundary fence. */
|
|
102
|
+
export function islandRegexpOf(e) {
|
|
103
|
+
if (e.type.kind !== "regex")
|
|
104
|
+
return null;
|
|
105
|
+
const loc = e.loc;
|
|
106
|
+
let src;
|
|
107
|
+
let flags;
|
|
108
|
+
if (e.kind === "regexLit") {
|
|
109
|
+
src = { kind: "strLit", value: e.pattern, type: STRING, loc };
|
|
110
|
+
flags = { kind: "strLit", value: e.flags, type: STRING, loc };
|
|
111
|
+
}
|
|
112
|
+
else if (pureReemittable(e)) {
|
|
113
|
+
src = { kind: "regexIntrinsic", method: "source", receiver: e, args: [], type: STRING, loc };
|
|
114
|
+
flags = { kind: "regexIntrinsic", method: "flags", receiver: e, args: [], type: STRING, loc };
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
const ctor = { kind: "jsOp", op: "globalGet", name: "RegExp", args: [], type: JSVAL, loc };
|
|
120
|
+
return {
|
|
121
|
+
kind: "jsOp",
|
|
122
|
+
op: "construct",
|
|
123
|
+
args: [
|
|
124
|
+
ctor,
|
|
125
|
+
{ kind: "jsMarshal", value: src, type: JSVAL, loc },
|
|
126
|
+
{ kind: "jsMarshal", value: flags, type: JSVAL, loc },
|
|
127
|
+
],
|
|
128
|
+
type: JSVAL,
|
|
129
|
+
loc,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/** The gate on every ISLAND_SURFACE lowering: under --dynamic the caller
|
|
133
|
+
* proceeds to engine ops; without it the use site is a per-site SC2012
|
|
134
|
+
* (poison-recovered, so every site in the program reports — and the
|
|
135
|
+
* coverage report groups them under "runs with --dynamic"). */
|
|
136
|
+
export function requireDynamicApi(L, feature, node) {
|
|
137
|
+
if (L.dynamic)
|
|
138
|
+
return;
|
|
139
|
+
L.pushDiag(requiresDynamicApiDiag(feature, locOf(node)));
|
|
140
|
+
throw new PoisonError();
|
|
141
|
+
}
|
|
142
|
+
/** Resolves an identifier to an island-backed ambient GLOBAL function
|
|
143
|
+
* (parseFloat, isFinite). Provenance, not name: the
|
|
144
|
+
* symbol's declaration must live in the ambient file, so a user function
|
|
145
|
+
* named `parseFloat` never matches. */
|
|
146
|
+
export function islandGlobalFnOf(L, ident) {
|
|
147
|
+
const entry = own(ISLAND_SURFACE.globals, ident.text);
|
|
148
|
+
if (!entry)
|
|
149
|
+
return null;
|
|
150
|
+
const symbol = L.resolveValueSymbol(ident);
|
|
151
|
+
if (!symbol || !L.isStdlibSymbol(symbol))
|
|
152
|
+
return null;
|
|
153
|
+
return entry;
|
|
154
|
+
}
|
|
155
|
+
/** USER-code `fetch(url)` / `fetch(url, init)` — the island-backed ambient
|
|
156
|
+
* global (provenance, not the name: a user's own `fetch` never matches).
|
|
157
|
+
* The engine's own fetch executes (the same one embedded npm code calls —
|
|
158
|
+
* scr_fetch.c over the native net/tls stack): the url marshals in (strings; template
|
|
159
|
+
* results), an init OBJECT LITERAL builds natively in the island through
|
|
160
|
+
* the existing 'any'-contextual literal path (RequestInit maps to jsval,
|
|
161
|
+
* so field values marshal individually and an AbortSignal handle passes
|
|
162
|
+
* straight through), and the engine's promise bridges to a static
|
|
163
|
+
* `promise of jsval` (jsBridgePromise) — `await fetch(...)` parks the
|
|
164
|
+
* fiber like any await and resumes with the Response HANDLE. Member
|
|
165
|
+
* reads/calls on the handle are engine ops; typed extraction happens at
|
|
166
|
+
* the user's narrowing sites through the validated-exit machinery.
|
|
167
|
+
* Without --dynamic every call site is its own SC2012. Null for
|
|
168
|
+
* anything that isn't THE ambient fetch, so lowerCall keeps trying. */
|
|
169
|
+
export function lowerFetchCall(L, call) {
|
|
170
|
+
const callee = call.expression;
|
|
171
|
+
if (!ts.isIdentifier(callee) || callee.text !== "fetch")
|
|
172
|
+
return null;
|
|
173
|
+
if (call.questionDotToken)
|
|
174
|
+
return null;
|
|
175
|
+
const symbol = L.resolveValueSymbol(callee);
|
|
176
|
+
if (!symbol || !L.isStdlibSymbol(symbol))
|
|
177
|
+
return null;
|
|
178
|
+
if (call.arguments.length < 1 || call.arguments.length > 2)
|
|
179
|
+
return null;
|
|
180
|
+
L.requireDynamicApi("'fetch'", call);
|
|
181
|
+
const loc = locOf(call);
|
|
182
|
+
const fetchFn = { kind: "jsOp", op: "globalGet", name: "fetch", args: [], type: JSVAL, loc };
|
|
183
|
+
// Init OBJECT LITERALS build natively in the island (the general
|
|
184
|
+
// 'any'-contextual literal path can't claim them: the optional param's
|
|
185
|
+
// contextual type is `RequestInit | undefined`, a union) — field
|
|
186
|
+
// values marshal individually, so an AbortSignal HANDLE passes
|
|
187
|
+
// through and dashed header keys ("content-type") are plain engine
|
|
188
|
+
// property names. Non-literal inits marshal as a whole (JSON-safe
|
|
189
|
+
// records; a signal field inside one fences with the boundary rule).
|
|
190
|
+
const args = call.arguments.map((a) => ts.isObjectLiteralExpression(a) ? lowerIslandObjectLiteral(L, a) : L.jsvalIn(L.lowerExpr(a), a));
|
|
191
|
+
const raw = { kind: "jsOp", op: "callFn", args: [fetchFn, ...args], type: JSVAL, loc };
|
|
192
|
+
return { kind: "jsBridgePromise", value: raw, type: { kind: "promise", inner: JSVAL }, loc };
|
|
193
|
+
}
|
|
194
|
+
/** Dynamic `import(spec)` — the island's module system at a USER site.
|
|
195
|
+
* Under --dynamic the call lowers to island.importDyn(key): the engine
|
|
196
|
+
* loads the module (embedded npm graph, a shipped local .js/.mjs the
|
|
197
|
+
* build embedded, or a builtin shim — collectDynamicImports resolved and
|
|
198
|
+
* embedded the specifier at collection time) and answers an ENGINE
|
|
199
|
+
* promise of the namespace object, bridged to a static
|
|
200
|
+
* `Promise<jsval>` (jsBridgePromise, the fetch precedent) — so `await
|
|
201
|
+
* import(x)` parks the fiber and resumes with the namespace HANDLE, and
|
|
202
|
+
* a load/evaluation failure crosses as a catchable rejection, exactly
|
|
203
|
+
* where Node puts it. Specifiers must be string literals: the module
|
|
204
|
+
* graph is a BUILD-time artifact — a runtime-computed name has nothing
|
|
205
|
+
* to embed, and the fence says so. Static builds report the per-site
|
|
206
|
+
* SC2012. Null for anything that isn't `import(...)`. */
|
|
207
|
+
export function lowerDynamicImportCall(L, call) {
|
|
208
|
+
if (call.expression.kind !== ts.SyntaxKind.ImportKeyword)
|
|
209
|
+
return null;
|
|
210
|
+
L.requireDynamicApi("'import()'", call);
|
|
211
|
+
const loc = locOf(call);
|
|
212
|
+
const arg = call.arguments[0];
|
|
213
|
+
if (arg === undefined || !ts.isStringLiteralLike(arg)) {
|
|
214
|
+
L.unsupported("SC1090", call, "dynamic import() of computed specifiers (the module graph embeds at " +
|
|
215
|
+
"build time — the specifier must be a string literal)");
|
|
216
|
+
}
|
|
217
|
+
if (call.arguments.length !== 1) {
|
|
218
|
+
L.unsupported("SC1090", call, "dynamic import() with import attributes");
|
|
219
|
+
}
|
|
220
|
+
const res = L.dynImports.get(`${call.getSourceFile().fileName}\u0000${arg.text}`);
|
|
221
|
+
if (!res) {
|
|
222
|
+
// Collection walks every file before bodies lower, so a missing
|
|
223
|
+
// entry is a lowerer bug, not user error.
|
|
224
|
+
throw new Error(`lowerer bug: unresolved dynamic import '${arg.text}'`);
|
|
225
|
+
}
|
|
226
|
+
if (res.kind === "program-module") {
|
|
227
|
+
return lowerOwnModuleImport(L, call, arg);
|
|
228
|
+
}
|
|
229
|
+
if (res.kind !== "module") {
|
|
230
|
+
throw new PoisonError(); // resolution failed — collection reported it
|
|
231
|
+
}
|
|
232
|
+
const raw = {
|
|
233
|
+
kind: "libCall",
|
|
234
|
+
fn: "island.importDyn",
|
|
235
|
+
args: [{ kind: "strLit", value: res.key, type: STRING, loc }],
|
|
236
|
+
type: JSVAL,
|
|
237
|
+
loc,
|
|
238
|
+
};
|
|
239
|
+
return { kind: "jsBridgePromise", value: raw, type: { kind: "promise", inner: JSVAL }, loc };
|
|
240
|
+
}
|
|
241
|
+
/** Dynamic `import()` of one of the program's OWN modules: the compiled
|
|
242
|
+
* module's exports, marshaled into the engine as a namespace object.
|
|
243
|
+
* The site lowers to `Promise.resolve().then(<builder>)` in the engine —
|
|
244
|
+
* the builder is a synthesized compiled function (dynNsBuilderOf) that
|
|
245
|
+
* runs on the engine MICROTASK, calls the target module's run-once %init
|
|
246
|
+
* (Node's evaluation point for a module first reached through import():
|
|
247
|
+
* after the importer's synchronous code, before the .then handlers), and
|
|
248
|
+
* returns the exports object. Node differences (numbered divergences,
|
|
249
|
+
* pinned in island.test.ts): the namespace is a SNAPSHOT taken when the
|
|
250
|
+
* import resolves (Node's is live), a plain engine object (no Module
|
|
251
|
+
* toStringTag, keys still sorted like Node's, each import minting a
|
|
252
|
+
* fresh object where Node caches one), and exports with no island
|
|
253
|
+
* representation (classes, generic functions, un-marshalable
|
|
254
|
+
* signatures) cross as trap functions that throw a pointed TypeError
|
|
255
|
+
* when USED — the namespace still builds, exactly like Node still
|
|
256
|
+
* resolves it. */
|
|
257
|
+
function lowerOwnModuleImport(L, call, arg) {
|
|
258
|
+
const loc = locOf(call);
|
|
259
|
+
let dep = null;
|
|
260
|
+
const modSym = L.checker.getSymbolAtLocation(arg);
|
|
261
|
+
for (const d of (modSym ? L.checker.declarationsOf(modSym) : [])) {
|
|
262
|
+
if (ts.isSourceFile(d) && !d.isDeclarationFile) {
|
|
263
|
+
dep = d;
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (dep !== null && (dep.fileName.endsWith(".cts") || isCjsJsFile(dep))) {
|
|
268
|
+
L.unsupported("SC1090", call, `dynamic import of the program's own CommonJS module '${arg.text}' ` +
|
|
269
|
+
"(its namespace comes from module.exports through Node's CJS lexer, " +
|
|
270
|
+
"which has no compiled story — require it, or import it statically)");
|
|
271
|
+
}
|
|
272
|
+
const builder = dep !== null ? dynNsBuilderOf(L, dep, loc) : null;
|
|
273
|
+
if (builder === null) {
|
|
274
|
+
L.unsupported("SC1090", call, `dynamic import of the program's own module '${arg.text}' ` +
|
|
275
|
+
"(this module is not part of the compiled module graph — import it statically)");
|
|
276
|
+
}
|
|
277
|
+
const promiseCtor = { kind: "jsOp", op: "globalGet", name: "Promise", args: [], type: JSVAL, loc };
|
|
278
|
+
const resolved = { kind: "jsOp", op: "callMethod", name: "resolve", args: [promiseCtor], type: JSVAL, loc };
|
|
279
|
+
const builderFn = { kind: "func", params: [], ret: JSVAL };
|
|
280
|
+
const marshaled = {
|
|
281
|
+
kind: "jsMarshal",
|
|
282
|
+
value: { kind: "closure", fnName: builder, captures: [], type: builderFn, loc },
|
|
283
|
+
type: JSVAL,
|
|
284
|
+
loc,
|
|
285
|
+
};
|
|
286
|
+
const chained = { kind: "jsOp", op: "callMethod", name: "then", args: [resolved, marshaled], type: JSVAL, loc };
|
|
287
|
+
return { kind: "jsBridgePromise", value: chained, type: { kind: "promise", inner: JSVAL }, loc };
|
|
288
|
+
}
|
|
289
|
+
/** The namespace-BUILDER function for one program module, synthesized on
|
|
290
|
+
* first demand and shared by every import() of that module (deterministic
|
|
291
|
+
* name per file tag, so the discovery and emit passes agree). Body: the
|
|
292
|
+
* module's guarded %init (skipped for the entry — it is running or ran),
|
|
293
|
+
* then `return { <sorted exports> }` as an engine object. Null when the
|
|
294
|
+
* module never joined the compiled graph (no %init exists — an empty
|
|
295
|
+
* preflight order or a cycle the extension refused). */
|
|
296
|
+
function dynNsBuilderOf(L, dep, loc) {
|
|
297
|
+
const cached = L.dynNsBuilders.get(dep);
|
|
298
|
+
if (cached !== undefined)
|
|
299
|
+
return cached;
|
|
300
|
+
const initName = L.initNameOf.get(dep);
|
|
301
|
+
if (initName === undefined)
|
|
302
|
+
return null;
|
|
303
|
+
const rawTag = L.fileTag.get(dep) ?? "";
|
|
304
|
+
const name = `%dynns.${rawTag === "" ? "e." : rawTag.replace(/^%/, "")}`;
|
|
305
|
+
L.dynNsBuilders.set(dep, name);
|
|
306
|
+
L.fnStack.push(newFnCtx(true, null, null, JSVAL));
|
|
307
|
+
try {
|
|
308
|
+
const body = [];
|
|
309
|
+
if (dep !== L.entry) {
|
|
310
|
+
body.push({
|
|
311
|
+
kind: "exprStmt",
|
|
312
|
+
expr: { kind: "call", callee: initName, args: [], type: VOID, loc },
|
|
313
|
+
loc,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
// Node sorts module-namespace keys (code-unit order); tsc erases
|
|
317
|
+
// type-only exports, so only VALUE exports appear.
|
|
318
|
+
const entries = [];
|
|
319
|
+
const modSym = L.checker.getSymbolAtLocation(dep);
|
|
320
|
+
modSym?.getExports().forEach((sym, key) => {
|
|
321
|
+
const n = String(key);
|
|
322
|
+
if (!n.startsWith("__") && n !== "export=")
|
|
323
|
+
entries.push([n, sym]);
|
|
324
|
+
});
|
|
325
|
+
entries.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
|
|
326
|
+
const args = [];
|
|
327
|
+
for (const [exportName, sym] of entries) {
|
|
328
|
+
const value = exportJsvalValue(L, exportName, sym, loc);
|
|
329
|
+
if (value === null)
|
|
330
|
+
continue;
|
|
331
|
+
args.push({ kind: "jsMarshal", value: { kind: "strLit", value: exportName, type: STRING, loc }, type: JSVAL, loc }, value);
|
|
332
|
+
}
|
|
333
|
+
body.push({ kind: "return", value: { kind: "jsOp", op: "objLit", args, type: JSVAL, loc }, loc });
|
|
334
|
+
const ctx = L.ctx;
|
|
335
|
+
L.liftedFns.push({
|
|
336
|
+
name,
|
|
337
|
+
params: [],
|
|
338
|
+
returnType: JSVAL,
|
|
339
|
+
locals: ctx.locals,
|
|
340
|
+
captures: ctx.captures ?? [],
|
|
341
|
+
body,
|
|
342
|
+
loc,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
finally {
|
|
346
|
+
L.fnStack.pop();
|
|
347
|
+
}
|
|
348
|
+
return name;
|
|
349
|
+
}
|
|
350
|
+
/** One export's island value for the namespace object — the jsvalIn
|
|
351
|
+
* marshal set (primitives and JSON-safe composites by deep copy, units as
|
|
352
|
+
* the engine's own, marshalable closures as host functions, jsval-bearing
|
|
353
|
+
* composites through the per-field lift), with every UN-marshalable
|
|
354
|
+
* export crossing as a trap function that throws a pointed TypeError when
|
|
355
|
+
* called or constructed — the namespace still builds (Node resolves it;
|
|
356
|
+
* only the USE has no compiled story). Null for exports that do not exist
|
|
357
|
+
* at runtime (type-only). */
|
|
358
|
+
function exportJsvalValue(L, name, sym, loc) {
|
|
359
|
+
const trap = (what) => islandTrapFnValue(L, `the '${name}' export is ${what} of the compiled program, which cannot cross into dynamically-executed code yet`, loc);
|
|
360
|
+
let resolved = sym;
|
|
361
|
+
if (sym.flags & ts.SymbolFlags.Alias) {
|
|
362
|
+
// `export type { x }` / `export { type x }`: erased at runtime.
|
|
363
|
+
for (const d of L.checker.declarationsOf(sym)) {
|
|
364
|
+
if (ts.isExportSpecifier(d)) {
|
|
365
|
+
const exportDecl = d.parent.parent;
|
|
366
|
+
if (d.isTypeOnly || (ts.isExportDeclaration(exportDecl) && exportDecl.isTypeOnly))
|
|
367
|
+
return null;
|
|
368
|
+
}
|
|
369
|
+
if (ts.isImportSpecifier(d)) {
|
|
370
|
+
const clause = d.parent.parent;
|
|
371
|
+
if (d.isTypeOnly || (ts.isImportClause(clause) && clause.phaseModifier === ts.SyntaxKind.TypeKeyword))
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
resolved = L.checker.getAliasedSymbol(sym);
|
|
376
|
+
}
|
|
377
|
+
if (!(resolved.flags & ts.SymbolFlags.Value))
|
|
378
|
+
return null; // pure type surface
|
|
379
|
+
const g = L.globalsBySymbol.get(resolved);
|
|
380
|
+
if (g) {
|
|
381
|
+
const ref = { kind: "varRef", localId: g.id, type: g.type, loc };
|
|
382
|
+
if (ref.type.kind === "jsval")
|
|
383
|
+
return ref;
|
|
384
|
+
if (isUnitType(ref.type)) {
|
|
385
|
+
return { kind: "jsOp", op: ref.type.kind === "undefinedT" ? "undefLit" : "nullLit", args: [], type: JSVAL, loc };
|
|
386
|
+
}
|
|
387
|
+
if (ref.type.kind === "func") {
|
|
388
|
+
return canMarshalTypedFuncIntoIsland(ref.type, (id) => L.shapes.get(id), (id) => L.unions.get(id))
|
|
389
|
+
? { kind: "jsMarshal", value: ref, type: JSVAL, loc }
|
|
390
|
+
: trap(`a function value of type '${L.fmt(ref.type)}'`);
|
|
391
|
+
}
|
|
392
|
+
if (L.boundarySafe(ref.type))
|
|
393
|
+
return { kind: "jsMarshal", value: ref, type: JSVAL, loc };
|
|
394
|
+
if (L.jsvalLiftable(ref.type))
|
|
395
|
+
return L.jsvalLiftExpr(ref, loc);
|
|
396
|
+
return trap(`a value of type '${L.fmt(ref.type)}'`);
|
|
397
|
+
}
|
|
398
|
+
const sig = L.fnSigsBySymbol.get(resolved);
|
|
399
|
+
const decl0 = L.checker.declarationsOf(resolved).find((d) => ts.isFunctionDeclaration(d) && (ts.isSourceFile(d.parent) || L.nsBlocks.get(d.parent) === "flattened"));
|
|
400
|
+
if (sig && decl0) {
|
|
401
|
+
if (!sig.params.every((p) => p.mode === "required")) {
|
|
402
|
+
return trap("a function with optional, default, or rest parameters");
|
|
403
|
+
}
|
|
404
|
+
const funcType = {
|
|
405
|
+
kind: "func",
|
|
406
|
+
params: sig.params.map((p) => p.type),
|
|
407
|
+
ret: sig.returnType,
|
|
408
|
+
};
|
|
409
|
+
if (!canMarshalTypedFuncIntoIsland(funcType, (id) => L.shapes.get(id), (id) => L.unions.get(id))) {
|
|
410
|
+
return trap(`a function of type '${L.fmt(funcType)}'`);
|
|
411
|
+
}
|
|
412
|
+
L.noteEdge(sig.name);
|
|
413
|
+
return {
|
|
414
|
+
kind: "jsMarshal",
|
|
415
|
+
value: { kind: "closure", fnName: sig.name, captures: [], type: funcType, loc },
|
|
416
|
+
type: JSVAL,
|
|
417
|
+
loc,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
if (L.genericFnsBySymbol.has(resolved))
|
|
421
|
+
return trap("a generic function");
|
|
422
|
+
const flags = resolved.flags;
|
|
423
|
+
if (flags & ts.SymbolFlags.Class)
|
|
424
|
+
return trap("a class");
|
|
425
|
+
if (flags & ts.SymbolFlags.Enum)
|
|
426
|
+
return trap("an enum object");
|
|
427
|
+
if (flags & ts.SymbolFlags.ValueModule || flags & ts.SymbolFlags.NamespaceModule) {
|
|
428
|
+
return trap("a namespace object");
|
|
429
|
+
}
|
|
430
|
+
return trap("a binding");
|
|
431
|
+
}
|
|
432
|
+
/** An engine value whose any USE throws: `new Function("<throw>")` — a
|
|
433
|
+
* real engine function, so the namespace member reads back fine (Node's
|
|
434
|
+
* namespace holds the value too), property probes answer like a
|
|
435
|
+
* function's, and calling or `new`-ing it runs the body, which throws
|
|
436
|
+
* the pointed TypeError. */
|
|
437
|
+
function islandTrapFnValue(L, message, loc) {
|
|
438
|
+
void L;
|
|
439
|
+
const code = `throw new TypeError(${JSON.stringify(message)})`;
|
|
440
|
+
return {
|
|
441
|
+
kind: "jsOp",
|
|
442
|
+
op: "construct",
|
|
443
|
+
args: [
|
|
444
|
+
{ kind: "jsOp", op: "globalGet", name: "Function", args: [], type: JSVAL, loc },
|
|
445
|
+
{ kind: "jsMarshal", value: { kind: "strLit", value: code, type: STRING, loc }, type: JSVAL, loc },
|
|
446
|
+
],
|
|
447
|
+
type: JSVAL,
|
|
448
|
+
loc,
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
/** An object literal built NATIVELY in the island — one engine object,
|
|
452
|
+
* each field marshaled individually (nested object/array literals
|
|
453
|
+
* recurse, island handles pass straight through, everything else takes
|
|
454
|
+
* the JSON-safe copy-marshal). The same jsOp the 'any'-contextual
|
|
455
|
+
* literal path emits, callable from lowerings that KNOW the literal is
|
|
456
|
+
* island-bound (fetch's init) even when the contextual type is a union
|
|
457
|
+
* the generic path declines. Identifier AND string-literal keys — engine
|
|
458
|
+
* property names have no identifier restriction ("content-type"). */
|
|
459
|
+
export function lowerIslandObjectLiteral(L, expr) {
|
|
460
|
+
const loc = locOf(expr);
|
|
461
|
+
const args = [];
|
|
462
|
+
for (const prop of expr.properties) {
|
|
463
|
+
if (!ts.isPropertyAssignment(prop) ||
|
|
464
|
+
!(ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name))) {
|
|
465
|
+
L.unsupported("SC1090", prop, "this property form in an island-built object literal (only `name: value` is supported)");
|
|
466
|
+
}
|
|
467
|
+
const nameLoc = locOf(prop.name);
|
|
468
|
+
args.push({
|
|
469
|
+
kind: "jsMarshal",
|
|
470
|
+
value: { kind: "strLit", value: prop.name.text, type: STRING, loc: nameLoc },
|
|
471
|
+
type: JSVAL, loc: nameLoc,
|
|
472
|
+
});
|
|
473
|
+
const init = prop.initializer;
|
|
474
|
+
if (ts.isObjectLiteralExpression(init)) {
|
|
475
|
+
args.push(lowerIslandObjectLiteral(L, init));
|
|
476
|
+
}
|
|
477
|
+
else if (ts.isArrayLiteralExpression(init) && !init.elements.some(ts.isSpreadElement)) {
|
|
478
|
+
const elems = init.elements.map((el) => L.jsvalIn(L.lowerExpr(el), el));
|
|
479
|
+
args.push({ kind: "jsOp", op: "arrLit", args: elems, type: JSVAL, loc: locOf(init) });
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
args.push(L.jsvalIn(L.lowerExpr(init), init));
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return { kind: "jsOp", op: "objLit", args, type: JSVAL, loc };
|
|
486
|
+
}
|
|
487
|
+
/** Method calls on the island-backed ambient surface: `Math.<fn>(...)`
|
|
488
|
+
* (the engine's own Math object executes) and the number/string methods
|
|
489
|
+
* the static runtime doesn't implement (`x.toFixed(2)`,
|
|
490
|
+
* `s.toUpperCase()`, ...). Each site is self-contained — the receiver
|
|
491
|
+
* and arguments marshal in, the engine executes with JS-exact semantics,
|
|
492
|
+
* and the result exits (validated) to the DECLARED static return type,
|
|
493
|
+
* so no jsval leaks into the program's types. tsc has already checked
|
|
494
|
+
* receiver, arity, and argument types against ambient/scriptc.d.ts; an
|
|
495
|
+
* ambient member missing from ISLAND_SURFACE returns null into the
|
|
496
|
+
* existing generic rejections. */
|
|
497
|
+
export function lowerIslandMethodCall(L, call, access) {
|
|
498
|
+
if (L.chainBlocked(access, call))
|
|
499
|
+
return null;
|
|
500
|
+
const name = access.name.text;
|
|
501
|
+
const loc = locOf(call);
|
|
502
|
+
const finish = (receiver, entry) => {
|
|
503
|
+
const args = call.arguments.map((a) => L.jsvalIn(L.lowerExpr(a), a));
|
|
504
|
+
const result = {
|
|
505
|
+
kind: "jsOp", op: "callMethod", name, args: [receiver, ...args], type: JSVAL, loc,
|
|
506
|
+
};
|
|
507
|
+
return { kind: "jsExit", value: result, type: entry.ret, loc };
|
|
508
|
+
};
|
|
509
|
+
// `AbortSignal.timeout(ms)` / `.abort(reason?)` / `.any(signals)` —
|
|
510
|
+
// the fetch-cancellation ambient: the engine's own AbortSignal mints
|
|
511
|
+
// the signal, which stays a HANDLE (its checker type, AbortSignal,
|
|
512
|
+
// maps to jsval — see ISLAND_AMBIENT_TYPES) so it can ride a
|
|
513
|
+
// RequestInit literal into fetch without a JSON detour. The prelude
|
|
514
|
+
// implements all three statics.
|
|
515
|
+
{
|
|
516
|
+
const sigMember = L.stdlibGlobalMember(access, "AbortSignal");
|
|
517
|
+
if ((sigMember === "timeout" || sigMember === "abort" || sigMember === "any") &&
|
|
518
|
+
call.arguments.length <= 1 &&
|
|
519
|
+
(sigMember === "abort" || call.arguments.length === 1)) {
|
|
520
|
+
L.requireDynamicApi(`'AbortSignal.${sigMember}'`, call);
|
|
521
|
+
const args = call.arguments.map((a) => L.jsvalIn(L.lowerExpr(a), a));
|
|
522
|
+
const signalGlobal = {
|
|
523
|
+
kind: "jsOp", op: "globalGet", name: "AbortSignal", args: [], type: JSVAL, loc,
|
|
524
|
+
};
|
|
525
|
+
return {
|
|
526
|
+
kind: "jsOp", op: "callMethod", name: sigMember,
|
|
527
|
+
args: [signalGlobal, ...args], type: JSVAL, loc,
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
const isMath = L.stdlibGlobalMember(access, "Math") !== null;
|
|
532
|
+
// The STATIC Math members (floor/min/max/random): one C call IS the
|
|
533
|
+
// JS operation — no island, no --dynamic. Only the tabled arity with
|
|
534
|
+
// plain (non-spread) arguments takes this path; other forms fall
|
|
535
|
+
// through to the spread fold / island / lib fence below.
|
|
536
|
+
const staticMath = isMath ? own(STATIC_MATH_FNS, name) : undefined;
|
|
537
|
+
if (staticMath &&
|
|
538
|
+
call.arguments.length === staticMath.arity &&
|
|
539
|
+
call.arguments.every((a) => !ts.isSpreadElement(a))) {
|
|
540
|
+
const args = call.arguments.map((a) => L.lowerExprExpecting(a, F64));
|
|
541
|
+
return { kind: "libCall", fn: staticMath.fn, args, type: F64, loc };
|
|
542
|
+
}
|
|
543
|
+
// `Math.max(...xs)` / `Math.min(...xs)` over a number[]: a STATIC
|
|
544
|
+
// runtime fold (JS-exact: NaN poisons, ±0 order by the JS rules, the
|
|
545
|
+
// empty array yields ∓Infinity like the zero-arg calls) — no island
|
|
546
|
+
// involved, so it works without --dynamic too. Only the exact
|
|
547
|
+
// one-spread form lowers; mixed spread/positional argument lists keep
|
|
548
|
+
// the nest-calls fence.
|
|
549
|
+
if (isMath &&
|
|
550
|
+
(name === "max" || name === "min") &&
|
|
551
|
+
call.arguments.length === 1 &&
|
|
552
|
+
ts.isSpreadElement(call.arguments[0])) {
|
|
553
|
+
const spread = call.arguments[0];
|
|
554
|
+
const src = L.lowerExpr(spread.expression);
|
|
555
|
+
if (src.type.kind !== "array" || src.type.elem.kind !== "f64") {
|
|
556
|
+
L.unsupported("SC1090", spread, `spreading '${L.fmt(src.type)}' into Math.${name} (only a number[] spreads)`);
|
|
557
|
+
}
|
|
558
|
+
return {
|
|
559
|
+
kind: "libCall",
|
|
560
|
+
fn: name === "max" ? "math.maxArr" : "math.minArr",
|
|
561
|
+
args: [src],
|
|
562
|
+
type: F64,
|
|
563
|
+
loc,
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
const mathFn = isMath ? own(ISLAND_SURFACE.math.fns, name) : undefined;
|
|
567
|
+
if (mathFn && call.arguments.length === mathFn.args.length) {
|
|
568
|
+
L.requireDynamicApi(`'Math.${name}'`, call);
|
|
569
|
+
return finish({ kind: "jsOp", op: "globalGet", name: "Math", args: [], type: JSVAL, loc }, mathFn);
|
|
570
|
+
}
|
|
571
|
+
const kind = L.mapTypeOf(L.typeOf(access.expression))?.kind;
|
|
572
|
+
const entry = kind === "f64"
|
|
573
|
+
? own(ISLAND_SURFACE.number, name)
|
|
574
|
+
: kind === "string"
|
|
575
|
+
? own(ISLAND_SURFACE.string, name)
|
|
576
|
+
: undefined;
|
|
577
|
+
if (!entry || call.arguments.length !== entry.args.length)
|
|
578
|
+
return null;
|
|
579
|
+
if (!L.isStdlibMember(access))
|
|
580
|
+
return null;
|
|
581
|
+
L.requireDynamicApi(`'.${name}()' on ${kind === "f64" ? "numbers" : "strings"}`, call);
|
|
582
|
+
return finish(L.jsvalIn(L.lowerExpr(access.expression), access.expression), entry);
|
|
583
|
+
}
|
|
584
|
+
/** `Math.PI` / `Math.E` property READS: getProp off globalGet("Math"),
|
|
585
|
+
* exiting to the declared number type. Math methods referenced without a
|
|
586
|
+
* call are rejected specifically (no value form exists, --dynamic or
|
|
587
|
+
* not). Null for non-Math receivers (the property chain keeps trying). */
|
|
588
|
+
export function lowerMathProperty(L, expr) {
|
|
589
|
+
const member = L.stdlibGlobalMember(expr, "Math");
|
|
590
|
+
if (member === null)
|
|
591
|
+
return null;
|
|
592
|
+
const loc = locOf(expr);
|
|
593
|
+
const propType = own(ISLAND_SURFACE.math.props, member);
|
|
594
|
+
if (propType !== undefined) {
|
|
595
|
+
L.requireDynamicApi(`'Math.${member}'`, expr);
|
|
596
|
+
const math = { kind: "jsOp", op: "globalGet", name: "Math", args: [], type: JSVAL, loc };
|
|
597
|
+
const read = { kind: "jsOp", op: "getProp", name: member, args: [math], type: JSVAL, loc };
|
|
598
|
+
return { kind: "jsExit", value: read, type: propType, loc };
|
|
599
|
+
}
|
|
600
|
+
if (own(ISLAND_SURFACE.math.fns, member) !== undefined ||
|
|
601
|
+
own(STATIC_MATH_FNS, member) !== undefined) {
|
|
602
|
+
L.unsupported("SC1090", expr, `Math methods as values (call '${member}' directly)`);
|
|
603
|
+
}
|
|
604
|
+
return null; // declared-but-untabled members fall through generically
|
|
605
|
+
}
|
|
606
|
+
/** The npm package a type is declared by ("commander", "@scope/pkg"),
|
|
607
|
+
* or null when the type isn't package-declared. Union parts are searched
|
|
608
|
+
* too: `string | Command` fails mapping as a whole, but the blame (and
|
|
609
|
+
* the --dynamic attribution) belongs to the package-declared part. */
|
|
610
|
+
export function npmPackageOf(L, type) {
|
|
611
|
+
const pkg = L.npmPackageOfSymbol(type.getAliasSymbol() ?? type.getSymbol());
|
|
612
|
+
if (pkg)
|
|
613
|
+
return pkg;
|
|
614
|
+
if (type.isUnionType()) {
|
|
615
|
+
for (const part of type.getTypes()) {
|
|
616
|
+
const partPkg = L.npmPackageOf(part);
|
|
617
|
+
if (partPkg)
|
|
618
|
+
return partPkg;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
return null;
|
|
622
|
+
}
|
|
623
|
+
/** The npm chokepoint for member reads and method calls in a STATIC
|
|
624
|
+
* build: a receiver whose type (or member whose symbol) a package's
|
|
625
|
+
* .d.ts declares means the operation runs in the embedded engine —
|
|
626
|
+
* attribute the site to the package instead of the generic property/
|
|
627
|
+
* method rejection. No-op under --dynamic (the island paths claimed
|
|
628
|
+
* these) and for non-package receivers, so callers' fallbacks apply. */
|
|
629
|
+
export function npmMemberFence(L, access) {
|
|
630
|
+
if (L.dynamic)
|
|
631
|
+
return;
|
|
632
|
+
const pkg = L.npmPackageOf(L.typeOf(access.expression)) ??
|
|
633
|
+
L.npmPackageOfSymbol(L.checker.getSymbolAtLocation(access.name));
|
|
634
|
+
if (!pkg)
|
|
635
|
+
return;
|
|
636
|
+
L.pushDiag(requiresDynamicPackageDiag(pkg, locOf(access)));
|
|
637
|
+
throw new PoisonError();
|
|
638
|
+
}
|
|
639
|
+
/** The npm package a SYMBOL is declared by, or null. The symbol half of
|
|
640
|
+
* npmPackageOf, also asked directly for import-alias bindings (whose
|
|
641
|
+
* aliased symbol is the package's export). */
|
|
642
|
+
export function npmPackageOfSymbol(L, sym) {
|
|
643
|
+
const decls = sym ? L.checker.declarationsOf(sym) : undefined;
|
|
644
|
+
if (!decls || decls.length === 0)
|
|
645
|
+
return null;
|
|
646
|
+
if (!decls.every((d) => L.isNpmFile(d.getSourceFile())))
|
|
647
|
+
return null;
|
|
648
|
+
return npmPackageNameOf(decls[0].getSourceFile().fileName);
|
|
649
|
+
}
|
|
650
|
+
//# sourceMappingURL=lower-island.js.map
|