@scriptc/compiler 0.0.8 → 0.0.10
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/ambient/scriptc-node-fallback.d.ts +13 -0
- package/dist/backend/cc.js +14 -0
- package/dist/backend/cc.js.map +1 -1
- package/dist/backend/emission/emit-exprs.js +244 -17
- package/dist/backend/emission/emit-exprs.js.map +1 -1
- package/dist/backend/emission/emit-types.d.ts +3 -0
- package/dist/backend/emission/emit-types.js +12 -0
- package/dist/backend/emission/emit-types.js.map +1 -1
- package/dist/backend/emission/emit-walkers.d.ts +7 -5
- package/dist/backend/emission/emit-walkers.js +52 -12
- package/dist/backend/emission/emit-walkers.js.map +1 -1
- package/dist/backend/emission/emitter.d.ts +1 -1
- package/dist/backend/emission/emitter.js +30 -9
- package/dist/backend/emission/emitter.js.map +1 -1
- package/dist/backend/llvm/dyn.d.ts +1 -0
- package/dist/backend/llvm/dyn.js +83 -1
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.js +357 -20
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/diagnostics/diagnostic.d.ts +73 -14
- package/dist/diagnostics/diagnostic.js +112 -31
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/diagnostics/render.js +8 -0
- package/dist/diagnostics/render.js.map +1 -1
- package/dist/frontend/lowering/lib-boundary.js +11 -1
- package/dist/frontend/lowering/lib-boundary.js.map +1 -1
- package/dist/frontend/lowering/lower-assert.js +16 -0
- package/dist/frontend/lowering/lower-assert.js.map +1 -1
- package/dist/frontend/lowering/lower-builtins.d.ts +24 -0
- package/dist/frontend/lowering/lower-builtins.js +256 -3
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.d.ts +71 -11
- package/dist/frontend/lowering/lower-calls.js +942 -18
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.js +27 -1
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-containers.d.ts +21 -2
- package/dist/frontend/lowering/lower-containers.js +386 -53
- package/dist/frontend/lowering/lower-containers.js.map +1 -1
- package/dist/frontend/lowering/lower-dgram.js +92 -17
- package/dist/frontend/lowering/lower-dgram.js.map +1 -1
- package/dist/frontend/lowering/lower-emitter.js +54 -0
- package/dist/frontend/lowering/lower-emitter.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.js +231 -17
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-modules.js +33 -2
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-namespaces.d.ts +25 -11
- package/dist/frontend/lowering/lower-namespaces.js +50 -15
- package/dist/frontend/lowering/lower-namespaces.js.map +1 -1
- package/dist/frontend/lowering/lower-server.js +211 -4
- package/dist/frontend/lowering/lower-server.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.d.ts +1 -5
- package/dist/frontend/lowering/lower-stmts.js +660 -19
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lower-stream.js +92 -5
- package/dist/frontend/lowering/lower-stream.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +102 -15
- package/dist/frontend/lowering/lowerer.js +378 -30
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/surfaces.js +20 -3
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/frontend/npm-static-rewrite.d.ts +7 -2
- package/dist/frontend/npm-static-rewrite.js +566 -138
- package/dist/frontend/npm-static-rewrite.js.map +1 -1
- package/dist/frontend/npm-static.js +11 -2
- package/dist/frontend/npm-static.js.map +1 -1
- package/dist/frontend/program.js +5 -5
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/shared.d.ts +6 -1
- package/dist/frontend/shared.js +46 -1
- package/dist/frontend/shared.js.map +1 -1
- package/dist/frontend/types.d.ts +13 -0
- package/dist/frontend/types.js +88 -9
- package/dist/frontend/types.js.map +1 -1
- package/dist/index.js +169 -32
- package/dist/index.js.map +1 -1
- package/dist/ir/nodes.d.ts +230 -12
- package/dist/ir/nodes.js +85 -6
- package/dist/ir/nodes.js.map +1 -1
- package/dist/ir/validate.js +109 -9
- package/dist/ir/validate.js.map +1 -1
- package/dist/library/fence-eval.d.ts +77 -0
- package/dist/library/fence-eval.js +385 -0
- package/dist/library/fence-eval.js.map +1 -0
- package/dist/library/profile.d.ts +11 -1
- package/dist/library/profile.js +102 -18
- package/dist/library/profile.js.map +1 -1
- package/package.json +2 -2
- package/surface-manifest.json +10 -3
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
import * as ts from "../ts7/adapter.js";
|
|
6
6
|
import { BOOL, BYTES_U8, CAUGHT, DYN, F64, JSVAL, STRING, UNDEFINED_T, VOID, arrayOf, bytesOf, funcOf, isRefCounted, isSupportedIndexValue, isUnitType, typeEquals } from "../../ir/nodes.js";
|
|
7
7
|
import { ARRAY_METHODS, MAP_METHODS, SET_COMBINE_METHODS, SET_METHODS, STR_METHODS } from "./surfaces.js";
|
|
8
|
-
import { isRequireMainFilename, probeLower, pureReemittable } from "./lower-exprs.js";
|
|
8
|
+
import { isRequireMainFilename, lowerDynObjectLiteral, probeLower, pureReemittable } from "./lower-exprs.js";
|
|
9
9
|
import { forOfVarTarget, lowerDestructuringAssign } from "./lower-stmts.js";
|
|
10
10
|
import { isJsSourceFile, locOf } from "../program.js";
|
|
11
11
|
import { DYN_DISPATCH_METHODS, islandPrimitiveExit } from "./lower-calls.js";
|
|
12
12
|
import { typeKey } from "../types.js";
|
|
13
|
-
import { own } from "./lowerer.js";
|
|
13
|
+
import { dynUndefinedExpr, own } from "./lowerer.js";
|
|
14
14
|
/** Ambient array method calls. `push`/`pop`/`indexOf`/`includes`/`join`
|
|
15
15
|
* lower to arrIntrinsic; the HOF family — `map`/`filter`/`forEach`/
|
|
16
16
|
* `find`/`some`/`every`/`flatMap`/`reduce`/`reduceRight` — desugars to a
|
|
@@ -2649,23 +2649,27 @@ export function lowerIteratorHelperCall(L, call, access) {
|
|
|
2649
2649
|
const terminal = access.name.text;
|
|
2650
2650
|
if (!ITER_TERMINALS.has(terminal))
|
|
2651
2651
|
return null;
|
|
2652
|
-
// Walk receiver: stage* ← values() ← array-typed source
|
|
2652
|
+
// Walk receiver: stage* ← values()/entries() ← array-typed source
|
|
2653
|
+
// (entries seeds the chain with [index, element] pairs — the
|
|
2654
|
+
// checker's own tuple type).
|
|
2653
2655
|
const stages = [];
|
|
2654
2656
|
let cur = access.expression;
|
|
2655
2657
|
let source = null;
|
|
2658
|
+
let srcProj = "values";
|
|
2656
2659
|
for (;;) {
|
|
2657
2660
|
if (!ts.isCallExpression(cur) || !ts.isPropertyAccessExpression(cur.expression))
|
|
2658
2661
|
return null;
|
|
2659
2662
|
if (cur.questionDotToken || cur.expression.questionDotToken)
|
|
2660
2663
|
return null;
|
|
2661
2664
|
const name = cur.expression.name.text;
|
|
2662
|
-
if (name === "values" && cur.arguments.length === 0) {
|
|
2665
|
+
if ((name === "values" || name === "entries") && cur.arguments.length === 0) {
|
|
2663
2666
|
const recvIr = L.mapTypeOf(L.typeOf(cur.expression.expression));
|
|
2664
2667
|
if (recvIr?.kind !== "array")
|
|
2665
2668
|
return null;
|
|
2666
2669
|
if (!L.isStdlibMember(cur.expression))
|
|
2667
2670
|
return null;
|
|
2668
2671
|
source = cur.expression.expression;
|
|
2672
|
+
srcProj = name;
|
|
2669
2673
|
break;
|
|
2670
2674
|
}
|
|
2671
2675
|
if (!ITER_STAGES.has(name) || cur.arguments.length !== 1 || ts.isSpreadElement(cur.arguments[0]))
|
|
@@ -2687,7 +2691,12 @@ export function lowerIteratorHelperCall(L, call, access) {
|
|
|
2687
2691
|
const items = L.lowerExpr(source);
|
|
2688
2692
|
if (items.type.kind !== "array")
|
|
2689
2693
|
return null;
|
|
2690
|
-
|
|
2694
|
+
// The entries() seed: the interned [number, T] tuple — the same shape
|
|
2695
|
+
// the checker's own [number, T] maps to, so stage callbacks typed
|
|
2696
|
+
// against the lib's pairs intern equal.
|
|
2697
|
+
let elem = srcProj === "entries"
|
|
2698
|
+
? { kind: "record", shapeId: L.shapes.intern([{ name: "0", type: F64 }, { name: "1", type: items.type.elem }], true) }
|
|
2699
|
+
: items.type.elem;
|
|
2691
2700
|
const stageIrs = [];
|
|
2692
2701
|
for (const s of stages) {
|
|
2693
2702
|
if (s.name === "take" || s.name === "drop") {
|
|
@@ -2774,12 +2783,12 @@ export function lowerIteratorHelperCall(L, call, access) {
|
|
|
2774
2783
|
L.noLowering(`.${terminal} at this result type`, call, "the chain's element and result types must be representable — annotate the callbacks' types");
|
|
2775
2784
|
}
|
|
2776
2785
|
const stageKeys = stageIrs.map((s) => (s.kind === "take" || s.kind === "drop" ? s.kind : `${s.kind}:${typeKey(s.fn.type)}`));
|
|
2777
|
-
const key = `iter:${typeKey(items.type)}:${stageKeys.join(",")}:${terminal}:${termArgs.map((a) => typeKey(a.type)).join(",")}`;
|
|
2786
|
+
const key = `iter:${srcProj}:${typeKey(items.type)}:${stageKeys.join(",")}:${terminal}:${termArgs.map((a) => typeKey(a.type)).join(",")}`;
|
|
2778
2787
|
let helper = L.arrHofHelpers.get(key);
|
|
2779
2788
|
if (!helper) {
|
|
2780
2789
|
helper = `%iter.${terminal}.${L.arrHofHelpers.size}`;
|
|
2781
2790
|
L.arrHofHelpers.set(key, helper);
|
|
2782
|
-
L.liftedFns.push(buildIterChainFn(L, helper, items.type, stageIrs, terminal, termArgs.map((a) => a.type), resultT, loc));
|
|
2791
|
+
L.liftedFns.push(buildIterChainFn(L, helper, items.type, srcProj, stageIrs, terminal, termArgs.map((a) => a.type), resultT, loc));
|
|
2783
2792
|
}
|
|
2784
2793
|
const budgetOrFn = stageIrs.map((s) => (s.kind === "take" || s.kind === "drop" ? s.budget : s.fn));
|
|
2785
2794
|
return { kind: "call", callee: helper, args: [items, ...budgetOrFn, ...termArgs], type: resultT, loc };
|
|
@@ -2862,7 +2871,7 @@ function internBudgetChecker(L, loc) {
|
|
|
2862
2871
|
* source order, flatMap as an inner loop over its returned array.
|
|
2863
2872
|
* Pre-loop, a zero take budget marks done immediately — Node pulls
|
|
2864
2873
|
* nothing at all through a take(0). */
|
|
2865
|
-
function buildIterChainFn(L, name, itemsT, stages, terminal, termArgTs, resultT, loc) {
|
|
2874
|
+
function buildIterChainFn(L, name, itemsT, srcProj, stages, terminal, termArgTs, resultT, loc) {
|
|
2866
2875
|
const ref = (localId, type) => ({ kind: "varRef", localId, type, loc });
|
|
2867
2876
|
const num = (value) => ({ kind: "numLit", value, type: F64, loc });
|
|
2868
2877
|
const boolLit = (value) => ({ kind: "boolLit", value, type: BOOL, loc });
|
|
@@ -2883,7 +2892,12 @@ function buildIterChainFn(L, name, itemsT, stages, terminal, termArgTs, resultT,
|
|
|
2883
2892
|
const doneId = addLocal("done", BOOL, true);
|
|
2884
2893
|
const done = () => ref(doneId, BOOL);
|
|
2885
2894
|
const setDone = { kind: "assign", localId: doneId, value: boolLit(true), loc };
|
|
2886
|
-
|
|
2895
|
+
// The entries() seed builds each pass's [index, element] pair — the
|
|
2896
|
+
// same interned tuple the caller computed its stage types against.
|
|
2897
|
+
const seedT = srcProj === "entries"
|
|
2898
|
+
? { kind: "record", shapeId: L.shapes.intern([{ name: "0", type: F64 }, { name: "1", type: itemsT.elem }], true) }
|
|
2899
|
+
: itemsT.elem;
|
|
2900
|
+
let elem = seedT;
|
|
2887
2901
|
const slots = [];
|
|
2888
2902
|
for (const s of stages) {
|
|
2889
2903
|
if (s.kind === "take" || s.kind === "drop") {
|
|
@@ -3128,7 +3142,19 @@ function buildIterChainFn(L, name, itemsT, stages, terminal, termArgTs, resultT,
|
|
|
3128
3142
|
}
|
|
3129
3143
|
}
|
|
3130
3144
|
const iId = addLocal("i", F64, true);
|
|
3131
|
-
const vId = addLocal("v",
|
|
3145
|
+
const vId = addLocal("v", seedT, false);
|
|
3146
|
+
const elemRead = () => ({ kind: "arrayGet", arr: ref("items.0", itemsT), index: ref(iId, F64), type: itemsT.elem, loc });
|
|
3147
|
+
const seedInit = srcProj === "entries"
|
|
3148
|
+
? {
|
|
3149
|
+
kind: "recordLit",
|
|
3150
|
+
fields: [
|
|
3151
|
+
{ name: "0", value: ref(iId, F64) },
|
|
3152
|
+
{ name: "1", value: elemRead() },
|
|
3153
|
+
],
|
|
3154
|
+
type: seedT,
|
|
3155
|
+
loc,
|
|
3156
|
+
}
|
|
3157
|
+
: elemRead();
|
|
3132
3158
|
const loop = {
|
|
3133
3159
|
kind: "for",
|
|
3134
3160
|
init: { kind: "varDecl", localId: iId, init: num(0), loc },
|
|
@@ -3142,8 +3168,8 @@ function buildIterChainFn(L, name, itemsT, stages, terminal, termArgTs, resultT,
|
|
|
3142
3168
|
},
|
|
3143
3169
|
update: { kind: "assign", localId: iId, value: { kind: "bin", op: "+", left: ref(iId, F64), right: num(1), type: F64, loc }, loc },
|
|
3144
3170
|
body: [
|
|
3145
|
-
{ kind: "varDecl", localId: vId, init:
|
|
3146
|
-
...emit(ref(vId,
|
|
3171
|
+
{ kind: "varDecl", localId: vId, init: seedInit, loc },
|
|
3172
|
+
...emit(ref(vId, seedT)),
|
|
3147
3173
|
],
|
|
3148
3174
|
loc,
|
|
3149
3175
|
};
|
|
@@ -3436,6 +3462,143 @@ export function lowerForOfSearchParams(L, stmt, iterable, proj) {
|
|
|
3436
3462
|
L.scopes.pop();
|
|
3437
3463
|
}
|
|
3438
3464
|
}
|
|
3465
|
+
/** for-of over an ARRAY's keys()/entries() projections consumed directly
|
|
3466
|
+
* by the head (`for (const [index, line] of lines.entries())` — the
|
|
3467
|
+
* dominant formatter idiom): the LIVE index walk — the length re-reads
|
|
3468
|
+
* every pass, exactly the array iterator's contract (elements appended
|
|
3469
|
+
* mid-walk are visited; a shrink ends the loop early) — yielding the
|
|
3470
|
+
* index (`keys`, a number) or the [index, element] pair (`entries`).
|
|
3471
|
+
* `values` never lands here: lowerForOf's receiver unwrap owns it. A
|
|
3472
|
+
* `[i, v]` head of plain identifiers binds straight from the reads (the
|
|
3473
|
+
* tuple never materializes); identifier heads and the remaining
|
|
3474
|
+
* patterns bind through the checker's own element type — the interned
|
|
3475
|
+
* [number, T] tuple record. Stored iterator OBJECTS keep their fence
|
|
3476
|
+
* (only the direct for-of position unwraps). */
|
|
3477
|
+
export function lowerForOfArrayIter(L, stmt, iterable, proj) {
|
|
3478
|
+
const loc = locOf(stmt);
|
|
3479
|
+
const arrT = iterable.type;
|
|
3480
|
+
const elemT = arrT.elem;
|
|
3481
|
+
const yieldsPair = proj === "entries";
|
|
3482
|
+
if (!ts.isVariableDeclarationList(stmt.initializer)) {
|
|
3483
|
+
L.unsupported("SC1090", stmt.initializer, "for-of over a pre-declared variable (declare the loop variable in the loop: for (const x of ...))");
|
|
3484
|
+
}
|
|
3485
|
+
const list = stmt.initializer;
|
|
3486
|
+
if ((list.flags & ts.NodeFlags.Using) !== 0) {
|
|
3487
|
+
L.unsupported("SC1090", list, "'using' declarations (dispose-at-scope-exit semantics)");
|
|
3488
|
+
}
|
|
3489
|
+
const isVar = (list.flags & ts.NodeFlags.BlockScoped) === 0;
|
|
3490
|
+
const isLet = (list.flags & ts.NodeFlags.Let) !== 0 || isVar;
|
|
3491
|
+
const decl = list.declarations[0]; // the grammar allows exactly one
|
|
3492
|
+
L.scopes.push(new Map());
|
|
3493
|
+
try {
|
|
3494
|
+
const arr = L.declareHiddenLocal("%arof", arrT);
|
|
3495
|
+
const i = L.declareHiddenLocal("%iterof", F64);
|
|
3496
|
+
i.mutable = true;
|
|
3497
|
+
const ref = (localId, type) => ({ kind: "varRef", localId, type, loc });
|
|
3498
|
+
const num = (value) => ({ kind: "numLit", value, type: F64, loc });
|
|
3499
|
+
const keyRead = () => ref(i.id, F64);
|
|
3500
|
+
const valRead = () => ({ kind: "arrayGet", arr: ref(arr.id, arrT), index: ref(i.id, F64), type: elemT, loc });
|
|
3501
|
+
const binds = [];
|
|
3502
|
+
const isPlainIdent = (el) => ts.isBindingElement(el) && el.name !== undefined && ts.isIdentifier(el.name) && !el.initializer && !el.dotDotDotToken;
|
|
3503
|
+
if (yieldsPair &&
|
|
3504
|
+
!isVar && // var pattern names assign hoisted slots — the generic desugar below owns them
|
|
3505
|
+
ts.isArrayBindingPattern(decl.name) &&
|
|
3506
|
+
decl.name.elements.length >= 1 && decl.name.elements.length <= 2 &&
|
|
3507
|
+
decl.name.elements.every(isPlainIdent)) {
|
|
3508
|
+
// `for (const [i, v] of xs.entries())` — bind straight from the
|
|
3509
|
+
// reads; the tuple never exists. `[i]` alone reads only the index.
|
|
3510
|
+
const els = decl.name.elements;
|
|
3511
|
+
const kLocal = L.declareLocal(els[0].name, els[0].name.text, F64, isLet);
|
|
3512
|
+
binds.push({ kind: "varDecl", localId: kLocal.id, init: keyRead(), loc });
|
|
3513
|
+
if (els[1]) {
|
|
3514
|
+
const vLocal = L.declareLocal(els[1].name, els[1].name.text, elemT, isLet);
|
|
3515
|
+
binds.push({ kind: "varDecl", localId: vLocal.id, init: valRead(), loc });
|
|
3516
|
+
}
|
|
3517
|
+
}
|
|
3518
|
+
else {
|
|
3519
|
+
// Identifier heads and the remaining patterns bind through the
|
|
3520
|
+
// checker's own element type — [number, T] for pair yields,
|
|
3521
|
+
// number otherwise — the Map desugar's exact shape.
|
|
3522
|
+
const declT = L.mapTypeOf(L.checker.getTypeAtLocation(decl.name));
|
|
3523
|
+
if (yieldsPair) {
|
|
3524
|
+
const shape = declT?.kind === "record" ? L.shapes.get(declT.shapeId) : null;
|
|
3525
|
+
if (declT?.kind !== "record" || !shape?.tuple || shape.fields.length !== 2 ||
|
|
3526
|
+
shape.fields.find((f) => f.name === "0")?.type.kind !== "f64" ||
|
|
3527
|
+
!typeEquals(shape.fields.find((f) => f.name === "1")?.type ?? F64, elemT)) {
|
|
3528
|
+
L.badType(decl.name, L.checker.getTypeAtLocation(decl.name)); // defensive: the lib declares [number, T]
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3531
|
+
else if (declT?.kind !== "f64") {
|
|
3532
|
+
L.badType(decl.name, L.checker.getTypeAtLocation(decl.name)); // defensive: the lib declares number
|
|
3533
|
+
}
|
|
3534
|
+
const elemInit = yieldsPair
|
|
3535
|
+
? {
|
|
3536
|
+
kind: "recordLit",
|
|
3537
|
+
fields: [
|
|
3538
|
+
{ name: "0", value: keyRead() },
|
|
3539
|
+
{ name: "1", value: valRead() },
|
|
3540
|
+
],
|
|
3541
|
+
type: declT,
|
|
3542
|
+
loc,
|
|
3543
|
+
}
|
|
3544
|
+
: keyRead();
|
|
3545
|
+
if (ts.isIdentifier(decl.name)) {
|
|
3546
|
+
const varTarget = forOfVarTarget(L, decl);
|
|
3547
|
+
if (varTarget) {
|
|
3548
|
+
// `for (var x of ...)`: one function-scoped binding, assigned
|
|
3549
|
+
// per pass — closures made in the loop share it, and the value
|
|
3550
|
+
// persists after the loop (both Node-exact for var).
|
|
3551
|
+
const tmp = L.declareHiddenLocal("%vof", declT);
|
|
3552
|
+
binds.push({ kind: "varDecl", localId: tmp.id, init: elemInit, loc });
|
|
3553
|
+
binds.push({
|
|
3554
|
+
kind: "assign",
|
|
3555
|
+
localId: varTarget.id,
|
|
3556
|
+
value: L.coerceInto(decl.name, { kind: "varRef", localId: tmp.id, type: declT, loc }, varTarget.type),
|
|
3557
|
+
loc,
|
|
3558
|
+
});
|
|
3559
|
+
}
|
|
3560
|
+
else {
|
|
3561
|
+
const local = L.declareLocal(decl.name, decl.name.text, declT, isLet);
|
|
3562
|
+
binds.push({ kind: "varDecl", localId: local.id, init: elemInit, loc });
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
3565
|
+
else {
|
|
3566
|
+
const tmp = L.declareHiddenLocal("%destr", declT);
|
|
3567
|
+
binds.push({ kind: "varDecl", localId: tmp.id, init: elemInit, loc });
|
|
3568
|
+
L.lowerBindingPattern(decl.name, () => ({ kind: "varRef", localId: tmp.id, type: declT, loc }), declT, isLet, binds);
|
|
3569
|
+
}
|
|
3570
|
+
}
|
|
3571
|
+
const body = L.inCtl("loop", () => L.lowerScopedBlock(stmt.statement));
|
|
3572
|
+
const loop = {
|
|
3573
|
+
kind: "for",
|
|
3574
|
+
init: { kind: "varDecl", localId: i.id, init: num(0), loc },
|
|
3575
|
+
cond: {
|
|
3576
|
+
kind: "bin",
|
|
3577
|
+
op: "<",
|
|
3578
|
+
left: ref(i.id, F64),
|
|
3579
|
+
right: { kind: "arrIntrinsic", method: "length", receiver: ref(arr.id, arrT), args: [], type: F64, loc },
|
|
3580
|
+
type: BOOL,
|
|
3581
|
+
loc,
|
|
3582
|
+
},
|
|
3583
|
+
update: {
|
|
3584
|
+
kind: "assign",
|
|
3585
|
+
localId: i.id,
|
|
3586
|
+
value: { kind: "bin", op: "+", left: ref(i.id, F64), right: num(1), type: F64, loc },
|
|
3587
|
+
loc,
|
|
3588
|
+
},
|
|
3589
|
+
body: [...binds, ...body],
|
|
3590
|
+
loc,
|
|
3591
|
+
};
|
|
3592
|
+
return {
|
|
3593
|
+
kind: "block",
|
|
3594
|
+
body: [{ kind: "varDecl", localId: arr.id, init: iterable, loc }, loop],
|
|
3595
|
+
loc,
|
|
3596
|
+
};
|
|
3597
|
+
}
|
|
3598
|
+
finally {
|
|
3599
|
+
L.scopes.pop();
|
|
3600
|
+
}
|
|
3601
|
+
}
|
|
3439
3602
|
export function lowerForOfMap(L, stmt, iterable, mapT, proj) {
|
|
3440
3603
|
return lowerForOfMapOrSet(L, stmt, iterable, mapT, proj);
|
|
3441
3604
|
}
|
|
@@ -4104,25 +4267,35 @@ export function lowerBytesNew(L, expr, symbol) {
|
|
|
4104
4267
|
const seed = { kind: "arrayLit", elems, type: arrayOf(F64), loc };
|
|
4105
4268
|
return { kind: "bytesNew", source: seed, type, loc };
|
|
4106
4269
|
}
|
|
4107
|
-
// The SYNTACTIC `new T(new
|
|
4108
|
-
//
|
|
4109
|
-
//
|
|
4110
|
-
//
|
|
4111
|
-
//
|
|
4112
|
-
//
|
|
4113
|
-
//
|
|
4114
|
-
//
|
|
4115
|
-
//
|
|
4270
|
+
// The SYNTACTIC `new T(new ArrayBuffer(n))` and `new T(new
|
|
4271
|
+
// SharedArrayBuffer(n))` forms — fresh-buffer construction (the
|
|
4272
|
+
// shared spelling is the Atomics.wait sleep idiom's). The buffer
|
|
4273
|
+
// never exists as a value: n must be a byte-length LITERAL divisible
|
|
4274
|
+
// by the element size (tsc would admit any number; a bad one is
|
|
4275
|
+
// Node's RangeError — rejected at compile time instead of
|
|
4276
|
+
// half-lowering), and the whole expression is a zero-filled typed
|
|
4277
|
+
// array of n/elemSize elements. Erasing the buffer is exact: nothing
|
|
4278
|
+
// else can ever reference it, so neither sharing (scriptc has no
|
|
4279
|
+
// threads) nor aliasing is observable — SEMANTICS.md documents the
|
|
4280
|
+
// stance. The RESIZABLE form (a maxByteLength options bag) fences by
|
|
4281
|
+
// name: resize needs the buffer to exist as a value, and none does.
|
|
4116
4282
|
if (ts.isNewExpression(argNode) &&
|
|
4117
4283
|
ts.isIdentifier(argNode.expression) &&
|
|
4118
|
-
argNode.expression.text === "
|
|
4284
|
+
(argNode.expression.text === "ArrayBuffer" ||
|
|
4285
|
+
argNode.expression.text === "SharedArrayBuffer") &&
|
|
4119
4286
|
L.isStdlibSymbol(L.resolveValueSymbol(argNode.expression) ?? undefined)) {
|
|
4287
|
+
const bufCtor = argNode.expression.text;
|
|
4288
|
+
if ((argNode.arguments?.length ?? 0) > 1) {
|
|
4289
|
+
L.noLowering(`new ${name} over a resizable ${bufCtor}`, argNode, "a maxByteLength options bag makes the buffer resizable, and resizing needs the buffer " +
|
|
4290
|
+
"to exist as a runtime value — no free-standing ArrayBuffer value exists (the buffer here " +
|
|
4291
|
+
"erases into the view): drop the options bag");
|
|
4292
|
+
}
|
|
4120
4293
|
const elemSize = elem === "u8" ? 1 : 4;
|
|
4121
4294
|
const lenArg = argNode.arguments?.length === 1 ? argNode.arguments[0] : undefined;
|
|
4122
4295
|
const lenT = lenArg ? L.typeOf(lenArg) : null;
|
|
4123
4296
|
const byteLen = lenT?.isNumberLiteralType() ? lenT.value : null;
|
|
4124
4297
|
if (byteLen === null || byteLen % elemSize !== 0 || byteLen < 0) {
|
|
4125
|
-
L.noLowering(`new ${name} over this
|
|
4298
|
+
L.noLowering(`new ${name} over this ${bufCtor}`, argNode, `the byte length must be a number literal divisible by ${elemSize} — new ${name}(new ${bufCtor}(${elemSize}))`);
|
|
4126
4299
|
}
|
|
4127
4300
|
const count = { kind: "numLit", value: byteLen / elemSize, type: F64, loc };
|
|
4128
4301
|
return { kind: "bytesNew", source: count, type, loc };
|
|
@@ -4156,8 +4329,37 @@ function lowerDataViewNew(L, expr) {
|
|
|
4156
4329
|
if (!bufNode || args.length > 3 || args.some(ts.isSpreadElement)) {
|
|
4157
4330
|
L.noLowering(`new DataView with ${args.length} arguments`, expr, "supported: new DataView(x.buffer), (x.buffer, byteOffset), or (x.buffer, byteOffset, byteLength)");
|
|
4158
4331
|
}
|
|
4332
|
+
// The FRESH-BUFFER form: `new DataView(new ArrayBuffer(n), ...)` — the
|
|
4333
|
+
// buffer erases into the view (nothing else can ever reference it, so
|
|
4334
|
+
// the aliasing a shared buffer would exhibit is unobservable): the
|
|
4335
|
+
// view's storage is a fresh zero-filled n-byte allocation. n must be a
|
|
4336
|
+
// non-negative integer LITERAL (tsc admits any number; a fractional
|
|
4337
|
+
// one is ToIndex truncation nothing here implements). byteOffset and
|
|
4338
|
+
// byteLength keep their runtime Node-RangeError story. The RESIZABLE
|
|
4339
|
+
// form (a maxByteLength options bag) fences by name.
|
|
4340
|
+
if (ts.isNewExpression(bufNode) &&
|
|
4341
|
+
ts.isIdentifier(bufNode.expression) &&
|
|
4342
|
+
bufNode.expression.text === "ArrayBuffer" &&
|
|
4343
|
+
L.isStdlibSymbol(L.resolveValueSymbol(bufNode.expression) ?? undefined)) {
|
|
4344
|
+
if ((bufNode.arguments?.length ?? 0) > 1) {
|
|
4345
|
+
L.noLowering("new DataView over a resizable ArrayBuffer", bufNode, "a maxByteLength options bag makes the buffer resizable, and resizing needs the buffer " +
|
|
4346
|
+
"to exist as a runtime value — no free-standing ArrayBuffer value exists (the buffer here " +
|
|
4347
|
+
"erases into the view): drop the options bag");
|
|
4348
|
+
}
|
|
4349
|
+
const lenArg = bufNode.arguments?.length === 1 ? bufNode.arguments[0] : undefined;
|
|
4350
|
+
const lenT = lenArg ? L.typeOf(lenArg) : null;
|
|
4351
|
+
const byteLen = lenT?.isNumberLiteralType() ? lenT.value : null;
|
|
4352
|
+
if (byteLen === null || !Number.isInteger(byteLen) || byteLen < 0) {
|
|
4353
|
+
L.noLowering("new DataView over this ArrayBuffer", bufNode, "the byte length must be a non-negative integer literal — new DataView(new ArrayBuffer(8))");
|
|
4354
|
+
}
|
|
4355
|
+
const count = { kind: "numLit", value: byteLen, type: F64, loc };
|
|
4356
|
+
const receiver = { kind: "bytesNew", source: count, type: BYTES_U8, loc };
|
|
4357
|
+
const idxArgs = args.slice(1).map((a) => L.lowerExprExpecting(a, F64));
|
|
4358
|
+
return { kind: "bytesIntrinsic", method: "dataViewNew", receiver, args: idxArgs, type: BYTES_U8, loc };
|
|
4359
|
+
}
|
|
4159
4360
|
const hint = "views compile over a typed array's own storage — new DataView(x.buffer, byteOffset?, byteLength?) " +
|
|
4160
|
-
"where x is a Uint8Array/Uint32Array/Float32Array/Buffer value
|
|
4361
|
+
"where x is a Uint8Array/Uint32Array/Float32Array/Buffer value — or a fresh buffer erased into " +
|
|
4362
|
+
"the view: new DataView(new ArrayBuffer(n), ...); free-standing ArrayBuffers have no representation";
|
|
4161
4363
|
if (!ts.isPropertyAccessExpression(bufNode) || bufNode.name.text !== "buffer" || bufNode.questionDotToken) {
|
|
4162
4364
|
L.noLowering("new DataView over this buffer expression", bufNode, hint);
|
|
4163
4365
|
}
|
|
@@ -4329,6 +4531,27 @@ export function lowerBytesMethodCall(L, call, access) {
|
|
|
4329
4531
|
args.push(L.lowerExprExpecting(call.arguments[1], BOOL));
|
|
4330
4532
|
return { kind: "bytesIntrinsic", method: dvGetter.method, receiver, args, type: F64, loc };
|
|
4331
4533
|
}
|
|
4534
|
+
// DataView setters — the getters' mirror: (byteOffset, value) plus the
|
|
4535
|
+
// optional littleEndian bool on the multi-byte kinds. Void results;
|
|
4536
|
+
// the same constant Node RangeError on a bad offset (may-throw seeds).
|
|
4537
|
+
// setBigUint64/setBigInt64 never lower (bigint ARGUMENTS have no
|
|
4538
|
+
// representation and no composed form exists) — they keep the member
|
|
4539
|
+
// fence, as does setFloat16.
|
|
4540
|
+
const dvSetter = own(DV_SETTERS, name);
|
|
4541
|
+
if (dvSetter !== undefined && receiverIr.elem === "u8") {
|
|
4542
|
+
const maxArgs = dvSetter.le ? 3 : 2;
|
|
4543
|
+
if (nArgs < 2 || nArgs > maxArgs) {
|
|
4544
|
+
L.noLowering(`.${name} with ${nArgs} arguments`, call);
|
|
4545
|
+
}
|
|
4546
|
+
const receiver = L.lowerExpr(access.expression);
|
|
4547
|
+
const args = [
|
|
4548
|
+
L.lowerExprExpecting(call.arguments[0], F64),
|
|
4549
|
+
L.lowerExprExpecting(call.arguments[1], F64),
|
|
4550
|
+
];
|
|
4551
|
+
if (nArgs === 3)
|
|
4552
|
+
args.push(L.lowerExprExpecting(call.arguments[2], BOOL));
|
|
4553
|
+
return { kind: "bytesIntrinsic", method: dvSetter.method, receiver, args, type: VOID, loc };
|
|
4554
|
+
}
|
|
4332
4555
|
return null;
|
|
4333
4556
|
}
|
|
4334
4557
|
/** Node's Buffer encoding names → the runtime's canonical spelling (every
|
|
@@ -4418,20 +4641,59 @@ function lowerBufferInstanceMethod(L, call, access, name, loc) {
|
|
|
4418
4641
|
}
|
|
4419
4642
|
return v;
|
|
4420
4643
|
};
|
|
4644
|
+
// The checked-dynamic crossing for a compare/equals argument slot: dyn
|
|
4645
|
+
// passes through, convertible statics (the invalid-input probes'
|
|
4646
|
+
// literals) wrap in dynFrom, `undefined` literals ride the same wrap.
|
|
4647
|
+
// Fences (never returns) when the value cannot cross — an island jsval.
|
|
4648
|
+
const chkArg = (i) => {
|
|
4649
|
+
// Object literals take the DOM literal path directly (method members
|
|
4650
|
+
// box as dyn functions — the typed record fence never applies).
|
|
4651
|
+
if (ts.isObjectLiteralExpression(args[i])) {
|
|
4652
|
+
return lowerDynObjectLiteral(L, args[i]);
|
|
4653
|
+
}
|
|
4654
|
+
const v = L.lowerExpr(args[i]);
|
|
4655
|
+
if (v.type.kind === "dyn")
|
|
4656
|
+
return v;
|
|
4657
|
+
if (v.kind === "unitLit" || L.dynConvertible(v.type)) {
|
|
4658
|
+
return { kind: "dynFrom", value: v, type: DYN, loc: v.loc };
|
|
4659
|
+
}
|
|
4660
|
+
L.noLowering(`.${name} of '${L.fmt(v.type)}' values`, args[i], "a Buffer/Uint8Array argument is the lowered shape (narrow unions first)");
|
|
4661
|
+
};
|
|
4662
|
+
const argIrKind = (i) => L.mapTypeOf(L.typeOf(args[i]));
|
|
4421
4663
|
if (name === "equals") {
|
|
4422
4664
|
if (nArgs !== 1)
|
|
4423
4665
|
L.noLowering(`.equals with ${nArgs} arguments`, call);
|
|
4424
|
-
|
|
4666
|
+
if (argIrKind(0)?.kind === "bytes") {
|
|
4667
|
+
const other = u8Arg(0);
|
|
4668
|
+
const receiver = L.lowerExpr(access.expression);
|
|
4669
|
+
return { kind: "bytesIntrinsic", method: "equals", receiver, args: [other], type: BOOL, loc };
|
|
4670
|
+
}
|
|
4671
|
+
// Not statically bytes (the invalid-input probes, untyped JS
|
|
4672
|
+
// helpers): Node's "otherBuffer" argument ladder runs at runtime.
|
|
4425
4673
|
const receiver = L.lowerExpr(access.expression);
|
|
4426
|
-
return { kind: "
|
|
4674
|
+
return { kind: "libCall", fn: "bytes.equalsChk", args: [receiver, chkArg(0)], type: BOOL, loc };
|
|
4427
4675
|
}
|
|
4428
4676
|
if (name === "compare") {
|
|
4429
|
-
if (nArgs
|
|
4677
|
+
if (nArgs > 5)
|
|
4430
4678
|
L.noLowering(`.compare with ${nArgs} arguments`, call);
|
|
4431
|
-
const
|
|
4432
|
-
|
|
4679
|
+
const fast = nArgs >= 1 &&
|
|
4680
|
+
argIrKind(0)?.kind === "bytes" &&
|
|
4681
|
+
call.arguments.slice(1).every((a) => L.mapTypeOf(L.typeOf(a))?.kind === "f64");
|
|
4682
|
+
if (fast) {
|
|
4683
|
+
const target = u8Arg(0);
|
|
4684
|
+
const idx = call.arguments.slice(1).map((a) => L.lowerExprExpecting(a, F64));
|
|
4685
|
+
const receiver = L.lowerExpr(access.expression);
|
|
4686
|
+
return { kind: "bytesIntrinsic", method: "compareBuf", receiver, args: [target, ...idx], type: F64, loc };
|
|
4687
|
+
}
|
|
4688
|
+
// An absent/ill-typed target or offset (`a.compare()`, string
|
|
4689
|
+
// offsets, explicit undefined): Node's target/targetStart/targetEnd/
|
|
4690
|
+
// sourceStart/sourceEnd ladder runs at runtime; absent slots pass
|
|
4691
|
+
// the undefined dyn (Node defaults apply there).
|
|
4433
4692
|
const receiver = L.lowerExpr(access.expression);
|
|
4434
|
-
|
|
4693
|
+
const slots = [];
|
|
4694
|
+
for (let i = 0; i < 5; i++)
|
|
4695
|
+
slots.push(i < nArgs ? chkArg(i) : dynUndefinedExpr(loc));
|
|
4696
|
+
return { kind: "libCall", fn: "bytes.compareChk", args: [receiver, ...slots], type: F64, loc };
|
|
4435
4697
|
}
|
|
4436
4698
|
if (name === "indexOf" || name === "lastIndexOf" || name === "includes") {
|
|
4437
4699
|
if (nArgs < 1 || nArgs > 3)
|
|
@@ -4568,6 +4830,16 @@ const DV_GETTERS = {
|
|
|
4568
4830
|
getBigUint64: { method: "dvGetBigUint64Number", le: true },
|
|
4569
4831
|
getBigInt64: { method: "dvGetBigInt64Number", le: true },
|
|
4570
4832
|
};
|
|
4833
|
+
const DV_SETTERS = {
|
|
4834
|
+
setUint8: { method: "dvSetUint8", le: false },
|
|
4835
|
+
setInt8: { method: "dvSetInt8", le: false },
|
|
4836
|
+
setUint16: { method: "dvSetUint16", le: true },
|
|
4837
|
+
setInt16: { method: "dvSetInt16", le: true },
|
|
4838
|
+
setUint32: { method: "dvSetUint32", le: true },
|
|
4839
|
+
setInt32: { method: "dvSetInt32", le: true },
|
|
4840
|
+
setFloat32: { method: "dvSetFloat32", le: true },
|
|
4841
|
+
setFloat64: { method: "dvSetFloat64", le: true },
|
|
4842
|
+
};
|
|
4571
4843
|
/** The Buffer statics — `Buffer.from(...)`, `Buffer.alloc(n)`,
|
|
4572
4844
|
* `Buffer.concat(list)`, `Buffer.isBuffer(x)` — on THE stdlib Buffer
|
|
4573
4845
|
* global (name + provenance; fallback and @types/node alike). Null when
|
|
@@ -4676,14 +4948,22 @@ export function lowerBufferStaticCall(L, call, access) {
|
|
|
4676
4948
|
if (args.length !== 2 || args.some(ts.isSpreadElement)) {
|
|
4677
4949
|
L.noLowering(`Buffer.compare with ${args.length} arguments`, call);
|
|
4678
4950
|
}
|
|
4679
|
-
const sides = args.map((a) =>
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4951
|
+
const sides = args.map((a) => L.lowerExpr(a));
|
|
4952
|
+
if (sides.every((v) => v.type.kind === "bytes" && v.type.elem === "u8")) {
|
|
4953
|
+
return { kind: "bytesIntrinsic", method: "compareBuf", receiver: sides[0], args: [sides[1]], type: F64, loc };
|
|
4954
|
+
}
|
|
4955
|
+
// A side that is not statically bytes (the invalid-input probes,
|
|
4956
|
+
// untyped JS helpers): Node's "buf1"/"buf2" argument ladder runs
|
|
4957
|
+
// at runtime — a well-typed dyn still compares.
|
|
4958
|
+
const dyns = sides.map((v, i) => {
|
|
4959
|
+
if (v.type.kind === "dyn")
|
|
4960
|
+
return v;
|
|
4961
|
+
if (v.kind === "unitLit" || L.dynConvertible(v.type)) {
|
|
4962
|
+
return { kind: "dynFrom", value: v, type: DYN, loc: v.loc };
|
|
4683
4963
|
}
|
|
4684
|
-
|
|
4964
|
+
L.noLowering(`Buffer.compare of '${L.fmt(v.type)}' values`, args[i], "Buffer/Uint8Array values compare (narrow unions first)");
|
|
4685
4965
|
});
|
|
4686
|
-
return { kind: "
|
|
4966
|
+
return { kind: "libCall", fn: "buffer.compareChk", args: dyns, type: F64, loc };
|
|
4687
4967
|
}
|
|
4688
4968
|
if (member === "isBuffer") {
|
|
4689
4969
|
// The type-predicate narrowing test. Lowered where it DECIDES
|
|
@@ -5192,8 +5472,10 @@ function lowerFromEntriesStringRows(L, call, argNode, argIr) {
|
|
|
5192
5472
|
* every source value (declared fields, and the overflow slot when the
|
|
5193
5473
|
* source carries one) must enter the target's value slot (identity, a
|
|
5194
5474
|
* width lift — arm wrap/re-tag/nested reshape, or JSON-safe into a dyn
|
|
5195
|
-
* slot), and every target field must be
|
|
5196
|
-
*
|
|
5475
|
+
* slot), and every target field must either be initialized directly by
|
|
5476
|
+
* a same-named source declared field (the rule that admits REQUIRED
|
|
5477
|
+
* declared members) or be optional-flavored (the fresh record's
|
|
5478
|
+
* default) and writable at runtime. */
|
|
5197
5479
|
export function lowerRecordOvfCaptureHelper(L, fromId, toId, loc) {
|
|
5198
5480
|
const from = L.shapes.get(fromId);
|
|
5199
5481
|
const to = L.shapes.get(toId);
|
|
@@ -5214,11 +5496,16 @@ export function lowerRecordOvfCaptureHelper(L, fromId, toId, loc) {
|
|
|
5214
5496
|
// signature have no overflow to carry over).
|
|
5215
5497
|
if (fIv && slotLift(fIv) === null)
|
|
5216
5498
|
return null;
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
// fields must be DOM-convertible; typed slots write through directly).
|
|
5220
|
-
const defaults = [];
|
|
5499
|
+
const inits = [];
|
|
5500
|
+
const consumed = new Set();
|
|
5221
5501
|
for (const tf of to.fields) {
|
|
5502
|
+
const sf = from.fields.find((f) => f.name === tf.name);
|
|
5503
|
+
const directLift = sf ? L.widthLiftPlan(sf.type, tf.type) : null;
|
|
5504
|
+
if (sf && directLift) {
|
|
5505
|
+
inits.push({ name: tf.name, kind: "direct", src: sf.type, lift: directLift });
|
|
5506
|
+
consumed.add(tf.name);
|
|
5507
|
+
continue;
|
|
5508
|
+
}
|
|
5222
5509
|
if (tf.type.kind !== "union")
|
|
5223
5510
|
return null;
|
|
5224
5511
|
const utag = L.armTag(tf.type.unionId, UNDEFINED_T);
|
|
@@ -5226,7 +5513,7 @@ export function lowerRecordOvfCaptureHelper(L, fromId, toId, loc) {
|
|
|
5226
5513
|
return null;
|
|
5227
5514
|
if (tIv.kind === "dyn" ? !L.dynConvertible(tf.type) : !typeEquals(tf.type, tIv))
|
|
5228
5515
|
return null;
|
|
5229
|
-
|
|
5516
|
+
inits.push({ name: tf.name, kind: "undef", unionId: tf.type.unionId, utag });
|
|
5230
5517
|
}
|
|
5231
5518
|
// Every source declared field must flow into the keyed-write slot —
|
|
5232
5519
|
// in DECLARATION order (insertion order below; declaredOrder omits
|
|
@@ -5239,11 +5526,30 @@ export function lowerRecordOvfCaptureHelper(L, fromId, toId, loc) {
|
|
|
5239
5526
|
: from.fields;
|
|
5240
5527
|
const fieldLifts = new Map();
|
|
5241
5528
|
for (const ff of orderedFields) {
|
|
5529
|
+
if (consumed.has(ff.name))
|
|
5530
|
+
continue; // direct-initialized above
|
|
5242
5531
|
const lift = slotLift(ff.type);
|
|
5243
5532
|
if (lift === null)
|
|
5244
5533
|
return null;
|
|
5245
5534
|
fieldLifts.set(ff.name, lift);
|
|
5246
5535
|
}
|
|
5536
|
+
// DISPATCH writes — keys that can hit a declared target slot: the
|
|
5537
|
+
// overflow loop's runtime keys, and a literal source-field name the
|
|
5538
|
+
// target also declares. Non-dyn slots store THROUGH on a collision, so
|
|
5539
|
+
// such writes need every declared field to BE the slot type (the
|
|
5540
|
+
// validator's recordKeySet rule); dyn slots validate per field
|
|
5541
|
+
// (dynCheck), so each field must be DOM-convertible. Writes to
|
|
5542
|
+
// literal names the target does NOT declare are overflowOnly and
|
|
5543
|
+
// exempt — which is what lets a direct-initialized required field
|
|
5544
|
+
// (`id: number` beside a `number | undefined` slot) coexist with a
|
|
5545
|
+
// declared-only source, while an index-signature source declines.
|
|
5546
|
+
const dispatchWrites = fIv !== undefined ||
|
|
5547
|
+
orderedFields.some((ff) => !consumed.has(ff.name) && to.fields.some((f) => f.name === ff.name));
|
|
5548
|
+
if (dispatchWrites) {
|
|
5549
|
+
if (tIv.kind === "dyn" ? !to.fields.every((f) => L.dynConvertible(f.type)) : !to.fields.every((f) => typeEquals(f.type, tIv))) {
|
|
5550
|
+
return null;
|
|
5551
|
+
}
|
|
5552
|
+
}
|
|
5247
5553
|
const key = `ovf:${fromId}:${toId}`;
|
|
5248
5554
|
const existing = L.widthHelpers.get(key);
|
|
5249
5555
|
if (existing)
|
|
@@ -5265,16 +5571,18 @@ export function lowerRecordOvfCaptureHelper(L, fromId, toId, loc) {
|
|
|
5265
5571
|
localId: "out.0",
|
|
5266
5572
|
init: {
|
|
5267
5573
|
kind: "recordLit",
|
|
5268
|
-
fields:
|
|
5574
|
+
fields: inits.map((d) => ({
|
|
5269
5575
|
name: d.name,
|
|
5270
|
-
value:
|
|
5271
|
-
kind: "
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5576
|
+
value: d.kind === "direct"
|
|
5577
|
+
? L.applyWidthLift(d.lift, { kind: "recordGet", obj: sRef, shapeId: fromId, field: d.name, type: d.src, loc }, to.fields.find((f) => f.name === d.name).type, loc)
|
|
5578
|
+
: {
|
|
5579
|
+
kind: "unionWrap",
|
|
5580
|
+
unionId: d.unionId,
|
|
5581
|
+
tag: d.utag,
|
|
5582
|
+
value: { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc },
|
|
5583
|
+
type: to.fields.find((f) => f.name === d.name).type,
|
|
5584
|
+
loc,
|
|
5585
|
+
},
|
|
5278
5586
|
})),
|
|
5279
5587
|
type: toT,
|
|
5280
5588
|
loc,
|
|
@@ -5283,8 +5591,10 @@ export function lowerRecordOvfCaptureHelper(L, fromId, toId, loc) {
|
|
|
5283
5591
|
},
|
|
5284
5592
|
];
|
|
5285
5593
|
// Source declared fields in declaration order, skipping unset
|
|
5286
|
-
// optionals (stance 37).
|
|
5594
|
+
// optionals (stance 37) and the direct-initialized (consumed) names.
|
|
5287
5595
|
for (const ff of orderedFields) {
|
|
5596
|
+
if (consumed.has(ff.name))
|
|
5597
|
+
continue;
|
|
5288
5598
|
const raw = { kind: "recordGet", obj: sRef, shapeId: fromId, field: ff.name, type: ff.type, loc };
|
|
5289
5599
|
const utag = ff.type.kind === "union" ? L.armTag(ff.type.unionId, UNDEFINED_T) : -1;
|
|
5290
5600
|
const write = {
|
|
@@ -5293,6 +5603,9 @@ export function lowerRecordOvfCaptureHelper(L, fromId, toId, loc) {
|
|
|
5293
5603
|
shapeId: toId,
|
|
5294
5604
|
key: { kind: "strLit", value: ff.name, type: STRING, loc },
|
|
5295
5605
|
value: intoSlot(raw, fieldLifts.get(ff.name)),
|
|
5606
|
+
// A literal name the target does not declare can only land in the
|
|
5607
|
+
// overflow — skip the declared dispatch (and its validator gate).
|
|
5608
|
+
...(to.fields.some((f) => f.name === ff.name) ? {} : { overflowOnly: true }),
|
|
5296
5609
|
loc,
|
|
5297
5610
|
};
|
|
5298
5611
|
body.push(utag >= 0 && ff.type.kind === "union"
|
|
@@ -6155,6 +6468,26 @@ export function lowerDynArrayFlatMapCall(L, call, access, recv) {
|
|
|
6155
6468
|
const { fnArg, arity } = hofCallbackArg(L, argNode, [DYN], DYN);
|
|
6156
6469
|
const ret = fnArg.type.ret;
|
|
6157
6470
|
if (ret.kind !== "array") {
|
|
6471
|
+
// A DYNAMIC-returning callback (`plugins.flatMap((plugin) =>
|
|
6472
|
+
// plugin.languages ?? [])` — the getSupportInfo shape, where the
|
|
6473
|
+
// callback's members ride the DOM): the call dispatches on the
|
|
6474
|
+
// RECEIVER's runtime kind (dynInvoke) with the callback boxed —
|
|
6475
|
+
// a wrapped ISLAND receiver runs the ENGINE's own JS-exact
|
|
6476
|
+
// Array.prototype.flatMap (the routed-ops lane), a native DOM
|
|
6477
|
+
// array runs the runtime's flatMap, and non-arrays throw Node's
|
|
6478
|
+
// TypeError. The result stays dyn, checked per use.
|
|
6479
|
+
if (ret.kind === "dyn" || ret.kind === "jsval") {
|
|
6480
|
+
const boxed = { kind: "dynFrom", value: fnArg, type: DYN, loc };
|
|
6481
|
+
return {
|
|
6482
|
+
kind: "dynInvoke",
|
|
6483
|
+
recv,
|
|
6484
|
+
method: "flatMap",
|
|
6485
|
+
calleeName: access.getText(),
|
|
6486
|
+
args: [boxed],
|
|
6487
|
+
type: DYN,
|
|
6488
|
+
loc,
|
|
6489
|
+
};
|
|
6490
|
+
}
|
|
6158
6491
|
L.unsupported("SC1090", call, `'.flatMap' on 'unknown' receivers whose callback returns '${L.fmt(ret)}' ` +
|
|
6159
6492
|
`(only callbacks returning a typed ARRAY compile — the results concatenate ` +
|
|
6160
6493
|
`without validation; return one-element/empty arrays instead of bare values)`);
|