@scriptc/compiler 0.1.4 → 0.1.6

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 (158) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +75 -11
  2. package/dist/backend/c/c-emitter.d.ts +5 -1
  3. package/dist/backend/c/c-emitter.js +22 -9
  4. package/dist/backend/c/c-emitter.js.map +1 -1
  5. package/dist/backend/c/exprs.d.ts +2 -2
  6. package/dist/backend/c/exprs.js +298 -27
  7. package/dist/backend/c/exprs.js.map +1 -1
  8. package/dist/backend/c/stmts.js +5 -3
  9. package/dist/backend/c/stmts.js.map +1 -1
  10. package/dist/backend/c/types.d.ts +2 -3
  11. package/dist/backend/c/types.js +8 -1
  12. package/dist/backend/c/types.js.map +1 -1
  13. package/dist/backend/c/walkers.js +1 -1
  14. package/dist/backend/c/walkers.js.map +1 -1
  15. package/dist/backend/link-plan.d.ts +1 -0
  16. package/dist/backend/link-plan.js +2 -1
  17. package/dist/backend/link-plan.js.map +1 -1
  18. package/dist/backend/llvm/dyn.js +1 -1
  19. package/dist/backend/llvm/dyn.js.map +1 -1
  20. package/dist/backend/llvm/emitter.js +25 -9
  21. package/dist/backend/llvm/emitter.js.map +1 -1
  22. package/dist/backend/llvm/expr-bytes.d.ts +1 -2
  23. package/dist/backend/llvm/expr-bytes.js +22 -17
  24. package/dist/backend/llvm/expr-bytes.js.map +1 -1
  25. package/dist/backend/llvm/expr-containers.d.ts +1 -0
  26. package/dist/backend/llvm/expr-containers.js +58 -4
  27. package/dist/backend/llvm/expr-containers.js.map +1 -1
  28. package/dist/backend/llvm/expr-context.d.ts +6 -2
  29. package/dist/backend/llvm/expr-primitives.js +22 -7
  30. package/dist/backend/llvm/expr-primitives.js.map +1 -1
  31. package/dist/backend/llvm/lib-filesystem.js +20 -0
  32. package/dist/backend/llvm/lib-filesystem.js.map +1 -1
  33. package/dist/backend/llvm/lib-network.js +53 -10
  34. package/dist/backend/llvm/lib-network.js.map +1 -1
  35. package/dist/backend/llvm/lib-process.js +4 -3
  36. package/dist/backend/llvm/lib-process.js.map +1 -1
  37. package/dist/backend/llvm/lib-shared.js +65 -0
  38. package/dist/backend/llvm/lib-shared.js.map +1 -1
  39. package/dist/backend/native-toolchain.d.ts +2 -0
  40. package/dist/backend/native-toolchain.js +2 -1
  41. package/dist/backend/native-toolchain.js.map +1 -1
  42. package/dist/backend/targets.d.ts +3 -0
  43. package/dist/backend/targets.js +13 -0
  44. package/dist/backend/targets.js.map +1 -1
  45. package/dist/coverage/surface-manifest.js +12 -5
  46. package/dist/coverage/surface-manifest.js.map +1 -1
  47. package/dist/executable/executable-cache.d.ts +2 -0
  48. package/dist/executable/executable-cache.js +3 -1
  49. package/dist/executable/executable-cache.js.map +1 -1
  50. package/dist/executable/executable-cache.test.js +3 -3
  51. package/dist/executable/executable-cache.test.js.map +1 -1
  52. package/dist/frontend/lowering/array-indexed-mutation.d.ts +8 -0
  53. package/dist/frontend/lowering/array-indexed-mutation.js +176 -0
  54. package/dist/frontend/lowering/array-indexed-mutation.js.map +1 -0
  55. package/dist/frontend/lowering/containers/bytes.d.ts +1 -1
  56. package/dist/frontend/lowering/containers/bytes.js +6 -5
  57. package/dist/frontend/lowering/containers/bytes.js.map +1 -1
  58. package/dist/frontend/lowering/containers/string-and-regexp.d.ts +14 -6
  59. package/dist/frontend/lowering/containers/string-and-regexp.js +420 -34
  60. package/dist/frontend/lowering/containers/string-and-regexp.js.map +1 -1
  61. package/dist/frontend/lowering/expressions/object-literals.js +8 -0
  62. package/dist/frontend/lowering/expressions/object-literals.js.map +1 -1
  63. package/dist/frontend/lowering/indexed-comparison.d.ts +6 -0
  64. package/dist/frontend/lowering/indexed-comparison.js +30 -0
  65. package/dist/frontend/lowering/indexed-comparison.js.map +1 -0
  66. package/dist/frontend/lowering/lower-builtins.d.ts +5 -6
  67. package/dist/frontend/lowering/lower-builtins.js +24 -16
  68. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  69. package/dist/frontend/lowering/lower-calls.d.ts +3 -2
  70. package/dist/frontend/lowering/lower-calls.js +522 -89
  71. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  72. package/dist/frontend/lowering/lower-classes.js +5 -29
  73. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  74. package/dist/frontend/lowering/lower-containers.d.ts +15 -11
  75. package/dist/frontend/lowering/lower-containers.js +588 -146
  76. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  77. package/dist/frontend/lowering/lower-exprs.d.ts +12 -0
  78. package/dist/frontend/lowering/lower-exprs.js +145 -11
  79. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  80. package/dist/frontend/lowering/lower-island.js +17 -6
  81. package/dist/frontend/lowering/lower-island.js.map +1 -1
  82. package/dist/frontend/lowering/lower-server.d.ts +2 -4
  83. package/dist/frontend/lowering/lower-server.js +288 -32
  84. package/dist/frontend/lowering/lower-server.js.map +1 -1
  85. package/dist/frontend/lowering/lower-stmts.js +4 -4
  86. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  87. package/dist/frontend/lowering/lowerer.js +37 -2
  88. package/dist/frontend/lowering/lowerer.js.map +1 -1
  89. package/dist/frontend/lowering/optional-arguments.d.ts +9 -1
  90. package/dist/frontend/lowering/optional-arguments.js +67 -1
  91. package/dist/frontend/lowering/optional-arguments.js.map +1 -1
  92. package/dist/frontend/lowering/pairs-snapshot.d.ts +3 -1
  93. package/dist/frontend/lowering/pairs-snapshot.js +12 -4
  94. package/dist/frontend/lowering/pairs-snapshot.js.map +1 -1
  95. package/dist/frontend/lowering/surfaces.d.ts +10 -14
  96. package/dist/frontend/lowering/surfaces.js +50 -28
  97. package/dist/frontend/lowering/surfaces.js.map +1 -1
  98. package/dist/frontend/npm-static-prune.d.ts +6 -0
  99. package/dist/frontend/npm-static-prune.js +203 -0
  100. package/dist/frontend/npm-static-prune.js.map +1 -0
  101. package/dist/frontend/npm-static-prune.test.d.ts +1 -0
  102. package/dist/frontend/npm-static-prune.test.js +48 -0
  103. package/dist/frontend/npm-static-prune.test.js.map +1 -0
  104. package/dist/frontend/npm.js +7 -6
  105. package/dist/frontend/npm.js.map +1 -1
  106. package/dist/frontend/program.js +15 -4
  107. package/dist/frontend/program.js.map +1 -1
  108. package/dist/frontend/provenance.js +8 -3
  109. package/dist/frontend/provenance.js.map +1 -1
  110. package/dist/frontend/type-mapper.js +16 -5
  111. package/dist/frontend/type-mapper.js.map +1 -1
  112. package/dist/index.d.ts +3 -3
  113. package/dist/index.js +13 -41
  114. package/dist/index.js.map +1 -1
  115. package/dist/ir/analysis.d.ts +5 -3
  116. package/dist/ir/analysis.js +25 -11
  117. package/dist/ir/analysis.js.map +1 -1
  118. package/dist/ir/analysis.test.js +50 -2
  119. package/dist/ir/analysis.test.js.map +1 -1
  120. package/dist/ir/constant-tables.d.ts +17 -0
  121. package/dist/ir/constant-tables.js +99 -0
  122. package/dist/ir/constant-tables.js.map +1 -0
  123. package/dist/ir/constant-tables.test.d.ts +1 -0
  124. package/dist/ir/constant-tables.test.js +78 -0
  125. package/dist/ir/constant-tables.test.js.map +1 -0
  126. package/dist/ir/integer-ranges.d.ts +13 -0
  127. package/dist/ir/integer-ranges.js +122 -0
  128. package/dist/ir/integer-ranges.js.map +1 -0
  129. package/dist/ir/integer-ranges.test.d.ts +1 -0
  130. package/dist/ir/integer-ranges.test.js +63 -0
  131. package/dist/ir/integer-ranges.test.js.map +1 -0
  132. package/dist/ir/ir.d.ts +56 -35
  133. package/dist/ir/ir.js +9 -1
  134. package/dist/ir/ir.js.map +1 -1
  135. package/dist/ir/ir.test.js +6 -0
  136. package/dist/ir/ir.test.js.map +1 -1
  137. package/dist/ir/scalar-records.d.ts +8 -0
  138. package/dist/ir/scalar-records.js +216 -0
  139. package/dist/ir/scalar-records.js.map +1 -0
  140. package/dist/ir/scalar-records.test.d.ts +1 -0
  141. package/dist/ir/scalar-records.test.js +142 -0
  142. package/dist/ir/scalar-records.test.js.map +1 -0
  143. package/dist/ir/validate.js +135 -34
  144. package/dist/ir/validate.js.map +1 -1
  145. package/dist/ir/validate.test.d.ts +1 -0
  146. package/dist/ir/validate.test.js +48 -0
  147. package/dist/ir/validate.test.js.map +1 -0
  148. package/dist/library/cache-primitives.d.ts +1 -1
  149. package/dist/library/cache-primitives.js +3 -3
  150. package/dist/library/cache-primitives.js.map +1 -1
  151. package/dist/library/fence-eval.js +11 -4
  152. package/dist/library/fence-eval.js.map +1 -1
  153. package/dist/library/library-cache.js +0 -1
  154. package/dist/library/library-cache.js.map +1 -1
  155. package/dist/library/library-cache.test.js +3 -3
  156. package/dist/library/library-cache.test.js.map +1 -1
  157. package/package.json +17 -17
  158. package/surface-manifest.json +90 -45
@@ -14,8 +14,36 @@ import { islandPrimitiveExit, lowerDynDispatchMethodCall } from "./lower-calls.j
14
14
  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
- import { boolLit, countedFor, numLit, varRef } from "../../ir/build.js";
18
- import { lowerOptionalArgument } from "./optional-arguments.js";
17
+ import { boolLit, countedFor, numLit, strLit, varRef } from "../../ir/build.js";
18
+ import { defaultAfterUndefined, lowerPositionArgument, lowerStaticallyUndefinedArgument, positionNumber } from "./optional-arguments.js";
19
+ import { lowerArrayCopyWithin, lowerArrayFill } from "./array-indexed-mutation.js";
20
+ function primitivePositionType(lowerer, type) {
21
+ if (type.kind === "union") {
22
+ return lowerer.unions.get(type.unionId)?.arms.every((arm) => primitivePositionType(lowerer, arm)) ?? false;
23
+ }
24
+ return type.kind === "f64" || type.kind === "string" || type.kind === "bool" ||
25
+ type.kind === "nullT" || type.kind === "undefinedT";
26
+ }
27
+ function lowerArrayPosition(lowerer, node, defaultValue, subject) {
28
+ if (!node)
29
+ return defaultValue;
30
+ if (ts.isSpreadElement(node))
31
+ lowerer.noLowering(`${subject} with a spread argument`, node);
32
+ const value = lowerPositionArgument(lowerer, node, defaultValue);
33
+ if (!primitivePositionType(lowerer, value.type)) {
34
+ lowerer.noLowering(`${subject} of '${lowerer.fmt(value.type)}' values`, node);
35
+ }
36
+ const loc = locOf(node);
37
+ const local = lowerer.declareHiddenLocal("%arrayPosition", value.type);
38
+ const ref = varRef(local.id, value.type, loc);
39
+ return {
40
+ kind: "seqExpr",
41
+ stmts: [{ kind: "varDecl", localId: local.id, init: value, loc }],
42
+ result: positionNumber(lowerer, ref, defaultValue, node, subject),
43
+ type: F64,
44
+ loc,
45
+ };
46
+ }
19
47
  /** Build the argument array for a mutating/copying operation while keeping
20
48
  * the receiver's scalar payload ABI. A missing read is represented as the
21
49
  * present UNDEFINED state in this temporary, so push/unshift and toSpliced
@@ -42,6 +70,68 @@ function lowerArrayValueItems(lowerer, values, elem, arrType, loc) {
42
70
  }
43
71
  return { kind: "seqExpr", stmts: body, result: outRef, type: arrType, loc };
44
72
  }
73
+ function lowerArraySpreadItems(lowerer, nodes, elem, arrType, loc) {
74
+ const out = lowerer.declareHiddenLocal("%arrayItems", arrType);
75
+ const outRef = varRef(out.id, arrType, loc);
76
+ const length = () => ({ kind: "arrIntrinsic", method: "length", receiver: outRef, args: [], type: F64, loc });
77
+ const body = [
78
+ { kind: "varDecl", localId: out.id, init: { kind: "arrayLit", elems: [], type: arrType, loc }, loc },
79
+ ];
80
+ for (const node of nodes) {
81
+ if (ts.isSpreadElement(node)) {
82
+ const source = lowerer.lowerExpr(node.expression);
83
+ if (!typeEquals(source.type, arrType)) {
84
+ lowerer.noLowering(`Array insertion spread from '${lowerer.fmt(source.type)}'`, node);
85
+ }
86
+ body.push({
87
+ kind: "exprStmt",
88
+ expr: { kind: "arrIntrinsic", method: "pushSpread", receiver: outRef, args: [source], type: F64, loc },
89
+ loc,
90
+ });
91
+ }
92
+ else {
93
+ const value = lowerer.lowerExpr(node);
94
+ const stored = lowerer.runtimeOptionalWidening(value.type, elem) !== null
95
+ ? value : lowerer.coerceInto(node, value, elem);
96
+ body.push(arrayValueStore(lowerer, outRef, length(), stored, elem, loc));
97
+ }
98
+ }
99
+ return { kind: "seqExpr", stmts: body, result: outRef, type: arrType, loc };
100
+ }
101
+ function lowerArrayJoinSeparator(lowerer, node, loc) {
102
+ const comma = strLit(",", loc);
103
+ if (!node)
104
+ return comma;
105
+ const undefinedValue = lowerStaticallyUndefinedArgument(lowerer, node);
106
+ if (undefinedValue)
107
+ return defaultAfterUndefined(undefinedValue, comma);
108
+ const value = lowerer.lowerExpr(node);
109
+ if (value.type.kind === "nullT")
110
+ return defaultAfterUndefined(value, strLit("null", loc));
111
+ if (value.type.kind === "union") {
112
+ const undefinedTag = lowerer.armTag(value.type.unionId, UNDEFINED_T);
113
+ if (undefinedTag >= 0) {
114
+ const local = lowerer.declareHiddenLocal("%joinSeparator", value.type);
115
+ const ref = varRef(local.id, value.type, loc);
116
+ return {
117
+ kind: "seqExpr",
118
+ stmts: [{ kind: "varDecl", localId: local.id, init: value, loc }],
119
+ result: {
120
+ kind: "ternary",
121
+ cond: { kind: "unionIsTag", unionId: value.type.unionId, tag: undefinedTag, value: ref, negated: false, type: BOOL, loc },
122
+ then: comma,
123
+ else_: lowerer.ensureString(ref, node),
124
+ type: STRING, loc,
125
+ },
126
+ type: STRING, loc,
127
+ };
128
+ }
129
+ }
130
+ if (value.type.kind === "dyn" || value.type.kind === "jsval") {
131
+ lowerer.noLowering(".join separator with a runtime-dependent undefined value", node);
132
+ }
133
+ return lowerer.ensureString(value, node);
134
+ }
45
135
  /** Callback-driven array producers bypass mapType's ordinary T[] mapping:
46
136
  * the callback is lowered first, then the helper's result array is built
47
137
  * directly from its IR return type. Fence element kinds ScrArr cannot hold
@@ -181,24 +271,51 @@ export function lowerArrayMethodCall(lowerer, call, access) {
181
271
  loc,
182
272
  };
183
273
  }
274
+ if (name === "fill") {
275
+ if (call.arguments.length > 3 || call.arguments.some(ts.isSpreadElement)) {
276
+ lowerer.noLowering(`.fill with ${call.arguments.length} arguments`, call);
277
+ }
278
+ const value = call.arguments[0] ? lowerer.lowerExpr(call.arguments[0]) : null;
279
+ if (value && (value.type.kind === "dyn" || value.type.kind === "jsval") &&
280
+ isRefCounted(elem) && elem.kind !== "string" && !typeEquals(value.type, elem)) {
281
+ lowerer.noLowering(".fill of a checked-dynamic reference into a static array", call.arguments[0]);
282
+ }
283
+ const writeUndefined = value === null || value.type.kind === "undefinedT";
284
+ return lowerArrayFill(lowerer, lowerer.lowerExpr(access.expression), value, writeUndefined, lowerArrayPosition(lowerer, call.arguments[1], numLit(0, loc), "array fill start"), lowerArrayPosition(lowerer, call.arguments[2], numLit(Infinity, loc), "array fill end"), receiverIr, loc);
285
+ }
286
+ if (name === "copyWithin") {
287
+ if (call.arguments.length > 3 || call.arguments.some(ts.isSpreadElement)) {
288
+ lowerer.noLowering(`.copyWithin with ${call.arguments.length} arguments`, call);
289
+ }
290
+ return lowerArrayCopyWithin(lowerer, lowerer.lowerExpr(access.expression), lowerArrayPosition(lowerer, call.arguments[0], numLit(0, loc), "array copyWithin target"), lowerArrayPosition(lowerer, call.arguments[1], numLit(0, loc), "array copyWithin start"), lowerArrayPosition(lowerer, call.arguments[2], numLit(Infinity, loc), "array copyWithin end"), receiverIr, loc);
291
+ }
184
292
  if (name === "with") {
185
293
  if (call.arguments.length !== 2 || call.arguments.some(ts.isSpreadElement)) {
186
294
  lowerer.noLowering(`.with with ${call.arguments.length} arguments`, call);
187
295
  }
188
296
  const receiver = lowerer.lowerExpr(access.expression);
189
- const index = lowerer.lowerExprExpecting(call.arguments[0], F64);
297
+ const index = lowerPositionArgument(lowerer, call.arguments[0], numLit(0, loc));
298
+ if (!primitivePositionType(lowerer, index.type)) {
299
+ lowerer.noLowering(`.with index of '${lowerer.fmt(index.type)}' values`, call.arguments[0]);
300
+ }
190
301
  const value = lowerer.lowerExpr(call.arguments[1]);
302
+ const receiverLocal = lowerer.declareHiddenLocal("%withRecv", receiverIr);
303
+ const indexLocal = lowerer.declareHiddenLocal("%withIndex", index.type);
304
+ const valueLocal = lowerer.declareHiddenLocal("%withValue", value.type);
305
+ const receiverRef = varRef(receiverLocal.id, receiverIr, loc);
306
+ const indexRef = varRef(indexLocal.id, index.type, loc);
307
+ const valueRef = varRef(valueLocal.id, value.type, loc);
308
+ const numericIndex = positionNumber(lowerer, indexRef, numLit(0, loc), call.arguments[0], "array with index");
309
+ const stmts = [
310
+ { kind: "varDecl", localId: receiverLocal.id, init: receiver, loc },
311
+ { kind: "varDecl", localId: indexLocal.id, init: index, loc },
312
+ { kind: "varDecl", localId: valueLocal.id, init: value, loc },
313
+ ];
191
314
  if (value.type.kind === "union" && lowerer.runtimeOptionalWidening(value.type, elem) !== null) {
192
315
  // `.with()` validates its index and returns a fresh copy. Stabilize
193
316
  // all three operands, then select the scalar replacement or the
194
317
  // present-undefined copy without evaluating a read twice.
195
- const receiverLocal = lowerer.declareHiddenLocal("%withRecv", receiverIr);
196
- const indexLocal = lowerer.declareHiddenLocal("%withIndex", F64);
197
- const valueLocal = lowerer.declareHiddenLocal("%withValue", value.type);
198
318
  const unionId = value.type.unionId;
199
- const receiverRef = varRef(receiverLocal.id, receiverIr, loc);
200
- const indexRef = varRef(indexLocal.id, F64, loc);
201
- const valueRef = varRef(valueLocal.id, value.type, loc);
202
319
  const undefTag = lowerer.armTag(unionId, UNDEFINED_T);
203
320
  const condition = {
204
321
  kind: "unionIsTag",
@@ -213,7 +330,7 @@ export function lowerArrayMethodCall(lowerer, call, access) {
213
330
  kind: "arrIntrinsic",
214
331
  method: "withUndefined",
215
332
  receiver: receiverRef,
216
- args: [indexRef],
333
+ args: [numericIndex],
217
334
  type: receiverIr,
218
335
  loc,
219
336
  };
@@ -221,62 +338,45 @@ export function lowerArrayMethodCall(lowerer, call, access) {
221
338
  kind: "arrIntrinsic",
222
339
  method: "with",
223
340
  receiver: receiverRef,
224
- args: [indexRef, lowerer.coerceToExpected(valueRef, elem)],
341
+ args: [numericIndex, lowerer.coerceToExpected(valueRef, elem)],
225
342
  type: receiverIr,
226
343
  loc,
227
344
  };
228
345
  return {
229
346
  kind: "seqExpr",
230
- stmts: [
231
- { kind: "varDecl", localId: receiverLocal.id, init: receiver, loc },
232
- { kind: "varDecl", localId: indexLocal.id, init: index, loc },
233
- { kind: "varDecl", localId: valueLocal.id, init: value, loc },
234
- ],
347
+ stmts,
235
348
  result: { kind: "ternary", cond: condition, then: missing, else_: present, type: receiverIr, loc },
236
349
  type: receiverIr,
237
350
  loc,
238
351
  };
239
352
  }
240
- const coercedValue = lowerer.coerceInto(call.arguments[1], value, elem);
353
+ const coercedValue = lowerer.coerceInto(call.arguments[1], valueRef, elem);
241
354
  return {
242
- kind: "arrIntrinsic",
243
- method: "with",
244
- receiver,
245
- args: [index, coercedValue],
355
+ kind: "seqExpr",
356
+ stmts,
357
+ result: { kind: "arrIntrinsic", method: "with", receiver: receiverRef, args: [numericIndex, coercedValue], type: receiverIr, loc },
246
358
  type: receiverIr,
247
359
  loc,
248
360
  };
249
361
  }
250
362
  if (name === "toSpliced") {
251
- if (call.arguments.some(ts.isSpreadElement)) {
252
- lowerer.unsupported("SC1090", call, "spread arguments to Array.toSpliced");
253
- }
254
363
  const receiver = lowerer.lowerExpr(access.expression);
255
- const start = call.arguments[0]
256
- ? lowerer.lowerExprExpecting(call.arguments[0], F64)
257
- : { kind: "numLit", value: 0, type: F64, loc };
258
- const deleteCountDefault = {
259
- kind: "numLit",
260
- value: NaN,
261
- type: F64,
262
- loc,
263
- };
264
- const deleteCount = call.arguments.length === 0
265
- ? { kind: "numLit", value: 0, type: F64, loc }
266
- : call.arguments[1]
267
- ? lowerOptionalArgument(lowerer, call.arguments[1], F64, deleteCountDefault)
268
- : { kind: "numLit", value: Infinity, type: F64, loc };
364
+ const start = lowerArrayPosition(lowerer, call.arguments[0], numLit(0, loc), "array toSpliced start");
365
+ const deleteCount = lowerArrayPosition(lowerer, call.arguments[1], numLit(call.arguments.length === 1 ? Infinity : 0, loc), "array toSpliced deleteCount");
269
366
  const itemNodes = call.arguments.slice(2);
270
- const itemProbes = itemNodes.map((arg) => tryLowerExpression(lowerer, arg));
367
+ const hasSpread = itemNodes.some(ts.isSpreadElement);
368
+ const itemProbes = hasSpread ? [] : itemNodes.map((arg) => tryLowerExpression(lowerer, arg));
271
369
  const statefulItems = itemProbes.some((probe) => probe !== null && lowerer.runtimeOptionalWidening(probe.type, elem) !== null);
272
- const items = statefulItems
273
- ? lowerArrayValueItems(lowerer, itemNodes.map((arg) => lowerer.lowerExpr(arg)), elem, receiverIr, loc)
274
- : {
275
- kind: "arrayLit",
276
- elems: itemNodes.map((arg) => lowerer.coerceInto(arg, lowerer.lowerExpr(arg), elem)),
277
- type: receiverIr,
278
- loc,
279
- };
370
+ const items = hasSpread
371
+ ? lowerArraySpreadItems(lowerer, itemNodes, elem, receiverIr, loc)
372
+ : statefulItems
373
+ ? lowerArrayValueItems(lowerer, itemNodes.map((arg) => lowerer.lowerExpr(arg)), elem, receiverIr, loc)
374
+ : {
375
+ kind: "arrayLit",
376
+ elems: itemNodes.map((arg) => lowerer.coerceInto(arg, lowerer.lowerExpr(arg), elem)),
377
+ type: receiverIr,
378
+ loc,
379
+ };
280
380
  return {
281
381
  kind: "arrIntrinsic",
282
382
  method: "toSpliced",
@@ -286,32 +386,27 @@ export function lowerArrayMethodCall(lowerer, call, access) {
286
386
  loc,
287
387
  };
288
388
  }
289
- // The lib declares wider call forms than the lowered surface —
290
- // indexOf/includes take a fromIndex, join's separator is optional, the
389
+ if (name === "flat")
390
+ return lowerArrayFlatCall(lowerer, call, access, receiverIr);
391
+ // The lib declares wider call forms than the lowered surface — the
291
392
  // predicate/mapping HOFs take a thisArg. Each unlowered form is fenced
292
393
  // per site (SC2020), never silently truncated to the supported
293
394
  // arguments. push/unshift lower every declared form (variadic, 0 args
294
395
  // included — Node returns the unchanged length); reduce/reduceRight
295
396
  // lower both declared forms (with and without an initial value).
296
397
  const arity = {
297
- push: [0, Number.MAX_SAFE_INTEGER], unshift: [0, Number.MAX_SAFE_INTEGER], pop: [0, 0], indexOf: [1, 1], includes: [1, 1], join: [1, 1],
398
+ 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: [0, 1],
298
399
  concat: [0, Number.MAX_SAFE_INTEGER],
299
- slice: [0, 2], shift: [0, 0], splice: [1, 2], at: [1, 1],
400
+ slice: [0, 2], shift: [0, 0], splice: [0, Number.MAX_SAFE_INTEGER], at: [0, 1],
300
401
  map: [1, 1], filter: [1, 1], forEach: [1, 1], find: [1, 1], findIndex: [1, 1], some: [1, 1],
301
402
  findLast: [1, 1], findLastIndex: [1, 1],
302
403
  every: [1, 1], flatMap: [1, 1], reduce: [1, 2], reduceRight: [1, 2],
303
404
  }[name];
304
405
  if (call.arguments.length < arity[0] || call.arguments.length > arity[1]) {
305
- const hint = name === "splice"
306
- ? "the removal forms lower — splice(start, deleteCount?); to insert, build a new array with slice and push"
307
- : name === "join"
308
- ? 'pass the separator explicitly: join(",")'
309
- : name === "indexOf" || name === "includes"
310
- ? "the fromIndex parameter has no lowering — slice first, or loop"
311
- : name === "map" || name === "filter" || name === "forEach" ||
312
- name === "find" || name === "some" || name === "every" || name === "flatMap"
313
- ? "the thisArg parameter has no lowering — use an arrow function"
314
- : undefined;
406
+ const hint = name === "map" || name === "filter" || name === "forEach" ||
407
+ name === "find" || name === "some" || name === "every" || name === "flatMap"
408
+ ? "the thisArg parameter has no lowering — use an arrow function"
409
+ : undefined;
315
410
  lowerer.noLowering(`.${name} with ${call.arguments.length} argument${call.arguments.length === 1 ? "" : "s"}`, call, hint);
316
411
  }
317
412
  if (name === "push" || name === "unshift") {
@@ -393,7 +488,7 @@ export function lowerArrayMethodCall(lowerer, call, access) {
393
488
  loc,
394
489
  };
395
490
  }
396
- if (name === "indexOf" || name === "includes") {
491
+ if (name === "indexOf" || name === "lastIndexOf" || name === "includes") {
397
492
  return lowerArraySearchCall(lowerer, call, access, name, elem);
398
493
  }
399
494
  if (name === "concat") {
@@ -452,33 +547,37 @@ export function lowerArrayMethodCall(lowerer, call, access) {
452
547
  return { kind: "call", callee: helper, args: [receiver, ...args], type: receiverIr, loc };
453
548
  }
454
549
  if (name === "slice") {
455
- // `a.slice(start?, end?)` — a fresh shallow copy of the index range,
456
- // JS-exact index handling (ToIntegerOrInfinity, negatives from the
457
- // end, clamping; omitted args are omitted from the IR — the backend
458
- // fills 0 / +Infinity, the string-slice convention). Ref elements
459
- // are RETAINED into the copy: the same references, exactly JS's
460
- // shallow copy. Every element kind slices — the receiver's own type
461
- // is the result type.
462
550
  const receiver = lowerer.lowerExpr(access.expression);
463
- const args = call.arguments.map((a) => lowerer.lowerExpr(a));
464
- for (let i = 0; i < args.length; i++) {
465
- if (args[i].type.kind !== "f64")
466
- lowerer.badType(call.arguments[i], lowerer.typeOf(call.arguments[i]));
467
- }
551
+ const args = [
552
+ lowerArrayPosition(lowerer, call.arguments[0], numLit(0, loc), "array slice start"),
553
+ lowerArrayPosition(lowerer, call.arguments[1], numLit(Infinity, loc), "array slice end"),
554
+ ];
468
555
  return { kind: "arrIntrinsic", method: "slice", receiver, args, type: receiverIr, loc };
469
556
  }
470
557
  if (name === "splice") {
471
- // The REMOVAL forms: splice(start) and splice(start, deleteCount) —
472
- // Node-exact relative/clamped indices, the removed elements back in
473
- // order (their ownership moves out of the receiver). Insertion
474
- // (3+ args) fenced by arity above.
475
558
  const receiver = lowerer.lowerExpr(access.expression);
476
- const args = call.arguments.map((a) => lowerer.lowerExpr(a));
477
- for (let i = 0; i < args.length; i++) {
478
- if (args[i].type.kind !== "f64")
479
- lowerer.badType(call.arguments[i], lowerer.typeOf(call.arguments[i]));
559
+ const args = [
560
+ lowerArrayPosition(lowerer, call.arguments[0], numLit(0, loc), "array splice start"),
561
+ lowerArrayPosition(lowerer, call.arguments[1], numLit(call.arguments.length === 1 ? Infinity : 0, loc), "array splice deleteCount"),
562
+ ];
563
+ if (call.arguments.length <= 2) {
564
+ return { kind: "arrIntrinsic", method: "splice", receiver, args, type: receiverIr, loc };
480
565
  }
481
- return { kind: "arrIntrinsic", method: "splice", receiver, args, type: receiverIr, loc };
566
+ const itemNodes = call.arguments.slice(2);
567
+ const hasSpread = itemNodes.some(ts.isSpreadElement);
568
+ const itemProbes = hasSpread ? [] : itemNodes.map((arg) => tryLowerExpression(lowerer, arg));
569
+ const statefulItems = itemProbes.some((probe) => probe !== null && lowerer.runtimeOptionalWidening(probe.type, elem) !== null);
570
+ const items = hasSpread
571
+ ? lowerArraySpreadItems(lowerer, itemNodes, elem, receiverIr, loc)
572
+ : statefulItems
573
+ ? lowerArrayValueItems(lowerer, itemNodes.map((arg) => lowerer.lowerExpr(arg)), elem, receiverIr, loc)
574
+ : {
575
+ kind: "arrayLit",
576
+ elems: itemNodes.map((arg) => lowerer.coerceInto(arg, lowerer.lowerExpr(arg), elem)),
577
+ type: receiverIr,
578
+ loc,
579
+ };
580
+ return { kind: "arrIntrinsic", method: "spliceInsert", receiver, args: [...args, items], type: receiverIr, loc };
482
581
  }
483
582
  if (name === "shift") {
484
583
  // JS shift exactly: undefined on an empty array, else the first
@@ -511,7 +610,7 @@ export function lowerArrayMethodCall(lowerer, call, access) {
511
610
  lowerer.unsupported("SC1090", call, "'.join()' on arrays of this element type (number, string, and boolean arrays join — unions of those with undefined/null arms too, the units printing empty like JS)");
512
611
  }
513
612
  const receiver = lowerer.lowerExpr(access.expression);
514
- const sep = lowerer.lowerExpr(call.arguments[0]);
613
+ const sep = lowerArrayJoinSeparator(lowerer, call.arguments[0], loc);
515
614
  return { kind: "arrIntrinsic", method: "join", receiver, args: [sep], type: STRING, loc };
516
615
  }
517
616
  if (name === "map" || name === "filter" || name === "forEach") {
@@ -528,17 +627,99 @@ export function lowerArrayMethodCall(lowerer, call, access) {
528
627
  // reduce / reduceRight
529
628
  return lowerArrayReduceCall(lowerer, call, access, name, elem);
530
629
  }
630
+ function literalFlatDepth(value) {
631
+ if (value.kind === "numLit")
632
+ return value.value;
633
+ if (value.kind === "strLit")
634
+ return Number(value.value);
635
+ if (value.kind === "boolLit")
636
+ return value.value ? 1 : 0;
637
+ if (value.kind === "unitLit")
638
+ return value.unit === "undefined" ? 1 : 0;
639
+ if (value.kind === "unary" && value.op === "-") {
640
+ const operand = literalFlatDepth(value.operand);
641
+ return operand === null ? null : -operand;
642
+ }
643
+ return null;
644
+ }
645
+ function lowerArrayFlatCall(lowerer, call, access, receiverType) {
646
+ const loc = locOf(call);
647
+ if (call.arguments.length > 1 || call.arguments.some(ts.isSpreadElement)) {
648
+ lowerer.noLowering(`.flat with ${call.arguments.length} arguments`, call);
649
+ }
650
+ const elemCouldNest = (elem) => elem.kind === "array" || elem.kind === "jsval" || elem.kind === "dyn" ||
651
+ (elem.kind === "union" && (lowerer.unions.get(elem.unionId)?.arms.some((arm) => arm.kind === "array" || arm.kind === "jsval" || arm.kind === "dyn") ?? true));
652
+ if (!elemCouldNest(receiverType.elem) && call.arguments[0]) {
653
+ const receiver = lowerer.lowerExpr(access.expression);
654
+ const depth = lowerArrayPosition(lowerer, call.arguments[0], numLit(1, loc), "array flat depth");
655
+ const local = lowerer.declareHiddenLocal("%flatReceiver", receiverType);
656
+ const ref = varRef(local.id, receiverType, loc);
657
+ return {
658
+ kind: "seqExpr",
659
+ stmts: [
660
+ { kind: "varDecl", localId: local.id, init: receiver, loc },
661
+ { kind: "exprStmt", expr: depth, loc },
662
+ ],
663
+ result: {
664
+ kind: "arrIntrinsic", method: "flatCopy", receiver: ref,
665
+ args: [{ kind: "arrayLit", elems: [], type: receiverType, loc }],
666
+ type: receiverType, loc,
667
+ },
668
+ type: receiverType, loc,
669
+ };
670
+ }
671
+ const depth = call.arguments[0] ? literalFlatDepth(lowerer.lowerExpr(call.arguments[0])) : 1;
672
+ if (depth === null)
673
+ lowerer.noLowering(".flat with a nonconstant depth", call);
674
+ let remaining = Number.isNaN(depth) || depth <= 0 ? 0 : Math.trunc(depth);
675
+ let result = lowerer.lowerExpr(access.expression);
676
+ let currentType = receiverType;
677
+ let flattened = false;
678
+ while (remaining > 0) {
679
+ const elem = currentType.elem;
680
+ if (elem.kind !== "array" && elemCouldNest(elem)) {
681
+ lowerer.noLowering(".flat over elements with runtime-dependent array shape", call);
682
+ }
683
+ if (elem.kind !== "array")
684
+ break;
685
+ result = {
686
+ kind: "arrIntrinsic", method: "flatOne", receiver: result,
687
+ args: [{ kind: "arrayLit", elems: [], type: elem, loc }],
688
+ type: elem, loc,
689
+ };
690
+ currentType = elem;
691
+ remaining--;
692
+ flattened = true;
693
+ }
694
+ if (!flattened) {
695
+ result = {
696
+ kind: "arrIntrinsic", method: "flatCopy", receiver: result,
697
+ args: [{ kind: "arrayLit", elems: [], type: currentType, loc }],
698
+ type: currentType, loc,
699
+ };
700
+ }
701
+ return result;
702
+ }
531
703
  /** `indexOf` and `includes` normally use the runtime's typed search helpers,
532
704
  * but an indexed array read is a `T | undefined` value. Keep that union
533
705
  * through a small lowered loop so the needle's missing state is observable:
534
- * indexOf skips holes, while includes treats holes as undefined. The same
535
- * loop also gives union-element arrays their value equality rather than
536
- * comparing the compiler's union boxes by pointer. */
706
+ * indexOf and lastIndexOf skip holes, while includes treats holes as
707
+ * undefined. The same loop also gives union-element arrays their value
708
+ * equality rather than comparing the compiler's union boxes by pointer. */
537
709
  function lowerArraySearchCall(lowerer, call, access, method, elem) {
538
710
  const loc = locOf(call);
539
711
  const receiver = lowerer.lowerExpr(access.expression);
540
- const argNode = call.arguments[0];
541
- let needle = lowerer.lowerExpr(argNode);
712
+ const argNode = call.arguments[0] ?? call;
713
+ if (call.arguments.some(ts.isSpreadElement))
714
+ lowerer.noLowering(`.${method} with spread arguments`, call);
715
+ let needle = call.arguments[0]
716
+ ? lowerer.lowerExpr(call.arguments[0])
717
+ : { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc };
718
+ const fromIndex = call.arguments[1]
719
+ ? lowerPositionArgument(lowerer, call.arguments[1], numLit(0, loc))
720
+ : method === "lastIndexOf"
721
+ ? { kind: "bin", op: "/", left: numLit(1, loc), right: numLit(0, loc), type: F64, loc }
722
+ : numLit(0, loc);
542
723
  // A derived CLASS VALUE against a base-classval element widens (the same
543
724
  // pointer — identity search is exact); the coercion path owns the ABI gate.
544
725
  if (needle.type.kind === "classval" && elem.kind === "classval" && !typeEquals(needle.type, elem)) {
@@ -546,21 +727,25 @@ function lowerArraySearchCall(lowerer, call, access, method, elem) {
546
727
  }
547
728
  // Preserve the existing fast path for an ordinary, non-union needle. Its
548
729
  // runtime implementation already has exact primitive/reference equality.
549
- if (elem.kind !== "union" && elem.kind !== "bigint" && typeEquals(needle.type, elem)) {
730
+ if (method !== "lastIndexOf" && call.arguments.length === 1 && elem.kind !== "union" && elem.kind !== "bigint" && typeEquals(needle.type, elem)) {
550
731
  return {
551
732
  kind: "arrIntrinsic",
552
733
  method,
553
734
  receiver,
554
735
  args: [needle],
555
- type: method === "indexOf" ? F64 : BOOL,
736
+ type: method === "includes" ? BOOL : F64,
556
737
  loc,
557
738
  };
558
739
  }
559
- const valueT = arrayValueType(lowerer, elem);
740
+ let valueT = arrayValueType(lowerer, elem);
560
741
  // jsval/dyn arrays have a single engine value representation rather than a
561
742
  // static undefined arm. Keep their existing runtime path and its explicit
562
743
  // type fence; typed static arrays take the state-aware helper below.
563
744
  if (valueT.kind !== "union") {
745
+ if (method === "lastIndexOf")
746
+ lowerer.noLowering(".lastIndexOf on an array of engine values", call);
747
+ if (call.arguments.length > 1)
748
+ lowerer.noLowering(`.${method} with fromIndex on '${lowerer.fmt(elem)}' elements`, call);
564
749
  if (!typeEquals(needle.type, elem))
565
750
  lowerer.badType(argNode, lowerer.typeOf(argNode));
566
751
  return {
@@ -568,15 +753,38 @@ function lowerArraySearchCall(lowerer, call, access, method, elem) {
568
753
  method,
569
754
  receiver,
570
755
  args: [needle],
571
- type: method === "indexOf" ? F64 : BOOL,
756
+ type: method === "includes" ? BOOL : F64,
572
757
  loc,
573
758
  };
574
759
  }
575
- const searchNeedle = lowerer.coerceInto(argNode, needle, valueT);
576
- if (!typeEquals(searchNeedle.type, valueT))
760
+ // Search uses strict equality, never conversion to the array's element
761
+ // type. Retain primitive needles of a different kind in the comparison
762
+ // union so a nonmatching kind returns a miss instead of throwing.
763
+ const needleArms = needle.type.kind === "union"
764
+ ? lowerer.unions.get(needle.type.unionId).arms
765
+ : [needle.type];
766
+ const scalarNeedle = needleArms.every((arm) => arm.kind === "f64" || arm.kind === "string" || arm.kind === "bool" ||
767
+ arm.kind === "bigint" || arm.kind === "symbol" || isUnitType(arm));
768
+ const valueArms = lowerer.unions.get(valueT.unionId).arms;
769
+ if (scalarNeedle && !valueArms.some((arm) => arm.kind === "func" || arm.kind === "set")) {
770
+ const arms = [...valueArms];
771
+ for (const arm of needleArms)
772
+ if (!arms.some((existing) => typeEquals(existing, arm)))
773
+ arms.push(arm);
774
+ if (arms.length !== valueArms.length)
775
+ valueT = { kind: "union", unionId: lowerer.unions.intern(arms) };
776
+ }
777
+ const directNeedle = method === "lastIndexOf" && elem.kind !== "union" && typeEquals(needle.type, elem);
778
+ const searchNeedle = directNeedle ? needle : lowerer.coerceInto(argNode, needle, valueT);
779
+ // Checked dynamic extraction can copy a composite. Its new identity cannot
780
+ // be compared to an array element as though it were the original value.
781
+ if (method === "lastIndexOf" && searchNeedle.kind === "dynCheck" && (elem.kind === "record" || elem.kind === "array")) {
782
+ lowerer.noLowering(".lastIndexOf with a checked dynamic reference needle", call);
783
+ }
784
+ if (!directNeedle && !typeEquals(searchNeedle.type, valueT))
577
785
  lowerer.badType(argNode, lowerer.typeOf(argNode));
578
- const helper = arraySearchHelper(lowerer, method, elem, valueT, loc);
579
- return { kind: "call", callee: helper, args: [receiver, searchNeedle], type: method === "indexOf" ? F64 : BOOL, loc };
786
+ const helper = arraySearchHelper(lowerer, method, elem, valueT, searchNeedle.type, directNeedle, fromIndex.type, call.arguments[1] ?? call, loc);
787
+ return { kind: "call", callee: helper, args: [receiver, searchNeedle, fromIndex], type: method === "includes" ? BOOL : F64, loc };
580
788
  }
581
789
  /** SameValueZero over a static value union. `unionEq` supplies strict
582
790
  * equality for every arm; the extra f64-arm test is the only difference for
@@ -620,25 +828,65 @@ function arraySearchEquality(lowerer, left, right, sameValueZero, loc) {
620
828
  };
621
829
  return { kind: "logical", op: "||", left: strict, right: bothNaN, type: BOOL, loc };
622
830
  }
623
- function arraySearchHelper(lowerer, method, elem, valueT, loc) {
624
- const key = `${method}:${typeKey(elem)}:${typeKey(valueT)}`;
831
+ function arraySearchHelper(lowerer, method, elem, valueT, needleT, directNeedle, fromT, fromNode, loc) {
832
+ const key = `${method}:${typeKey(elem)}:${typeKey(valueT)}:${typeKey(needleT)}:${directNeedle}:${typeKey(fromT)}`;
625
833
  const existing = lowerer.arrHofHelpers.get(key);
626
834
  if (existing)
627
835
  return existing;
628
836
  const name = `%arr.${method}.${lowerer.arrHofHelpers.size}`;
629
837
  lowerer.arrHofHelpers.set(key, name);
630
838
  const arrT = arrayOf(elem);
631
- const resultT = method === "indexOf" ? F64 : BOOL;
839
+ const resultT = method === "includes" ? BOOL : F64;
632
840
  const arrRef = varRef("a.0", arrT, loc);
633
841
  const indexRef = varRef("i.0", F64, loc);
634
- const needleRef = varRef("needle.0", valueT, loc);
842
+ const needleRef = varRef("needle.0", needleT, loc);
635
843
  const stateRef = varRef("state.0", F64, loc);
636
844
  const nextRef = varRef("next.0", F64, loc);
845
+ const fromRef = varRef("from.0", fromT, loc);
846
+ const numberRef = varRef("number.0", F64, loc);
847
+ const integerRef = varRef("integer.0", F64, loc);
848
+ // Snapshot length before coercing fromIndex, then apply ToIntegerOrInfinity.
849
+ const number = positionNumber(lowerer, fromRef, numLit(0, loc), fromNode, "array search position");
850
+ const integer = {
851
+ kind: "ternary",
852
+ cond: { kind: "libCall", fn: "num.isNaN", args: [numberRef], type: BOOL, loc },
853
+ then: numLit(0, loc),
854
+ else_: { kind: "libCall", fn: "math.trunc", args: [numberRef], type: F64, loc },
855
+ type: F64,
856
+ loc,
857
+ };
858
+ const forwardStart = {
859
+ kind: "ternary",
860
+ cond: { kind: "bin", op: "<", left: integerRef, right: numLit(0, loc), type: BOOL, loc },
861
+ then: {
862
+ kind: "libCall", fn: "math.max",
863
+ args: [{ kind: "bin", op: "+", left: varRef("n.0", F64, loc), right: integerRef, type: F64, loc }, numLit(0, loc)],
864
+ type: F64, loc,
865
+ },
866
+ // Adding positive zero canonicalizes a truncated negative zero.
867
+ else_: { kind: "bin", op: "+", left: integerRef, right: numLit(0, loc), type: F64, loc },
868
+ type: F64,
869
+ loc,
870
+ };
871
+ const start = method === "lastIndexOf" ? {
872
+ kind: "ternary",
873
+ cond: { kind: "bin", op: "<", left: integerRef, right: numLit(0, loc), type: BOOL, loc },
874
+ then: { kind: "bin", op: "+", left: varRef("n.0", F64, loc), right: integerRef, type: F64, loc },
875
+ else_: {
876
+ kind: "libCall", fn: "math.min",
877
+ args: [
878
+ { kind: "bin", op: "+", left: integerRef, right: numLit(0, loc), type: F64, loc },
879
+ { kind: "bin", op: "-", left: varRef("n.0", F64, loc), right: numLit(1, loc), type: F64, loc },
880
+ ],
881
+ type: F64, loc,
882
+ },
883
+ type: F64, loc,
884
+ } : forwardStart;
637
885
  const rawRead = { kind: "arrayGet", arr: arrRef, index: indexRef, type: elem, loc };
638
886
  const missing = lowerer.wrappedUndefined(valueT, loc);
639
887
  if (!missing)
640
888
  throw new InternalCompilerError("array search helper needs an undefined arm");
641
- const value = {
889
+ const optionalValue = {
642
890
  kind: "ternary",
643
891
  cond: { kind: "bin", op: "===", left: stateRef, right: numLit(1, loc), type: BOOL, loc },
644
892
  then: typeEquals(elem, valueT) ? rawRead : lowerer.coerceToExpected(rawRead, valueT),
@@ -646,12 +894,14 @@ function arraySearchHelper(lowerer, method, elem, valueT, loc) {
646
894
  type: valueT,
647
895
  loc,
648
896
  };
649
- const equal = arraySearchEquality(lowerer, varRef("value.0", valueT, loc), needleRef, method === "includes", loc);
650
- const match = method === "indexOf"
897
+ const value = directNeedle ? rawRead : optionalValue;
898
+ const readT = directNeedle ? elem : valueT;
899
+ const equal = arraySearchEquality(lowerer, varRef("value.0", readT, loc), needleRef, method === "includes", loc);
900
+ const match = method !== "includes"
651
901
  ? {
652
902
  kind: "logical",
653
903
  op: "&&",
654
- left: { kind: "bin", op: "!==", left: stateRef, right: numLit(0, loc), type: BOOL, loc },
904
+ left: { kind: "bin", op: directNeedle ? "===" : "!==", left: stateRef, right: numLit(directNeedle ? 1 : 0, loc), type: BOOL, loc },
655
905
  right: equal,
656
906
  type: BOOL,
657
907
  loc,
@@ -660,7 +910,7 @@ function arraySearchHelper(lowerer, method, elem, valueT, loc) {
660
910
  const undefinedTag = lowerer.armTag(valueT.unionId, UNDEFINED_T);
661
911
  if (undefinedTag < 0)
662
912
  throw new InternalCompilerError("array search helper needs an undefined tag");
663
- const needleIsUndefined = {
913
+ const needleIsUndefined = directNeedle ? boolLit(false, loc) : {
664
914
  kind: "unionIsTag",
665
915
  unionId: valueT.unionId,
666
916
  tag: undefinedTag,
@@ -672,7 +922,7 @@ function arraySearchHelper(lowerer, method, elem, valueT, loc) {
672
922
  const advance = {
673
923
  kind: "assign",
674
924
  localId: "i.0",
675
- value: { kind: "bin", op: "+", left: indexRef, right: numLit(1, loc), type: F64, loc },
925
+ value: { kind: "bin", op: method === "lastIndexOf" ? "-" : "+", left: indexRef, right: numLit(1, loc), type: F64, loc },
676
926
  loc,
677
927
  };
678
928
  const visitPresent = [
@@ -680,13 +930,13 @@ function arraySearchHelper(lowerer, method, elem, valueT, loc) {
680
930
  {
681
931
  kind: "if",
682
932
  cond: match,
683
- then: [{ kind: "return", value: method === "indexOf" ? indexRef : boolLit(true, loc), loc }],
933
+ then: [{ kind: "return", value: method === "includes" ? boolLit(true, loc) : indexRef, loc }],
684
934
  else_: null,
685
935
  loc,
686
936
  },
687
937
  advance,
688
938
  ];
689
- const skipHole = [
939
+ const skipHole = method === "lastIndexOf" ? [advance] : [
690
940
  {
691
941
  kind: "varDecl",
692
942
  localId: "next.0",
@@ -712,28 +962,36 @@ function arraySearchHelper(lowerer, method, elem, valueT, loc) {
712
962
  name,
713
963
  params: [
714
964
  { localId: "a.0", name: "a", type: arrT },
715
- { localId: "needle.0", name: "needle", type: valueT },
965
+ { localId: "needle.0", name: "needle", type: needleT },
966
+ { localId: "from.0", name: "from", type: fromT },
716
967
  ],
717
968
  returnType: resultT,
718
969
  locals: [
719
970
  { id: "a.0", name: "a", type: arrT, mutable: true },
720
- { id: "needle.0", name: "needle", type: valueT, mutable: true },
971
+ { id: "needle.0", name: "needle", type: needleT, mutable: true },
972
+ { id: "from.0", name: "from", type: fromT, mutable: false },
973
+ { id: "number.0", name: "number", type: F64, mutable: false },
974
+ { id: "integer.0", name: "integer", type: F64, mutable: false },
721
975
  { id: "n.0", name: "n", type: F64, mutable: false },
722
976
  { id: "i.0", name: "i", type: F64, mutable: true },
723
977
  { id: "state.0", name: "state", type: F64, mutable: false },
724
978
  { id: "next.0", name: "next", type: F64, mutable: false },
725
- { id: "value.0", name: "value", type: valueT, mutable: false },
979
+ { id: "value.0", name: "value", type: readT, mutable: false },
726
980
  ],
727
981
  body: [
728
982
  readLenStmt(arrT, loc),
729
- { kind: "varDecl", localId: "i.0", init: numLit(0, loc), loc },
983
+ { kind: "varDecl", localId: "number.0", init: number, loc },
984
+ { kind: "varDecl", localId: "integer.0", init: integer, loc },
985
+ { kind: "varDecl", localId: "i.0", init: start, loc },
730
986
  {
731
987
  kind: "while",
732
- cond: { kind: "bin", op: "<", left: indexRef, right: varRef("n.0", F64, loc), type: BOOL, loc },
988
+ cond: method === "lastIndexOf"
989
+ ? { kind: "bin", op: ">=", left: indexRef, right: numLit(0, loc), type: BOOL, loc }
990
+ : { kind: "bin", op: "<", left: indexRef, right: varRef("n.0", F64, loc), type: BOOL, loc },
733
991
  body: loopBody,
734
992
  loc,
735
993
  },
736
- { kind: "return", value: method === "indexOf" ? numLit(-1, loc) : boolLit(false, loc), loc },
994
+ { kind: "return", value: method === "includes" ? boolLit(false, loc) : numLit(-1, loc), loc },
737
995
  ],
738
996
  loc,
739
997
  });
@@ -1291,15 +1549,8 @@ export function lowerTupleReadMethodCall(lowerer, call, access) {
1291
1549
  const helper = arrayHofHelper(lowerer, "map", snapshotElem, fnRet, arity, loc, outElem);
1292
1550
  return { kind: "call", callee: helper, args: [snapshot, fnArg], type: arrayOf(outElem), loc };
1293
1551
  }
1294
- /** An OOB-SAFE indexed read: `xs[i]`
1295
- * answers the interned `elem | undefined` union — the element when `i`
1296
- * is an integer in [0, len), JS's property-miss undefined otherwise —
1297
- * instead of the trap divergence 4 documents for program code.
1298
- * Package JS is inference-typed, guard-style code (`registeredArguments
1299
- * .slice(-1)[0]`, commander's last-element probe), and the trap would
1300
- * fire on working Node idioms; program files keep the documented trap
1301
- * (their annotations can prove bounds). Existing element unions retag
1302
- * into the canonical union with an added undefined arm. */
1552
+ /** An ordinary indexed read: values retain their element type, while
1553
+ * holes, missing properties, and present undefined yield undefined. */
1303
1554
  export function lowerSafeIndexRead(lowerer, arr, index, loc) {
1304
1555
  const elem = arr.type.elem;
1305
1556
  if (elem.kind === "void" || elem.kind === "dyn")
@@ -1328,6 +1579,21 @@ export function lowerSafeIndexRead(lowerer, arr, index, loc) {
1328
1579
  }
1329
1580
  return { kind: "call", callee: name, args: [arr, index], type: resultT, loc };
1330
1581
  }
1582
+ /** Fuse only our own f64-array read helper with immediate ToNumber.
1583
+ * Reuse its arguments verbatim so the receiver and index still evaluate
1584
+ * exactly once, in order, before the read. Do not specialize user calls,
1585
+ * union-element arrays, or optional values stored in locals. */
1586
+ export function tryLowerNumericIndexRead(lowerer, operand, loc) {
1587
+ if (operand.kind !== "call" || operand.callee !== lowerer.arrHofHelpers.get(`idxOr:${typeKey(F64)}`))
1588
+ return null;
1589
+ const [arr, index] = operand.args;
1590
+ if (operand.args.length !== 2 || arr?.type.kind !== "array" || arr.type.elem.kind !== "f64" || index?.type.kind !== "f64")
1591
+ return null;
1592
+ // The intrinsic owns the evaluated receiver through index evaluation,
1593
+ // then borrows it for one slot lookup. No extra helper parameter or
1594
+ // separate state/getter expressions need to retain the array again.
1595
+ return { kind: "arrIntrinsic", method: "getNumber", receiver: arr, args: [index], type: F64, loc };
1596
+ }
1331
1597
  /** Backward-compatible name for the npm-static probe path. */
1332
1598
  export const lowerNpmStaticSafeIndexRead = lowerSafeIndexRead;
1333
1599
  /** Interned synthetic function for one (elem, argument-shape) concat —
@@ -2182,22 +2448,26 @@ function reverseCountedForLoop(loc, body) {
2182
2448
  * through). */
2183
2449
  function lowerArrayAtCall(lowerer, call, access, elem) {
2184
2450
  const loc = locOf(call);
2451
+ if (call.arguments.some(ts.isSpreadElement))
2452
+ lowerer.noLowering(".at with spread arguments", call);
2185
2453
  const receiver = lowerer.lowerExpr(access.expression);
2186
- const index = lowerer.lowerExprExpecting(call.arguments[0], F64);
2454
+ const indexNode = call.arguments[0];
2455
+ const index = lowerPositionArgument(lowerer, indexNode, numLit(0, loc));
2187
2456
  const resultT = lowerer.irTypeOf(call);
2188
2457
  if (resultT.kind !== "union")
2189
2458
  lowerer.badType(call, lowerer.typeOf(call)); // defensive: T | undefined always maps to a union
2190
2459
  const undefTag = lowerer.armTag(resultT.unionId, UNDEFINED_T);
2191
2460
  if (undefTag < 0)
2192
2461
  lowerer.badType(call, lowerer.typeOf(call));
2193
- const key = `at:${typeKey(elem)}:${typeKey(resultT)}`;
2462
+ const key = `at:${typeKey(elem)}:${typeKey(resultT)}:${typeKey(index.type)}`;
2194
2463
  let name = lowerer.arrHofHelpers.get(key);
2195
2464
  if (!name) {
2196
2465
  name = `%arr.at.${lowerer.arrHofHelpers.size}`;
2197
2466
  lowerer.arrHofHelpers.set(key, name);
2198
2467
  const arrT = arrayOf(elem);
2199
2468
  const t = varRef("t.0", F64, loc);
2200
- const i = varRef("i.0", F64, loc);
2469
+ const i = varRef("i.0", index.type, loc);
2470
+ const number = varRef("number.0", F64, loc);
2201
2471
  const n = varRef("n.0", F64, loc);
2202
2472
  const found = lowerer.coerceToExpected(arrayValueRead(lowerer, varRef("a.0", arrT, loc), t, elem, loc), resultT);
2203
2473
  const miss = {
@@ -2212,20 +2482,21 @@ function lowerArrayAtCall(lowerer, call, access, elem) {
2212
2482
  const lt = (l, r) => ({ kind: "bin", op: "<", left: l, right: r, type: BOOL, loc });
2213
2483
  const body = [
2214
2484
  readLenStmt(arrT, loc),
2485
+ { kind: "varDecl", localId: "number.0", init: positionNumber(lowerer, i, numLit(0, loc), indexNode ?? call, "array at position"), loc },
2215
2486
  // ToIntegerOrInfinity: floor is trunc for i >= 0; negatives mirror
2216
2487
  // (trunc(i) = -floor(-i)); NaN becomes 0. ±Infinity floors to
2217
2488
  // itself and falls out of range below, exactly the spec.
2218
- { kind: "varDecl", localId: "t.0", init: floorOf(i), loc },
2489
+ { kind: "varDecl", localId: "t.0", init: floorOf(number), loc },
2219
2490
  {
2220
2491
  kind: "if",
2221
- cond: lt(i, numLit(0, loc)),
2222
- then: [{ kind: "assign", localId: "t.0", value: { kind: "bin", op: "-", left: numLit(0, loc), right: floorOf({ kind: "bin", op: "-", left: numLit(0, loc), right: i, type: F64, loc }), type: F64, loc }, loc }],
2492
+ cond: lt(number, numLit(0, loc)),
2493
+ then: [{ kind: "assign", localId: "t.0", value: { kind: "bin", op: "-", left: numLit(0, loc), right: floorOf({ kind: "bin", op: "-", left: numLit(0, loc), right: number, type: F64, loc }), type: F64, loc }, loc }],
2223
2494
  else_: null,
2224
2495
  loc,
2225
2496
  },
2226
2497
  {
2227
2498
  kind: "if",
2228
- cond: { kind: "libCall", fn: "num.isNaN", args: [i], type: BOOL, loc },
2499
+ cond: { kind: "libCall", fn: "num.isNaN", args: [number], type: BOOL, loc },
2229
2500
  then: [{ kind: "assign", localId: "t.0", value: numLit(0, loc), loc }],
2230
2501
  else_: null,
2231
2502
  loc,
@@ -2257,13 +2528,14 @@ function lowerArrayAtCall(lowerer, call, access, elem) {
2257
2528
  name,
2258
2529
  params: [
2259
2530
  { localId: "a.0", name: "a", type: arrT },
2260
- { localId: "i.0", name: "i", type: F64 },
2531
+ { localId: "i.0", name: "i", type: index.type },
2261
2532
  ],
2262
2533
  returnType: resultT,
2263
2534
  locals: [
2264
2535
  { id: "a.0", name: "a", type: arrT, mutable: true },
2265
- { id: "i.0", name: "i", type: F64, mutable: true },
2536
+ { id: "i.0", name: "i", type: index.type, mutable: false },
2266
2537
  { id: "n.0", name: "n", type: F64, mutable: false },
2538
+ { id: "number.0", name: "number", type: F64, mutable: false },
2267
2539
  { id: "t.0", name: "t", type: F64, mutable: true },
2268
2540
  ],
2269
2541
  body,
@@ -2272,6 +2544,84 @@ function lowerArrayAtCall(lowerer, call, access, elem) {
2272
2544
  }
2273
2545
  return { kind: "call", callee: name, args: [receiver, index], type: resultT, loc };
2274
2546
  }
2547
+ /** The Array constructor's element and count forms, shared by calls and new. */
2548
+ export function lowerArrayConstructor(lowerer, expr, args) {
2549
+ const loc = locOf(expr);
2550
+ if (args.some(ts.isSpreadElement)) {
2551
+ lowerer.noLowering("Array constructor with spread arguments", expr);
2552
+ }
2553
+ let result = lowerer.mapTypeOf(lowerer.typeOf(expr));
2554
+ if (result?.kind !== "array") {
2555
+ const contextual = lowerer.checker.getContextualType(expr);
2556
+ if (contextual)
2557
+ result = lowerer.mapTypeOf(contextual);
2558
+ }
2559
+ if (result?.kind !== "array" || !isSupportedArrayElem(result.elem)) {
2560
+ lowerer.badType(expr, lowerer.typeOf(expr));
2561
+ }
2562
+ const argType = args.length === 1 ? lowerer.mapTypeOf(lowerer.typeOf(args[0])) : null;
2563
+ if (argType?.kind === "dyn" || argType?.kind === "jsval") {
2564
+ lowerer.noLowering("Array constructor with a dynamically typed sole argument", args[0]);
2565
+ }
2566
+ if (args.length === 1 && (argType?.kind === "f64" ||
2567
+ (argType?.kind === "union" && lowerer.armTag(argType.unionId, F64) >= 0))) {
2568
+ const count = lowerer.lowerExpr(args[0]);
2569
+ const numberTag = count.type.kind === "union" ? lowerer.armTag(count.type.unionId, F64) : -1;
2570
+ if (count.type.kind !== "f64" && numberTag < 0) {
2571
+ lowerer.noLowering("Array constructor length with a value that may be missing", args[0]);
2572
+ }
2573
+ const input = count.type.kind === "union" ? lowerer.declareHiddenLocal("%arrayCtorArg", count.type) : null;
2574
+ const inputRef = input ? varRef(input.id, count.type, loc) : null;
2575
+ const local = lowerer.declareHiddenLocal("%arrayLengthCtor", result);
2576
+ const ref = varRef(local.id, result, loc);
2577
+ const length = inputRef && count.type.kind === "union"
2578
+ ? { kind: "unionNarrow", unionId: count.type.unionId, tag: numberTag, value: inputRef, type: F64, loc }
2579
+ : count;
2580
+ const resize = { kind: "arraySetLength", arr: ref, length, loc };
2581
+ return {
2582
+ kind: "seqExpr",
2583
+ stmts: [
2584
+ ...(input ? [{ kind: "varDecl", localId: input.id, init: count, loc }] : []),
2585
+ { kind: "varDecl", localId: local.id, init: { kind: "arrayLit", elems: [], type: result, loc }, loc },
2586
+ ...(inputRef && count.type.kind === "union"
2587
+ ? [{ kind: "if",
2588
+ cond: { kind: "unionIsTag", unionId: count.type.unionId, tag: numberTag, value: inputRef, negated: false, type: BOOL, loc },
2589
+ then: [resize],
2590
+ else_: [arrayValueStore(lowerer, ref, numLit(0, loc), lowerer.coerceInto(args[0], inputRef, result.elem), result.elem, loc)],
2591
+ loc }]
2592
+ : [resize]),
2593
+ ],
2594
+ result: ref,
2595
+ type: result,
2596
+ loc,
2597
+ };
2598
+ }
2599
+ return { kind: "arrayLit", elems: args.map((arg) => lowerer.lowerExprExpecting(arg, result.elem)), type: result, loc };
2600
+ }
2601
+ /** `Array.of` creates an array of its arguments, including a lone number. */
2602
+ export function lowerArrayOfCall(lowerer, call, access) {
2603
+ if (call.questionDotToken || access.questionDotToken || access.name.text !== "of" ||
2604
+ !lowerer.isStdlibGlobal(access.expression, "Array"))
2605
+ return null;
2606
+ let result = lowerer.mapTypeOf(lowerer.typeOf(call));
2607
+ if (result?.kind !== "array") {
2608
+ const contextual = lowerer.checker.getContextualType(call);
2609
+ if (contextual)
2610
+ result = lowerer.mapTypeOf(contextual);
2611
+ }
2612
+ if (result?.kind !== "array" || !isSupportedArrayElem(result.elem)) {
2613
+ lowerer.badType(call, lowerer.typeOf(call));
2614
+ }
2615
+ if (call.arguments.some(ts.isSpreadElement)) {
2616
+ return lowerArraySpreadItems(lowerer, call.arguments, result.elem, result, locOf(call));
2617
+ }
2618
+ return {
2619
+ kind: "arrayLit",
2620
+ elems: call.arguments.map((arg) => lowerer.lowerExprExpecting(arg, result.elem)),
2621
+ type: result,
2622
+ loc: locOf(call),
2623
+ };
2624
+ }
2275
2625
  /** `Array.from({ length: n }, mapfn)` — the counted-generation idiom — on
2276
2626
  * THE stdlib Array global. The source must be an OBJECT LITERAL whose
2277
2627
  * single property is `length` (the shape the idiom always spells; the
@@ -2281,8 +2631,10 @@ function lowerArrayAtCall(lowerer, call, access, elem) {
2281
2631
  * checker's own `unknown` for it — and pushing each result. The loop
2282
2632
  * bound is `i <= n - 1`, which IS ToLength for the finite lengths that
2283
2633
  * terminate (fractional lengths truncate, negative/NaN produce an empty
2284
- * array — Node-exact). Every other Array.from shape (arrays, iterables,
2285
- * no mapper) keeps the fence. Null when the callee isn't an
2634
+ * array — Node-exact). Array inputs copy through the iterator's indexed
2635
+ * values, including holes as present undefined, and may map each value.
2636
+ * Set inputs copy their values; strings iterate by code point and may map.
2637
+ * Other iterable shapes keep the fence. Null when the callee isn't an
2286
2638
  * Array-static access. */
2287
2639
  export function lowerArrayFromCall(lowerer, call, access) {
2288
2640
  if (call.questionDotToken || access.questionDotToken)
@@ -2317,23 +2669,70 @@ export function lowerArrayFromCall(lowerer, call, access) {
2317
2669
  return { kind: "arrayNewLen", length: n, type: arrT, loc };
2318
2670
  }
2319
2671
  }
2320
- // `Array.from(s)` on a STRING: the string iterator's code-point walk
2321
- // into a fresh string[] (astral characters stay whole, where a
2322
- // charAt/index walk would truncate the surrogate halves) — the same
2323
- // interned helper `[...s]` lowers through.
2324
- if (args.length === 1 && !ts.isObjectLiteralExpression(args[0])) {
2325
- const src = lowerer.lowerExpr(args[0]);
2326
- if (src.type.kind === "string")
2672
+ // Array iteration reads length again after every mapper call. Without
2673
+ // a mapper, a dense reversed copy followed by reversal preserves order
2674
+ // and materializes holes while keeping element reference identity.
2675
+ if ((args.length === 1 || args.length === 2) && !ts.isObjectLiteralExpression(args[0])) {
2676
+ const source = lowerer.lowerExpr(args[0]);
2677
+ const src = source.type.kind === "string" && args.length === 2 ? strCharsCall(lowerer, source, loc) : source;
2678
+ if (src.type.kind === "array") {
2679
+ const arrT = src.type;
2680
+ if (args.length === 1) {
2681
+ const dense = { kind: "arrIntrinsic", method: "toReversed", receiver: src, args: [], type: arrT, loc };
2682
+ return { kind: "arrIntrinsic", method: "reverse", receiver: dense, args: [], type: arrT, loc };
2683
+ }
2684
+ const mapper = args[1];
2685
+ const valueT = arrayValueType(lowerer, arrT.elem);
2686
+ const firstParam = ts.isArrowFunction(mapper) || ts.isFunctionExpression(mapper)
2687
+ ? mapper.parameters[0]?.name : undefined;
2688
+ const paramSymbol = firstParam && ts.isIdentifier(firstParam)
2689
+ ? lowerer.checker.getSymbolAtLocation(firstParam) : undefined;
2690
+ const previous = paramSymbol ? lowerer.runtimeOptionalBindingTypes.get(paramSymbol) : undefined;
2691
+ if (paramSymbol)
2692
+ lowerer.runtimeOptionalBindingTypes.set(paramSymbol, valueT);
2693
+ let callback;
2694
+ try {
2695
+ callback = hofCallbackArg(lowerer, mapper, [valueT], arrT);
2696
+ }
2697
+ finally {
2698
+ if (paramSymbol) {
2699
+ if (previous === undefined)
2700
+ lowerer.runtimeOptionalBindingTypes.delete(paramSymbol);
2701
+ else
2702
+ lowerer.runtimeOptionalBindingTypes.set(paramSymbol, previous);
2703
+ }
2704
+ }
2705
+ const { fnArg, arity } = callback;
2706
+ if (arity > 2)
2707
+ lowerer.noLowering("Array.from mapper with an array parameter", args[1]);
2708
+ const fnRet = fnArg.type.ret;
2709
+ if (fnRet.kind === "void" || fnRet.kind === "func")
2710
+ lowerer.badType(call, lowerer.typeOf(call));
2711
+ fenceProducedArrayElem(lowerer, call, "'Array.from(array, mapper)'", fnRet);
2712
+ const outElem = callbackArrayElem(lowerer, call, fnRet);
2713
+ const key = `fromArray:${typeKey(arrT.elem)}:${typeKey(outElem)}:${typeKey(fnRet)}:${arity}`;
2714
+ let helper = lowerer.arrHofHelpers.get(key);
2715
+ if (!helper) {
2716
+ helper = `%arr.fromArray.${lowerer.arrHofHelpers.size}`;
2717
+ lowerer.arrHofHelpers.set(key, helper);
2718
+ lowerer.liftedFns.push(buildArrayFromArrayFn(lowerer, helper, arrT.elem, outElem, fnRet, arity, loc));
2719
+ }
2720
+ return { kind: "call", callee: helper, args: [src, fnArg], type: arrayOf(outElem), loc };
2721
+ }
2722
+ if (src.type.kind === "set" && args.length === 1) {
2723
+ return { kind: "setIntrinsic", method: "toArray", receiver: src, args: [], type: arrayOf(src.type.elem), loc };
2724
+ }
2725
+ // String iteration advances by code point; the same helper serves
2726
+ // `[...s]` and both Array.from(s) forms.
2727
+ if (src.type.kind === "string" && args.length === 1)
2327
2728
  return strCharsCall(lowerer, src, loc);
2328
- lowerer.noLowering("Array.from with this argument shape", call, "Array.from({ length: n }, (v, i) => ...) and Array.from(aString) are the lowered " +
2329
- "forms — copy arrays with [...a] and drain Map/Set iterators where they are made");
2729
+ lowerer.noLowering("Array.from with this argument shape", call, "Array.from(array), Array.from(array, mapper), Array.from(aString), Array.from(aString, mapper), and Array.from({ length: n }, mapper) are the lowered forms");
2330
2730
  }
2331
2731
  const n = args.length === 2 && ts.isObjectLiteralExpression(args[0]) && args[0].properties.length === 1
2332
2732
  ? lowerLengthProp(lowerer, args[0].properties[0])
2333
2733
  : null;
2334
2734
  if (!n) {
2335
- lowerer.noLowering("Array.from with this argument shape", call, "Array.from({ length: n }, (v, i) => ...) is the lowered form — copy arrays " +
2336
- "with [...a] and drain Map/Set iterators where they are made");
2735
+ lowerer.noLowering("Array.from with this argument shape", call, "Array.from(array), Array.from(array, mapper), Array.from(aString), Array.from(aString, mapper), and Array.from({ length: n }, mapper) are the lowered forms");
2337
2736
  }
2338
2737
  if (n.type.kind !== "f64")
2339
2738
  lowerer.badType(args[0], lowerer.typeOf(args[0]));
@@ -2362,6 +2761,49 @@ export function lowerArrayFromCall(lowerer, call, access) {
2362
2761
  }
2363
2762
  return { kind: "call", callee: helper, args: [n, fnArg], type: arrayOf(fnRet), loc };
2364
2763
  }
2764
+ /** Map an array iterator to a fresh dense array. The source length remains
2765
+ * live so mapper mutations affect which later indexes are visited. */
2766
+ function buildArrayFromArrayFn(lowerer, name, elem, outElem, fnRet, arity, loc) {
2767
+ const arrT = arrayOf(elem);
2768
+ const outT = arrayOf(outElem);
2769
+ const fnT = funcOf([arrayValueType(lowerer, elem), F64].slice(0, arity), fnRet);
2770
+ const a = varRef("a.0", arrT, loc);
2771
+ const i = varRef("i.0", F64, loc);
2772
+ const out = varRef("out.0", outT, loc);
2773
+ const mapped = {
2774
+ kind: "callValue",
2775
+ callee: varRef("f.0", fnT, loc),
2776
+ args: [arrayValueRead(lowerer, a, i, elem, loc), i].slice(0, arity),
2777
+ type: fnRet,
2778
+ loc,
2779
+ };
2780
+ return {
2781
+ name,
2782
+ params: [{ localId: "a.0", name: "a", type: arrT }, { localId: "f.0", name: "f", type: fnT }],
2783
+ returnType: outT,
2784
+ locals: [
2785
+ { id: "a.0", name: "a", type: arrT, mutable: true },
2786
+ { id: "f.0", name: "f", type: fnT, mutable: true },
2787
+ { id: "out.0", name: "out", type: outT, mutable: false },
2788
+ { id: "i.0", name: "i", type: F64, mutable: true },
2789
+ ],
2790
+ body: [
2791
+ { kind: "varDecl", localId: "out.0", init: { kind: "arrayLit", elems: [], type: outT, loc }, loc },
2792
+ { kind: "varDecl", localId: "i.0", init: numLit(0, loc), loc },
2793
+ {
2794
+ kind: "while",
2795
+ cond: { kind: "bin", op: "<", left: i, right: { kind: "arrIntrinsic", method: "length", receiver: a, args: [], type: F64, loc }, type: BOOL, loc },
2796
+ body: [
2797
+ arrayValueStore(lowerer, out, i, mapped, outElem, loc),
2798
+ { kind: "assign", localId: "i.0", value: { kind: "bin", op: "+", left: i, right: numLit(1, loc), type: F64, loc }, loc },
2799
+ ],
2800
+ loc,
2801
+ },
2802
+ { kind: "return", value: out, loc },
2803
+ ],
2804
+ loc,
2805
+ };
2806
+ }
2365
2807
  /** `Array.from(s)` / `[...s]` on a STRING: the code-point split into a
2366
2808
  * fresh string[], through one interned helper per module. */
2367
2809
  export function strCharsCall(lowerer, src, loc) {