@scriptc/compiler 0.0.9 → 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.
Files changed (62) hide show
  1. package/dist/backend/emission/emit-exprs.js +136 -12
  2. package/dist/backend/emission/emit-exprs.js.map +1 -1
  3. package/dist/backend/emission/emit-walkers.js +29 -2
  4. package/dist/backend/emission/emit-walkers.js.map +1 -1
  5. package/dist/backend/llvm/dyn.d.ts +1 -0
  6. package/dist/backend/llvm/dyn.js +83 -1
  7. package/dist/backend/llvm/dyn.js.map +1 -1
  8. package/dist/backend/llvm/emitter.js +151 -9
  9. package/dist/backend/llvm/emitter.js.map +1 -1
  10. package/dist/diagnostics/diagnostic.d.ts +21 -7
  11. package/dist/diagnostics/diagnostic.js +35 -16
  12. package/dist/diagnostics/diagnostic.js.map +1 -1
  13. package/dist/diagnostics/render.js +8 -0
  14. package/dist/diagnostics/render.js.map +1 -1
  15. package/dist/frontend/lowering/lib-boundary.js +11 -1
  16. package/dist/frontend/lowering/lib-boundary.js.map +1 -1
  17. package/dist/frontend/lowering/lower-builtins.d.ts +13 -0
  18. package/dist/frontend/lowering/lower-builtins.js +146 -1
  19. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  20. package/dist/frontend/lowering/lower-calls.d.ts +41 -0
  21. package/dist/frontend/lowering/lower-calls.js +524 -10
  22. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  23. package/dist/frontend/lowering/lower-containers.d.ts +17 -0
  24. package/dist/frontend/lowering/lower-containers.js +193 -10
  25. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  26. package/dist/frontend/lowering/lower-dgram.js +92 -17
  27. package/dist/frontend/lowering/lower-dgram.js.map +1 -1
  28. package/dist/frontend/lowering/lower-exprs.js +179 -10
  29. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  30. package/dist/frontend/lowering/lower-modules.js +33 -2
  31. package/dist/frontend/lowering/lower-modules.js.map +1 -1
  32. package/dist/frontend/lowering/lower-namespaces.d.ts +25 -11
  33. package/dist/frontend/lowering/lower-namespaces.js +50 -15
  34. package/dist/frontend/lowering/lower-namespaces.js.map +1 -1
  35. package/dist/frontend/lowering/lower-server.js +211 -4
  36. package/dist/frontend/lowering/lower-server.js.map +1 -1
  37. package/dist/frontend/lowering/lower-stmts.d.ts +1 -5
  38. package/dist/frontend/lowering/lower-stmts.js +216 -7
  39. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  40. package/dist/frontend/lowering/lower-stream.js +65 -4
  41. package/dist/frontend/lowering/lower-stream.js.map +1 -1
  42. package/dist/frontend/lowering/lowerer.d.ts +36 -1
  43. package/dist/frontend/lowering/lowerer.js +106 -3
  44. package/dist/frontend/lowering/lowerer.js.map +1 -1
  45. package/dist/frontend/types.d.ts +13 -0
  46. package/dist/frontend/types.js +84 -7
  47. package/dist/frontend/types.js.map +1 -1
  48. package/dist/index.js +20 -8
  49. package/dist/index.js.map +1 -1
  50. package/dist/ir/nodes.d.ts +99 -5
  51. package/dist/ir/nodes.js +44 -3
  52. package/dist/ir/nodes.js.map +1 -1
  53. package/dist/ir/validate.js +53 -3
  54. package/dist/ir/validate.js.map +1 -1
  55. package/dist/library/fence-eval.d.ts +77 -0
  56. package/dist/library/fence-eval.js +385 -0
  57. package/dist/library/fence-eval.js.map +1 -0
  58. package/dist/library/profile.d.ts +11 -1
  59. package/dist/library/profile.js +102 -18
  60. package/dist/library/profile.js.map +1 -1
  61. package/package.json +2 -2
  62. package/surface-manifest.json +1 -1
@@ -170,7 +170,7 @@ export declare function lowerVarStatement(L: Lowerer, stmt: ts.VariableStatement
170
170
  export declare function lowerDestructuringDecl(L: Lowerer, decl: ts.VariableDeclaration, isLet: boolean): IrStmt[];
171
171
  /** One pattern level: emits a declaration (or global assignment) per
172
172
  * bound name, recursing through hidden temps for nested patterns. */
173
- export declare function lowerBindingPattern(L: Lowerer, pattern: ts.ArrayBindingPattern | ts.ObjectBindingPattern, srcRef: () => IrExpr, srcType: IrType, isLet: boolean, out: IrStmt[]): void;
173
+ export declare function lowerBindingPattern(L: Lowerer, pattern: ts.ArrayBindingPattern | ts.ObjectBindingPattern, srcRef: () => IrExpr, srcType: IrType, isLet: boolean, out: IrStmt[], dynSpell?: string): void;
174
174
  /** The STATIC property name of a destructuring key: identifiers spell
175
175
  * themselves, string/numeric-literal keys and COMPUTED keys fold through
176
176
  * foldedStringKeyOf — pure single-name expressions only (`{ [k]: v }`
@@ -209,10 +209,6 @@ export declare function enginePatternSpec(L: Lowerer, pattern: ts.ArrayBindingPa
209
209
  * exit), rest, and transportable defaults all ride. False when the
210
210
  * pattern has no engine form (the caller fences). */
211
211
  export declare function lowerJsvalBindingPattern(L: Lowerer, pattern: ts.ArrayBindingPattern | ts.ObjectBindingPattern, srcRef: () => IrExpr, isLet: boolean, out: IrStmt[]): boolean;
212
- /** Binds one destructured name to its value — the identifier tail of
213
- * lowerVarDecl (module-global assignment for pre-registered file-scope
214
- * names, declareLocal otherwise), or a nested pattern through its own
215
- * hidden temp. */
216
212
  export declare function bindPatternTarget(L: Lowerer, name: ts.BindingName, value: IrExpr, isLet: boolean, out: IrStmt[]): void;
217
213
  /** For-loop initializers. let/const stay restricted to ONE declarator (JS
218
214
  * per-iteration binding copies for several captured loop variables are
@@ -10,10 +10,10 @@ import { enforceLibBoundary } from "./lib-boundary.js";
10
10
  import { cjsExportAssignmentOf, cjsExportDiscardReason, cjsExportTargetLiteral, isCjsJsFile, isJsSourceFile, locOf, requireSpecOf } from "../program.js";
11
11
  import { COMPOUND_ASSIGN_OPS, STR_METHODS, UNSUPPORTED_STMT, isStdlibMember, sideEffectFreeOptionValue, stdlibGlobalAliasDecl, stdlibGlobalNameOf } from "./surfaces.js";
12
12
  import { isProvenanceSourceFile } from "../provenance-registry.js";
13
- import { lowerImportEquals, nsWritableTarget } from "./lower-namespaces.js";
13
+ import { ambientUndefVarRootOf, lowerImportEquals, nsUndefRead, nsWritableTarget } from "./lower-namespaces.js";
14
14
  import { expandoWritableTarget, lowerExpandoAssignStmt } from "./lower-expando.js";
15
- import { lowerForOfMap, lowerForOfSearchParams, lowerForOfSet, objectIterOverIndexShape, strCharsCall } from "./lower-containers.js";
16
- import { bindingContextualGenericFnNodeOf, bindingGenericFnAliasInfoOf, bindingGenericFnInfoOf, bindingGenericFnNodeOf, implicitLocalFnInfoOf, implicitLocalFnNodeOf, recordKeysArrayCall } from "./lower-calls.js";
15
+ import { lowerForOfArrayIter, lowerForOfMap, lowerForOfSearchParams, lowerForOfSet, objectIterOverIndexShape, strCharsCall } from "./lower-containers.js";
16
+ import { bindingContextualGenericFnNodeOf, bindingGenericFnAliasInfoOf, bindingGenericFnInfoOf, bindingGenericFnNodeOf, deadUnmappableBinding, implicitLocalFnInfoOf, implicitLocalFnNodeOf, nullishExprUnitOf, nullishGenericBindingUnitOf, recordKeysArrayCall } from "./lower-calls.js";
17
17
  import { isMixinFnBinding, mixinResultBindingClassOf } from "./lower-mixins.js";
18
18
  import { genericIfaceBindingKeepsClass } from "./lower-classes.js";
19
19
  import { lowerStreamUnderscoreAssign, streamClassAliasDecl, streamSidesOf } from "./lower-stream.js";
@@ -1105,12 +1105,24 @@ export function lowerDestructuringDecl(L, decl, isLet) {
1105
1105
  }
1106
1106
  const tmp = L.declareHiddenLocal("%destr", init.type);
1107
1107
  out.push({ kind: "varDecl", localId: tmp.id, init, loc });
1108
- L.lowerBindingPattern(decl.name, () => ({ kind: "varRef", localId: tmp.id, type: init.type, loc }), init.type, isLet, out);
1108
+ // V8's destructuring TypeError spells STATIC sources: an identifier
1109
+ // source reads "<name> is not iterable", an identifier-callee call
1110
+ // "<name> is not a function or its return value is not iterable";
1111
+ // everything else (property reads, method calls, parameters) gets the
1112
+ // runtime kind wording — exactly V8's CallPrinter behavior. Only the
1113
+ // DYN pattern arm consumes this.
1114
+ const src = decl.initializer;
1115
+ const dynSpell = ts.isIdentifier(src)
1116
+ ? `${src.text} is not iterable`
1117
+ : ts.isCallExpression(src) && ts.isIdentifier(src.expression)
1118
+ ? `${src.expression.text} is not a function or its return value is not iterable`
1119
+ : undefined;
1120
+ L.lowerBindingPattern(decl.name, () => ({ kind: "varRef", localId: tmp.id, type: init.type, loc }), init.type, isLet, out, dynSpell);
1109
1121
  return out;
1110
1122
  }
1111
1123
  /** One pattern level: emits a declaration (or global assignment) per
1112
1124
  * bound name, recursing through hidden temps for nested patterns. */
1113
- export function lowerBindingPattern(L, pattern, srcRef, srcType, isLet, out) {
1125
+ export function lowerBindingPattern(L, pattern, srcRef, srcType, isLet, out, dynSpell) {
1114
1126
  if (ts.isArrayBindingPattern(pattern)) {
1115
1127
  // Tuple sources: each position is a field read of the tuple's record
1116
1128
  // shape — the positional twin of object destructuring below.
@@ -1231,6 +1243,68 @@ export function lowerBindingPattern(L, pattern, srcRef, srcType, isLet, out) {
1231
1243
  L.lowerBindingPattern(pattern, () => ({ kind: "varRef", localId: chars.id, type: charsT, loc }), charsT, isLet, out);
1232
1244
  return;
1233
1245
  }
1246
+ // DYN sources (`const [a, b] = d` over a DOM value, `([_, code]) =>`
1247
+ // params destructuring dyn callback arguments — the suite harness's
1248
+ // _expectWarning shapes): the source packs ONCE through the spread
1249
+ // walk (dyn.iterPack — arrays element-by-element, strings by code
1250
+ // point, bytes by byte; every other kind throws V8's destructuring
1251
+ // TypeError, the compile-time spelling when the source has one),
1252
+ // then positions bind the pack's index keys as dyn values — reads
1253
+ // past the end bind undefined, exactly JS. Defaults fire on the DOM
1254
+ // undefined (JS's rule); rest elements keep the fence (surplus
1255
+ // packing over the DOM needs a slice the IR does not spell yet).
1256
+ if (srcType.kind === "dyn") {
1257
+ const loc = locOf(pattern);
1258
+ const pack = L.declareHiddenLocal("%dpack", DYN);
1259
+ out.push({
1260
+ kind: "varDecl",
1261
+ localId: pack.id,
1262
+ init: {
1263
+ kind: "libCall",
1264
+ fn: "dyn.iterPack",
1265
+ args: [srcRef(), { kind: "strLit", value: dynSpell ?? "", type: STRING, loc }],
1266
+ type: DYN,
1267
+ loc,
1268
+ },
1269
+ loc,
1270
+ });
1271
+ pattern.elements.forEach((el, i) => {
1272
+ if (ts.isOmittedExpression(el) || el.name === undefined)
1273
+ return; // hole: the position skips
1274
+ const elLoc = locOf(el);
1275
+ if (el.dotDotDotToken) {
1276
+ L.unsupported("SC1031", el, "rest elements over checked-dynamic sources");
1277
+ }
1278
+ let value = {
1279
+ kind: "dynKeyGet",
1280
+ key: { kind: "strLit", value: String(i), type: STRING, loc: elLoc },
1281
+ value: { kind: "varRef", localId: pack.id, type: DYN, loc: elLoc },
1282
+ type: DYN,
1283
+ loc: elLoc,
1284
+ };
1285
+ if (el.initializer) {
1286
+ // The default fires exactly on the DOM undefined (JS's rule),
1287
+ // its value converting into the DOM like any dyn-slot value.
1288
+ const rl = L.declareHiddenLocal("%delem", DYN);
1289
+ out.push({ kind: "varDecl", localId: rl.id, init: value, loc: elLoc });
1290
+ const ref = () => ({ kind: "varRef", localId: rl.id, type: DYN, loc: elLoc });
1291
+ const dflt = L.coerceToExpected(L.lowerExpr(el.initializer), DYN);
1292
+ if (dflt.type.kind !== "dyn") {
1293
+ L.unsupported("SC1031", el.initializer, `defaults of '${L.fmt(dflt.type)}' type over checked-dynamic sources (the value cannot convert into the DOM)`);
1294
+ }
1295
+ value = {
1296
+ kind: "ternary",
1297
+ cond: { kind: "dynTest", test: "undefined", value: ref(), type: BOOL, loc: elLoc },
1298
+ then: dflt,
1299
+ else_: ref(),
1300
+ type: DYN,
1301
+ loc: elLoc,
1302
+ };
1303
+ }
1304
+ L.bindPatternTarget(el.name, value, isLet, out);
1305
+ });
1306
+ return;
1307
+ }
1234
1308
  if (srcType.kind !== "array") {
1235
1309
  L.unsupported("SC1031", pattern, `array destructuring of non-array values (the source is ${srcType.kind}-typed)`);
1236
1310
  }
@@ -2376,6 +2450,13 @@ export function lowerJsvalBindingPattern(L, pattern, srcRef, isLet, out) {
2376
2450
  * lowerVarDecl (module-global assignment for pre-registered file-scope
2377
2451
  * names, declareLocal otherwise), or a nested pattern through its own
2378
2452
  * hidden temp. */
2453
+ /** An island-world ('any'-flavored) checker spelling: bare jsval or an
2454
+ * array of jsvals (`(string | object)[]` absorbed by its jsval arm) —
2455
+ * the shapes the runtime-world local rule keeps DYN when the value
2456
+ * lowered checked-dynamic. */
2457
+ function jsvalFlavoredType(t) {
2458
+ return t.kind === "jsval" || (t.kind === "array" && t.elem.kind === "jsval");
2459
+ }
2379
2460
  export function bindPatternTarget(L, name, value, isLet, out) {
2380
2461
  const loc = value.loc;
2381
2462
  if (ts.isIdentifier(name)) {
@@ -2396,7 +2477,21 @@ export function bindPatternTarget(L, name, value, isLet, out) {
2396
2477
  out.push({ kind: "assign", localId: hoisted.id, value: L.coerceInto(name, value, hoisted.type), loc });
2397
2478
  return;
2398
2479
  }
2399
- const type = L.irTypeOf(name);
2480
+ // The runtime-world local rule, dyn side (383(d)'s dispatch stance):
2481
+ // a pattern binding whose VALUE lowered checked-dynamic stays dyn
2482
+ // where the checker spells an island-world type (`function f({
2483
+ // plugins = [] } = {})` over a JSDoc '(string | object)[]=' member —
2484
+ // the read is a DOM keyed read whose members may be wrapped island
2485
+ // values) or a type with no mapping at all (the varDecl rule's
2486
+ // unmappable-declared-type stance, `string | object` bindings
2487
+ // included). Marshaling here would fence or deep-copy; the value's
2488
+ // world is the honest dispatch.
2489
+ const dynStays = value.type.kind === "dyn" &&
2490
+ (() => {
2491
+ const mapped = L.mapTypeOf(L.typeOf(name));
2492
+ return mapped === null || (L.dynamic && jsvalFlavoredType(mapped));
2493
+ })();
2494
+ const type = dynStays ? DYN : L.irTypeOf(name);
2400
2495
  if (type.kind === "void")
2401
2496
  L.badType(name, L.typeOf(name));
2402
2497
  const coerced = L.coerceInto(name, value, type);
@@ -2463,8 +2558,48 @@ export function lowerVarDecl(L, decl, isLet) {
2463
2558
  // put its fences on the build.
2464
2559
  if (provenanceElidedConstDecl(L, decl))
2465
2560
  return null;
2561
+ // A TRAP declaration — the initializer's chain roots at an
2562
+ // ambient-undefined name (`declare function factory...; const make =
2563
+ // factory<T>()`): Node throws the root's ReferenceError while
2564
+ // evaluating the initializer, module init unwinds THERE, and nothing
2565
+ // after — including every reference to this binding — ever runs. The
2566
+ // statement lowers to exactly that throw; the binding registers as a
2567
+ // trap (references lower to the same never-reached shape), and no
2568
+ // storage or type mapping is needed for a value that never exists.
2569
+ if (decl.initializer !== undefined) {
2570
+ const root = ambientUndefVarRootOf(L, decl.initializer);
2571
+ if (root !== null) {
2572
+ for (const nameNode of boundIdentifiersOf(decl.name)) {
2573
+ const sym = L.checker.getSymbolAtLocation(nameNode);
2574
+ if (sym)
2575
+ L.trapBindings.add(sym);
2576
+ }
2577
+ return {
2578
+ kind: "exprStmt",
2579
+ expr: nsUndefRead(L, root.text, decl.initializer, F64),
2580
+ loc: locOf(decl),
2581
+ };
2582
+ }
2583
+ }
2466
2584
  if (!ts.isIdentifier(decl.name))
2467
2585
  L.unsupported("SC1031", decl.name);
2586
+ // A NULLISH generic binding (`const i: I<A & B> = null as any` — the
2587
+ // declared type has no mapping, the value is provably null/undefined
2588
+ // forever): no storage exists; reads know the value (member reads and
2589
+ // method calls lower to Node's exact TypeError, nullish-to-nullish
2590
+ // flows to nothing), so the declaration emits nothing.
2591
+ if (nullishGenericBindingUnitOf(L, L.checker.getSymbolAtLocation(decl.name) ?? null) !== null) {
2592
+ return null;
2593
+ }
2594
+ // A DEAD unmappable binding (`var xs2: typeof Array;`, the write-only
2595
+ // `var f2: { <T, U>(x: T, y: U): T }`): never read anywhere in the
2596
+ // program, its initializer (if any) a side-effect-free value — Node
2597
+ // materializes the value and drops it, zero observable effect — so
2598
+ // the declaration emits nothing instead of fencing on a type the
2599
+ // program never consumes.
2600
+ if (deadUnmappableBinding(L, L.checker.getSymbolAtLocation(decl.name) ?? null, decl)) {
2601
+ return null;
2602
+ }
2468
2603
  // An initializer-LESS declaration named `module` or `exports` at the
2469
2604
  // top level of an import/export-free TS file shadows the CommonJS
2470
2605
  // wrapper binding: Node hosts such a file as CJS, where
@@ -2686,6 +2821,16 @@ export function lowerVarDecl(L, decl, isLet) {
2686
2821
  // uncheckedOverloadHandleCall.
2687
2822
  (init.type.kind === "jsval" && uncheckedOverloadHandleCall(L, decl.initializer) ? JSVAL : null))
2688
2823
  : null) ??
2824
+ // The runtime-world local rule, dyn side (383(d)'s dispatch stance —
2825
+ // the island-HANDLE local rule's mirror): a binding whose
2826
+ // INITIALIZER lowered checked-dynamic stays dyn even where the
2827
+ // checker spells 'any' (`const parsers = options.parsers ?? {}` over
2828
+ // an 'object'-typed bag — the read is a DOM keyed read). Marshaling
2829
+ // into the engine here would deep-copy data and sever aliasing; the
2830
+ // value's world is the honest dispatch, and every use site already
2831
+ // handles dyn (validated exits, routed engine ops for wrapped
2832
+ // island members).
2833
+ (L.dynamic && init.type.kind === "dyn" && jsvalFlavoredType(L.mapTypeOf(L.typeOf(decl.name)) ?? DYN) ? DYN : null) ??
2689
2834
  (bindingTainted ? null : L.mapTypeOf(L.typeOf(decl.name))) ??
2690
2835
  (init.type.kind === "dyn" ? DYN : null);
2691
2836
  // A JS `let x = {}`: TS's empty-object-literal type admits ANY later
@@ -3437,6 +3582,37 @@ export function lowerExprStatement(L, expr) {
3437
3582
  const stmtNode = expr.parent;
3438
3583
  while (ts.isParenthesizedExpression(expr))
3439
3584
  expr = expr.expression;
3585
+ // Assignment statements over the no-storage binding families:
3586
+ // - `f1 = f2` where the RHS roots at an ambient-undefined name:
3587
+ // Node evaluates the RHS first and dies on the root's
3588
+ // ReferenceError before any binding is touched — the statement IS
3589
+ // that throw;
3590
+ // - `a = b` between NULLISH generic bindings: the value is known
3591
+ // and the target has no storage — nothing happens;
3592
+ // - a write to a DEAD binding (registration proved every write's
3593
+ // RHS side-effect-free): Node builds the value and drops it —
3594
+ // nothing happens.
3595
+ if (ts.isBinaryExpression(expr) &&
3596
+ expr.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
3597
+ ts.isIdentifier(expr.left)) {
3598
+ const rhsRoot = ambientUndefVarRootOf(L, expr.right);
3599
+ if (rhsRoot !== null) {
3600
+ return {
3601
+ kind: "exprStmt",
3602
+ expr: nsUndefRead(L, rhsRoot.text, expr.right, F64),
3603
+ loc: locOf(expr),
3604
+ };
3605
+ }
3606
+ const targetSym = L.resolveValueSymbol(expr.left);
3607
+ if (targetSym !== null) {
3608
+ if (nullishGenericBindingUnitOf(L, targetSym) !== null && nullishExprUnitOf(L, expr.right) !== null) {
3609
+ return { kind: "block", body: [], loc: locOf(expr) };
3610
+ }
3611
+ if (L.deadBindings.has(targetSym)) {
3612
+ return { kind: "block", body: [], loc: locOf(expr) };
3613
+ }
3614
+ }
3615
+ }
3440
3616
  // A bare module-namespace binding in STATEMENT position (`ns;` for
3441
3617
  // `import * as ns from ...` — the corpus's canonical "the import
3442
3618
  // linked" assertion): Node evaluates an initialized binding (namespace
@@ -3594,8 +3770,25 @@ export function lowerExprStatement(L, expr) {
3594
3770
  // o.x = v on an island receiver: engine property write; the
3595
3771
  // value marshals in (a static RHS crosses by value/copy).
3596
3772
  const obj = L.lowerExpr(expr.left.expression);
3597
- const value = L.jsvalIn(L.lowerExpr(expr.right), expr.right);
3598
3773
  const loc = locOf(expr);
3774
+ // Dispatch follows the RUNTIME world (383(d)): a checker-'any'
3775
+ // receiver whose value LOWERED checked-dynamic (`bag.nested.x
3776
+ // = v` behind an 'object'-typed bag) takes the DOM keyed
3777
+ // write — the routed JSVAL arm lands it on the real engine
3778
+ // object.
3779
+ if (obj.type.kind === "dyn") {
3780
+ const key = { kind: "strLit", value: expr.left.name.text, type: STRING, loc: locOf(expr.left.name) };
3781
+ const value = L.coerceToExpected(L.lowerExpr(expr.right), DYN);
3782
+ if (value.type.kind !== "dyn") {
3783
+ L.unsupported("SC1100", expr.right, `assigning '${L.fmt(value.type)}' values through 'unknown' receivers`);
3784
+ }
3785
+ return {
3786
+ kind: "exprStmt",
3787
+ expr: { kind: "libCall", fn: "dyn.keySet", args: [obj, key, value], type: VOID, loc },
3788
+ loc,
3789
+ };
3790
+ }
3791
+ const value = L.jsvalIn(L.lowerExpr(expr.right), expr.right);
3599
3792
  return {
3600
3793
  kind: "exprStmt",
3601
3794
  expr: { kind: "jsOp", op: "setProp", name: expr.left.name.text, args: [obj, value], type: VOID, loc },
@@ -5111,6 +5304,15 @@ export function lowerForOf(L, stmt) {
5111
5304
  if (recv?.kind === "searchParams") {
5112
5305
  return lowerForOfSearchParams(L, stmt, L.lowerExpr(src.expression.expression), proj);
5113
5306
  }
5307
+ // ARRAY keys()/entries() projections: the live index walk yielding
5308
+ // the index or the [index, element] pair (lower-containers).
5309
+ // `values` falls through to the receiver unwrap below.
5310
+ if (recv?.kind === "array" && (proj === "keys" || proj === "entries")) {
5311
+ const container = L.lowerExpr(src.expression.expression);
5312
+ if (container.type.kind === "array") {
5313
+ return lowerForOfArrayIter(L, stmt, container, proj);
5314
+ }
5315
+ }
5114
5316
  }
5115
5317
  }
5116
5318
  // `for (const m of s.matchAll(re))` — direct call or a stored-const
@@ -5274,6 +5476,13 @@ export function lowerForOf(L, stmt) {
5274
5476
  if (iterable.type.kind === "jsval") {
5275
5477
  return lowerForOfIsland(L, stmt, iterable, labels);
5276
5478
  }
5479
+ // A CHECKED-DYNAMIC iterable (`unknown[]` and the collapsed
5480
+ // `(string | object)[]` are DOM values now, so the TYPE maps —
5481
+ // badType would tell a stale component story): the OPERATION is
5482
+ // the gap — name it, with the validated-cast escape hatch.
5483
+ if (iterable.type.kind === "dyn") {
5484
+ L.unsupported("SC1090", stmt.expression, "for-of over checked-dynamic ('unknown') values (validate into a typed array first — `u as string[]` — or index with a counted loop: length and element reads compile)");
5485
+ }
5277
5486
  if (iterable.type.kind !== "array") {
5278
5487
  L.badType(stmt.expression, L.typeOf(stmt.expression));
5279
5488
  }