@scriptc/compiler 0.0.16 → 0.0.18

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 (64) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +4 -4
  2. package/dist/backend/cc.d.ts +5 -0
  3. package/dist/backend/cc.js +2 -0
  4. package/dist/backend/cc.js.map +1 -1
  5. package/dist/backend/emission/emit-async.js +35 -2
  6. package/dist/backend/emission/emit-async.js.map +1 -1
  7. package/dist/backend/emission/emit-exprs.js +44 -6
  8. package/dist/backend/emission/emit-exprs.js.map +1 -1
  9. package/dist/backend/emission/emitter.js +69 -11
  10. package/dist/backend/emission/emitter.js.map +1 -1
  11. package/dist/backend/emission/may-throw.d.ts +3 -3
  12. package/dist/backend/emission/may-throw.js +15 -4
  13. package/dist/backend/emission/may-throw.js.map +1 -1
  14. package/dist/backend/llvm/emitter.js +200 -21
  15. package/dist/backend/llvm/emitter.js.map +1 -1
  16. package/dist/coverage/surface-manifest.js +27 -9
  17. package/dist/coverage/surface-manifest.js.map +1 -1
  18. package/dist/frontend/lowering/lower-builtins.js +4 -7
  19. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  20. package/dist/frontend/lowering/lower-calls.d.ts +4 -4
  21. package/dist/frontend/lowering/lower-calls.js +44 -11
  22. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  23. package/dist/frontend/lowering/lower-containers.d.ts +22 -17
  24. package/dist/frontend/lowering/lower-containers.js +628 -51
  25. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  26. package/dist/frontend/lowering/lower-exprs.js +19 -1
  27. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  28. package/dist/frontend/lowering/lower-island.d.ts +2 -2
  29. package/dist/frontend/lowering/lower-island.js +57 -9
  30. package/dist/frontend/lowering/lower-island.js.map +1 -1
  31. package/dist/frontend/lowering/lower-modules.js +144 -10
  32. package/dist/frontend/lowering/lower-modules.js.map +1 -1
  33. package/dist/frontend/lowering/lower-stmts.d.ts +8 -0
  34. package/dist/frontend/lowering/lower-stmts.js +408 -21
  35. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  36. package/dist/frontend/lowering/lowerer.d.ts +38 -0
  37. package/dist/frontend/lowering/lowerer.js +149 -2
  38. package/dist/frontend/lowering/lowerer.js.map +1 -1
  39. package/dist/frontend/lowering/surfaces.d.ts +8 -0
  40. package/dist/frontend/lowering/surfaces.js +10 -0
  41. package/dist/frontend/lowering/surfaces.js.map +1 -1
  42. package/dist/frontend/shared.d.ts +4 -0
  43. package/dist/frontend/shared.js +9 -3
  44. package/dist/frontend/shared.js.map +1 -1
  45. package/dist/frontend/ts7/program.js +5 -4
  46. package/dist/frontend/ts7/program.js.map +1 -1
  47. package/dist/index.d.ts +7 -0
  48. package/dist/index.js +7 -3
  49. package/dist/index.js.map +1 -1
  50. package/dist/ir/nodes.d.ts +53 -22
  51. package/dist/ir/nodes.js +42 -0
  52. package/dist/ir/nodes.js.map +1 -1
  53. package/dist/ir/serialize.d.ts +1 -1
  54. package/dist/ir/serialize.js +1 -1
  55. package/dist/ir/validate.js +99 -39
  56. package/dist/ir/validate.js.map +1 -1
  57. package/dist/library/fence-eval.js +9 -1
  58. package/dist/library/fence-eval.js.map +1 -1
  59. package/dist/library/int-infer.js +191 -25
  60. package/dist/library/int-infer.js.map +1 -1
  61. package/dist/library/int-infer.test.js +206 -1
  62. package/dist/library/int-infer.test.js.map +1 -1
  63. package/package.json +2 -2
  64. package/surface-manifest.json +27 -3
@@ -5,12 +5,75 @@
5
5
  import * as ts from "../ts7/adapter.js";
6
6
  import { BOOL, BYTES_U8, CAUGHT, DYN, F64, JSVAL, STRING, UNDEFINED_T, VOID, arrayOf, bytesOf, funcOf, isRefCounted, isSupportedIndexValue, isUnitType, typeEquals } from "../../ir/nodes.js";
7
7
  import { ARRAY_METHODS, MAP_METHODS, SET_COMBINE_METHODS, SET_METHODS, STR_METHODS } from "./surfaces.js";
8
- import { isRequireMainFilename, lowerDynObjectLiteral, probeLower, pureReemittable } from "./lower-exprs.js";
8
+ import { droppableStatic, isRequireMainFilename, lowerDynObjectLiteral, probeLower, pureReemittable } from "./lower-exprs.js";
9
9
  import { forOfVarTarget, lowerDestructuringAssign } from "./lower-stmts.js";
10
10
  import { isJsSourceFile, locOf } from "../program.js";
11
11
  import { DYN_DISPATCH_METHODS, islandPrimitiveExit } from "./lower-calls.js";
12
12
  import { typeKey } from "../types.js";
13
13
  import { dynUndefinedExpr, own } from "./lowerer.js";
14
+ /** Lower an expression whose checker type is statically `undefined`/`void`.
15
+ * Optional builtin arguments use this before their ordinary expected-type
16
+ * coercion so every equivalent spelling (`undefined`, `void 0`, a typed
17
+ * binding, or an effectful call returning undefined) selects the default.
18
+ * An effectful `void e` is exact in this context: evaluate e, then produce
19
+ * undefined, instead of hitting value-position void's general fence. */
20
+ function lowerStaticallyUndefinedArg(L, node) {
21
+ const peelErasableWrappers = (value) => {
22
+ let expr = value;
23
+ while (ts.isParenthesizedExpression(expr) ||
24
+ ts.isAsExpression(expr) ||
25
+ ts.isTypeAssertion(expr) ||
26
+ ts.isSatisfiesExpression(expr)) {
27
+ expr = expr.expression;
28
+ }
29
+ return expr;
30
+ };
31
+ let expr = node;
32
+ while (ts.isParenthesizedExpression(expr))
33
+ expr = expr.expression;
34
+ if ((L.typeOf(expr).flags &
35
+ (ts.TypeFlags.Undefined | ts.TypeFlags.Void)) ===
36
+ 0) {
37
+ return null;
38
+ }
39
+ // TypeScript erases `as`, angle-bracket assertions, and `satisfies`.
40
+ // Peel them only for the void recognition: a non-void assertion still
41
+ // lowers through the ordinary path below, preserving the checked-dynamic
42
+ // cast discipline. Nested voids add no effects of their own, so the one
43
+ // innermost operand carries the complete runtime evaluation.
44
+ expr = peelErasableWrappers(expr);
45
+ let sawVoid = false;
46
+ while (ts.isVoidExpression(expr)) {
47
+ sawVoid = true;
48
+ expr = peelErasableWrappers(expr.expression);
49
+ }
50
+ if (sawVoid) {
51
+ // The caller discards this token before producing the parameter
52
+ // default, so the operand itself carries exactly the required effects;
53
+ // no bare undefined value needs to enter the IR.
54
+ return L.lowerExpr(expr);
55
+ }
56
+ return L.lowerExpr(node);
57
+ }
58
+ /** Preserve a statically-undefined argument's effects, then answer the
59
+ * optional parameter's already-lowered default value. */
60
+ function defaultAfterUndefined(value, defaultValue) {
61
+ if (droppableStatic(value))
62
+ return defaultValue;
63
+ return {
64
+ kind: "seqExpr",
65
+ stmts: [{ kind: "exprStmt", expr: value, loc: value.loc }],
66
+ result: defaultValue,
67
+ type: defaultValue.type,
68
+ loc: value.loc,
69
+ };
70
+ }
71
+ function lowerOptionalDefaultArg(L, node, expected, defaultValue) {
72
+ const undefinedArg = lowerStaticallyUndefinedArg(L, node);
73
+ return undefinedArg
74
+ ? defaultAfterUndefined(undefinedArg, defaultValue)
75
+ : L.lowerExprExpecting(node, expected);
76
+ }
14
77
  /** Ambient array method calls. `push`/`pop`/`indexOf`/`includes`/`join`
15
78
  * lower to arrIntrinsic; the HOF family — `map`/`filter`/`forEach`/
16
79
  * `find`/`some`/`every`/`flatMap`/`reduce`/`reduceRight` — desugars to a
@@ -105,8 +168,72 @@ export function lowerArrayMethodCall(L, call, access) {
105
168
  elem = receiver.type.elem;
106
169
  }
107
170
  }
108
- if (name === "sort")
171
+ if (name === "sort" || name === "toSorted") {
109
172
  return lowerArraySortCall(L, call, access, elem, receiverIr);
173
+ }
174
+ if (name === "toReversed") {
175
+ if (call.arguments.length !== 0) {
176
+ L.noLowering(`.toReversed with ${call.arguments.length} arguments`, call);
177
+ }
178
+ return {
179
+ kind: "arrIntrinsic",
180
+ method: "toReversed",
181
+ receiver: L.lowerExpr(access.expression),
182
+ args: [],
183
+ type: receiverIr,
184
+ loc,
185
+ };
186
+ }
187
+ if (name === "with") {
188
+ if (call.arguments.length !== 2 || call.arguments.some(ts.isSpreadElement)) {
189
+ L.noLowering(`.with with ${call.arguments.length} arguments`, call);
190
+ }
191
+ const receiver = L.lowerExpr(access.expression);
192
+ const index = L.lowerExprExpecting(call.arguments[0], F64);
193
+ const value = L.coerceInto(call.arguments[1], L.lowerExpr(call.arguments[1]), elem);
194
+ return {
195
+ kind: "arrIntrinsic",
196
+ method: "with",
197
+ receiver,
198
+ args: [index, value],
199
+ type: receiverIr,
200
+ loc,
201
+ };
202
+ }
203
+ if (name === "toSpliced") {
204
+ if (call.arguments.some(ts.isSpreadElement)) {
205
+ L.unsupported("SC1090", call, "spread arguments to Array.toSpliced");
206
+ }
207
+ const receiver = L.lowerExpr(access.expression);
208
+ const start = call.arguments[0]
209
+ ? L.lowerExprExpecting(call.arguments[0], F64)
210
+ : { kind: "numLit", value: 0, type: F64, loc };
211
+ const deleteCountDefault = {
212
+ kind: "numLit",
213
+ value: NaN,
214
+ type: F64,
215
+ loc,
216
+ };
217
+ const deleteCount = call.arguments.length === 0
218
+ ? { kind: "numLit", value: 0, type: F64, loc }
219
+ : call.arguments[1]
220
+ ? lowerOptionalDefaultArg(L, call.arguments[1], F64, deleteCountDefault)
221
+ : { kind: "numLit", value: Infinity, type: F64, loc };
222
+ const items = {
223
+ kind: "arrayLit",
224
+ elems: call.arguments.slice(2).map((arg) => L.coerceInto(arg, L.lowerExpr(arg), elem)),
225
+ type: receiverIr,
226
+ loc,
227
+ };
228
+ return {
229
+ kind: "arrIntrinsic",
230
+ method: "toSpliced",
231
+ receiver,
232
+ args: [start, deleteCount, items],
233
+ type: receiverIr,
234
+ loc,
235
+ };
236
+ }
110
237
  // The lib declares wider call forms than the lowered surface —
111
238
  // indexOf/includes take a fromIndex, join's separator is optional, the
112
239
  // predicate/mapping HOFs take a thisArg. Each unlowered form is fenced
@@ -1287,40 +1414,45 @@ function reduceHelper(L, method, elem, accT, arity, hasInit, loc) {
1287
1414
  L.liftedFns.push({ name, params, returnType: accT, locals, body, loc });
1288
1415
  return name;
1289
1416
  }
1290
- /** `a.sort(cmp)` — in-place, returns the receiver (JS's `this`), desugared
1291
- * to an interned synthetic function like the other array HOFs. The loop
1292
- * is a binary-free INSERTION sort: stable (equal-comparing elements keep
1293
- * their source order, which is what Node's stable TimSort produces for
1294
- * any consistent comparator) and JS-faithful on the comparator contract —
1295
- * an element moves left only while cmp(left, v) > 0, so a NaN or 0
1296
- * result holds position exactly like the spec's "treat as equal". The
1297
- * SEQUENCE of comparator calls differs from V8's TimSort (SEMANTICS.md);
1298
- * results are identical for consistent comparators. The comparator-less
1299
- * form lowers for STRING elements only — JS's default converts every
1300
- * element to string and compares UTF-16 units, which for strings is the
1301
- * relational operator the compiler already has (an interned synthesized
1302
- * comparator); for numbers that default is the notorious string sort
1303
- * ([10, 9, 1] → [1, 10, 9]), deliberately fenced toward an explicit
1417
+ /** `a.sort(cmp)` / `a.toSorted(cmp)`, desugared to an interned synthetic
1418
+ * function like the other array HOFs. sort mutates and returns the receiver;
1419
+ * toSorted takes its shallow snapshot INSIDE the helper, after the receiver
1420
+ * and comparator expressions have both been evaluated, then sorts and
1421
+ * returns that copy without touching the receiver. The loop is a
1422
+ * binary-free INSERTION sort: stable (equal-comparing elements keep their
1423
+ * source order, which is what Node's stable TimSort produces for any
1424
+ * consistent comparator) and JS-faithful on the comparator contract — an
1425
+ * element moves left only while cmp(left, v) > 0, so a NaN or 0 result holds
1426
+ * position exactly like the spec's "treat as equal". The SEQUENCE of
1427
+ * comparator calls differs from V8's TimSort (SEMANTICS.md); results are
1428
+ * identical for consistent comparators. The comparator-less form lowers for
1429
+ * STRING elements only — JS's default converts every element to string and
1430
+ * compares UTF-16 units, so the interned synthesized comparator selects the
1431
+ * runtime's code-unit ordering rather than scriptc's documented code-point
1432
+ * relational operators. For numbers that default is the notorious string
1433
+ * sort ([10, 9, 1] → [1, 10, 9]), deliberately fenced toward an explicit
1304
1434
  * comparator. */
1305
1435
  export function lowerArraySortCall(L, call, access, elem, arrT) {
1306
1436
  const loc = locOf(call);
1437
+ const method = access.name.text === "toSorted" ? "toSorted" : "sort";
1438
+ const copyFirst = method === "toSorted";
1307
1439
  if (call.arguments.length === 0 && elem.kind === "string") {
1308
1440
  const receiver = L.lowerExpr(access.expression);
1309
1441
  const cmp = defaultStringCmpHelper(L, loc);
1310
1442
  const fnT = funcOf([STRING, STRING], F64);
1311
- const key = `sort:${typeKey(STRING)}:2`;
1443
+ const key = `${method}:${typeKey(STRING)}:2`;
1312
1444
  let helper = L.arrHofHelpers.get(key);
1313
1445
  if (!helper) {
1314
- helper = `%arr.sort.${L.arrHofHelpers.size}`;
1446
+ helper = `%arr.${method}.${L.arrHofHelpers.size}`;
1315
1447
  L.arrHofHelpers.set(key, helper);
1316
- L.liftedFns.push(buildArraySortFn(helper, STRING, 2, loc));
1448
+ L.liftedFns.push(buildArraySortFn(helper, STRING, 2, copyFirst, null, loc));
1317
1449
  }
1318
1450
  const fnArg = { kind: "closure", fnName: cmp, captures: [], type: fnT, loc };
1319
1451
  return { kind: "call", callee: helper, args: [receiver, fnArg], type: arrT, loc };
1320
1452
  }
1321
1453
  if (call.arguments.length !== 1) {
1322
- L.noLowering(`.sort with ${call.arguments.length} arguments`, call, "the default string-conversion ordering lowers only for string[] — pass a comparator: " +
1323
- "sort((a, b) => a - b) for numbers");
1454
+ L.noLowering(`.${method} with ${call.arguments.length} arguments`, call, "the default string-conversion ordering lowers only for string[] — pass a comparator: " +
1455
+ `${method}((a, b) => a - b) for numbers`);
1324
1456
  }
1325
1457
  const receiver = L.lowerExpr(access.expression);
1326
1458
  const argNode = call.arguments[0];
@@ -1335,12 +1467,13 @@ export function lowerArraySortCall(L, call, access, elem, arrT) {
1335
1467
  L.badType(argNode, L.typeOf(argNode));
1336
1468
  }
1337
1469
  const arity = fnArg.type.params.length;
1338
- const key = `sort:${typeKey(elem)}:${arity}`;
1470
+ const key = `${method}:${typeKey(elem)}:${arity}`;
1339
1471
  let helper = L.arrHofHelpers.get(key);
1340
1472
  if (!helper) {
1341
- helper = `%arr.sort.${L.arrHofHelpers.size}`;
1473
+ helper = `%arr.${method}.${L.arrHofHelpers.size}`;
1342
1474
  L.arrHofHelpers.set(key, helper);
1343
- L.liftedFns.push(buildArraySortFn(helper, elem, arity, loc));
1475
+ const undefinedTag = elem.kind === "union" ? L.armTag(elem.unionId, UNDEFINED_T) : -1;
1476
+ L.liftedFns.push(buildArraySortFn(helper, elem, arity, copyFirst, undefinedTag >= 0 ? undefinedTag : null, loc));
1344
1477
  }
1345
1478
  return { kind: "call", callee: helper, args: [receiver, fnArg], type: arrT, loc };
1346
1479
  }
@@ -1348,9 +1481,9 @@ export function lowerArraySortCall(L, call, access, elem, arrT) {
1348
1481
  *
1349
1482
  * (a, b) => a < b ? -1 : a > b ? 1 : 0
1350
1483
  *
1351
- * For strings, the spec's default (ToString both, compare UTF-16 units)
1352
- * IS the relational operator, so this synthesized comparator makes
1353
- * `xs.sort()` exactly `xs.sort((a, b) => ...)`. */
1484
+ * The dedicated UTF-16 comparison flag keeps this exact across
1485
+ * supplementary-plane code points and U+E000..U+FFFF, where the runtime's
1486
+ * ordinary code-point relational order deliberately differs. */
1354
1487
  function defaultStringCmpHelper(L, loc) {
1355
1488
  const key = "sortCmpStr";
1356
1489
  const existing = L.arrHofHelpers.get(key);
@@ -1361,7 +1494,7 @@ function defaultStringCmpHelper(L, loc) {
1361
1494
  const ref = (localId) => ({ kind: "varRef", localId, type: STRING, loc });
1362
1495
  const num = (value) => ({ kind: "numLit", value, type: F64, loc });
1363
1496
  const cmp = (op) => ({
1364
- kind: "strCmp", op, left: ref("a.0"), right: ref("b.0"), type: BOOL, loc,
1497
+ kind: "strCmp", op, left: ref("a.0"), right: ref("b.0"), utf16: true, type: BOOL, loc,
1365
1498
  });
1366
1499
  const body = [
1367
1500
  {
@@ -1396,16 +1529,18 @@ function defaultStringCmpHelper(L, loc) {
1396
1529
  /** The insertion-sort loop, from existing IR nodes:
1397
1530
  *
1398
1531
  * n = a.length;
1399
- * for (i = 1; i < n; i++) {
1400
- * v = a[i]; j = i - 1;
1401
- * while (j >= 0) {
1402
- * if (f(a[j], v) > 0) { a[j + 1] = a[j]; j = j - 1; } else break;
1532
+ * for (i = 1; i < n; i++) {
1533
+ * v = a[i]; j = i - 1;
1534
+ * while (j >= 0) {
1535
+ * if (CompareArrayElements(a[j], v, f) > 0) {
1536
+ * a[j + 1] = a[j]; j = j - 1;
1537
+ * } else break;
1403
1538
  * }
1404
1539
  * a[j + 1] = v;
1405
1540
  * }
1406
1541
  * return a;
1407
1542
  */
1408
- function buildArraySortFn(name, elem, arity, loc) {
1543
+ function buildArraySortFn(name, elem, arity, copyFirst, undefinedTag, loc) {
1409
1544
  const arrT = arrayOf(elem);
1410
1545
  const fnT = funcOf([elem, elem].slice(0, arity), F64);
1411
1546
  const ref = (localId, type) => ({ kind: "varRef", localId, type, loc });
@@ -1413,26 +1548,80 @@ function buildArraySortFn(name, elem, arity, loc) {
1413
1548
  const j = ref("j.0", F64);
1414
1549
  const at = (index) => ({ kind: "arrayGet", arr: ref("a.0", arrT), index, type: elem, loc });
1415
1550
  const jPlus1 = { kind: "bin", op: "+", left: j, right: num(1), type: F64, loc };
1551
+ const isUndefined = (value) => {
1552
+ if (elem.kind === "union" && undefinedTag !== null) {
1553
+ return {
1554
+ kind: "unionIsTag",
1555
+ unionId: elem.unionId,
1556
+ tag: undefinedTag,
1557
+ negated: false,
1558
+ value,
1559
+ type: BOOL,
1560
+ loc,
1561
+ };
1562
+ }
1563
+ if (elem.kind === "jsval") {
1564
+ return {
1565
+ kind: "jsOp",
1566
+ op: "eq",
1567
+ args: [
1568
+ value,
1569
+ { kind: "jsOp", op: "undefLit", args: [], type: JSVAL, loc },
1570
+ ],
1571
+ type: BOOL,
1572
+ loc,
1573
+ };
1574
+ }
1575
+ return null;
1576
+ };
1577
+ const compareGreater = {
1578
+ kind: "bin",
1579
+ op: ">",
1580
+ left: {
1581
+ kind: "callValue",
1582
+ callee: ref("f.0", fnT),
1583
+ args: [at(j), ref("v.0", elem)].slice(0, arity),
1584
+ type: F64,
1585
+ loc,
1586
+ },
1587
+ right: num(0),
1588
+ type: BOOL,
1589
+ loc,
1590
+ };
1591
+ const leftUndefined = isUndefined(at(j));
1592
+ const valueUndefined = isUndefined(ref("v.0", elem));
1593
+ // CompareArrayElements: undefined always sinks and never reaches the
1594
+ // user comparator. Ternaries preserve that callback suppression.
1595
+ const shouldShift = leftUndefined !== null && valueUndefined !== null
1596
+ ? {
1597
+ kind: "ternary",
1598
+ cond: leftUndefined,
1599
+ then: {
1600
+ kind: "unary",
1601
+ op: "!",
1602
+ operand: valueUndefined,
1603
+ type: BOOL,
1604
+ loc,
1605
+ },
1606
+ else_: {
1607
+ kind: "ternary",
1608
+ cond: valueUndefined,
1609
+ then: { kind: "boolLit", value: false, type: BOOL, loc },
1610
+ else_: compareGreater,
1611
+ type: BOOL,
1612
+ loc,
1613
+ },
1614
+ type: BOOL,
1615
+ loc,
1616
+ }
1617
+ : compareGreater;
1416
1618
  const shiftLoop = {
1417
1619
  kind: "while",
1418
1620
  cond: { kind: "bin", op: ">=", left: j, right: num(0), type: BOOL, loc },
1419
1621
  body: [
1420
1622
  {
1421
1623
  kind: "if",
1422
- cond: {
1423
- kind: "bin",
1424
- op: ">",
1425
- left: {
1426
- kind: "callValue",
1427
- callee: ref("f.0", fnT),
1428
- args: [at(j), ref("v.0", elem)].slice(0, arity),
1429
- type: F64,
1430
- loc,
1431
- },
1432
- right: num(0),
1433
- type: BOOL,
1434
- loc,
1435
- },
1624
+ cond: shouldShift,
1436
1625
  then: [
1437
1626
  { kind: "arraySet", arr: ref("a.0", arrT), index: jPlus1, value: at(j), loc },
1438
1627
  { kind: "assign", localId: "j.0", value: { kind: "bin", op: "-", left: j, right: num(1), type: F64, loc }, loc },
@@ -1444,6 +1633,21 @@ function buildArraySortFn(name, elem, arity, loc) {
1444
1633
  loc,
1445
1634
  };
1446
1635
  const body = [
1636
+ ...(copyFirst
1637
+ ? [{
1638
+ kind: "assign",
1639
+ localId: "a.0",
1640
+ value: {
1641
+ kind: "arrIntrinsic",
1642
+ method: "slice",
1643
+ receiver: ref("a.0", arrT),
1644
+ args: [],
1645
+ type: arrT,
1646
+ loc,
1647
+ },
1648
+ loc,
1649
+ }]
1650
+ : []),
1447
1651
  readLenStmt(arrT, loc),
1448
1652
  {
1449
1653
  kind: "for",
@@ -1484,6 +1688,294 @@ function buildArraySortFn(name, elem, arity, loc) {
1484
1688
  loc,
1485
1689
  };
1486
1690
  }
1691
+ /** Uint8Array.prototype.toSorted. The receiver/comparator expressions are
1692
+ * evaluated before entering the helper; the helper snapshots with
1693
+ * TypedArray.prototype.slice before its first comparison, then performs
1694
+ * the same stable insertion walk as Array.toSorted. Uint8Array's default
1695
+ * comparator is numeric ascending, so its comparator-less form needs no
1696
+ * string-conversion machinery. */
1697
+ function lowerBytesToSortedCall(L, call, access, bytesT) {
1698
+ const loc = locOf(call);
1699
+ if (call.arguments.length > 1 || call.arguments.some(ts.isSpreadElement)) {
1700
+ L.noLowering(`.toSorted with ${call.arguments.length} arguments on Uint8Array`, call);
1701
+ }
1702
+ const receiver = L.lowerExpr(access.expression);
1703
+ const undefinedArg = call.arguments[0]
1704
+ ? lowerStaticallyUndefinedArg(L, call.arguments[0])
1705
+ : null;
1706
+ if (call.arguments.length === 0 || undefinedArg) {
1707
+ const key = "bytes.toSorted:u8:default";
1708
+ let helper = L.arrHofHelpers.get(key);
1709
+ if (!helper) {
1710
+ helper = `%bytes.toSorted.${L.arrHofHelpers.size}`;
1711
+ L.arrHofHelpers.set(key, helper);
1712
+ L.liftedFns.push(buildBytesSortFn(helper, 0, false, loc));
1713
+ }
1714
+ if (!undefinedArg || droppableStatic(undefinedArg)) {
1715
+ return { kind: "call", callee: helper, args: [receiver], type: bytesT, loc };
1716
+ }
1717
+ // The default helper takes no comparator argument. Snapshot the
1718
+ // receiver into a hidden local so the discarded undefined argument
1719
+ // still evaluates after the receiver and before the helper call.
1720
+ const saved = L.declareHiddenLocal("%bytesSortRecv", bytesT);
1721
+ const savedRef = {
1722
+ kind: "varRef",
1723
+ localId: saved.id,
1724
+ type: bytesT,
1725
+ loc,
1726
+ };
1727
+ return {
1728
+ kind: "seqExpr",
1729
+ stmts: [
1730
+ { kind: "varDecl", localId: saved.id, init: receiver, loc },
1731
+ { kind: "exprStmt", expr: undefinedArg, loc: undefinedArg.loc },
1732
+ ],
1733
+ result: {
1734
+ kind: "call",
1735
+ callee: helper,
1736
+ args: [savedRef],
1737
+ type: bytesT,
1738
+ loc,
1739
+ },
1740
+ type: bytesT,
1741
+ loc,
1742
+ };
1743
+ }
1744
+ const argNode = call.arguments[0];
1745
+ const fnArg = L.lowerExpr(argNode);
1746
+ if (fnArg.type.kind !== "func" ||
1747
+ fnArg.type.params.length > 2 ||
1748
+ !fnArg.type.params.every((p) => p.kind === "f64") ||
1749
+ fnArg.type.ret.kind !== "f64") {
1750
+ L.badType(argNode, L.typeOf(argNode));
1751
+ }
1752
+ const arity = fnArg.type.params.length;
1753
+ const key = `bytes.toSorted:u8:${arity}`;
1754
+ let helper = L.arrHofHelpers.get(key);
1755
+ if (!helper) {
1756
+ helper = `%bytes.toSorted.${L.arrHofHelpers.size}`;
1757
+ L.arrHofHelpers.set(key, helper);
1758
+ L.liftedFns.push(buildBytesSortFn(helper, arity, true, loc));
1759
+ }
1760
+ return {
1761
+ kind: "call",
1762
+ callee: helper,
1763
+ args: [receiver, fnArg],
1764
+ type: bytesT,
1765
+ loc,
1766
+ };
1767
+ }
1768
+ function buildBytesSortFn(name, arity, hasComparator, loc) {
1769
+ const bytesT = BYTES_U8;
1770
+ const fnT = funcOf([F64, F64].slice(0, arity), F64);
1771
+ const ref = (localId, type) => ({
1772
+ kind: "varRef",
1773
+ localId,
1774
+ type,
1775
+ loc,
1776
+ });
1777
+ const num = (value) => ({
1778
+ kind: "numLit",
1779
+ value,
1780
+ type: F64,
1781
+ loc,
1782
+ });
1783
+ const j = ref("j.0", F64);
1784
+ const at = (index) => ({
1785
+ kind: "bytesIntrinsic",
1786
+ method: "get",
1787
+ receiver: ref("a.0", bytesT),
1788
+ args: [index],
1789
+ type: F64,
1790
+ loc,
1791
+ });
1792
+ const jPlus1 = {
1793
+ kind: "bin",
1794
+ op: "+",
1795
+ left: j,
1796
+ right: num(1),
1797
+ type: F64,
1798
+ loc,
1799
+ };
1800
+ const compare = hasComparator
1801
+ ? {
1802
+ kind: "callValue",
1803
+ callee: ref("f.0", fnT),
1804
+ args: [at(j), ref("v.0", F64)].slice(0, arity),
1805
+ type: F64,
1806
+ loc,
1807
+ }
1808
+ : {
1809
+ kind: "bin",
1810
+ op: "-",
1811
+ left: at(j),
1812
+ right: ref("v.0", F64),
1813
+ type: F64,
1814
+ loc,
1815
+ };
1816
+ const shiftLoop = {
1817
+ kind: "while",
1818
+ cond: {
1819
+ kind: "bin",
1820
+ op: ">=",
1821
+ left: j,
1822
+ right: num(0),
1823
+ type: BOOL,
1824
+ loc,
1825
+ },
1826
+ body: [
1827
+ {
1828
+ kind: "if",
1829
+ cond: {
1830
+ kind: "bin",
1831
+ op: ">",
1832
+ left: compare,
1833
+ right: num(0),
1834
+ type: BOOL,
1835
+ loc,
1836
+ },
1837
+ then: [
1838
+ {
1839
+ kind: "bytesSet",
1840
+ arr: ref("a.0", bytesT),
1841
+ index: jPlus1,
1842
+ value: at(j),
1843
+ loc,
1844
+ },
1845
+ {
1846
+ kind: "assign",
1847
+ localId: "j.0",
1848
+ value: {
1849
+ kind: "bin",
1850
+ op: "-",
1851
+ left: j,
1852
+ right: num(1),
1853
+ type: F64,
1854
+ loc,
1855
+ },
1856
+ loc,
1857
+ },
1858
+ ],
1859
+ else_: [{ kind: "break", loc }],
1860
+ loc,
1861
+ },
1862
+ ],
1863
+ loc,
1864
+ };
1865
+ const params = [
1866
+ { localId: "a.0", name: "a", type: bytesT },
1867
+ ...(hasComparator
1868
+ ? [{ localId: "f.0", name: "f", type: fnT }]
1869
+ : []),
1870
+ ];
1871
+ const locals = [
1872
+ { id: "a.0", name: "a", type: bytesT, mutable: true },
1873
+ ...(hasComparator
1874
+ ? [{ id: "f.0", name: "f", type: fnT, mutable: true }]
1875
+ : []),
1876
+ { id: "n.0", name: "n", type: F64, mutable: false },
1877
+ { id: "i.0", name: "i", type: F64, mutable: true },
1878
+ { id: "v.0", name: "v", type: F64, mutable: false },
1879
+ { id: "j.0", name: "j", type: F64, mutable: true },
1880
+ ];
1881
+ const body = [
1882
+ {
1883
+ kind: "assign",
1884
+ localId: "a.0",
1885
+ value: {
1886
+ kind: "bytesIntrinsic",
1887
+ method: "slice",
1888
+ receiver: ref("a.0", bytesT),
1889
+ args: [],
1890
+ type: bytesT,
1891
+ loc,
1892
+ },
1893
+ loc,
1894
+ },
1895
+ {
1896
+ kind: "varDecl",
1897
+ localId: "n.0",
1898
+ init: {
1899
+ kind: "bytesIntrinsic",
1900
+ method: "length",
1901
+ receiver: ref("a.0", bytesT),
1902
+ args: [],
1903
+ type: F64,
1904
+ loc,
1905
+ },
1906
+ loc,
1907
+ },
1908
+ {
1909
+ kind: "for",
1910
+ init: {
1911
+ kind: "varDecl",
1912
+ localId: "i.0",
1913
+ init: num(1),
1914
+ loc,
1915
+ },
1916
+ cond: {
1917
+ kind: "bin",
1918
+ op: "<",
1919
+ left: ref("i.0", F64),
1920
+ right: ref("n.0", F64),
1921
+ type: BOOL,
1922
+ loc,
1923
+ },
1924
+ update: {
1925
+ kind: "assign",
1926
+ localId: "i.0",
1927
+ value: {
1928
+ kind: "bin",
1929
+ op: "+",
1930
+ left: ref("i.0", F64),
1931
+ right: num(1),
1932
+ type: F64,
1933
+ loc,
1934
+ },
1935
+ loc,
1936
+ },
1937
+ body: [
1938
+ {
1939
+ kind: "varDecl",
1940
+ localId: "v.0",
1941
+ init: at(ref("i.0", F64)),
1942
+ loc,
1943
+ },
1944
+ {
1945
+ kind: "varDecl",
1946
+ localId: "j.0",
1947
+ init: {
1948
+ kind: "bin",
1949
+ op: "-",
1950
+ left: ref("i.0", F64),
1951
+ right: num(1),
1952
+ type: F64,
1953
+ loc,
1954
+ },
1955
+ loc,
1956
+ },
1957
+ shiftLoop,
1958
+ {
1959
+ kind: "bytesSet",
1960
+ arr: ref("a.0", bytesT),
1961
+ index: jPlus1,
1962
+ value: ref("v.0", F64),
1963
+ loc,
1964
+ },
1965
+ ],
1966
+ loc,
1967
+ },
1968
+ { kind: "return", value: ref("a.0", bytesT), loc },
1969
+ ];
1970
+ return {
1971
+ name,
1972
+ params,
1973
+ returnType: bytesT,
1974
+ locals,
1975
+ body,
1976
+ loc,
1977
+ };
1978
+ }
1487
1979
  /** `n = a.length` — the once-up-front length read every array HOF loop
1488
1980
  * starts with (locals a.0/n.0 by convention). */
1489
1981
  function readLenStmt(arrT, loc) {
@@ -3462,7 +3954,7 @@ export function lowerForOfSearchParams(L, stmt, iterable, proj) {
3462
3954
  L.scopes.pop();
3463
3955
  }
3464
3956
  }
3465
- /** for-of over an ARRAY's keys()/entries() projections consumed directly
3957
+ /** for-of over an ARRAY/typed-array keys()/entries() projection consumed directly
3466
3958
  * by the head (`for (const [index, line] of lines.entries())` — the
3467
3959
  * dominant formatter idiom): the LIVE index walk — the length re-reads
3468
3960
  * every pass, exactly the array iterator's contract (elements appended
@@ -3477,7 +3969,7 @@ export function lowerForOfSearchParams(L, stmt, iterable, proj) {
3477
3969
  export function lowerForOfArrayIter(L, stmt, iterable, proj) {
3478
3970
  const loc = locOf(stmt);
3479
3971
  const arrT = iterable.type;
3480
- const elemT = arrT.elem;
3972
+ const elemT = arrT.kind === "array" ? arrT.elem : F64;
3481
3973
  const yieldsPair = proj === "entries";
3482
3974
  if (!ts.isVariableDeclarationList(stmt.initializer)) {
3483
3975
  L.unsupported("SC1090", stmt.initializer, "for-of over a pre-declared variable (declare the loop variable in the loop: for (const x of ...))");
@@ -3497,7 +3989,22 @@ export function lowerForOfArrayIter(L, stmt, iterable, proj) {
3497
3989
  const ref = (localId, type) => ({ kind: "varRef", localId, type, loc });
3498
3990
  const num = (value) => ({ kind: "numLit", value, type: F64, loc });
3499
3991
  const keyRead = () => ref(i.id, F64);
3500
- const valRead = () => ({ kind: "arrayGet", arr: ref(arr.id, arrT), index: ref(i.id, F64), type: elemT, loc });
3992
+ const valRead = () => arrT.kind === "array"
3993
+ ? {
3994
+ kind: "arrayGet",
3995
+ arr: ref(arr.id, arrT),
3996
+ index: ref(i.id, F64),
3997
+ type: elemT,
3998
+ loc,
3999
+ }
4000
+ : {
4001
+ kind: "bytesIntrinsic",
4002
+ method: "get",
4003
+ receiver: ref(arr.id, arrT),
4004
+ args: [ref(i.id, F64)],
4005
+ type: F64,
4006
+ loc,
4007
+ };
3501
4008
  const binds = [];
3502
4009
  const isPlainIdent = (el) => ts.isBindingElement(el) && el.name !== undefined && ts.isIdentifier(el.name) && !el.initializer && !el.dotDotDotToken;
3503
4010
  if (yieldsPair &&
@@ -3576,7 +4083,23 @@ export function lowerForOfArrayIter(L, stmt, iterable, proj) {
3576
4083
  kind: "bin",
3577
4084
  op: "<",
3578
4085
  left: ref(i.id, F64),
3579
- right: { kind: "arrIntrinsic", method: "length", receiver: ref(arr.id, arrT), args: [], type: F64, loc },
4086
+ right: arrT.kind === "array"
4087
+ ? {
4088
+ kind: "arrIntrinsic",
4089
+ method: "length",
4090
+ receiver: ref(arr.id, arrT),
4091
+ args: [],
4092
+ type: F64,
4093
+ loc,
4094
+ }
4095
+ : {
4096
+ kind: "bytesIntrinsic",
4097
+ method: "length",
4098
+ receiver: ref(arr.id, arrT),
4099
+ args: [],
4100
+ type: F64,
4101
+ loc,
4102
+ },
3580
4103
  type: BOOL,
3581
4104
  loc,
3582
4105
  },
@@ -4406,6 +4929,60 @@ export function lowerBytesMethodCall(L, call, access) {
4406
4929
  return null;
4407
4930
  const loc = locOf(call);
4408
4931
  const nArgs = call.arguments.length;
4932
+ if (receiverIr.elem === "u8" && name === "toSorted") {
4933
+ return lowerBytesToSortedCall(L, call, access, receiverIr);
4934
+ }
4935
+ if (receiverIr.elem === "u8" && name === "toReversed") {
4936
+ if (nArgs !== 0) {
4937
+ L.noLowering(`.toReversed with ${nArgs} arguments on Uint8Array`, call);
4938
+ }
4939
+ return {
4940
+ kind: "bytesIntrinsic",
4941
+ method: "toReversed",
4942
+ receiver: L.lowerExpr(access.expression),
4943
+ args: [],
4944
+ type: receiverIr,
4945
+ loc,
4946
+ };
4947
+ }
4948
+ if (receiverIr.elem === "u8" && name === "with") {
4949
+ if (nArgs !== 2 || call.arguments.some(ts.isSpreadElement)) {
4950
+ L.noLowering(`.with with ${nArgs} arguments on Uint8Array`, call);
4951
+ }
4952
+ return {
4953
+ kind: "bytesIntrinsic",
4954
+ method: "with",
4955
+ receiver: L.lowerExpr(access.expression),
4956
+ args: [
4957
+ L.lowerExprExpecting(call.arguments[0], F64),
4958
+ L.lowerExprExpecting(call.arguments[1], F64),
4959
+ ],
4960
+ type: receiverIr,
4961
+ loc,
4962
+ };
4963
+ }
4964
+ if (receiverIr.elem === "u8" && name === "join") {
4965
+ if (nArgs > 1 || call.arguments.some(ts.isSpreadElement)) {
4966
+ L.noLowering(`.join with ${nArgs} arguments on Uint8Array`, call);
4967
+ }
4968
+ const separatorDefault = {
4969
+ kind: "strLit",
4970
+ value: ",",
4971
+ type: STRING,
4972
+ loc,
4973
+ };
4974
+ const separator = call.arguments[0]
4975
+ ? lowerOptionalDefaultArg(L, call.arguments[0], STRING, separatorDefault)
4976
+ : separatorDefault;
4977
+ return {
4978
+ kind: "bytesIntrinsic",
4979
+ method: "join",
4980
+ receiver: L.lowerExpr(access.expression),
4981
+ args: [separator],
4982
+ type: STRING,
4983
+ loc,
4984
+ };
4985
+ }
4409
4986
  if (name === "slice" || name === "subarray") {
4410
4987
  if (nArgs > 2) {
4411
4988
  L.noLowering(`.${name} with ${nArgs} arguments on typed arrays`, call);