@scriptc/compiler 0.1.5 → 0.1.7

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 (139) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +0 -1
  2. package/ambient/scriptc.d.ts +13 -5
  3. package/dist/backend/c/c-emitter.d.ts +5 -0
  4. package/dist/backend/c/c-emitter.js +15 -0
  5. package/dist/backend/c/c-emitter.js.map +1 -1
  6. package/dist/backend/c/exprs.js +81 -9
  7. package/dist/backend/c/exprs.js.map +1 -1
  8. package/dist/backend/c/stmts.js +25 -1
  9. package/dist/backend/c/stmts.js.map +1 -1
  10. package/dist/backend/c/walkers.js +2 -3
  11. package/dist/backend/c/walkers.js.map +1 -1
  12. package/dist/backend/debug-symbols.d.ts +8 -0
  13. package/dist/backend/debug-symbols.js +52 -0
  14. package/dist/backend/debug-symbols.js.map +1 -0
  15. package/dist/backend/link-plan.d.ts +1 -0
  16. package/dist/backend/link-plan.js +2 -0
  17. package/dist/backend/link-plan.js.map +1 -1
  18. package/dist/backend/linker.js +8 -0
  19. package/dist/backend/linker.js.map +1 -1
  20. package/dist/backend/llvm/blocks.d.ts +2 -0
  21. package/dist/backend/llvm/blocks.js +18 -2
  22. package/dist/backend/llvm/blocks.js.map +1 -1
  23. package/dist/backend/llvm/debug-info.d.ts +32 -0
  24. package/dist/backend/llvm/debug-info.js +203 -0
  25. package/dist/backend/llvm/debug-info.js.map +1 -0
  26. package/dist/backend/llvm/dyn.js +4 -10
  27. package/dist/backend/llvm/dyn.js.map +1 -1
  28. package/dist/backend/llvm/emitter.d.ts +2 -0
  29. package/dist/backend/llvm/emitter.js +49 -4
  30. package/dist/backend/llvm/emitter.js.map +1 -1
  31. package/dist/backend/llvm/expr-containers.js +28 -2
  32. package/dist/backend/llvm/expr-containers.js.map +1 -1
  33. package/dist/backend/llvm/expr-dynamic.js +10 -0
  34. package/dist/backend/llvm/expr-dynamic.js.map +1 -1
  35. package/dist/backend/llvm/lib-filesystem.js +37 -0
  36. package/dist/backend/llvm/lib-filesystem.js.map +1 -1
  37. package/dist/backend/llvm/lib-process.js +4 -3
  38. package/dist/backend/llvm/lib-process.js.map +1 -1
  39. package/dist/backend/llvm/lib-shared.js +9 -0
  40. package/dist/backend/llvm/lib-shared.js.map +1 -1
  41. package/dist/backend/llvm/split.js +4 -1
  42. package/dist/backend/llvm/split.js.map +1 -1
  43. package/dist/backend/llvm/split.test.js +26 -1
  44. package/dist/backend/llvm/split.test.js.map +1 -1
  45. package/dist/backend/native-toolchain.js +58 -8
  46. package/dist/backend/native-toolchain.js.map +1 -1
  47. package/dist/backend/native-toolchain.test.js +10 -0
  48. package/dist/backend/native-toolchain.test.js.map +1 -1
  49. package/dist/backend/source-locations.d.ts +12 -0
  50. package/dist/backend/source-locations.js +32 -0
  51. package/dist/backend/source-locations.js.map +1 -0
  52. package/dist/backend/source-locations.test.d.ts +1 -0
  53. package/dist/backend/source-locations.test.js +16 -0
  54. package/dist/backend/source-locations.test.js.map +1 -0
  55. package/dist/coverage/surface-manifest.js +6 -1
  56. package/dist/coverage/surface-manifest.js.map +1 -1
  57. package/dist/diagnostics/diagnostic.d.ts +4 -0
  58. package/dist/diagnostics/diagnostic.js +12 -0
  59. package/dist/diagnostics/diagnostic.js.map +1 -1
  60. package/dist/executable/executable-cache.js +28 -4
  61. package/dist/executable/executable-cache.js.map +1 -1
  62. package/dist/executable/executable-cache.test.js +23 -0
  63. package/dist/executable/executable-cache.test.js.map +1 -1
  64. package/dist/frontend/arguments-usage.d.ts +6 -0
  65. package/dist/frontend/arguments-usage.js +40 -0
  66. package/dist/frontend/arguments-usage.js.map +1 -0
  67. package/dist/frontend/binding-source.d.ts +4 -0
  68. package/dist/frontend/binding-source.js +18 -0
  69. package/dist/frontend/binding-source.js.map +1 -0
  70. package/dist/frontend/lowering/array-indexed-mutation.d.ts +8 -0
  71. package/dist/frontend/lowering/array-indexed-mutation.js +176 -0
  72. package/dist/frontend/lowering/array-indexed-mutation.js.map +1 -0
  73. package/dist/frontend/lowering/containers/string-and-regexp.d.ts +7 -0
  74. package/dist/frontend/lowering/containers/string-and-regexp.js +190 -13
  75. package/dist/frontend/lowering/containers/string-and-regexp.js.map +1 -1
  76. package/dist/frontend/lowering/expressions/object-literals.js +13 -8
  77. package/dist/frontend/lowering/expressions/object-literals.js.map +1 -1
  78. package/dist/frontend/lowering/expressions/optional-chains.d.ts +5 -6
  79. package/dist/frontend/lowering/expressions/optional-chains.js +7 -6
  80. package/dist/frontend/lowering/expressions/optional-chains.js.map +1 -1
  81. package/dist/frontend/lowering/indexed-comparison.d.ts +6 -0
  82. package/dist/frontend/lowering/indexed-comparison.js +30 -0
  83. package/dist/frontend/lowering/indexed-comparison.js.map +1 -0
  84. package/dist/frontend/lowering/lower-builtins.d.ts +1 -1
  85. package/dist/frontend/lowering/lower-builtins.js +21 -10
  86. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  87. package/dist/frontend/lowering/lower-calls.d.ts +2 -9
  88. package/dist/frontend/lowering/lower-calls.js +406 -40
  89. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  90. package/dist/frontend/lowering/lower-classes.js +3 -27
  91. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  92. package/dist/frontend/lowering/lower-containers.d.ts +8 -2
  93. package/dist/frontend/lowering/lower-containers.js +454 -104
  94. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  95. package/dist/frontend/lowering/lower-exprs.js +134 -18
  96. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  97. package/dist/frontend/lowering/lower-island.d.ts +1 -1
  98. package/dist/frontend/lowering/lower-island.js +1 -1
  99. package/dist/frontend/lowering/lower-modules.js +19 -8
  100. package/dist/frontend/lowering/lower-modules.js.map +1 -1
  101. package/dist/frontend/lowering/lower-stmts.js +4 -37
  102. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  103. package/dist/frontend/lowering/lowerer.js +14 -3
  104. package/dist/frontend/lowering/lowerer.js.map +1 -1
  105. package/dist/frontend/lowering/surfaces.d.ts +3 -1
  106. package/dist/frontend/lowering/surfaces.js +50 -7
  107. package/dist/frontend/lowering/surfaces.js.map +1 -1
  108. package/dist/frontend/program.d.ts +5 -0
  109. package/dist/frontend/program.js +216 -32
  110. package/dist/frontend/program.js.map +1 -1
  111. package/dist/frontend/program.test.d.ts +1 -0
  112. package/dist/frontend/program.test.js +117 -0
  113. package/dist/frontend/program.test.js.map +1 -0
  114. package/dist/frontend/type-mapper.d.ts +4 -2
  115. package/dist/frontend/type-mapper.js +249 -186
  116. package/dist/frontend/type-mapper.js.map +1 -1
  117. package/dist/frontend/type-mapper.test.d.ts +1 -0
  118. package/dist/frontend/type-mapper.test.js +86 -0
  119. package/dist/frontend/type-mapper.test.js.map +1 -0
  120. package/dist/index.d.ts +2 -1
  121. package/dist/index.js +7 -2
  122. package/dist/index.js.map +1 -1
  123. package/dist/ir/analysis.js +4 -2
  124. package/dist/ir/analysis.js.map +1 -1
  125. package/dist/ir/ir.d.ts +33 -14
  126. package/dist/ir/ir.js +5 -1
  127. package/dist/ir/ir.js.map +1 -1
  128. package/dist/ir/validate.js +57 -31
  129. package/dist/ir/validate.js.map +1 -1
  130. package/dist/ir/validate.test.js +17 -0
  131. package/dist/ir/validate.test.js.map +1 -1
  132. package/dist/library/compiler-self-identity.d.ts +1 -1
  133. package/dist/library/compiler-self-identity.js +31 -15
  134. package/dist/library/compiler-self-identity.js.map +1 -1
  135. package/dist/library/compiler-self-identity.test.d.ts +1 -0
  136. package/dist/library/compiler-self-identity.test.js +93 -0
  137. package/dist/library/compiler-self-identity.test.js.map +1 -0
  138. package/package.json +17 -17
  139. package/surface-manifest.json +155 -4
@@ -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, lowerPositionArgument, positionNumber } 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,30 +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
- // join's separator is optional, and the predicate/mapping HOFs take
291
- // a thisArg. Each unlowered form is fenced
389
+ if (name === "flat")
390
+ return lowerArrayFlatCall(lowerer, call, access, receiverIr);
391
+ // The lib declares wider call forms than the lowered surface — the
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, 2], lastIndexOf: [1, 2], includes: [0, 2], 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 === "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;
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;
313
410
  lowerer.noLowering(`.${name} with ${call.arguments.length} argument${call.arguments.length === 1 ? "" : "s"}`, call, hint);
314
411
  }
315
412
  if (name === "push" || name === "unshift") {
@@ -450,33 +547,37 @@ export function lowerArrayMethodCall(lowerer, call, access) {
450
547
  return { kind: "call", callee: helper, args: [receiver, ...args], type: receiverIr, loc };
451
548
  }
452
549
  if (name === "slice") {
453
- // `a.slice(start?, end?)` — a fresh shallow copy of the index range,
454
- // JS-exact index handling (ToIntegerOrInfinity, negatives from the
455
- // end, clamping; omitted args are omitted from the IR — the backend
456
- // fills 0 / +Infinity, the string-slice convention). Ref elements
457
- // are RETAINED into the copy: the same references, exactly JS's
458
- // shallow copy. Every element kind slices — the receiver's own type
459
- // is the result type.
460
550
  const receiver = lowerer.lowerExpr(access.expression);
461
- const args = call.arguments.map((a) => lowerer.lowerExpr(a));
462
- for (let i = 0; i < args.length; i++) {
463
- if (args[i].type.kind !== "f64")
464
- lowerer.badType(call.arguments[i], lowerer.typeOf(call.arguments[i]));
465
- }
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
+ ];
466
555
  return { kind: "arrIntrinsic", method: "slice", receiver, args, type: receiverIr, loc };
467
556
  }
468
557
  if (name === "splice") {
469
- // The REMOVAL forms: splice(start) and splice(start, deleteCount) —
470
- // Node-exact relative/clamped indices, the removed elements back in
471
- // order (their ownership moves out of the receiver). Insertion
472
- // (3+ args) fenced by arity above.
473
558
  const receiver = lowerer.lowerExpr(access.expression);
474
- const args = call.arguments.map((a) => lowerer.lowerExpr(a));
475
- for (let i = 0; i < args.length; i++) {
476
- if (args[i].type.kind !== "f64")
477
- 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 };
478
565
  }
479
- 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 };
480
581
  }
481
582
  if (name === "shift") {
482
583
  // JS shift exactly: undefined on an empty array, else the first
@@ -509,7 +610,7 @@ export function lowerArrayMethodCall(lowerer, call, access) {
509
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)");
510
611
  }
511
612
  const receiver = lowerer.lowerExpr(access.expression);
512
- const sep = lowerer.lowerExpr(call.arguments[0]);
613
+ const sep = lowerArrayJoinSeparator(lowerer, call.arguments[0], loc);
513
614
  return { kind: "arrIntrinsic", method: "join", receiver, args: [sep], type: STRING, loc };
514
615
  }
515
616
  if (name === "map" || name === "filter" || name === "forEach") {
@@ -526,6 +627,79 @@ export function lowerArrayMethodCall(lowerer, call, access) {
526
627
  // reduce / reduceRight
527
628
  return lowerArrayReduceCall(lowerer, call, access, name, elem);
528
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
+ }
529
703
  /** `indexOf` and `includes` normally use the runtime's typed search helpers,
530
704
  * but an indexed array read is a `T | undefined` value. Keep that union
531
705
  * through a small lowered loop so the needle's missing state is observable:
@@ -2274,22 +2448,26 @@ function reverseCountedForLoop(loc, body) {
2274
2448
  * through). */
2275
2449
  function lowerArrayAtCall(lowerer, call, access, elem) {
2276
2450
  const loc = locOf(call);
2451
+ if (call.arguments.some(ts.isSpreadElement))
2452
+ lowerer.noLowering(".at with spread arguments", call);
2277
2453
  const receiver = lowerer.lowerExpr(access.expression);
2278
- const index = lowerer.lowerExprExpecting(call.arguments[0], F64);
2454
+ const indexNode = call.arguments[0];
2455
+ const index = lowerPositionArgument(lowerer, indexNode, numLit(0, loc));
2279
2456
  const resultT = lowerer.irTypeOf(call);
2280
2457
  if (resultT.kind !== "union")
2281
2458
  lowerer.badType(call, lowerer.typeOf(call)); // defensive: T | undefined always maps to a union
2282
2459
  const undefTag = lowerer.armTag(resultT.unionId, UNDEFINED_T);
2283
2460
  if (undefTag < 0)
2284
2461
  lowerer.badType(call, lowerer.typeOf(call));
2285
- const key = `at:${typeKey(elem)}:${typeKey(resultT)}`;
2462
+ const key = `at:${typeKey(elem)}:${typeKey(resultT)}:${typeKey(index.type)}`;
2286
2463
  let name = lowerer.arrHofHelpers.get(key);
2287
2464
  if (!name) {
2288
2465
  name = `%arr.at.${lowerer.arrHofHelpers.size}`;
2289
2466
  lowerer.arrHofHelpers.set(key, name);
2290
2467
  const arrT = arrayOf(elem);
2291
2468
  const t = varRef("t.0", F64, loc);
2292
- const i = varRef("i.0", F64, loc);
2469
+ const i = varRef("i.0", index.type, loc);
2470
+ const number = varRef("number.0", F64, loc);
2293
2471
  const n = varRef("n.0", F64, loc);
2294
2472
  const found = lowerer.coerceToExpected(arrayValueRead(lowerer, varRef("a.0", arrT, loc), t, elem, loc), resultT);
2295
2473
  const miss = {
@@ -2304,20 +2482,21 @@ function lowerArrayAtCall(lowerer, call, access, elem) {
2304
2482
  const lt = (l, r) => ({ kind: "bin", op: "<", left: l, right: r, type: BOOL, loc });
2305
2483
  const body = [
2306
2484
  readLenStmt(arrT, loc),
2485
+ { kind: "varDecl", localId: "number.0", init: positionNumber(lowerer, i, numLit(0, loc), indexNode ?? call, "array at position"), loc },
2307
2486
  // ToIntegerOrInfinity: floor is trunc for i >= 0; negatives mirror
2308
2487
  // (trunc(i) = -floor(-i)); NaN becomes 0. ±Infinity floors to
2309
2488
  // itself and falls out of range below, exactly the spec.
2310
- { kind: "varDecl", localId: "t.0", init: floorOf(i), loc },
2489
+ { kind: "varDecl", localId: "t.0", init: floorOf(number), loc },
2311
2490
  {
2312
2491
  kind: "if",
2313
- cond: lt(i, numLit(0, loc)),
2314
- 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 }],
2315
2494
  else_: null,
2316
2495
  loc,
2317
2496
  },
2318
2497
  {
2319
2498
  kind: "if",
2320
- cond: { kind: "libCall", fn: "num.isNaN", args: [i], type: BOOL, loc },
2499
+ cond: { kind: "libCall", fn: "num.isNaN", args: [number], type: BOOL, loc },
2321
2500
  then: [{ kind: "assign", localId: "t.0", value: numLit(0, loc), loc }],
2322
2501
  else_: null,
2323
2502
  loc,
@@ -2349,13 +2528,14 @@ function lowerArrayAtCall(lowerer, call, access, elem) {
2349
2528
  name,
2350
2529
  params: [
2351
2530
  { localId: "a.0", name: "a", type: arrT },
2352
- { localId: "i.0", name: "i", type: F64 },
2531
+ { localId: "i.0", name: "i", type: index.type },
2353
2532
  ],
2354
2533
  returnType: resultT,
2355
2534
  locals: [
2356
2535
  { id: "a.0", name: "a", type: arrT, mutable: true },
2357
- { id: "i.0", name: "i", type: F64, mutable: true },
2536
+ { id: "i.0", name: "i", type: index.type, mutable: false },
2358
2537
  { id: "n.0", name: "n", type: F64, mutable: false },
2538
+ { id: "number.0", name: "number", type: F64, mutable: false },
2359
2539
  { id: "t.0", name: "t", type: F64, mutable: true },
2360
2540
  ],
2361
2541
  body,
@@ -2364,6 +2544,84 @@ function lowerArrayAtCall(lowerer, call, access, elem) {
2364
2544
  }
2365
2545
  return { kind: "call", callee: name, args: [receiver, index], type: resultT, loc };
2366
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
+ }
2367
2625
  /** `Array.from({ length: n }, mapfn)` — the counted-generation idiom — on
2368
2626
  * THE stdlib Array global. The source must be an OBJECT LITERAL whose
2369
2627
  * single property is `length` (the shape the idiom always spells; the
@@ -2373,8 +2631,10 @@ function lowerArrayAtCall(lowerer, call, access, elem) {
2373
2631
  * checker's own `unknown` for it — and pushing each result. The loop
2374
2632
  * bound is `i <= n - 1`, which IS ToLength for the finite lengths that
2375
2633
  * terminate (fractional lengths truncate, negative/NaN produce an empty
2376
- * array — Node-exact). Every other Array.from shape (arrays, iterables,
2377
- * 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
2378
2638
  * Array-static access. */
2379
2639
  export function lowerArrayFromCall(lowerer, call, access) {
2380
2640
  if (call.questionDotToken || access.questionDotToken)
@@ -2409,23 +2669,70 @@ export function lowerArrayFromCall(lowerer, call, access) {
2409
2669
  return { kind: "arrayNewLen", length: n, type: arrT, loc };
2410
2670
  }
2411
2671
  }
2412
- // `Array.from(s)` on a STRING: the string iterator's code-point walk
2413
- // into a fresh string[] (astral characters stay whole, where a
2414
- // charAt/index walk would truncate the surrogate halves) — the same
2415
- // interned helper `[...s]` lowers through.
2416
- if (args.length === 1 && !ts.isObjectLiteralExpression(args[0])) {
2417
- const src = lowerer.lowerExpr(args[0]);
2418
- 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)
2419
2728
  return strCharsCall(lowerer, src, loc);
2420
- lowerer.noLowering("Array.from with this argument shape", call, "Array.from({ length: n }, (v, i) => ...) and Array.from(aString) are the lowered " +
2421
- "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");
2422
2730
  }
2423
2731
  const n = args.length === 2 && ts.isObjectLiteralExpression(args[0]) && args[0].properties.length === 1
2424
2732
  ? lowerLengthProp(lowerer, args[0].properties[0])
2425
2733
  : null;
2426
2734
  if (!n) {
2427
- lowerer.noLowering("Array.from with this argument shape", call, "Array.from({ length: n }, (v, i) => ...) is the lowered form — copy arrays " +
2428
- "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");
2429
2736
  }
2430
2737
  if (n.type.kind !== "f64")
2431
2738
  lowerer.badType(args[0], lowerer.typeOf(args[0]));
@@ -2454,6 +2761,49 @@ export function lowerArrayFromCall(lowerer, call, access) {
2454
2761
  }
2455
2762
  return { kind: "call", callee: helper, args: [n, fnArg], type: arrayOf(fnRet), loc };
2456
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
+ }
2457
2807
  /** `Array.from(s)` / `[...s]` on a STRING: the code-point split into a
2458
2808
  * fresh string[], through one interned helper per module. */
2459
2809
  export function strCharsCall(lowerer, src, loc) {