@scriptc/compiler 0.0.36 → 0.1.1

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 (113) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +7 -5
  2. package/ambient/scriptc-overrides.d.ts +0 -7
  3. package/ambient/scriptc.d.ts +1 -1
  4. package/dist/backend/c/exprs.js +69 -45
  5. package/dist/backend/c/exprs.js.map +1 -1
  6. package/dist/backend/c/may-throw.js +3 -0
  7. package/dist/backend/c/may-throw.js.map +1 -1
  8. package/dist/backend/c/stmts.js +18 -0
  9. package/dist/backend/c/stmts.js.map +1 -1
  10. package/dist/backend/c/walkers.js +5 -7
  11. package/dist/backend/c/walkers.js.map +1 -1
  12. package/dist/backend/external-c.d.ts +33 -0
  13. package/dist/backend/external-c.js +51 -0
  14. package/dist/backend/external-c.js.map +1 -0
  15. package/dist/backend/link-plan.d.ts +37 -0
  16. package/dist/backend/link-plan.js +26 -0
  17. package/dist/backend/link-plan.js.map +1 -0
  18. package/dist/backend/linker.d.ts +22 -0
  19. package/dist/backend/linker.js +236 -0
  20. package/dist/backend/linker.js.map +1 -0
  21. package/dist/backend/llvm/dyn.js +7 -0
  22. package/dist/backend/llvm/dyn.js.map +1 -1
  23. package/dist/backend/llvm/emitter.js +34 -7
  24. package/dist/backend/llvm/emitter.js.map +1 -1
  25. package/dist/backend/llvm/expr-containers.js +68 -54
  26. package/dist/backend/llvm/expr-containers.js.map +1 -1
  27. package/dist/backend/llvm/expr-context.d.ts +1 -1
  28. package/dist/backend/llvm/expr-dispatch.js +2 -0
  29. package/dist/backend/llvm/expr-dispatch.js.map +1 -1
  30. package/dist/backend/llvm/expr-primitives.d.ts +1 -1
  31. package/dist/backend/llvm/expr-primitives.js +20 -0
  32. package/dist/backend/llvm/expr-primitives.js.map +1 -1
  33. package/dist/backend/llvm/expr-stream-bridges.js +25 -1
  34. package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
  35. package/dist/backend/llvm/walkers.js +31 -4
  36. package/dist/backend/llvm/walkers.js.map +1 -1
  37. package/dist/backend/native-codegen.d.ts +8 -1
  38. package/dist/backend/native-codegen.js +39 -25
  39. package/dist/backend/native-codegen.js.map +1 -1
  40. package/dist/backend/native-codegen.test.js +42 -4
  41. package/dist/backend/native-codegen.test.js.map +1 -1
  42. package/dist/backend/native-link-info.d.ts +1 -1
  43. package/dist/backend/native-link-info.js +42 -18
  44. package/dist/backend/native-link-info.js.map +1 -1
  45. package/dist/backend/native-toolchain.test.js +5 -1
  46. package/dist/backend/native-toolchain.test.js.map +1 -1
  47. package/dist/backend/runtime-pack-targets.test.d.ts +1 -0
  48. package/dist/backend/runtime-pack-targets.test.js +54 -0
  49. package/dist/backend/runtime-pack-targets.test.js.map +1 -0
  50. package/dist/backend/runtime-pack.d.ts +5 -30
  51. package/dist/backend/runtime-pack.js +46 -141
  52. package/dist/backend/runtime-pack.js.map +1 -1
  53. package/dist/backend/runtime-pack.test.js +49 -14
  54. package/dist/backend/runtime-pack.test.js.map +1 -1
  55. package/dist/backend/targets.d.ts +63 -17
  56. package/dist/backend/targets.js +251 -35
  57. package/dist/backend/targets.js.map +1 -1
  58. package/dist/backend/targets.test.js +27 -5
  59. package/dist/backend/targets.test.js.map +1 -1
  60. package/dist/coverage/surface-manifest.js +10 -1
  61. package/dist/coverage/surface-manifest.js.map +1 -1
  62. package/dist/frontend/lowering/array-values.d.ts +8 -0
  63. package/dist/frontend/lowering/array-values.js +60 -0
  64. package/dist/frontend/lowering/array-values.js.map +1 -0
  65. package/dist/frontend/lowering/lower-array-sort.d.ts +22 -0
  66. package/dist/frontend/lowering/lower-array-sort.js +619 -0
  67. package/dist/frontend/lowering/lower-array-sort.js.map +1 -0
  68. package/dist/frontend/lowering/lower-builtins.js +164 -4
  69. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  70. package/dist/frontend/lowering/lower-calls.d.ts +3 -4
  71. package/dist/frontend/lowering/lower-calls.js +260 -27
  72. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  73. package/dist/frontend/lowering/lower-containers.js +728 -655
  74. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  75. package/dist/frontend/lowering/lower-exprs.d.ts +4 -0
  76. package/dist/frontend/lowering/lower-exprs.js +769 -101
  77. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  78. package/dist/frontend/lowering/lower-inspect.js +47 -33
  79. package/dist/frontend/lowering/lower-inspect.js.map +1 -1
  80. package/dist/frontend/lowering/lower-island.d.ts +5 -4
  81. package/dist/frontend/lowering/lower-island.js +10 -5
  82. package/dist/frontend/lowering/lower-island.js.map +1 -1
  83. package/dist/frontend/lowering/lower-server.js +10 -0
  84. package/dist/frontend/lowering/lower-server.js.map +1 -1
  85. package/dist/frontend/lowering/lower-stmts.js +302 -71
  86. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  87. package/dist/frontend/lowering/lowerer.d.ts +74 -2
  88. package/dist/frontend/lowering/lowerer.js +1087 -10
  89. package/dist/frontend/lowering/lowerer.js.map +1 -1
  90. package/dist/frontend/lowering/surfaces.d.ts +10 -2
  91. package/dist/frontend/lowering/surfaces.js +15 -3
  92. package/dist/frontend/lowering/surfaces.js.map +1 -1
  93. package/dist/index.d.ts +3 -3
  94. package/dist/index.js +45 -23
  95. package/dist/index.js.map +1 -1
  96. package/dist/ir/ir.d.ts +65 -11
  97. package/dist/ir/ir.js +25 -25
  98. package/dist/ir/ir.js.map +1 -1
  99. package/dist/ir/serialize.d.ts +1 -1
  100. package/dist/ir/serialize.js +1 -1
  101. package/dist/ir/validate.js +84 -37
  102. package/dist/ir/validate.js.map +1 -1
  103. package/dist/library/fence-eval.js +3 -1
  104. package/dist/library/fence-eval.js.map +1 -1
  105. package/dist/library/int-infer.js +12 -1
  106. package/dist/library/int-infer.js.map +1 -1
  107. package/dist/library/int-infer.test.js +4 -4
  108. package/dist/library/library-cache.test.js +5 -5
  109. package/dist/startup-cache.d.ts +9 -1
  110. package/dist/startup-cache.js +16 -1
  111. package/dist/startup-cache.js.map +1 -1
  112. package/package.json +17 -4
  113. package/surface-manifest.json +5 -5
@@ -4,6 +4,7 @@ import { InternalCompilerError } from "../../errors.js";
4
4
  * flow (if/while/for/for-of/do, switch, try/catch, jumps with the
5
5
  * finally-crossing fence), and blocked-binding poisoning. */
6
6
  import * as ts from "../ts7/adapter.js";
7
+ import { arrayValueRead, arrayValueStore, arrayValueType } from "./array-values.js";
7
8
  import { lowerForOfGenerator, lowerYieldStarStatement } from "./lower-generators.js";
8
9
  import { BOOL, BYTES_U8, CAUGHT, DYN, F64, JSVAL, STRING, UNDEFINED_T, VOID, arrayOf, isUnitType, shapeHasAccessorSlots, typeEquals } from "../../ir/ir.js";
9
10
  import { PoisonError, boundIdentifiersOf, dynFallbackType, dynUndefinedExpr, importCallHandleType, neverTaintedJsType, stmtUsesIsland, uncheckedOverloadHandleCall } from "./lowerer.js";
@@ -13,7 +14,7 @@ import { COMPOUND_ASSIGN_OPS, STR_METHODS, UNSUPPORTED_STMT, isStdlibMember, sid
13
14
  import { isProvenanceSourceFile } from "../provenance-registry.js";
14
15
  import { ambientUndefVarRootOf, lowerImportEquals, nsUndefRead, nsWritableTarget, trapDeclRootOf } from "./lower-namespaces.js";
15
16
  import { expandoWritableTarget, lowerExpandoAssignStmt } from "./lower-expando.js";
16
- import { lowerForOfArrayIter, lowerForOfMap, lowerForOfSearchParams, lowerForOfSet, objectIterOverIndexShape, strCharsCall } from "./lower-containers.js";
17
+ import { lowerForOfArrayIter, lowerForOfMap, lowerForOfSearchParams, lowerForOfSet, lowerSafeIndexRead, objectIterOverIndexShape, strCharsCall } from "./lower-containers.js";
17
18
  import { bindingContextualGenericFnNodeOf, bindingGenericFnAliasInfoOf, bindingGenericFnInfoOf, bindingGenericFnNodeOf, deadUnmappableBinding, implicitLocalFnInfoOf, implicitLocalFnNodeOf, nullishExprUnitOf, nullishGenericBindingUnitOf, recordKeysArrayCall } from "./lower-calls.js";
18
19
  import { isMixinFnBinding, mixinResultBindingClassOf } from "./lower-mixins.js";
19
20
  import { genericIfaceBindingKeepsClass } from "./lower-classes.js";
@@ -21,13 +22,13 @@ import { lowerStreamUnderscoreAssign, streamClassAliasDecl, streamSidesOf } from
21
22
  import { lowerHttpResPropertyAssignment, lowerHttpServerTimeoutAssignment, lowerServerCloseOverrideAssignment } from "./lower-server.js";
22
23
  import { builtinMemberRequireDecl, builtinNamespaceDestructureModuleOf, createRequireBindingDecl, createRequireCalleeFileOf, createRequireNamespaceDecl, textCodecBindingDecl } from "./lower-builtins.js";
23
24
  import { lowerEnumDeclaration } from "./lower-enums.js";
24
- import { abstractPropertyDeclOf, aliasTypeofNarrows, isMatchSliceType, lowerAbsenceProbe, lowerGroupsProjection, matchResultNamedGroupsOf, probeLower, pureReemittable, runtimeOptionalTrueIds, symbolFieldInfo, withRuntimeOptionalNarrowed } from "./lower-exprs.js";
25
+ import { abstractPropertyDeclOf, aliasTypeofNarrows, isMatchSliceType, lowerAbsenceProbe, lowerGroupsProjection, lowerOptionalNumber, matchResultNamedGroupsOf, probeLower, pureReemittable, runtimeOptionalTrueIds, symbolFieldInfo, withRuntimeOptionalNarrowed } from "./lower-exprs.js";
25
26
  import { checkerPanicDiag, isCheckerPanic, requiresDynamicDiag } from "../../diagnostics/diagnostic.js";
26
27
  import { isParseArgsDynTypeName, isUnitOnlyTsType, unitOnlyUnion } from "../type-mapper.js";
27
28
  import { canonicalBuiltinModule } from "../builtin-modules.js";
28
29
  import { isRelativeSpecifier } from "../workspace-registry.js";
29
30
  import { probeNodeRequireRefusal } from "../npm.js";
30
- import { varRef } from "../../ir/build.js";
31
+ import { numLit, varRef } from "../../ir/build.js";
31
32
  /** `const X = /* @__PURE__ *\/ makeX()` at a module's top level: every
32
33
  * declarator initialized by a call (or `new`) carrying the bundler PURE
33
34
  * annotation in its leading trivia — the shape --provenance-sources may
@@ -1264,6 +1265,11 @@ export function lowerDestructuringDecl(lowerer, decl, isLet) {
1264
1265
  /** One pattern level: emits a declaration (or global assignment) per
1265
1266
  * bound name, recursing through hidden temps for nested patterns. */
1266
1267
  export function lowerBindingPattern(lowerer, pattern, srcRef, srcType, isLet, out, dynSpell, allowDynObject = false) {
1268
+ const present = patternSourceValue(lowerer, srcRef(), ts.isArrayBindingPattern(pattern), out, locOf(pattern), dynSpell);
1269
+ if (!typeEquals(present.type, srcType)) {
1270
+ lowerer.lowerBindingPattern(pattern, () => present, present.type, isLet, out, dynSpell, allowDynObject);
1271
+ return;
1272
+ }
1267
1273
  if (ts.isArrayBindingPattern(pattern)) {
1268
1274
  lowerer.fenceStaticHeadersIteration(pattern);
1269
1275
  // Tuple sources: each position is a field read of the tuple's record
@@ -1500,29 +1506,22 @@ export function lowerBindingPattern(lowerer, pattern, srcRef, srcType, isLet, ou
1500
1506
  if (ts.isOmittedExpression(el) || el.name === undefined)
1501
1507
  return; // hole: position skipped, like JS over an array
1502
1508
  const loc = locOf(el);
1503
- // `[current, ...rest]` over an ARRAY source: the rest binds a
1504
- // fresh tail copy — `arr.slice(i)`, exactly the JS surplus packing
1505
- // (tsc pins rest to the last position; an empty tail is `[]`) —
1506
- // and a NESTED rest pattern (`[...[a, b]]`) destructures the copy.
1509
+ // Array rest drains values from the iterator, so holes become
1510
+ // present undefined in the fresh tail. Nested rest patterns then
1511
+ // destructure that materialized copy.
1507
1512
  if (el.dotDotDotToken) {
1508
1513
  if (el.initializer) {
1509
1514
  lowerer.unsupported("SC1031", el, "defaults on rest elements"); // tsc rejects; defensive
1510
1515
  }
1511
- const value = {
1512
- kind: "arrIntrinsic",
1513
- method: "slice",
1514
- receiver: srcRef(),
1515
- args: [{ kind: "numLit", value: i, type: F64, loc }],
1516
- type: srcType,
1517
- loc,
1518
- };
1516
+ const value = arrayRestValue(srcRef(), i, srcType, loc);
1519
1517
  lowerer.bindPatternTarget(el.name, value, isLet, out);
1520
1518
  return;
1521
1519
  }
1522
- let value = {
1520
+ const position = { kind: "numLit", value: i, type: F64, loc };
1521
+ let value = lowerSafeIndexRead(lowerer, srcRef(), position, loc) ?? {
1523
1522
  kind: "arrayGet",
1524
1523
  arr: srcRef(),
1525
- index: { kind: "numLit", value: i, type: F64, loc },
1524
+ index: position,
1526
1525
  type: srcType.elem,
1527
1526
  loc,
1528
1527
  };
@@ -1842,6 +1841,33 @@ export function lowerBindingPattern(lowerer, pattern, srcRef, srcType, isLet, ou
1842
1841
  lowerer.bindPatternTarget(el.name, value, isLet, out);
1843
1842
  }
1844
1843
  }
1844
+ /** Destructuring a missing array element throws before reading members.
1845
+ * Only unwrap a union after testing every nullish arm; this also keeps
1846
+ * dense record/tuple callbacks on their existing payload representation. */
1847
+ function patternSourceValue(lowerer, value, arrayPattern, out, loc, spelling) {
1848
+ if (value.type.kind !== "union")
1849
+ return value;
1850
+ const unionId = value.type.unionId;
1851
+ const arms = lowerer.unions.get(unionId)?.arms;
1852
+ const present = arms?.filter((arm) => !isUnitType(arm));
1853
+ if (!arms || present?.length !== 1 || arms.length === 1)
1854
+ return value;
1855
+ for (const [tag, arm] of arms.entries()) {
1856
+ if (!isUnitType(arm))
1857
+ continue;
1858
+ const unit = arm.kind === "undefinedT" ? "undefined" : "null";
1859
+ const message = arrayPattern
1860
+ ? spelling ?? `${unit} is not iterable (cannot read property Symbol(Symbol.iterator))`
1861
+ : `Cannot destructure ${unit}`;
1862
+ out.push({
1863
+ kind: "if",
1864
+ cond: { kind: "unionIsTag", unionId, tag, value, negated: false, type: BOOL, loc },
1865
+ then: [{ kind: "throw", value: { kind: "libCall", fn: "error.new", args: [{ kind: "strLit", value: message, type: STRING, loc }], type: { kind: "object", className: "%TypeError" }, loc }, loc }],
1866
+ else_: null, loc,
1867
+ });
1868
+ }
1869
+ return { kind: "unionNarrow", unionId, tag: lowerer.armTag(unionId, present[0]), value, type: present[0], loc };
1870
+ }
1845
1871
  /** The type a pattern element binds at — the checker's type of the bound
1846
1872
  * identifier, or of the nested pattern itself (its implied type). Null
1847
1873
  * when the type has no static mapping (the caller fences). */
@@ -2138,13 +2164,9 @@ function undefArmDefault(lowerer, blame, init, value, bodyT) {
2138
2164
  const dflt = lowerer.lowerExprExpecting(init, bodyT);
2139
2165
  return { kind: "ternary", cond: isUndef, then: dflt, else_: present, type: bodyT, loc };
2140
2166
  }
2141
- /** `[a = dflt]` over an ARRAY source: the default fires when the position
2142
- * is past the end OR holds undefined (JS's one rule — both spell the
2143
- * read as undefined there), so the defaulted read carries a bounds test
2144
- * where the plain read would trap (the array divergence's carve-out).
2145
- * Elements with an undefined arm land in a hidden temp first (in-bounds
2146
- * undefined and past-the-end collapse to the same arm), then the
2147
- * ordinary undefined-arm default applies. */
2167
+ /** An array binding default tests the value produced by the read. Holes,
2168
+ * out-of-range positions, and explicit undefined share the same arm.
2169
+ * Typed-array reads retain the separate bounds check below. */
2148
2170
  function arrayPositionDefault(lowerer, el, read, srcRef, index, elemT, out) {
2149
2171
  const name = el.name; // callers skip nameless elisions
2150
2172
  const bodyT = patternBindingType(lowerer, name);
@@ -2157,6 +2179,11 @@ function arrayPositionDefault(lowerer, el, read, srcRef, index, elemT, out) {
2157
2179
  * arrives from the existing binding instead of the checker. */
2158
2180
  function arrayPositionDefaultValue(lowerer, blame, init, read, srcRef, index, elemT, bodyT, out) {
2159
2181
  const loc = read.loc;
2182
+ if (read.type.kind === "union" && lowerer.armTag(read.type.unionId, UNDEFINED_T) >= 0) {
2183
+ const tmp = lowerer.declareHiddenLocal("%delem", read.type);
2184
+ out.push({ kind: "varDecl", localId: tmp.id, init: read, loc });
2185
+ return undefArmDefault(lowerer, blame, init, varRef(tmp.id, read.type, loc), bodyT);
2186
+ }
2160
2187
  // The unit-only element (a declared-empty-tuple source riding the
2161
2188
  // array representation): every read is undefined — the default IS the
2162
2189
  // binding.
@@ -2213,6 +2240,15 @@ function arrayPositionDefaultValue(lowerer, blame, init, read, srcRef, index, el
2213
2240
  loc,
2214
2241
  };
2215
2242
  }
2243
+ /** Array rest copies values and materializes holes. Removing the prefix
2244
+ * through the copying splice primitive preserves the payload ABI. */
2245
+ function arrayRestValue(receiver, from, type, loc) {
2246
+ return {
2247
+ kind: "arrIntrinsic", method: "toSpliced", receiver,
2248
+ args: [numLit(0, loc), numLit(from, loc), { kind: "arrayLit", elems: [], type, loc }],
2249
+ type, loc,
2250
+ };
2251
+ }
2216
2252
  /** The fresh tail for `[head, ...rest]` over a TUPLE source, packed under
2217
2253
  * the checker's own rest type: an ARRAY of the remaining positions (each
2218
2254
  * read coercing into the element type) or a tuple RECORD when the
@@ -2790,12 +2826,31 @@ export function bindPatternTarget(lowerer, name, value, isLet, out, allowDynObje
2790
2826
  const mapped = lowerer.mapTypeOf(lowerer.typeOf(name));
2791
2827
  return mapped === null || (lowerer.dynamic && jsvalFlavoredType(mapped));
2792
2828
  })();
2793
- const type = dynStays ? DYN : lowerer.irTypeOf(name);
2829
+ let type = dynStays ? DYN : lowerer.runtimeOptionalBindingType(name, lowerer.irTypeOf(name)) ?? lowerer.irTypeOf(name);
2794
2830
  if (type.kind === "void")
2795
2831
  lowerer.badType(name, lowerer.typeOf(name));
2832
+ // Array destructuring positions are ordinary indexed reads. When the
2833
+ // checker was built without noUncheckedIndexedAccess it still spells
2834
+ // the binding as the bare element type, but the runtime value carries
2835
+ // the same `T | undefined` possibility as a direct array read. Adopt
2836
+ // that union for an unannotated pattern binding so undefined survives
2837
+ // through later locals, templates, operators, and narrowing instead
2838
+ // of being extracted with a checked arm read.
2839
+ let runtimeOptional = false;
2840
+ if (!dynStays && value.type.kind === "union" &&
2841
+ lowerer.armTag(value.type.unionId, UNDEFINED_T) >= 0 &&
2842
+ typeEquals(lowerer.stripUndefinedArm(value.type), type)) {
2843
+ type = value.type;
2844
+ runtimeOptional = true;
2845
+ }
2796
2846
  const coerced = lowerer.coerceInto(name, value, type);
2797
2847
  const local = lowerer.declareLocal(name, name.text, type, isLet);
2798
2848
  out.push({ kind: "varDecl", localId: local.id, init: coerced, loc });
2849
+ if (runtimeOptional) {
2850
+ const runtimeOptionalRoot = lowerer.runtimeOptionalRootOf(local);
2851
+ lowerer.runtimeOptionalLocals.add(runtimeOptionalRoot);
2852
+ lowerer.runtimeOptionalStorageLocals.add(runtimeOptionalRoot);
2853
+ }
2799
2854
  return;
2800
2855
  }
2801
2856
  const tmp = lowerer.declareHiddenLocal("%destr", value.type);
@@ -3026,8 +3081,23 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3026
3081
  // (statics are zero/NULL-initialized; uninitialized globals need no
3027
3082
  // statement at all).
3028
3083
  const declSymbol = lowerer.checker.getSymbolAtLocation(decl.name);
3029
- const g = declSymbol ? lowerer.globalsBySymbol.get(declSymbol) : undefined;
3084
+ const g = ts.isIdentifier(decl.name)
3085
+ ? lowerer.globalOf(decl.name)
3086
+ : (declSymbol ? lowerer.globalsBySymbol.get(declSymbol) : undefined);
3030
3087
  if (g) {
3088
+ let promotedGlobal = lowerer.runtimeOptionalBindingType(decl.name, g.type);
3089
+ if (g.type.kind === "func" && decl.initializer !== undefined && ts.isIdentifier(decl.initializer)) {
3090
+ const sig = lowerer.fnSigOf(decl.initializer);
3091
+ if (sig) {
3092
+ promotedGlobal = {
3093
+ ...g.type,
3094
+ params: sig.params.filter((p) => p.mode !== "dynRest").map((p) => p.type),
3095
+ ret: sig.returnType,
3096
+ };
3097
+ }
3098
+ }
3099
+ if (!typeEquals(promotedGlobal, g.type))
3100
+ g.type = promotedGlobal;
3031
3101
  if (!decl.initializer) {
3032
3102
  // `let x: string | undefined;` at file scope: READABLE before any
3033
3103
  // assignment — JS gives undefined, so the slot must hold the
@@ -3048,7 +3118,16 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3048
3118
  return { kind: "assign", localId: g.id, value: wrapped, loc: locOf(decl) };
3049
3119
  return null;
3050
3120
  }
3051
- const init = lowerer.lowerExprExpecting(decl.initializer, g.type);
3121
+ const raw = lowerVariableInitializer(lowerer, decl.initializer);
3122
+ const arithmetic = raw.type.kind === "union"
3123
+ ? lowerer.unions.get(raw.type.unionId)?.arms
3124
+ : undefined;
3125
+ if (g.type.kind === "string" && arithmetic?.length === 2 &&
3126
+ arithmetic.some((a) => a.kind === "f64") && arithmetic.some((a) => a.kind === "string")) {
3127
+ g.type = raw.type;
3128
+ lowerer.runtimeOptionalArithmeticGlobals.add(g);
3129
+ }
3130
+ const init = lowerer.coerceInto(decl.initializer, raw, g.type);
3052
3131
  return { kind: "assign", localId: g.id, value: init, loc: locOf(decl) };
3053
3132
  }
3054
3133
  // A forward-captured const pre-declared as a TDZ box (an earlier
@@ -3108,7 +3187,7 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3108
3187
  try {
3109
3188
  init = immediatelyGuardedAbsenceProbe(lowerer, decl)
3110
3189
  ? (lowerAbsenceProbe(lowerer, decl.initializer) ?? lowerer.lowerExpr(decl.initializer))
3111
- : lowerer.lowerExpr(decl.initializer);
3190
+ : lowerVariableInitializer(lowerer, decl.initializer);
3112
3191
  }
3113
3192
  catch (e) {
3114
3193
  if (e instanceof PoisonError) {
@@ -3194,12 +3273,30 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3194
3273
  // and TypeScript's fresh-array probe idioms; an explicit annotation keeps
3195
3274
  // the caller's checked slot contract.
3196
3275
  let runtimeOptional = false;
3197
- if (!decl.type && type !== null && init.type.kind === "union" &&
3276
+ if (type !== null && init.type.kind === "union" &&
3277
+ lowerer.armTag(init.type.unionId, UNDEFINED_T) >= 0) {
3278
+ const present = lowerer.stripUndefinedArm(init.type);
3279
+ if (!typeEquals(present, type) && lowerer.widthLiftPlan(present, type) !== null) {
3280
+ const widened = type.kind === "union" ? lowerer.withUndefinedArmOf(type) : lowerer.withUndefinedArm(type);
3281
+ if (widened && lowerer.widthLiftPlan(init.type, widened) !== null) {
3282
+ type = widened;
3283
+ runtimeOptional = true;
3284
+ }
3285
+ }
3286
+ }
3287
+ if (type !== null && init.type.kind === "union" &&
3198
3288
  lowerer.armTag(init.type.unionId, UNDEFINED_T) >= 0 &&
3199
3289
  typeEquals(lowerer.stripUndefinedArm(init.type), type)) {
3200
3290
  type = init.type;
3201
3291
  runtimeOptional = true;
3202
3292
  }
3293
+ if (type?.kind === "array" && init.type.kind === "array" &&
3294
+ init.type.elem.kind === "union" &&
3295
+ lowerer.armTag(init.type.elem.unionId, UNDEFINED_T) >= 0 &&
3296
+ typeEquals(lowerer.stripUndefinedArm(init.type.elem), type.elem)) {
3297
+ type = init.type;
3298
+ runtimeOptional = true;
3299
+ }
3203
3300
  // A JS binding whose checker type spells a FUNCTION but whose VALUE is
3204
3301
  // already checked-dynamic (`const cb = mustCall(fn)` — the helper's
3205
3302
  // return rides its own box): keep the box. Extracting into the spelled
@@ -3241,6 +3338,20 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3241
3338
  }
3242
3339
  if (!type)
3243
3340
  lowerer.badType(decl.name, lowerer.typeOf(decl.name));
3341
+ const arithmeticType = decl.initializer ? lowerer.runtimeOptionalArithmeticTypes.get(decl.initializer) : undefined;
3342
+ const isStringArithmeticUnion = (t) => {
3343
+ if (t.kind !== "union")
3344
+ return false;
3345
+ const arms = lowerer.unions.get(t.unionId)?.arms;
3346
+ return !!arms && arms.length === 2 &&
3347
+ arms.some((a) => a.kind === "f64") && arms.some((a) => a.kind === "string");
3348
+ };
3349
+ const runtimeStringArithmetic = type?.kind === "string" &&
3350
+ ((arithmeticType && typeEquals(arithmeticType, init.type)) || isStringArithmeticUnion(init.type));
3351
+ if (runtimeStringArithmetic) {
3352
+ type = init.type;
3353
+ }
3354
+ type = lowerer.runtimeOptionalBindingType(decl.name, type);
3244
3355
  // `const x: void = undefined` / `let y: undefined = undefined`: the
3245
3356
  // unit-only union — the initializer's unit literal wraps into its arm
3246
3357
  // like any optional completion. Non-literal void initializers (a
@@ -3333,6 +3444,9 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3333
3444
  // subtyping (`const p: {a: number} = wider;`) is rejected, not coerced.
3334
3445
  init = lowerer.coerceInto(decl.initializer, init, type);
3335
3446
  const local = lowerer.declareLocal(decl.name, decl.name.text, type, isLet);
3447
+ if (runtimeStringArithmetic && isStringArithmeticUnion(init.type)) {
3448
+ lowerer.runtimeOptionalArithmeticLocals.add(lowerer.runtimeOptionalRootOf(local));
3449
+ }
3336
3450
  if (runtimeOptional) {
3337
3451
  const runtimeOptionalRoot = lowerer.runtimeOptionalRootOf(local);
3338
3452
  lowerer.runtimeOptionalLocals.add(runtimeOptionalRoot);
@@ -3340,6 +3454,40 @@ export function lowerVarDecl(lowerer, decl, isLet) {
3340
3454
  }
3341
3455
  return { kind: "varDecl", localId: local.id, init, loc: locOf(decl) };
3342
3456
  }
3457
+ function lowerVariableInitializer(lowerer, node) {
3458
+ let value = node;
3459
+ while (ts.isParenthesizedExpression(value) ||
3460
+ ts.isAsExpression(value) ||
3461
+ ts.isTypeAssertion(value) ||
3462
+ ts.isSatisfiesExpression(value)) {
3463
+ value = value.expression;
3464
+ }
3465
+ if (!ts.isObjectLiteralExpression(value))
3466
+ return lowerer.lowerExpr(node);
3467
+ const previous = new Map();
3468
+ for (const property of value.properties) {
3469
+ if (!ts.isPropertyAssignment(property) || !ts.isComputedPropertyName(property.name))
3470
+ continue;
3471
+ const keyNode = property.name.expression;
3472
+ const optional = lowerer.runtimeOptionalIdentifierValue(keyNode)?.value ??
3473
+ lowerAbsenceProbe(lowerer, keyNode);
3474
+ if (!optional || lowerer.runtimeOptionalWidening(optional.type, STRING) === null)
3475
+ continue;
3476
+ previous.set(keyNode, lowerer.chainRecvByNode.get(keyNode));
3477
+ lowerer.chainRecvByNode.set(keyNode, lowerer.ensureString(optional, keyNode));
3478
+ }
3479
+ try {
3480
+ return lowerer.lowerExpr(node);
3481
+ }
3482
+ finally {
3483
+ for (const [keyNode, prior] of previous) {
3484
+ if (prior)
3485
+ lowerer.chainRecvByNode.set(keyNode, prior);
3486
+ else
3487
+ lowerer.chainRecvByNode.delete(keyNode);
3488
+ }
3489
+ }
3490
+ }
3343
3491
  function immediatelyGuardedAbsenceProbe(lowerer, decl) {
3344
3492
  if (decl.type || !ts.isIdentifier(decl.name) || !decl.initializer)
3345
3493
  return false;
@@ -3432,8 +3580,8 @@ export function lowerSwitch(lowerer, stmt) {
3432
3580
  * exactly `disc === test`), because the backend switch compares plain
3433
3581
  * primitives only. The desugar is JS-exact for the shape it accepts and
3434
3582
  * fences everything it cannot reproduce:
3435
- * - the discriminant rides every test, so it must be a plain
3436
- * side-effect-free read (bind it to a const first otherwise);
3583
+ * - the discriminant evaluates exactly once; effectful values stabilize
3584
+ * in a hidden local that every generated equality test reuses;
3437
3585
  * - tests evaluate lazily in source order (the chain's short-circuit IS
3438
3586
  * the switch's test order — grouped empty cases `case a: case b:` OR
3439
3587
  * their tests, still in order);
@@ -3452,8 +3600,15 @@ function lowerUnionSwitch(lowerer, stmt, disc) {
3452
3600
  if (disc.type.kind !== "union")
3453
3601
  throw new InternalCompilerError("lowerer bug: non-union disc");
3454
3602
  const unionType = disc.type;
3455
- if (!pureReemittable(disc) || !lowerer.eqComparableUnion(unionType.unionId)) {
3456
- lowerer.unsupported("SC1090", stmt.expression, "switch on union-typed values that aren't plain reads (the tests re-read the discriminant — bind it to a const first, or switch on a discriminant field)");
3603
+ if (!lowerer.eqComparableUnion(unionType.unionId)) {
3604
+ lowerer.unsupported("SC1090", stmt.expression, "switch on a union whose arms do not have static equality");
3605
+ }
3606
+ const prefix = [];
3607
+ let stableDisc = disc;
3608
+ if (!pureReemittable(disc)) {
3609
+ const temp = lowerer.declareHiddenLocal("%switch", unionType);
3610
+ prefix.push({ kind: "varDecl", localId: temp.id, init: disc, loc });
3611
+ stableDisc = { kind: "varRef", localId: temp.id, type: unionType, loc };
3457
3612
  }
3458
3613
  const clauses = stmt.caseBlock.clauses;
3459
3614
  // An unlabeled break at a clause's END exits the switch — the chain's
@@ -3480,13 +3635,19 @@ function lowerUnionSwitch(lowerer, stmt, disc) {
3480
3635
  }
3481
3636
  }
3482
3637
  }
3638
+ const statementExits = (last) => {
3639
+ if (!last)
3640
+ return false;
3641
+ if ((ts.isBreakStatement(last) && !last.label) ||
3642
+ ts.isReturnStatement(last) ||
3643
+ ts.isThrowStatement(last) ||
3644
+ ts.isContinueStatement(last))
3645
+ return true;
3646
+ return ts.isBlock(last) && statementExits(last.statements[last.statements.length - 1]);
3647
+ };
3483
3648
  const exits = (clause) => {
3484
3649
  const last = clause.statements[clause.statements.length - 1];
3485
- return (!!last &&
3486
- ((ts.isBreakStatement(last) && !last.label) ||
3487
- ts.isReturnStatement(last) ||
3488
- ts.isThrowStatement(last) ||
3489
- ts.isContinueStatement(last)));
3650
+ return statementExits(last);
3490
3651
  };
3491
3652
  // The whole case-body sequence is ONE lexical scope, like the real
3492
3653
  // switch lowering.
@@ -3515,14 +3676,14 @@ function lowerUnionSwitch(lowerer, stmt, disc) {
3515
3676
  // never matches; coercing the literal into the union would hit
3516
3677
  // the stranded-arm trap and throw where JS just skips the case).
3517
3678
  const unitTest = test.kind === "unitLit"
3518
- ? lowerer.lowerUnitComparison(disc, test, false, locOf(clause.expression))
3679
+ ? lowerer.lowerUnitComparison(stableDisc, test, false, locOf(clause.expression))
3519
3680
  : null;
3520
3681
  pendingTests.push(unitTest ?? {
3521
3682
  kind: "unionEq",
3522
3683
  unionId: unionType.unionId,
3523
3684
  negated: false,
3524
3685
  sameValue: false,
3525
- left: disc,
3686
+ left: stableDisc,
3526
3687
  right: lowerer.coerceInto(clause.expression, test, unionType),
3527
3688
  type: BOOL,
3528
3689
  loc: locOf(clause.expression),
@@ -3563,7 +3724,7 @@ function lowerUnionSwitch(lowerer, stmt, disc) {
3563
3724
  }
3564
3725
  chain = [{ kind: "if", cond, then: g.body, else_: chain.length > 0 ? chain : null, loc }];
3565
3726
  }
3566
- return { kind: "block", body: chain, loc };
3727
+ return { kind: "block", body: [...prefix, ...chain], loc };
3567
3728
  }
3568
3729
  finally {
3569
3730
  lowerer.scopes.pop();
@@ -3650,7 +3811,10 @@ function lowerDeleteStatement(lowerer, expr) {
3650
3811
  return { kind: "strLit", value: target.name.text, type: STRING, loc: locOf(target.name) };
3651
3812
  }
3652
3813
  const keyNode = target.argumentExpression;
3653
- const key = lowerer.lowerExpr(keyNode);
3814
+ let key = lowerer.lowerExpr(keyNode);
3815
+ if (lowerer.runtimeOptionalWidening(key.type, STRING) !== null) {
3816
+ key = lowerer.ensureString(key, keyNode);
3817
+ }
3654
3818
  if (key.type.kind !== "string") {
3655
3819
  lowerer.unsupported("SC1090", keyNode, `'delete' with '${lowerer.fmt(key.type)}' keys (index-signature and env keys are strings)`);
3656
3820
  }
@@ -4135,6 +4299,21 @@ export function lowerExprStatement(lowerer, expr) {
4135
4299
  if (ts.isElementAccessExpression(expr.left))
4136
4300
  return lowerer.lowerElementWrite(expr);
4137
4301
  if (ts.isPropertyAccessExpression(expr.left)) {
4302
+ // Native arrays have a writable length property. The runtime grows
4303
+ // by creating holes and truncates with RC-aware removal; keep this
4304
+ // as a statement operation so invalid lengths can use the normal
4305
+ // pending-exception path.
4306
+ if (!expr.left.questionDotToken && expr.left.name.text === "length") {
4307
+ const arrayType = lowerer.mapTypeOf(lowerer.typeOf(expr.left.expression));
4308
+ if (arrayType?.kind === "array") {
4309
+ const arr = lowerer.lowerExpr(expr.left.expression);
4310
+ if (arr.type.kind !== "array") {
4311
+ lowerer.unsupported("SC1090", expr.left.expression, "writing the length of an array-typed value that remains an island handle");
4312
+ }
4313
+ const length = lowerer.lowerExprExpecting(expr.right, F64);
4314
+ return { kind: "arraySetLength", arr, length, loc: locOf(expr) };
4315
+ }
4316
+ }
4138
4317
  // `process.env.NAME = v` — setenv(3): later env reads and spawned
4139
4318
  // children observe the write, exactly Node. Values are strings
4140
4319
  // (Node stringifies everything; a non-string RHS fences instead
@@ -4571,6 +4750,7 @@ function lowerCompoundToTarget(lowerer, expr, compound, target) {
4571
4750
  const loc = locOf(expr);
4572
4751
  const read = { kind: "varRef", localId: target.id, type: target.type, loc: locOf(expr.left) };
4573
4752
  const rhs = lowerer.lowerExpr(expr.right);
4753
+ const numericRhs = lowerOptionalNumber(lowerer, rhs, loc);
4574
4754
  let value;
4575
4755
  if (target.type.kind === "jsval" || rhs.type.kind === "jsval") {
4576
4756
  const JS_COMPOUND = { "+": "add", "-": "sub", "*": "mul", "/": "div", "%": "mod", "**": "pow" };
@@ -4587,8 +4767,8 @@ function lowerCompoundToTarget(lowerer, expr, compound, target) {
4587
4767
  else if (compound === "+" && target.type.kind === "string") {
4588
4768
  value = { kind: "strConcat", left: read, right: lowerer.ensureString(rhs, expr.right), type: STRING, loc };
4589
4769
  }
4590
- else if (target.type.kind === "f64" && rhs.type.kind === "f64") {
4591
- value = { kind: "bin", op: compound, left: read, right: rhs, type: F64, loc };
4770
+ else if (target.type.kind === "f64" && numericRhs.type.kind === "f64") {
4771
+ value = { kind: "bin", op: compound, left: read, right: numericRhs, type: F64, loc };
4592
4772
  }
4593
4773
  else if ((target.type.kind === "dyn" || rhs.type.kind === "dyn") &&
4594
4774
  (target.type.kind === "dyn" || target.type.kind === "f64") &&
@@ -4843,6 +5023,16 @@ export function lowerDestructuringAssignParts(lowerer, target, rhs, loc) {
4843
5023
  * source-spelling TypeError machinery cannot apply), `blame` the node
4844
5024
  * fences point at. */
4845
5025
  function destructuringAssignInto(lowerer, target, init, rhs, blame, loc) {
5026
+ if (init.type.kind === "union") {
5027
+ const source = lowerer.declareHiddenLocal("%destrSource", init.type);
5028
+ const out = [{ kind: "varDecl", localId: source.id, init, loc }];
5029
+ const value = varRef(source.id, init.type, loc);
5030
+ const present = patternSourceValue(lowerer, value, ts.isArrayLiteralExpression(target), out, loc);
5031
+ if (!typeEquals(present.type, init.type)) {
5032
+ const sub = destructuringAssignInto(lowerer, target, present, rhs, blame, loc);
5033
+ return { stmts: [...out, ...sub.stmts], value };
5034
+ }
5035
+ }
4846
5036
  // IR-string sources in JS files split two ways the carrier type
4847
5037
  // cannot: a STDLIB GLOBAL (`({ subtle } = globalThis.crypto)`) — the
4848
5038
  // declaration path binds member identity tokens into FRESH bindings,
@@ -5316,14 +5506,10 @@ function lowerAssignTargetInto(lowerer, out, targetNode, blame, dflt, valueOf) {
5316
5506
  }
5317
5507
  const idx = lowerer.declareHiddenLocal("%dtIdx", F64);
5318
5508
  out.push({ kind: "varDecl", localId: idx.id, init: index, loc });
5319
- const value = lowerer.coerceInto(blame, valueOf(recvT.elem), recvT.elem);
5320
- out.push({
5321
- kind: "arraySet",
5322
- arr: { kind: "varRef", localId: recv.id, type: arr.type, loc },
5323
- index: { kind: "varRef", localId: idx.id, type: F64, loc },
5324
- value,
5325
- loc,
5326
- });
5509
+ const value = valueOf(recvT.elem);
5510
+ const stored = lowerer.declareHiddenLocal("%dtValue", value.type);
5511
+ out.push({ kind: "varDecl", localId: stored.id, init: value, loc });
5512
+ out.push(arrayValueStore(lowerer, varRef(recv.id, arr.type, loc), varRef(idx.id, F64, loc), varRef(stored.id, value.type, loc), recvT.elem, loc));
5327
5513
  return;
5328
5514
  }
5329
5515
  }
@@ -5499,11 +5685,9 @@ function lowerArrayAssignInto(lowerer, out, target, tmpRef, blame, loc) {
5499
5685
  loc: locOf(at),
5500
5686
  };
5501
5687
  }
5502
- // Plain arrays read by index — a pattern wider than the runtime
5503
- // array traps (the arrayGet discipline, divergence 4's policy; JS
5504
- // would answer undefined).
5688
+ // Assignment patterns consume the same values as binding patterns.
5505
5689
  const elemType = srcType.elem;
5506
- return { kind: "arrayGet", arr: tmpRef(), index: { kind: "numLit", value: i, type: F64, loc: locOf(at) }, type: elemType, loc: locOf(at) };
5690
+ return arrayValueRead(lowerer, tmpRef(), numLit(i, locOf(at)), elemType, locOf(at));
5507
5691
  };
5508
5692
  elements.forEach((el, i) => {
5509
5693
  if (ts.isOmittedExpression(el))
@@ -5556,7 +5740,7 @@ function lowerArrayAssignInto(lowerer, out, target, tmpRef, blame, loc) {
5556
5740
  }
5557
5741
  return isTuple
5558
5742
  ? tupleTailValue(lowerer, el, tmpRef, srcType, shape, i, targetT)
5559
- : { kind: "arrIntrinsic", method: "slice", receiver: tmpRef(), args: [{ kind: "numLit", value: i, type: F64, loc: locOf(el) }], type: srcType, loc: locOf(el) };
5743
+ : arrayRestValue(tmpRef(), i, srcType, locOf(el));
5560
5744
  }
5561
5745
  if (isTuple && defaultNode !== null && targetT !== null && !shape.fields.some((f) => f.name === String(i))) {
5562
5746
  // Past the tuple's end the read is always undefined — the default
@@ -5777,6 +5961,19 @@ export function lowerForOf(lowerer, stmt) {
5777
5961
  }
5778
5962
  iterSrc ??= stmt.expression;
5779
5963
  let iterable = lowerer.lowerExpr(iterSrc);
5964
+ // A nested loop receiver can be an OOB-safe array read from the outer
5965
+ // loop (`for (const row of grid) for (const cell of row)`). The checker
5966
+ // sees the inner value as an array, while its runtime slot is array |
5967
+ // undefined; validate the receiver before selecting the array iterator.
5968
+ const checkerIterable = lowerer.mapTypeOf(lowerer.typeOf(iterSrc));
5969
+ if (iterable.type.kind === "union" && lowerer.armTag(iterable.type.unionId, UNDEFINED_T) >= 0 && checkerIterable?.kind === "array") {
5970
+ const present = lowerer.stripUndefinedArm(iterable.type);
5971
+ const helper = present.kind === "array"
5972
+ ? lowerer.narrowedArmHelper(iterable.type.unionId, present, locOf(iterSrc))
5973
+ : null;
5974
+ if (helper)
5975
+ iterable = { kind: "call", callee: helper, args: [iterable], type: present, loc: locOf(iterSrc) };
5976
+ }
5780
5977
  if (iterable.type.kind === "bytes") {
5781
5978
  return lowerForOfBytes(lowerer, stmt, iterable, labels);
5782
5979
  }
@@ -5902,6 +6099,29 @@ export function lowerForOf(lowerer, stmt) {
5902
6099
  lowerer.badType(stmt.expression, lowerer.typeOf(stmt.expression));
5903
6100
  }
5904
6101
  }
6102
+ const elemValueT = arrayValueType(lowerer, iterable.type.elem);
6103
+ // Native arrays retain their payload ABI; the loop binds the value yielded
6104
+ // by Get, which is undefined for both holes and present undefined slots.
6105
+ const sourceT = iterable.type;
6106
+ const source = lowerer.declareHiddenLocal("%arrayIterator", sourceT);
6107
+ const cursor = lowerer.declareHiddenLocal("%arrayCursor", F64);
6108
+ cursor.mutable = true;
6109
+ const forValues = (localId, body) => ({
6110
+ kind: "block",
6111
+ body: [
6112
+ { kind: "varDecl", localId: source.id, init: iterable, loc: locOf(stmt) },
6113
+ {
6114
+ kind: "for",
6115
+ init: { kind: "varDecl", localId: cursor.id, init: numLit(0, locOf(stmt)), loc: locOf(stmt) },
6116
+ cond: { kind: "bin", op: "<", left: varRef(cursor.id, F64, locOf(stmt)), right: { kind: "arrIntrinsic", method: "length", receiver: varRef(source.id, sourceT, locOf(stmt)), args: [], type: F64, loc: locOf(stmt) }, type: BOOL, loc: locOf(stmt) },
6117
+ update: { kind: "assign", localId: cursor.id, value: { kind: "bin", op: "+", left: varRef(cursor.id, F64, locOf(stmt)), right: numLit(1, locOf(stmt)), type: F64, loc: locOf(stmt) }, loc: locOf(stmt) },
6118
+ body: [{ kind: "varDecl", localId, init: arrayValueRead(lowerer, varRef(source.id, sourceT, locOf(stmt)), varRef(cursor.id, F64, locOf(stmt)), sourceT.elem, locOf(stmt)), loc: locOf(stmt) }, ...body],
6119
+ ...(labels && { labels }),
6120
+ loc: locOf(stmt),
6121
+ },
6122
+ ],
6123
+ loc: locOf(stmt),
6124
+ });
5905
6125
  if (!ts.isVariableDeclarationList(stmt.initializer)) {
5906
6126
  // `for (x of xs)` over a PRE-DECLARED writable binding: JS assigns
5907
6127
  // the existing binding once per pass — one shared binding across
@@ -5914,18 +6134,18 @@ export function lowerForOf(lowerer, stmt) {
5914
6134
  while (ts.isParenthesizedExpression(target))
5915
6135
  target = target.expression;
5916
6136
  if (ts.isArrayLiteralExpression(target) || ts.isObjectLiteralExpression(target)) {
5917
- const elemType = iterable.type.elem;
6137
+ const elemType = elemValueT;
5918
6138
  const loc = locOf(target);
5919
6139
  const tmp = lowerer.declareHiddenLocal("%vof", elemType);
5920
6140
  const elemRef = { kind: "varRef", localId: tmp.id, type: elemType, loc };
5921
6141
  const assigns = lowerDestructuringAssign(lowerer, target, elemRef, target, loc);
5922
6142
  const body = lowerer.inCtl("loop", () => lowerer.lowerScopedBlock(stmt.statement), labels);
5923
- return { kind: "forOf", localId: tmp.id, iterable, body: [assigns, ...body], ...(labels && { labels }), loc: locOf(stmt) };
6143
+ return forValues(tmp.id, [assigns, ...body]);
5924
6144
  }
5925
6145
  if (ts.isIdentifier(target)) {
5926
6146
  const writable = lowerer.resolveWritable(target);
5927
6147
  if (writable) {
5928
- const elemType = iterable.type.elem;
6148
+ const elemType = elemValueT;
5929
6149
  const loc = locOf(target);
5930
6150
  const tmp = lowerer.declareHiddenLocal("%vof", elemType);
5931
6151
  const write = {
@@ -5935,7 +6155,7 @@ export function lowerForOf(lowerer, stmt) {
5935
6155
  loc,
5936
6156
  };
5937
6157
  const body = lowerer.inCtl("loop", () => lowerer.lowerScopedBlock(stmt.statement), labels);
5938
- return { kind: "forOf", localId: tmp.id, iterable, body: [write, ...body], ...(labels && { labels }), loc: locOf(stmt) };
6158
+ return forValues(tmp.id, [write, ...body]);
5939
6159
  }
5940
6160
  }
5941
6161
  lowerer.unsupported("SC1090", stmt.initializer, "for-of over a pre-declared variable (declare the loop variable in the loop: for (const x of ...))");
@@ -5955,13 +6175,13 @@ export function lowerForOf(lowerer, stmt) {
5955
6175
  if (ts.isArrayBindingPattern(decl.name) || ts.isObjectBindingPattern(decl.name)) {
5956
6176
  lowerer.scopes.push(new Map());
5957
6177
  try {
5958
- const elemType = iterable.type.elem;
6178
+ const elemType = elemValueT;
5959
6179
  const loc = locOf(decl.name);
5960
6180
  const tmp = lowerer.declareHiddenLocal("%destr", elemType);
5961
6181
  const binds = [];
5962
6182
  lowerer.lowerBindingPattern(decl.name, () => ({ kind: "varRef", localId: tmp.id, type: elemType, loc }), elemType, isLet, binds);
5963
6183
  const body = lowerer.inCtl("loop", () => lowerer.lowerScopedBlock(stmt.statement), labels);
5964
- return { kind: "forOf", localId: tmp.id, iterable, body: [...binds, ...body], ...(labels && { labels }), loc: locOf(stmt) };
6184
+ return forValues(tmp.id, [...binds, ...body]);
5965
6185
  }
5966
6186
  finally {
5967
6187
  lowerer.scopes.pop();
@@ -5977,7 +6197,7 @@ export function lowerForOf(lowerer, stmt) {
5977
6197
  // after the loop (both Node-exact for var).
5978
6198
  const varTarget = forOfVarTarget(lowerer, decl);
5979
6199
  if (varTarget) {
5980
- const elemType = iterable.type.elem;
6200
+ const elemType = elemValueT;
5981
6201
  const loc = locOf(decl.name);
5982
6202
  const tmp = lowerer.declareHiddenLocal("%vof", elemType);
5983
6203
  const write = {
@@ -5987,11 +6207,16 @@ export function lowerForOf(lowerer, stmt) {
5987
6207
  loc,
5988
6208
  };
5989
6209
  const body = lowerer.inCtl("loop", () => lowerer.lowerScopedBlock(stmt.statement), labels);
5990
- return { kind: "forOf", localId: tmp.id, iterable, body: [write, ...body], ...(labels && { labels }), loc: locOf(stmt) };
6210
+ return forValues(tmp.id, [write, ...body]);
6211
+ }
6212
+ const local = lowerer.declareLocal(decl.name, decl.name.text, elemValueT, isLet);
6213
+ if (!typeEquals(elemValueT, sourceT.elem)) {
6214
+ const root = lowerer.runtimeOptionalRootOf(local);
6215
+ lowerer.runtimeOptionalLocals.add(root);
6216
+ lowerer.runtimeOptionalStorageLocals.add(root);
5991
6217
  }
5992
- const local = lowerer.declareLocal(decl.name, decl.name.text, iterable.type.elem, isLet);
5993
6218
  const body = lowerer.inCtl("loop", () => lowerer.lowerScopedBlock(stmt.statement), labels);
5994
- return { kind: "forOf", localId: local.id, iterable, body, ...(labels && { labels }), loc: locOf(stmt) };
6219
+ return forValues(local.id, body);
5995
6220
  }
5996
6221
  finally {
5997
6222
  lowerer.scopes.pop();
@@ -6087,8 +6312,9 @@ function lowerForOfStoredNumericIterator(lowerer, stmt, state, labels) {
6087
6312
  type: F64,
6088
6313
  loc,
6089
6314
  };
6315
+ const valueT = sourceT.kind === "array" ? arrayValueType(lowerer, F64) : F64;
6090
6316
  const sourceValue = () => sourceT.kind === "array"
6091
- ? { kind: "arrayGet", arr: sourceRef(), index: indexRef(), type: F64, loc }
6317
+ ? arrayValueRead(lowerer, sourceRef(), indexRef(), F64, loc)
6092
6318
  : {
6093
6319
  kind: "bytesIntrinsic",
6094
6320
  method: "get",
@@ -6102,9 +6328,14 @@ function lowerForOfStoredNumericIterator(lowerer, stmt, state, labels) {
6102
6328
  try {
6103
6329
  const varTarget = exprTarget ?? (decl ? forOfVarTarget(lowerer, decl) : null);
6104
6330
  const value = varTarget
6105
- ? lowerer.declareHiddenLocal("%numiterValue", F64)
6106
- : lowerer.declareLocal(decl.name, decl.name.text, F64, isLet);
6107
- const valueRef = varRef(value.id, F64, loc);
6331
+ ? lowerer.declareHiddenLocal("%numiterValue", valueT)
6332
+ : lowerer.declareLocal(decl.name, decl.name.text, valueT, isLet);
6333
+ if (!varTarget && valueT.kind === "union") {
6334
+ const root = lowerer.runtimeOptionalRootOf(value);
6335
+ lowerer.runtimeOptionalLocals.add(root);
6336
+ lowerer.runtimeOptionalStorageLocals.add(root);
6337
+ }
6338
+ const valueRef = varRef(value.id, valueT, loc);
6108
6339
  const blame = decl?.name ?? exprTargetNode ?? stmt.initializer;
6109
6340
  const head = [
6110
6341
  {