@scriptc/compiler 0.1.3 → 0.1.5

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 (217) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +275 -62
  2. package/dist/backend/c/async.d.ts +25 -0
  3. package/dist/backend/c/async.js +167 -0
  4. package/dist/backend/c/async.js.map +1 -1
  5. package/dist/backend/c/c-emitter.d.ts +15 -1
  6. package/dist/backend/c/c-emitter.js +36 -11
  7. package/dist/backend/c/c-emitter.js.map +1 -1
  8. package/dist/backend/c/exprs.d.ts +2 -2
  9. package/dist/backend/c/exprs.js +556 -71
  10. package/dist/backend/c/exprs.js.map +1 -1
  11. package/dist/backend/c/stmts.js +5 -3
  12. package/dist/backend/c/stmts.js.map +1 -1
  13. package/dist/backend/c/types.d.ts +2 -3
  14. package/dist/backend/c/types.js +10 -1
  15. package/dist/backend/c/types.js.map +1 -1
  16. package/dist/backend/c/walkers.js +1 -1
  17. package/dist/backend/c/walkers.js.map +1 -1
  18. package/dist/backend/link-plan.d.ts +3 -1
  19. package/dist/backend/link-plan.js +3 -1
  20. package/dist/backend/link-plan.js.map +1 -1
  21. package/dist/backend/llvm/dyn.js +1 -1
  22. package/dist/backend/llvm/dyn.js.map +1 -1
  23. package/dist/backend/llvm/emitter.js +42 -12
  24. package/dist/backend/llvm/emitter.js.map +1 -1
  25. package/dist/backend/llvm/expr-bytes.d.ts +1 -2
  26. package/dist/backend/llvm/expr-bytes.js +28 -23
  27. package/dist/backend/llvm/expr-bytes.js.map +1 -1
  28. package/dist/backend/llvm/expr-callbacks.d.ts +9 -0
  29. package/dist/backend/llvm/expr-callbacks.js +125 -0
  30. package/dist/backend/llvm/expr-callbacks.js.map +1 -1
  31. package/dist/backend/llvm/expr-containers.d.ts +1 -0
  32. package/dist/backend/llvm/expr-containers.js +32 -4
  33. package/dist/backend/llvm/expr-containers.js.map +1 -1
  34. package/dist/backend/llvm/expr-context.d.ts +19 -3
  35. package/dist/backend/llvm/expr-dispatch.js +1 -0
  36. package/dist/backend/llvm/expr-dispatch.js.map +1 -1
  37. package/dist/backend/llvm/expr-primitives.d.ts +1 -1
  38. package/dist/backend/llvm/expr-primitives.js +49 -13
  39. package/dist/backend/llvm/expr-primitives.js.map +1 -1
  40. package/dist/backend/llvm/expr-primitives.test.d.ts +1 -0
  41. package/dist/backend/llvm/expr-primitives.test.js +38 -0
  42. package/dist/backend/llvm/expr-primitives.test.js.map +1 -0
  43. package/dist/backend/llvm/expr-stream-callbacks.d.ts +3 -0
  44. package/dist/backend/llvm/expr-stream-callbacks.js +69 -0
  45. package/dist/backend/llvm/expr-stream-callbacks.js.map +1 -1
  46. package/dist/backend/llvm/lib-filesystem.js +80 -10
  47. package/dist/backend/llvm/lib-filesystem.js.map +1 -1
  48. package/dist/backend/llvm/lib-network.js +69 -10
  49. package/dist/backend/llvm/lib-network.js.map +1 -1
  50. package/dist/backend/llvm/lib-process.js +156 -6
  51. package/dist/backend/llvm/lib-process.js.map +1 -1
  52. package/dist/backend/llvm/lib-shared.js +100 -0
  53. package/dist/backend/llvm/lib-shared.js.map +1 -1
  54. package/dist/backend/llvm/lib-stream.js +23 -0
  55. package/dist/backend/llvm/lib-stream.js.map +1 -1
  56. package/dist/backend/native-toolchain.d.ts +5 -0
  57. package/dist/backend/native-toolchain.js +4 -1
  58. package/dist/backend/native-toolchain.js.map +1 -1
  59. package/dist/backend/runtime-pack.test.js +28 -10
  60. package/dist/backend/runtime-pack.test.js.map +1 -1
  61. package/dist/backend/targets.d.ts +7 -0
  62. package/dist/backend/targets.js +26 -0
  63. package/dist/backend/targets.js.map +1 -1
  64. package/dist/backend/targets.test.js +8 -1
  65. package/dist/backend/targets.test.js.map +1 -1
  66. package/dist/coverage/surface-manifest.js +14 -4
  67. package/dist/coverage/surface-manifest.js.map +1 -1
  68. package/dist/diagnostics/diagnostic.js +1 -1
  69. package/dist/diagnostics/diagnostic.js.map +1 -1
  70. package/dist/executable/executable-cache.d.ts +4 -0
  71. package/dist/executable/executable-cache.js +6 -1
  72. package/dist/executable/executable-cache.js.map +1 -1
  73. package/dist/executable/executable-cache.test.js +3 -3
  74. package/dist/executable/executable-cache.test.js.map +1 -1
  75. package/dist/frontend/fork-target.d.ts +17 -0
  76. package/dist/frontend/fork-target.js +256 -0
  77. package/dist/frontend/fork-target.js.map +1 -0
  78. package/dist/frontend/lowering/abstract-equality.d.ts +6 -0
  79. package/dist/frontend/lowering/abstract-equality.js +250 -0
  80. package/dist/frontend/lowering/abstract-equality.js.map +1 -0
  81. package/dist/frontend/lowering/containers/bytes.d.ts +1 -1
  82. package/dist/frontend/lowering/containers/bytes.js +6 -5
  83. package/dist/frontend/lowering/containers/bytes.js.map +1 -1
  84. package/dist/frontend/lowering/containers/string-and-regexp.d.ts +7 -6
  85. package/dist/frontend/lowering/containers/string-and-regexp.js +237 -28
  86. package/dist/frontend/lowering/containers/string-and-regexp.js.map +1 -1
  87. package/dist/frontend/lowering/expressions/object-literals.js +8 -0
  88. package/dist/frontend/lowering/expressions/object-literals.js.map +1 -1
  89. package/dist/frontend/lowering/lower-assert.js +71 -2
  90. package/dist/frontend/lowering/lower-assert.js.map +1 -1
  91. package/dist/frontend/lowering/lower-builtins.d.ts +54 -28
  92. package/dist/frontend/lowering/lower-builtins.js +957 -116
  93. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  94. package/dist/frontend/lowering/lower-calls.d.ts +32 -23
  95. package/dist/frontend/lowering/lower-calls.js +835 -206
  96. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  97. package/dist/frontend/lowering/lower-classes.js +41 -10
  98. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  99. package/dist/frontend/lowering/lower-containers.d.ts +7 -9
  100. package/dist/frontend/lowering/lower-containers.js +293 -51
  101. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  102. package/dist/frontend/lowering/lower-event-emitter.js +212 -21
  103. package/dist/frontend/lowering/lower-event-emitter.js.map +1 -1
  104. package/dist/frontend/lowering/lower-exprs.d.ts +12 -0
  105. package/dist/frontend/lowering/lower-exprs.js +263 -93
  106. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  107. package/dist/frontend/lowering/lower-inspect.d.ts +4 -0
  108. package/dist/frontend/lowering/lower-inspect.js +1 -1
  109. package/dist/frontend/lowering/lower-inspect.js.map +1 -1
  110. package/dist/frontend/lowering/lower-island.d.ts +6 -3
  111. package/dist/frontend/lowering/lower-island.js +182 -12
  112. package/dist/frontend/lowering/lower-island.js.map +1 -1
  113. package/dist/frontend/lowering/lower-modules.d.ts +6 -0
  114. package/dist/frontend/lowering/lower-modules.js +184 -38
  115. package/dist/frontend/lowering/lower-modules.js.map +1 -1
  116. package/dist/frontend/lowering/lower-namespaces.js +7 -0
  117. package/dist/frontend/lowering/lower-namespaces.js.map +1 -1
  118. package/dist/frontend/lowering/lower-server.d.ts +3 -5
  119. package/dist/frontend/lowering/lower-server.js +413 -54
  120. package/dist/frontend/lowering/lower-server.js.map +1 -1
  121. package/dist/frontend/lowering/lower-stmts.js +122 -19
  122. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  123. package/dist/frontend/lowering/lower-stream.d.ts +3 -0
  124. package/dist/frontend/lowering/lower-stream.js +7 -0
  125. package/dist/frontend/lowering/lower-stream.js.map +1 -1
  126. package/dist/frontend/lowering/lowerer.d.ts +86 -9
  127. package/dist/frontend/lowering/lowerer.js +449 -74
  128. package/dist/frontend/lowering/lowerer.js.map +1 -1
  129. package/dist/frontend/lowering/optional-arguments.d.ts +9 -1
  130. package/dist/frontend/lowering/optional-arguments.js +67 -1
  131. package/dist/frontend/lowering/optional-arguments.js.map +1 -1
  132. package/dist/frontend/lowering/pairs-snapshot.d.ts +3 -1
  133. package/dist/frontend/lowering/pairs-snapshot.js +12 -4
  134. package/dist/frontend/lowering/pairs-snapshot.js.map +1 -1
  135. package/dist/frontend/lowering/surfaces.d.ts +34 -14
  136. package/dist/frontend/lowering/surfaces.js +177 -104
  137. package/dist/frontend/lowering/surfaces.js.map +1 -1
  138. package/dist/frontend/npm-static-declarations.d.ts +4 -0
  139. package/dist/frontend/npm-static-declarations.js +56 -0
  140. package/dist/frontend/npm-static-declarations.js.map +1 -1
  141. package/dist/frontend/npm-static-declarations.test.js +23 -2
  142. package/dist/frontend/npm-static-declarations.test.js.map +1 -1
  143. package/dist/frontend/npm-static-prune.d.ts +6 -0
  144. package/dist/frontend/npm-static-prune.js +203 -0
  145. package/dist/frontend/npm-static-prune.js.map +1 -0
  146. package/dist/frontend/npm-static-prune.test.d.ts +1 -0
  147. package/dist/frontend/npm-static-prune.test.js +48 -0
  148. package/dist/frontend/npm-static-prune.test.js.map +1 -0
  149. package/dist/frontend/npm-static.js +6 -5
  150. package/dist/frontend/npm-static.js.map +1 -1
  151. package/dist/frontend/npm.d.ts +15 -3
  152. package/dist/frontend/npm.js +87 -14
  153. package/dist/frontend/npm.js.map +1 -1
  154. package/dist/frontend/npm.test.js +76 -9
  155. package/dist/frontend/npm.test.js.map +1 -1
  156. package/dist/frontend/program.js +107 -14
  157. package/dist/frontend/program.js.map +1 -1
  158. package/dist/frontend/provenance.js +8 -3
  159. package/dist/frontend/provenance.js.map +1 -1
  160. package/dist/frontend/resolve.d.ts +6 -0
  161. package/dist/frontend/resolve.js +7 -4
  162. package/dist/frontend/resolve.js.map +1 -1
  163. package/dist/frontend/type-mapper.d.ts +5 -0
  164. package/dist/frontend/type-mapper.js +80 -29
  165. package/dist/frontend/type-mapper.js.map +1 -1
  166. package/dist/index.d.ts +8 -3
  167. package/dist/index.js +34 -41
  168. package/dist/index.js.map +1 -1
  169. package/dist/ir/analysis.d.ts +5 -3
  170. package/dist/ir/analysis.js +25 -11
  171. package/dist/ir/analysis.js.map +1 -1
  172. package/dist/ir/analysis.test.js +50 -2
  173. package/dist/ir/analysis.test.js.map +1 -1
  174. package/dist/ir/constant-tables.d.ts +17 -0
  175. package/dist/ir/constant-tables.js +99 -0
  176. package/dist/ir/constant-tables.js.map +1 -0
  177. package/dist/ir/constant-tables.test.d.ts +1 -0
  178. package/dist/ir/constant-tables.test.js +78 -0
  179. package/dist/ir/constant-tables.test.js.map +1 -0
  180. package/dist/ir/integer-ranges.d.ts +13 -0
  181. package/dist/ir/integer-ranges.js +122 -0
  182. package/dist/ir/integer-ranges.js.map +1 -0
  183. package/dist/ir/integer-ranges.test.d.ts +1 -0
  184. package/dist/ir/integer-ranges.test.js +63 -0
  185. package/dist/ir/integer-ranges.test.js.map +1 -0
  186. package/dist/ir/ir.d.ts +130 -76
  187. package/dist/ir/ir.js +69 -4
  188. package/dist/ir/ir.js.map +1 -1
  189. package/dist/ir/ir.test.js +20 -1
  190. package/dist/ir/ir.test.js.map +1 -1
  191. package/dist/ir/scalar-records.d.ts +8 -0
  192. package/dist/ir/scalar-records.js +216 -0
  193. package/dist/ir/scalar-records.js.map +1 -0
  194. package/dist/ir/scalar-records.test.d.ts +1 -0
  195. package/dist/ir/scalar-records.test.js +142 -0
  196. package/dist/ir/scalar-records.test.js.map +1 -0
  197. package/dist/ir/serialize.d.ts +1 -1
  198. package/dist/ir/serialize.js +1 -1
  199. package/dist/ir/serialize.js.map +1 -1
  200. package/dist/ir/validate.js +256 -20
  201. package/dist/ir/validate.js.map +1 -1
  202. package/dist/ir/validate.test.d.ts +1 -0
  203. package/dist/ir/validate.test.js +31 -0
  204. package/dist/ir/validate.test.js.map +1 -0
  205. package/dist/library/cache-primitives.d.ts +1 -1
  206. package/dist/library/cache-primitives.js +3 -3
  207. package/dist/library/cache-primitives.js.map +1 -1
  208. package/dist/library/fence-eval.js +11 -4
  209. package/dist/library/fence-eval.js.map +1 -1
  210. package/dist/library/int-infer.test.js +4 -4
  211. package/dist/library/int-infer.test.js.map +1 -1
  212. package/dist/library/library-cache.js +0 -1
  213. package/dist/library/library-cache.js.map +1 -1
  214. package/dist/library/library-cache.test.js +8 -8
  215. package/dist/library/library-cache.test.js.map +1 -1
  216. package/package.json +17 -17
  217. package/surface-manifest.json +186 -79
@@ -19,20 +19,18 @@ export declare function lowerArrayMethodCall(lowerer: Lowerer, call: ts.CallExpr
19
19
  * parameter type). Null when this isn't a tuple slice/map — writers and
20
20
  * the rest of the surface keep their fences. */
21
21
  export declare function lowerTupleReadMethodCall(lowerer: Lowerer, call: ts.CallExpression, access: ts.PropertyAccessExpression): IrExpr | null;
22
- /** An OOB-SAFE indexed read: `xs[i]`
23
- * answers the interned `elem | undefined` union — the element when `i`
24
- * is an integer in [0, len), JS's property-miss undefined otherwise —
25
- * instead of the trap divergence 4 documents for program code.
26
- * Package JS is inference-typed, guard-style code (`registeredArguments
27
- * .slice(-1)[0]`, commander's last-element probe), and the trap would
28
- * fire on working Node idioms; program files keep the documented trap
29
- * (their annotations can prove bounds). Existing element unions retag
30
- * into the canonical union with an added undefined arm. */
22
+ /** An ordinary indexed read: values retain their element type, while
23
+ * holes, missing properties, and present undefined yield undefined. */
31
24
  export declare function lowerSafeIndexRead(lowerer: Lowerer, arr: IrExpr & {
32
25
  type: {
33
26
  kind: "array";
34
27
  };
35
28
  }, index: IrExpr, loc: SrcLoc): IrExpr | null;
29
+ /** Fuse only our own f64-array read helper with immediate ToNumber.
30
+ * Reuse its arguments verbatim so the receiver and index still evaluate
31
+ * exactly once, in order, before the read. Do not specialize user calls,
32
+ * union-element arrays, or optional values stored in locals. */
33
+ export declare function tryLowerNumericIndexRead(lowerer: Lowerer, operand: IrExpr, loc: SrcLoc): IrExpr | null;
36
34
  /** Backward-compatible name for the npm-static probe path. */
37
35
  export declare const lowerNpmStaticSafeIndexRead: typeof lowerSafeIndexRead;
38
36
  /** `Array.from({ length: n }, mapfn)` — the counted-generation idiom — on
@@ -15,7 +15,7 @@ import { buildArraySortFn } from "./lower-array-sort.js";
15
15
  import { arrayIndexPresent, arrayValueRead, arrayValueStore, arrayValueType, currentArrayIndexPresent } from "./array-values.js";
16
16
  import { typeKey } from "../type-mapper.js";
17
17
  import { boolLit, countedFor, numLit, varRef } from "../../ir/build.js";
18
- import { lowerOptionalArgument } from "./optional-arguments.js";
18
+ import { lowerOptionalArgument, lowerPositionArgument, positionNumber } from "./optional-arguments.js";
19
19
  /** Build the argument array for a mutating/copying operation while keeping
20
20
  * the receiver's scalar payload ABI. A missing read is represented as the
21
21
  * present UNDEFINED state in this temporary, so push/unshift and toSpliced
@@ -95,6 +95,11 @@ export function lowerArrayMethodCall(lowerer, call, access) {
95
95
  probedUntyped = true;
96
96
  }
97
97
  }
98
+ if (receiverIr?.kind === "union") {
99
+ const unionHof = lowerArrayUnionHofCall(lowerer, call, access, receiverIr);
100
+ if (unionHof)
101
+ return unionHof;
102
+ }
98
103
  if (receiverIr?.kind !== "array")
99
104
  return null;
100
105
  if (!probedUntyped && !lowerer.isStdlibMember(access))
@@ -282,14 +287,14 @@ export function lowerArrayMethodCall(lowerer, call, access) {
282
287
  };
283
288
  }
284
289
  // The lib declares wider call forms than the lowered surface —
285
- // indexOf/includes take a fromIndex, join's separator is optional, the
286
- // predicate/mapping HOFs take a thisArg. Each unlowered form is fenced
290
+ // join's separator is optional, and the predicate/mapping HOFs take
291
+ // a thisArg. Each unlowered form is fenced
287
292
  // per site (SC2020), never silently truncated to the supported
288
293
  // arguments. push/unshift lower every declared form (variadic, 0 args
289
294
  // included — Node returns the unchanged length); reduce/reduceRight
290
295
  // lower both declared forms (with and without an initial value).
291
296
  const arity = {
292
- push: [0, Number.MAX_SAFE_INTEGER], unshift: [0, Number.MAX_SAFE_INTEGER], pop: [0, 0], indexOf: [1, 1], includes: [1, 1], join: [1, 1],
297
+ push: [0, Number.MAX_SAFE_INTEGER], unshift: [0, Number.MAX_SAFE_INTEGER], pop: [0, 0], indexOf: [1, 2], lastIndexOf: [1, 2], includes: [0, 2], join: [1, 1],
293
298
  concat: [0, Number.MAX_SAFE_INTEGER],
294
299
  slice: [0, 2], shift: [0, 0], splice: [1, 2], at: [1, 1],
295
300
  map: [1, 1], filter: [1, 1], forEach: [1, 1], find: [1, 1], findIndex: [1, 1], some: [1, 1],
@@ -301,12 +306,10 @@ export function lowerArrayMethodCall(lowerer, call, access) {
301
306
  ? "the removal forms lower — splice(start, deleteCount?); to insert, build a new array with slice and push"
302
307
  : name === "join"
303
308
  ? 'pass the separator explicitly: join(",")'
304
- : name === "indexOf" || name === "includes"
305
- ? "the fromIndex parameter has no lowering — slice first, or loop"
306
- : name === "map" || name === "filter" || name === "forEach" ||
307
- name === "find" || name === "some" || name === "every" || name === "flatMap"
308
- ? "the thisArg parameter has no lowering — use an arrow function"
309
- : undefined;
309
+ : name === "map" || name === "filter" || name === "forEach" ||
310
+ name === "find" || name === "some" || name === "every" || name === "flatMap"
311
+ ? "the thisArg parameter has no lowering — use an arrow function"
312
+ : undefined;
310
313
  lowerer.noLowering(`.${name} with ${call.arguments.length} argument${call.arguments.length === 1 ? "" : "s"}`, call, hint);
311
314
  }
312
315
  if (name === "push" || name === "unshift") {
@@ -388,7 +391,7 @@ export function lowerArrayMethodCall(lowerer, call, access) {
388
391
  loc,
389
392
  };
390
393
  }
391
- if (name === "indexOf" || name === "includes") {
394
+ if (name === "indexOf" || name === "lastIndexOf" || name === "includes") {
392
395
  return lowerArraySearchCall(lowerer, call, access, name, elem);
393
396
  }
394
397
  if (name === "concat") {
@@ -526,14 +529,23 @@ export function lowerArrayMethodCall(lowerer, call, access) {
526
529
  /** `indexOf` and `includes` normally use the runtime's typed search helpers,
527
530
  * but an indexed array read is a `T | undefined` value. Keep that union
528
531
  * through a small lowered loop so the needle's missing state is observable:
529
- * indexOf skips holes, while includes treats holes as undefined. The same
530
- * loop also gives union-element arrays their value equality rather than
531
- * comparing the compiler's union boxes by pointer. */
532
+ * indexOf and lastIndexOf skip holes, while includes treats holes as
533
+ * undefined. The same loop also gives union-element arrays their value
534
+ * equality rather than comparing the compiler's union boxes by pointer. */
532
535
  function lowerArraySearchCall(lowerer, call, access, method, elem) {
533
536
  const loc = locOf(call);
534
537
  const receiver = lowerer.lowerExpr(access.expression);
535
- const argNode = call.arguments[0];
536
- let needle = lowerer.lowerExpr(argNode);
538
+ const argNode = call.arguments[0] ?? call;
539
+ if (call.arguments.some(ts.isSpreadElement))
540
+ lowerer.noLowering(`.${method} with spread arguments`, call);
541
+ let needle = call.arguments[0]
542
+ ? lowerer.lowerExpr(call.arguments[0])
543
+ : { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc };
544
+ const fromIndex = call.arguments[1]
545
+ ? lowerPositionArgument(lowerer, call.arguments[1], numLit(0, loc))
546
+ : method === "lastIndexOf"
547
+ ? { kind: "bin", op: "/", left: numLit(1, loc), right: numLit(0, loc), type: F64, loc }
548
+ : numLit(0, loc);
537
549
  // A derived CLASS VALUE against a base-classval element widens (the same
538
550
  // pointer — identity search is exact); the coercion path owns the ABI gate.
539
551
  if (needle.type.kind === "classval" && elem.kind === "classval" && !typeEquals(needle.type, elem)) {
@@ -541,21 +553,25 @@ function lowerArraySearchCall(lowerer, call, access, method, elem) {
541
553
  }
542
554
  // Preserve the existing fast path for an ordinary, non-union needle. Its
543
555
  // runtime implementation already has exact primitive/reference equality.
544
- if (elem.kind !== "union" && elem.kind !== "bigint" && typeEquals(needle.type, elem)) {
556
+ if (method !== "lastIndexOf" && call.arguments.length === 1 && elem.kind !== "union" && elem.kind !== "bigint" && typeEquals(needle.type, elem)) {
545
557
  return {
546
558
  kind: "arrIntrinsic",
547
559
  method,
548
560
  receiver,
549
561
  args: [needle],
550
- type: method === "indexOf" ? F64 : BOOL,
562
+ type: method === "includes" ? BOOL : F64,
551
563
  loc,
552
564
  };
553
565
  }
554
- const valueT = arrayValueType(lowerer, elem);
566
+ let valueT = arrayValueType(lowerer, elem);
555
567
  // jsval/dyn arrays have a single engine value representation rather than a
556
568
  // static undefined arm. Keep their existing runtime path and its explicit
557
569
  // type fence; typed static arrays take the state-aware helper below.
558
570
  if (valueT.kind !== "union") {
571
+ if (method === "lastIndexOf")
572
+ lowerer.noLowering(".lastIndexOf on an array of engine values", call);
573
+ if (call.arguments.length > 1)
574
+ lowerer.noLowering(`.${method} with fromIndex on '${lowerer.fmt(elem)}' elements`, call);
559
575
  if (!typeEquals(needle.type, elem))
560
576
  lowerer.badType(argNode, lowerer.typeOf(argNode));
561
577
  return {
@@ -563,15 +579,38 @@ function lowerArraySearchCall(lowerer, call, access, method, elem) {
563
579
  method,
564
580
  receiver,
565
581
  args: [needle],
566
- type: method === "indexOf" ? F64 : BOOL,
582
+ type: method === "includes" ? BOOL : F64,
567
583
  loc,
568
584
  };
569
585
  }
570
- const searchNeedle = lowerer.coerceInto(argNode, needle, valueT);
571
- if (!typeEquals(searchNeedle.type, valueT))
586
+ // Search uses strict equality, never conversion to the array's element
587
+ // type. Retain primitive needles of a different kind in the comparison
588
+ // union so a nonmatching kind returns a miss instead of throwing.
589
+ const needleArms = needle.type.kind === "union"
590
+ ? lowerer.unions.get(needle.type.unionId).arms
591
+ : [needle.type];
592
+ const scalarNeedle = needleArms.every((arm) => arm.kind === "f64" || arm.kind === "string" || arm.kind === "bool" ||
593
+ arm.kind === "bigint" || arm.kind === "symbol" || isUnitType(arm));
594
+ const valueArms = lowerer.unions.get(valueT.unionId).arms;
595
+ if (scalarNeedle && !valueArms.some((arm) => arm.kind === "func" || arm.kind === "set")) {
596
+ const arms = [...valueArms];
597
+ for (const arm of needleArms)
598
+ if (!arms.some((existing) => typeEquals(existing, arm)))
599
+ arms.push(arm);
600
+ if (arms.length !== valueArms.length)
601
+ valueT = { kind: "union", unionId: lowerer.unions.intern(arms) };
602
+ }
603
+ const directNeedle = method === "lastIndexOf" && elem.kind !== "union" && typeEquals(needle.type, elem);
604
+ const searchNeedle = directNeedle ? needle : lowerer.coerceInto(argNode, needle, valueT);
605
+ // Checked dynamic extraction can copy a composite. Its new identity cannot
606
+ // be compared to an array element as though it were the original value.
607
+ if (method === "lastIndexOf" && searchNeedle.kind === "dynCheck" && (elem.kind === "record" || elem.kind === "array")) {
608
+ lowerer.noLowering(".lastIndexOf with a checked dynamic reference needle", call);
609
+ }
610
+ if (!directNeedle && !typeEquals(searchNeedle.type, valueT))
572
611
  lowerer.badType(argNode, lowerer.typeOf(argNode));
573
- const helper = arraySearchHelper(lowerer, method, elem, valueT, loc);
574
- return { kind: "call", callee: helper, args: [receiver, searchNeedle], type: method === "indexOf" ? F64 : BOOL, loc };
612
+ const helper = arraySearchHelper(lowerer, method, elem, valueT, searchNeedle.type, directNeedle, fromIndex.type, call.arguments[1] ?? call, loc);
613
+ return { kind: "call", callee: helper, args: [receiver, searchNeedle, fromIndex], type: method === "includes" ? BOOL : F64, loc };
575
614
  }
576
615
  /** SameValueZero over a static value union. `unionEq` supplies strict
577
616
  * equality for every arm; the extra f64-arm test is the only difference for
@@ -615,25 +654,65 @@ function arraySearchEquality(lowerer, left, right, sameValueZero, loc) {
615
654
  };
616
655
  return { kind: "logical", op: "||", left: strict, right: bothNaN, type: BOOL, loc };
617
656
  }
618
- function arraySearchHelper(lowerer, method, elem, valueT, loc) {
619
- const key = `${method}:${typeKey(elem)}:${typeKey(valueT)}`;
657
+ function arraySearchHelper(lowerer, method, elem, valueT, needleT, directNeedle, fromT, fromNode, loc) {
658
+ const key = `${method}:${typeKey(elem)}:${typeKey(valueT)}:${typeKey(needleT)}:${directNeedle}:${typeKey(fromT)}`;
620
659
  const existing = lowerer.arrHofHelpers.get(key);
621
660
  if (existing)
622
661
  return existing;
623
662
  const name = `%arr.${method}.${lowerer.arrHofHelpers.size}`;
624
663
  lowerer.arrHofHelpers.set(key, name);
625
664
  const arrT = arrayOf(elem);
626
- const resultT = method === "indexOf" ? F64 : BOOL;
665
+ const resultT = method === "includes" ? BOOL : F64;
627
666
  const arrRef = varRef("a.0", arrT, loc);
628
667
  const indexRef = varRef("i.0", F64, loc);
629
- const needleRef = varRef("needle.0", valueT, loc);
668
+ const needleRef = varRef("needle.0", needleT, loc);
630
669
  const stateRef = varRef("state.0", F64, loc);
631
670
  const nextRef = varRef("next.0", F64, loc);
671
+ const fromRef = varRef("from.0", fromT, loc);
672
+ const numberRef = varRef("number.0", F64, loc);
673
+ const integerRef = varRef("integer.0", F64, loc);
674
+ // Snapshot length before coercing fromIndex, then apply ToIntegerOrInfinity.
675
+ const number = positionNumber(lowerer, fromRef, numLit(0, loc), fromNode, "array search position");
676
+ const integer = {
677
+ kind: "ternary",
678
+ cond: { kind: "libCall", fn: "num.isNaN", args: [numberRef], type: BOOL, loc },
679
+ then: numLit(0, loc),
680
+ else_: { kind: "libCall", fn: "math.trunc", args: [numberRef], type: F64, loc },
681
+ type: F64,
682
+ loc,
683
+ };
684
+ const forwardStart = {
685
+ kind: "ternary",
686
+ cond: { kind: "bin", op: "<", left: integerRef, right: numLit(0, loc), type: BOOL, loc },
687
+ then: {
688
+ kind: "libCall", fn: "math.max",
689
+ args: [{ kind: "bin", op: "+", left: varRef("n.0", F64, loc), right: integerRef, type: F64, loc }, numLit(0, loc)],
690
+ type: F64, loc,
691
+ },
692
+ // Adding positive zero canonicalizes a truncated negative zero.
693
+ else_: { kind: "bin", op: "+", left: integerRef, right: numLit(0, loc), type: F64, loc },
694
+ type: F64,
695
+ loc,
696
+ };
697
+ const start = method === "lastIndexOf" ? {
698
+ kind: "ternary",
699
+ cond: { kind: "bin", op: "<", left: integerRef, right: numLit(0, loc), type: BOOL, loc },
700
+ then: { kind: "bin", op: "+", left: varRef("n.0", F64, loc), right: integerRef, type: F64, loc },
701
+ else_: {
702
+ kind: "libCall", fn: "math.min",
703
+ args: [
704
+ { kind: "bin", op: "+", left: integerRef, right: numLit(0, loc), type: F64, loc },
705
+ { kind: "bin", op: "-", left: varRef("n.0", F64, loc), right: numLit(1, loc), type: F64, loc },
706
+ ],
707
+ type: F64, loc,
708
+ },
709
+ type: F64, loc,
710
+ } : forwardStart;
632
711
  const rawRead = { kind: "arrayGet", arr: arrRef, index: indexRef, type: elem, loc };
633
712
  const missing = lowerer.wrappedUndefined(valueT, loc);
634
713
  if (!missing)
635
714
  throw new InternalCompilerError("array search helper needs an undefined arm");
636
- const value = {
715
+ const optionalValue = {
637
716
  kind: "ternary",
638
717
  cond: { kind: "bin", op: "===", left: stateRef, right: numLit(1, loc), type: BOOL, loc },
639
718
  then: typeEquals(elem, valueT) ? rawRead : lowerer.coerceToExpected(rawRead, valueT),
@@ -641,12 +720,14 @@ function arraySearchHelper(lowerer, method, elem, valueT, loc) {
641
720
  type: valueT,
642
721
  loc,
643
722
  };
644
- const equal = arraySearchEquality(lowerer, varRef("value.0", valueT, loc), needleRef, method === "includes", loc);
645
- const match = method === "indexOf"
723
+ const value = directNeedle ? rawRead : optionalValue;
724
+ const readT = directNeedle ? elem : valueT;
725
+ const equal = arraySearchEquality(lowerer, varRef("value.0", readT, loc), needleRef, method === "includes", loc);
726
+ const match = method !== "includes"
646
727
  ? {
647
728
  kind: "logical",
648
729
  op: "&&",
649
- left: { kind: "bin", op: "!==", left: stateRef, right: numLit(0, loc), type: BOOL, loc },
730
+ left: { kind: "bin", op: directNeedle ? "===" : "!==", left: stateRef, right: numLit(directNeedle ? 1 : 0, loc), type: BOOL, loc },
650
731
  right: equal,
651
732
  type: BOOL,
652
733
  loc,
@@ -655,7 +736,7 @@ function arraySearchHelper(lowerer, method, elem, valueT, loc) {
655
736
  const undefinedTag = lowerer.armTag(valueT.unionId, UNDEFINED_T);
656
737
  if (undefinedTag < 0)
657
738
  throw new InternalCompilerError("array search helper needs an undefined tag");
658
- const needleIsUndefined = {
739
+ const needleIsUndefined = directNeedle ? boolLit(false, loc) : {
659
740
  kind: "unionIsTag",
660
741
  unionId: valueT.unionId,
661
742
  tag: undefinedTag,
@@ -667,7 +748,7 @@ function arraySearchHelper(lowerer, method, elem, valueT, loc) {
667
748
  const advance = {
668
749
  kind: "assign",
669
750
  localId: "i.0",
670
- value: { kind: "bin", op: "+", left: indexRef, right: numLit(1, loc), type: F64, loc },
751
+ value: { kind: "bin", op: method === "lastIndexOf" ? "-" : "+", left: indexRef, right: numLit(1, loc), type: F64, loc },
671
752
  loc,
672
753
  };
673
754
  const visitPresent = [
@@ -675,13 +756,13 @@ function arraySearchHelper(lowerer, method, elem, valueT, loc) {
675
756
  {
676
757
  kind: "if",
677
758
  cond: match,
678
- then: [{ kind: "return", value: method === "indexOf" ? indexRef : boolLit(true, loc), loc }],
759
+ then: [{ kind: "return", value: method === "includes" ? boolLit(true, loc) : indexRef, loc }],
679
760
  else_: null,
680
761
  loc,
681
762
  },
682
763
  advance,
683
764
  ];
684
- const skipHole = [
765
+ const skipHole = method === "lastIndexOf" ? [advance] : [
685
766
  {
686
767
  kind: "varDecl",
687
768
  localId: "next.0",
@@ -707,28 +788,36 @@ function arraySearchHelper(lowerer, method, elem, valueT, loc) {
707
788
  name,
708
789
  params: [
709
790
  { localId: "a.0", name: "a", type: arrT },
710
- { localId: "needle.0", name: "needle", type: valueT },
791
+ { localId: "needle.0", name: "needle", type: needleT },
792
+ { localId: "from.0", name: "from", type: fromT },
711
793
  ],
712
794
  returnType: resultT,
713
795
  locals: [
714
796
  { id: "a.0", name: "a", type: arrT, mutable: true },
715
- { id: "needle.0", name: "needle", type: valueT, mutable: true },
797
+ { id: "needle.0", name: "needle", type: needleT, mutable: true },
798
+ { id: "from.0", name: "from", type: fromT, mutable: false },
799
+ { id: "number.0", name: "number", type: F64, mutable: false },
800
+ { id: "integer.0", name: "integer", type: F64, mutable: false },
716
801
  { id: "n.0", name: "n", type: F64, mutable: false },
717
802
  { id: "i.0", name: "i", type: F64, mutable: true },
718
803
  { id: "state.0", name: "state", type: F64, mutable: false },
719
804
  { id: "next.0", name: "next", type: F64, mutable: false },
720
- { id: "value.0", name: "value", type: valueT, mutable: false },
805
+ { id: "value.0", name: "value", type: readT, mutable: false },
721
806
  ],
722
807
  body: [
723
808
  readLenStmt(arrT, loc),
724
- { kind: "varDecl", localId: "i.0", init: numLit(0, loc), loc },
809
+ { kind: "varDecl", localId: "number.0", init: number, loc },
810
+ { kind: "varDecl", localId: "integer.0", init: integer, loc },
811
+ { kind: "varDecl", localId: "i.0", init: start, loc },
725
812
  {
726
813
  kind: "while",
727
- cond: { kind: "bin", op: "<", left: indexRef, right: varRef("n.0", F64, loc), type: BOOL, loc },
814
+ cond: method === "lastIndexOf"
815
+ ? { kind: "bin", op: ">=", left: indexRef, right: numLit(0, loc), type: BOOL, loc }
816
+ : { kind: "bin", op: "<", left: indexRef, right: varRef("n.0", F64, loc), type: BOOL, loc },
728
817
  body: loopBody,
729
818
  loc,
730
819
  },
731
- { kind: "return", value: method === "indexOf" ? numLit(-1, loc) : boolLit(false, loc), loc },
820
+ { kind: "return", value: method === "includes" ? boolLit(false, loc) : numLit(-1, loc), loc },
732
821
  ],
733
822
  loc,
734
823
  });
@@ -828,6 +917,18 @@ function hofCallbackArg(lowerer, argNode, lead, arrT, bindUntyped = false) {
828
917
  }
829
918
  lowerer.implicitParamTypes = previousImplicit;
830
919
  }
920
+ // Array storage widens indexed reads with undefined so holes remain
921
+ // observable. The HOF helper guards every callback behind arrayHas,
922
+ // so an exact-ABI builtin closure may safely adapt from its declared
923
+ // parameter to that guarded union. Keep this exception scoped to the
924
+ // compiler-generated builtin values; ordinary callback-value policy is
925
+ // unchanged.
926
+ if (fnArg.type.kind === "func" && lowerer.isBuiltinCallableValue(fnArg)) {
927
+ const callbackType = funcOf(full.slice(0, fnArg.type.params.length), fnArg.type.ret);
928
+ if (!typeEquals(fnArg.type, callbackType)) {
929
+ fnArg = lowerer.coerceToExpected(fnArg, callbackType);
930
+ }
931
+ }
831
932
  if (fnArg.type.kind !== "func" ||
832
933
  fnArg.type.params.length > full.length ||
833
934
  !fnArg.type.params.every((p, i) => typeEquals(p, full[i]))) {
@@ -1043,6 +1144,139 @@ function arrayHofHelper(lowerer, method, elem, fnRet, arity, loc, outElem = fnRe
1043
1144
  lowerer.liftedFns.push(buildArrayHofFn(lowerer, name, method, elem, fnRet, arity, loc, outElem));
1044
1145
  return name;
1045
1146
  }
1147
+ /** The TS 5.2+ callable-union array rule: a `T[] | U[]` receiver exposes
1148
+ * common HOFs with a callback over `T | U`. The runtime value remains the
1149
+ * original concrete array arm — never a copied `(T | U)[]` — and an
1150
+ * arm-specific function adapter wraps the element and receiver arguments
1151
+ * into the union-wide callback ABI. That preserves sparse-array reads,
1152
+ * callback mutation, and the callback's third-argument identity. */
1153
+ function lowerArrayUnionHofCall(lowerer, call, access, receiverT) {
1154
+ const method = access.name.text;
1155
+ if (method !== "map" && method !== "forEach")
1156
+ return null;
1157
+ if (!lowerer.isStdlibMember(access))
1158
+ return null;
1159
+ const def = lowerer.unions.get(receiverT.unionId);
1160
+ if (!def || def.arms.length < 2 || !def.arms.every((arm) => arm.kind === "array"))
1161
+ return null;
1162
+ if (call.arguments.length !== 1 || !call.arguments[0])
1163
+ return null;
1164
+ const arrays = def.arms;
1165
+ const valueArms = [];
1166
+ const addValueArm = (type) => {
1167
+ if (type.kind === "dyn" || type.kind === "jsval" || type.kind === "caught" || type.kind === "void")
1168
+ return false;
1169
+ if (type.kind === "union") {
1170
+ const inner = lowerer.unions.get(type.unionId);
1171
+ if (!inner)
1172
+ return false;
1173
+ for (const arm of inner.arms) {
1174
+ if (!valueArms.some((candidate) => typeEquals(candidate, arm)))
1175
+ valueArms.push(arm);
1176
+ }
1177
+ return true;
1178
+ }
1179
+ if (!valueArms.some((candidate) => typeEquals(candidate, type)))
1180
+ valueArms.push(type);
1181
+ return true;
1182
+ };
1183
+ for (const array of arrays) {
1184
+ if (!addValueArm(array.elem))
1185
+ return null;
1186
+ }
1187
+ const valueT = valueArms.length === 1
1188
+ ? valueArms[0]
1189
+ : { kind: "union", unionId: lowerer.unions.intern(valueArms) };
1190
+ const argNode = call.arguments[0];
1191
+ const { fnArg, arity } = hofCallbackArg(lowerer, argNode, [valueT], receiverT);
1192
+ const fnRet = fnArg.type.ret;
1193
+ if (method === "map" && (fnRet.kind === "void" || fnRet.kind === "func")) {
1194
+ lowerer.badType(call, lowerer.typeOf(call));
1195
+ }
1196
+ if (method === "map")
1197
+ fenceProducedArrayElem(lowerer, call, "'.map()'", fnRet);
1198
+ const outElem = method === "map" ? callbackArrayElem(lowerer, call, fnRet) : VOID;
1199
+ const resultT = method === "map" ? arrayOf(outElem) : VOID;
1200
+ for (const array of arrays) {
1201
+ const armFnT = funcOf([arrayValueType(lowerer, array.elem), F64, array].slice(0, arity), fnRet);
1202
+ if (!lowerer.cleanFuncAdaptable(fnArg.type, armFnT))
1203
+ return null;
1204
+ }
1205
+ const helper = arrayUnionHofHelper(lowerer, method, receiverT, arrays, fnArg.type, arity, fnRet, outElem, locOf(call));
1206
+ return {
1207
+ kind: "call",
1208
+ callee: helper,
1209
+ args: [lowerer.coerceInto(access.expression, lowerer.lowerExpr(access.expression), receiverT), fnArg],
1210
+ type: resultT,
1211
+ loc: locOf(call),
1212
+ };
1213
+ }
1214
+ function arrayUnionHofHelper(lowerer, method, receiverT, arrays, callbackT, arity, fnRet, outElem, loc) {
1215
+ const resultT = method === "map" ? arrayOf(outElem) : VOID;
1216
+ const key = `union:${method}:${receiverT.unionId}:${typeKey(callbackT)}:${typeKey(resultT)}`;
1217
+ const existing = lowerer.arrHofHelpers.get(key);
1218
+ if (existing)
1219
+ return existing;
1220
+ const name = `%arr.union.${lowerer.arrHofHelpers.size}`;
1221
+ lowerer.arrHofHelpers.set(key, name);
1222
+ const receiver = varRef("a.0", receiverT, loc);
1223
+ const callback = varRef("f.0", callbackT, loc);
1224
+ const branch = (array) => {
1225
+ const tag = lowerer.armTag(receiverT.unionId, array);
1226
+ const concrete = {
1227
+ kind: "unionNarrow",
1228
+ unionId: receiverT.unionId,
1229
+ tag,
1230
+ value: receiver,
1231
+ type: array,
1232
+ loc,
1233
+ };
1234
+ const armFnT = funcOf([arrayValueType(lowerer, array.elem), F64, array].slice(0, arity), fnRet);
1235
+ const adapted = lowerer.coerceToExpected(callback, armFnT);
1236
+ if (!typeEquals(adapted.type, armFnT)) {
1237
+ throw new InternalCompilerError("lowerer bug: union-array callback stopped adapting");
1238
+ }
1239
+ const callee = arrayHofHelper(lowerer, method, array.elem, fnRet, arity, loc, outElem);
1240
+ const invoke = { kind: "call", callee, args: [concrete, adapted], type: resultT, loc };
1241
+ return resultT.kind === "void"
1242
+ ? [{ kind: "exprStmt", expr: invoke, loc }, { kind: "return", value: null, loc }]
1243
+ : [{ kind: "return", value: invoke, loc }];
1244
+ };
1245
+ let body = branch(arrays[arrays.length - 1]);
1246
+ for (let i = arrays.length - 2; i >= 0; i--) {
1247
+ const array = arrays[i];
1248
+ body = [{
1249
+ kind: "if",
1250
+ cond: {
1251
+ kind: "unionIsTag",
1252
+ unionId: receiverT.unionId,
1253
+ tag: lowerer.armTag(receiverT.unionId, array),
1254
+ negated: false,
1255
+ value: receiver,
1256
+ type: BOOL,
1257
+ loc,
1258
+ },
1259
+ then: branch(array),
1260
+ else_: body,
1261
+ loc,
1262
+ }];
1263
+ }
1264
+ lowerer.liftedFns.push({
1265
+ name,
1266
+ params: [
1267
+ { localId: "a.0", name: "a", type: receiverT },
1268
+ { localId: "f.0", name: "f", type: callbackT },
1269
+ ],
1270
+ returnType: resultT,
1271
+ locals: [
1272
+ { id: "a.0", name: "a", type: receiverT, mutable: false },
1273
+ { id: "f.0", name: "f", type: callbackT, mutable: false },
1274
+ ],
1275
+ body,
1276
+ loc,
1277
+ });
1278
+ return name;
1279
+ }
1046
1280
  /** READ-ONLY array methods on TUPLE receivers — `t.slice(...)` and
1047
1281
  * `t.map(f)`: a tuple is a fixed-shape record, but these methods never
1048
1282
  * write, so the positions snapshot into a fresh array (the for-of-over-
@@ -1141,15 +1375,8 @@ export function lowerTupleReadMethodCall(lowerer, call, access) {
1141
1375
  const helper = arrayHofHelper(lowerer, "map", snapshotElem, fnRet, arity, loc, outElem);
1142
1376
  return { kind: "call", callee: helper, args: [snapshot, fnArg], type: arrayOf(outElem), loc };
1143
1377
  }
1144
- /** An OOB-SAFE indexed read: `xs[i]`
1145
- * answers the interned `elem | undefined` union — the element when `i`
1146
- * is an integer in [0, len), JS's property-miss undefined otherwise —
1147
- * instead of the trap divergence 4 documents for program code.
1148
- * Package JS is inference-typed, guard-style code (`registeredArguments
1149
- * .slice(-1)[0]`, commander's last-element probe), and the trap would
1150
- * fire on working Node idioms; program files keep the documented trap
1151
- * (their annotations can prove bounds). Existing element unions retag
1152
- * into the canonical union with an added undefined arm. */
1378
+ /** An ordinary indexed read: values retain their element type, while
1379
+ * holes, missing properties, and present undefined yield undefined. */
1153
1380
  export function lowerSafeIndexRead(lowerer, arr, index, loc) {
1154
1381
  const elem = arr.type.elem;
1155
1382
  if (elem.kind === "void" || elem.kind === "dyn")
@@ -1178,6 +1405,21 @@ export function lowerSafeIndexRead(lowerer, arr, index, loc) {
1178
1405
  }
1179
1406
  return { kind: "call", callee: name, args: [arr, index], type: resultT, loc };
1180
1407
  }
1408
+ /** Fuse only our own f64-array read helper with immediate ToNumber.
1409
+ * Reuse its arguments verbatim so the receiver and index still evaluate
1410
+ * exactly once, in order, before the read. Do not specialize user calls,
1411
+ * union-element arrays, or optional values stored in locals. */
1412
+ export function tryLowerNumericIndexRead(lowerer, operand, loc) {
1413
+ if (operand.kind !== "call" || operand.callee !== lowerer.arrHofHelpers.get(`idxOr:${typeKey(F64)}`))
1414
+ return null;
1415
+ const [arr, index] = operand.args;
1416
+ if (operand.args.length !== 2 || arr?.type.kind !== "array" || arr.type.elem.kind !== "f64" || index?.type.kind !== "f64")
1417
+ return null;
1418
+ // The intrinsic owns the evaluated receiver through index evaluation,
1419
+ // then borrows it for one slot lookup. No extra helper parameter or
1420
+ // separate state/getter expressions need to retain the array again.
1421
+ return { kind: "arrIntrinsic", method: "getNumber", receiver: arr, args: [index], type: F64, loc };
1422
+ }
1181
1423
  /** Backward-compatible name for the npm-static probe path. */
1182
1424
  export const lowerNpmStaticSafeIndexRead = lowerSafeIndexRead;
1183
1425
  /** Interned synthetic function for one (elem, argument-shape) concat —