@scriptc/compiler 0.0.6 → 0.0.8

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 (75) hide show
  1. package/dist/backend/emission/emit-exprs.js +47 -1
  2. package/dist/backend/emission/emit-exprs.js.map +1 -1
  3. package/dist/backend/emission/emit-island.js +9 -1
  4. package/dist/backend/emission/emit-island.js.map +1 -1
  5. package/dist/backend/emission/emit-stmts.js +3 -1
  6. package/dist/backend/emission/emit-stmts.js.map +1 -1
  7. package/dist/backend/emission/emit-walkers.js +22 -8
  8. package/dist/backend/emission/emit-walkers.js.map +1 -1
  9. package/dist/backend/emission/emitter.js +12 -1
  10. package/dist/backend/emission/emitter.js.map +1 -1
  11. package/dist/backend/emission/may-throw.js +4 -3
  12. package/dist/backend/emission/may-throw.js.map +1 -1
  13. package/dist/backend/llvm/emitter.js +105 -9
  14. package/dist/backend/llvm/emitter.js.map +1 -1
  15. package/dist/diagnostics/diagnostic.d.ts +49 -1
  16. package/dist/diagnostics/diagnostic.js +119 -5
  17. package/dist/diagnostics/diagnostic.js.map +1 -1
  18. package/dist/frontend/lib-contract.d.ts +114 -0
  19. package/dist/frontend/lib-contract.js +372 -0
  20. package/dist/frontend/lib-contract.js.map +1 -0
  21. package/dist/frontend/lowering/lower-builtins.js +16 -1
  22. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  23. package/dist/frontend/lowering/lower-calls.d.ts +47 -7
  24. package/dist/frontend/lowering/lower-calls.js +560 -36
  25. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  26. package/dist/frontend/lowering/lower-classes.d.ts +14 -1
  27. package/dist/frontend/lowering/lower-classes.js +85 -15
  28. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  29. package/dist/frontend/lowering/lower-containers.js +43 -1
  30. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  31. package/dist/frontend/lowering/lower-emitter.js +1 -1
  32. package/dist/frontend/lowering/lower-emitter.js.map +1 -1
  33. package/dist/frontend/lowering/lower-exprs.d.ts +26 -3
  34. package/dist/frontend/lowering/lower-exprs.js +474 -44
  35. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  36. package/dist/frontend/lowering/lower-island.d.ts +7 -0
  37. package/dist/frontend/lowering/lower-island.js +71 -10
  38. package/dist/frontend/lowering/lower-island.js.map +1 -1
  39. package/dist/frontend/lowering/lower-mixins.d.ts +1 -1
  40. package/dist/frontend/lowering/lower-mixins.js +1 -1
  41. package/dist/frontend/lowering/lower-modules.js +95 -4
  42. package/dist/frontend/lowering/lower-modules.js.map +1 -1
  43. package/dist/frontend/lowering/lower-stmts.js +430 -44
  44. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  45. package/dist/frontend/lowering/lowerer.d.ts +19 -3
  46. package/dist/frontend/lowering/lowerer.js +188 -11
  47. package/dist/frontend/lowering/lowerer.js.map +1 -1
  48. package/dist/frontend/types.d.ts +28 -1
  49. package/dist/frontend/types.js +300 -8
  50. package/dist/frontend/types.js.map +1 -1
  51. package/dist/index.d.ts +11 -2
  52. package/dist/index.js +96 -7
  53. package/dist/index.js.map +1 -1
  54. package/dist/ir/nodes.d.ts +48 -2
  55. package/dist/ir/nodes.js +103 -3
  56. package/dist/ir/nodes.js.map +1 -1
  57. package/dist/ir/validate.js +35 -6
  58. package/dist/ir/validate.js.map +1 -1
  59. package/dist/library/profile.d.ts +53 -3
  60. package/dist/library/profile.js +143 -12
  61. package/dist/library/profile.js.map +1 -1
  62. package/dist/library/sidecar-validate.d.ts +4 -0
  63. package/dist/library/sidecar-validate.js +506 -0
  64. package/dist/library/sidecar-validate.js.map +1 -0
  65. package/dist/library/sidecar.d.ts +191 -0
  66. package/dist/library/sidecar.js +687 -0
  67. package/dist/library/sidecar.js.map +1 -0
  68. package/dist/library/trap-teaching.d.ts +11 -0
  69. package/dist/library/trap-teaching.js +49 -0
  70. package/dist/library/trap-teaching.js.map +1 -0
  71. package/dist/library/wyhash.d.ts +12 -0
  72. package/dist/library/wyhash.js +116 -0
  73. package/dist/library/wyhash.js.map +1 -0
  74. package/package.json +2 -2
  75. package/surface-manifest.json +37 -2
@@ -13,8 +13,9 @@ import { isProvenanceSourceFile } from "../provenance-registry.js";
13
13
  import { lowerImportEquals, nsWritableTarget } from "./lower-namespaces.js";
14
14
  import { expandoWritableTarget, lowerExpandoAssignStmt } from "./lower-expando.js";
15
15
  import { lowerForOfMap, lowerForOfSearchParams, lowerForOfSet, objectIterOverIndexShape } from "./lower-containers.js";
16
- import { bindingGenericFnInfoOf, bindingGenericFnNodeOf, implicitLocalFnInfoOf, implicitLocalFnNodeOf, recordKeysArrayCall } from "./lower-calls.js";
16
+ import { bindingContextualGenericFnNodeOf, bindingGenericFnAliasInfoOf, bindingGenericFnInfoOf, bindingGenericFnNodeOf, implicitLocalFnInfoOf, implicitLocalFnNodeOf, recordKeysArrayCall } from "./lower-calls.js";
17
17
  import { isMixinFnBinding, mixinResultBindingClassOf } from "./lower-mixins.js";
18
+ import { genericIfaceBindingKeepsClass } from "./lower-classes.js";
18
19
  import { lowerStreamUnderscoreAssign, streamClassAliasDecl, streamSidesOf } from "./lower-stream.js";
19
20
  import { lowerHttpResPropertyAssignment, lowerServerCloseOverrideAssignment } from "./lower-server.js";
20
21
  import { builtinMemberRequireDecl, builtinNamespaceDestructureModuleOf } from "./lower-builtins.js";
@@ -1295,7 +1296,25 @@ export function lowerBindingPattern(L, pattern, srcRef, srcType, isLet, out) {
1295
1296
  continue;
1296
1297
  const loc = locOf(el);
1297
1298
  if (el.dotDotDotToken) {
1298
- L.unsupported("SC1031", el, "rest elements over class-instance sources (the remaining-properties object has no lowering yet)");
1299
+ // `{ a, ...rest }` over a class instance: the remaining
1300
+ // instance FIELDS pack fresh (classInstanceRestValue — JS's
1301
+ // CopyDataProperties copies own enumerable properties, which
1302
+ // for a class instance are exactly the fields).
1303
+ const consumed = new Set();
1304
+ for (const sib of pattern.elements) {
1305
+ if (sib === el || sib.name === undefined || sib.dotDotDotToken)
1306
+ continue;
1307
+ const keyNode = sib.propertyName ?? (ts.isIdentifier(sib.name) ? sib.name : null);
1308
+ if (keyNode === null)
1309
+ continue; // defensive: a pattern target always carries a propertyName
1310
+ const folded = patternKeyNameOf(L, keyNode);
1311
+ if (folded === null) {
1312
+ L.unsupported("SC1031", el, "rest bindings beside computed keys (the consumed set is a runtime fact)");
1313
+ }
1314
+ consumed.add(folded);
1315
+ }
1316
+ L.bindPatternTarget(el.name, classInstanceRestValue(L, el, srcRef, srcType, info, consumed, patternBindingType(L, el.name)), isLet, out);
1317
+ continue;
1299
1318
  }
1300
1319
  const prop = el.propertyName ?? el.name;
1301
1320
  const propName = ts.isIdentifier(prop) || ts.isComputedPropertyName(prop) || ts.isStringLiteralLike(prop) || ts.isNumericLiteral(prop)
@@ -1319,6 +1338,14 @@ export function lowerBindingPattern(L, pattern, srcRef, srcType, isLet, out) {
1319
1338
  field: propName,
1320
1339
  fieldType: getF ? getF.sig.ret : setF.sig.params[0].type,
1321
1340
  }, loc, el);
1341
+ // A defaulted GETTER result lands in a hidden temp first: the
1342
+ // default's ternary mentions its operand twice (test + present
1343
+ // arm), and JS calls the getter once per element.
1344
+ if (el.initializer) {
1345
+ const got = L.declareHiddenLocal("%dget", value.type);
1346
+ out.push({ kind: "varDecl", localId: got.id, init: value, loc });
1347
+ value = { kind: "varRef", localId: got.id, type: value.type, loc };
1348
+ }
1322
1349
  }
1323
1350
  else {
1324
1351
  L.unsupported("SC1031", el, L.findMethodOn(info, propName)
@@ -1372,12 +1399,20 @@ export function lowerBindingPattern(L, pattern, srcRef, srcType, isLet, out) {
1372
1399
  if (getSlotT?.kind !== "func") {
1373
1400
  L.unsupported("SC1031", el, `destructuring the setter-only property '${propName}' (Node would bind undefined)`);
1374
1401
  }
1375
- if (el.initializer) {
1376
- L.unsupported("SC1031", el, "defaults on destructured get/set accessor properties");
1377
- }
1378
1402
  const loc = locOf(el);
1379
1403
  const closure = { kind: "recordGet", obj: srcRef(), shapeId: srcType.shapeId, field: `%get:${propName}`, type: getSlotT, loc };
1380
- L.bindPatternTarget(el.name, { kind: "callValue", callee: closure, args: [], type: getSlotT.ret, loc }, isLet, out);
1404
+ // The getter call IS the element's read; a default applies to its
1405
+ // RESULT exactly like a data field's (undefined-arm test, lazy).
1406
+ // The result lands in a hidden temp first: the default's ternary
1407
+ // mentions its operand twice (test + present arm), and the getter
1408
+ // must run ONCE (JS calls it once per element).
1409
+ let accessorValue = { kind: "callValue", callee: closure, args: [], type: getSlotT.ret, loc };
1410
+ if (el.initializer) {
1411
+ const got = L.declareHiddenLocal("%dget", accessorValue.type);
1412
+ out.push({ kind: "varDecl", localId: got.id, init: accessorValue, loc });
1413
+ accessorValue = applyBindingDefault(L, el, { kind: "varRef", localId: got.id, type: accessorValue.type, loc });
1414
+ }
1415
+ L.bindPatternTarget(el.name, accessorValue, isLet, out);
1381
1416
  continue;
1382
1417
  }
1383
1418
  const fieldType = shape.fields.find((f) => f.name === propName)?.type;
@@ -1440,7 +1475,12 @@ export function patternKeyNameOf(L, prop) {
1440
1475
  return prop.text;
1441
1476
  if (ts.isComputedPropertyName(prop))
1442
1477
  return L.foldedStringKeyOf(prop.expression);
1443
- if (ts.isStringLiteral(prop) || ts.isNoSubstitutionTemplateLiteral(prop) || ts.isNumericLiteral(prop)) {
1478
+ // A numeric-literal key spells JS's canonical ToPropertyKey form
1479
+ // directly (`{ 2: x }` names the field "2") — a pattern position is
1480
+ // not an expression, so the checker-type fold below does not apply.
1481
+ if (ts.isNumericLiteral(prop))
1482
+ return String(Number(prop.text));
1483
+ if (ts.isStringLiteral(prop) || ts.isNoSubstitutionTemplateLiteral(prop)) {
1444
1484
  return L.foldedStringKeyOf(prop);
1445
1485
  }
1446
1486
  return null;
@@ -1681,6 +1721,63 @@ function objectRestValue(L, el, srcRef, srcType, shape) {
1681
1721
  });
1682
1722
  return { kind: "recordLit", fields, type: restT, loc };
1683
1723
  }
1724
+ /** The fresh record for `{ a, ...rest }` over a CLASS-INSTANCE source:
1725
+ * JS's CopyDataProperties copies the OWN ENUMERABLE properties — the
1726
+ * instance FIELDS, base chain included, in property-creation order
1727
+ * (base constructors assign theirs first) — and never the prototype
1728
+ * members (methods and accessors stay behind; the checker's rest type
1729
+ * excludes them too, so the two sides agree). The packed record must
1730
+ * agree with the checker's rest type field-for-field — a non-public
1731
+ * field IS copied by JS but the rest type cannot name it (silent
1732
+ * divergence; fence) — and key-for-key in ORDER: the packed shape's
1733
+ * JSON/Object.keys order is first-seen global metadata, and a checker
1734
+ * rest type that reorders (own-before-inherited) would make the
1735
+ * enumeration surfaces diverge from Node, so it fences instead of
1736
+ * silently reordering. Runtime-provided chains (Error/EventEmitter/
1737
+ * stream roots) carry runtime-internal state no record copy can
1738
+ * reproduce — fenced. ES-#private fields are own but non-enumerable:
1739
+ * JS skips them and so does the packing. */
1740
+ function classInstanceRestValue(L, blame, srcRef, srcType, info, consumed, restT) {
1741
+ const loc = locOf(blame);
1742
+ for (let c = info; c; c = c.base) {
1743
+ if (c.builtinError || c.builtinEmitter || c.builtinStream) {
1744
+ L.unsupported("SC1031", blame, "rest bindings over runtime-provided class instances (Error/EventEmitter/stream internals have no record form)");
1745
+ }
1746
+ }
1747
+ if (restT?.kind !== "record") {
1748
+ L.unsupported("SC1031", blame, restT
1749
+ ? `rest bindings packing the remaining properties as '${L.fmt(restT)}' (only plain record types pack)`
1750
+ : "rest bindings whose packed type has no static mapping (only plain record types pack)");
1751
+ }
1752
+ const restShape = L.shapes.get(restT.shapeId);
1753
+ if (!restShape)
1754
+ throw new Error(`lowerer bug: rest binding over unknown shape ${restT.shapeId}`);
1755
+ if (restShape.indexValue || restShape.tuple) {
1756
+ L.unsupported("SC1031", blame, "rest bindings over index-signature shapes (the undeclared entries would need overflow packing)");
1757
+ }
1758
+ const remaining = info.def.fields.filter((f) => !f.name.startsWith("#") && !f.name.startsWith("%") && !consumed.has(f.name));
1759
+ for (const f of remaining) {
1760
+ if (!restShape.fields.some((rf) => rf.name === f.name)) {
1761
+ L.unsupported("SC1031", blame, `rest bindings over instances of '${info.def.jsName ?? info.def.name}' (JS copies the non-public field '${f.name}' into the rest object, which the rest type cannot name)`);
1762
+ }
1763
+ }
1764
+ for (const rf of restShape.fields) {
1765
+ if (!remaining.some((f) => f.name === rf.name)) {
1766
+ L.unsupported("SC1031", blame, `the rest field '${rf.name}' is not an instance field of '${info.def.jsName ?? info.def.name}'`);
1767
+ }
1768
+ }
1769
+ const emitOrder = restShape.declaredOrder ?? restShape.fields.map((f) => f.name);
1770
+ if (emitOrder.length !== remaining.length || emitOrder.some((n, i) => n !== remaining[i].name)) {
1771
+ L.unsupported("SC1031", blame, "rest bindings over class instances whose packed key order cannot match Node's (Object.keys/JSON.stringify would enumerate the copied fields in a different order)");
1772
+ }
1773
+ const fields = remaining.map((f) => {
1774
+ const fieldType = info.fields.get(f.name) ?? f.type;
1775
+ const read = L.fieldGetExpr({ container: "class", obj: srcRef(), className: srcType.className, field: f.name, fieldType }, loc, blame);
1776
+ const restFT = restShape.fields.find((rf) => rf.name === f.name).type;
1777
+ return { name: f.name, value: L.coerceInto(blame, read, restFT) };
1778
+ });
1779
+ return { kind: "recordLit", fields, type: restT, loc };
1780
+ }
1684
1781
  /** The fences the island's element-wise object path shares: no rest
1685
1782
  * (surplus packing over a handle) and no defaults (an engine undefined
1686
1783
  * test) — the static positions now take both through their own
@@ -2144,12 +2241,19 @@ export function lowerVarDecl(L, decl, isLet) {
2144
2241
  // shapes (reassigned bindings, declarations inside functions) fence
2145
2242
  // by name inside; a collection-time report dedupes.
2146
2243
  {
2147
- const gfnNode = bindingGenericFnNodeOf(decl);
2244
+ const gfnNode = bindingGenericFnNodeOf(decl) ?? bindingContextualGenericFnNodeOf(L, decl);
2148
2245
  if (gfnNode) {
2149
2246
  bindingGenericFnInfoOf(L, decl, gfnNode);
2150
2247
  return null;
2151
2248
  }
2152
2249
  }
2250
+ // `const h = id` — an ALIAS of a generic function: the alias's symbol
2251
+ // registers the target's info (calls and pinned values resolve like
2252
+ // the target's own name), the binding has no runtime value, and the
2253
+ // statement emits nothing (collectGlobals skipped module-scope
2254
+ // globals by the same test; block-scoped aliases register here).
2255
+ if (bindingGenericFnAliasInfoOf(L, decl))
2256
+ return null;
2153
2257
  // `const knownBy = (cmd) => ...` — an IMPLICIT-ANY function-value
2154
2258
  // binding in npm-static JS (function-scope bindings register here, in
2155
2259
  // source order before any use can lower; file-scope ones registered at
@@ -2403,6 +2507,20 @@ export function lowerVarDecl(L, decl, isLet) {
2403
2507
  // bridge is an independent observer of the same settlement).
2404
2508
  if (init.type.kind === "jsval" && type.kind === "promise")
2405
2509
  type = JSVAL;
2510
+ // A STATIC array of island HANDLES under an unannotated declared type
2511
+ // spelling evolved elements (`const kept = fns.filter(...)` over an
2512
+ // evolving-`any` receiver — the handle-element lowering keeps the
2513
+ // elements jsval, while tsc's evolving-array analysis types the
2514
+ // result by the pushed elements): the value's element type is the
2515
+ // truth, so the binding keeps the handle-element array (the
2516
+ // island-HANDLE local story, one level down) and later method calls
2517
+ // ride the handle-element lowering. Explicit annotations keep the
2518
+ // validated-boundary fence.
2519
+ if (!decl.type &&
2520
+ init.type.kind === "array" && init.type.elem.kind === "jsval" &&
2521
+ type.kind === "array" && type.elem.kind !== "jsval") {
2522
+ type = init.type;
2523
+ }
2406
2524
  // `const runner = options.runner || defaultRunner`: the checker types
2407
2525
  // the || as the union/merge of two structurally-compatible function
2408
2526
  // types — a signature whose RETURN is the arms' union, or the
@@ -2434,6 +2552,14 @@ export function lowerVarDecl(L, decl, isLet) {
2434
2552
  type = init.type;
2435
2553
  }
2436
2554
  }
2555
+ // `const r: Repo = new MemRepo()` over an all-generic-method
2556
+ // interface: the binding keeps the initializer's CLASS representation
2557
+ // (the record shape maps empty and the width copy would drop the
2558
+ // class the generic-method calls monomorphize against) — see
2559
+ // genericIfaceBindingKeepsClass.
2560
+ if (type.kind === "record" && init.type.kind === "object" && genericIfaceBindingKeepsClass(L, decl, type)) {
2561
+ type = init.type;
2562
+ }
2437
2563
  // A TDZ box minted DURING this very initializer (a callback inside it
2438
2564
  // captured this const — predeclareForwardCapture's current-statement
2439
2565
  // case): the binding and its scope-entry varDecl already exist, so
@@ -2574,7 +2700,15 @@ export function lowerUnionSwitch(L, stmt, disc) {
2574
2700
  !pureReemittable(test)) {
2575
2701
  L.unsupported("SC1090", clause.expression, "effectful case tests in a union-typed switch (bind the test value to a const first)");
2576
2702
  }
2577
- pendingTests.push({
2703
+ // A unit-literal test takes the unit-comparison lowering: a tag
2704
+ // test when the arm exists, the constant FALSE when the union
2705
+ // lacks it (`case null:` on a `number | undefined` — legal TS,
2706
+ // never matches; coercing the literal into the union would hit
2707
+ // the stranded-arm trap and throw where JS just skips the case).
2708
+ const unitTest = test.kind === "unitLit"
2709
+ ? L.lowerUnitComparison(disc, test, false, locOf(clause.expression))
2710
+ : null;
2711
+ pendingTests.push(unitTest ?? {
2578
2712
  kind: "unionEq",
2579
2713
  unionId: unionType.unionId,
2580
2714
  negated: false,
@@ -3883,6 +4017,129 @@ function destructuringAssignInto(L, target, init, rhs, blame, loc) {
3883
4017
  }
3884
4018
  return { stmts: out, value };
3885
4019
  }
4020
+ // A CLASS-INSTANCE source (`({ a, b } = inst)`): the declaration
4021
+ // path's desugar in assignment position — one member read per
4022
+ // element, left to right (declared fields read their slots, accessor
4023
+ // properties call their getters through the same fieldGetExpr
4024
+ // dispatch), defaults against the TARGET's own type, rest packing
4025
+ // the remaining instance fields (classInstanceRestValue). Methods
4026
+ // and names no class on the chain declares keep the declaration
4027
+ // path's named fences.
4028
+ if (init.type.kind === "object") {
4029
+ const objT = init.type;
4030
+ if (!L.classes.get(objT.className))
4031
+ L.flushDeferredClass(objT.className);
4032
+ const info = L.classes.get(objT.className);
4033
+ if (info) {
4034
+ for (const prop of target.properties) {
4035
+ const propLoc = locOf(prop);
4036
+ if (ts.isSpreadAssignment(prop)) {
4037
+ let restTo = prop.expression;
4038
+ while (ts.isParenthesizedExpression(restTo))
4039
+ restTo = restTo.expression;
4040
+ const consumed = new Set();
4041
+ for (const p of target.properties) {
4042
+ if (ts.isShorthandPropertyAssignment(p))
4043
+ consumed.add(p.name.text);
4044
+ else if (ts.isPropertyAssignment(p)) {
4045
+ const n = patternKeyNameOf(L, p.name);
4046
+ if (n !== null)
4047
+ consumed.add(n);
4048
+ }
4049
+ }
4050
+ if (!ts.isIdentifier(restTo)) {
4051
+ lowerAssignTargetInto(L, out, restTo, prop, null, (targetT) => classInstanceRestValue(L, prop, tmpRef, objT, info, consumed, targetT));
4052
+ continue;
4053
+ }
4054
+ const targetBinding = L.resolveWritable(restTo);
4055
+ if (!targetBinding) {
4056
+ L.rejectUnresolved(restTo, `assignment to '${restTo.text}' (not a writable local or module global)`);
4057
+ }
4058
+ const packed = classInstanceRestValue(L, prop, tmpRef, objT, info, consumed, targetBinding.type);
4059
+ out.push({ kind: "assign", localId: targetBinding.id, value: L.coerceInto(prop, packed, targetBinding.type), loc: propLoc });
4060
+ continue;
4061
+ }
4062
+ let fieldName;
4063
+ let bindTo;
4064
+ let dfltInit = null;
4065
+ if (ts.isShorthandPropertyAssignment(prop)) {
4066
+ fieldName = prop.name.text;
4067
+ bindTo = prop.name;
4068
+ dfltInit = prop.objectAssignmentInitializer ?? null;
4069
+ }
4070
+ else if (ts.isPropertyAssignment(prop)) {
4071
+ const folded = patternKeyNameOf(L, prop.name);
4072
+ if (folded === null) {
4073
+ L.unsupported("SC1031", prop, "destructuring assignment with computed keys that do not fold to one property name");
4074
+ }
4075
+ fieldName = folded;
4076
+ bindTo = prop.initializer;
4077
+ if (ts.isBinaryExpression(bindTo) && bindTo.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
4078
+ dfltInit = bindTo.right;
4079
+ bindTo = bindTo.left;
4080
+ }
4081
+ }
4082
+ else {
4083
+ L.unsupported("SC1031", prop, "destructuring assignment with getter/setter or method properties");
4084
+ }
4085
+ const dflt = dfltInit;
4086
+ const readOf = (targetT) => {
4087
+ const fieldType = info.fields.get(fieldName);
4088
+ const getF = fieldType === undefined ? L.findMethodOn(info, `get:${fieldName}`) : null;
4089
+ const setF = fieldType === undefined ? L.findMethodOn(info, `set:${fieldName}`) : null;
4090
+ let v;
4091
+ if (fieldType !== undefined) {
4092
+ v = L.fieldGetExpr({ container: "class", obj: tmpRef(), className: objT.className, field: fieldName, fieldType }, propLoc, prop);
4093
+ }
4094
+ else if (getF || setF) {
4095
+ v = L.fieldGetExpr({ container: "accessor", obj: tmpRef(), className: objT.className, field: fieldName, fieldType: getF ? getF.sig.ret : setF.sig.params[0].type }, propLoc, prop);
4096
+ // A defaulted getter result temps first — the default's
4097
+ // ternary mentions its operand twice, and JS calls the
4098
+ // getter once per element.
4099
+ if (dflt && targetT) {
4100
+ const got = L.declareHiddenLocal("%dget", v.type);
4101
+ out.push({ kind: "varDecl", localId: got.id, init: v, loc: propLoc });
4102
+ v = { kind: "varRef", localId: got.id, type: v.type, loc: propLoc };
4103
+ }
4104
+ }
4105
+ else {
4106
+ L.unsupported("SC1031", prop, L.findMethodOn(info, fieldName)
4107
+ ? `destructuring the method '${fieldName}' (a detached method loses its receiver — call it through the instance)`
4108
+ : `destructuring the property '${fieldName}' the class '${info.def.jsName ?? info.def.name}' does not declare`);
4109
+ }
4110
+ if (dflt && targetT)
4111
+ v = undefArmDefault(L, prop, dflt, v, targetT);
4112
+ return v;
4113
+ };
4114
+ while (ts.isParenthesizedExpression(bindTo))
4115
+ bindTo = bindTo.expression;
4116
+ if (!ts.isIdentifier(bindTo)) {
4117
+ lowerAssignTargetInto(L, out, bindTo, prop, dfltInit, readOf);
4118
+ continue;
4119
+ }
4120
+ let targetBinding;
4121
+ if (ts.isShorthandPropertyAssignment(prop)) {
4122
+ const valueSymbol = L.checker.getShorthandAssignmentValueSymbol(prop) ?? null;
4123
+ const local = valueSymbol ? L.resolveKey(valueSymbol, bindTo) : null;
4124
+ const g = valueSymbol ? L.globalsBySymbol.get(valueSymbol) : undefined;
4125
+ targetBinding = local ? { id: local.id, type: local.type } : g ? { id: g.id, type: g.type } : null;
4126
+ }
4127
+ else {
4128
+ targetBinding = L.resolveWritable(bindTo);
4129
+ }
4130
+ if (!targetBinding) {
4131
+ L.rejectUnresolved(bindTo, `assignment to '${bindTo.text}' (not a writable local or module global)`);
4132
+ }
4133
+ out.push({
4134
+ kind: "assign",
4135
+ localId: targetBinding.id,
4136
+ value: L.coerceInto(prop, readOf(targetBinding.type), targetBinding.type),
4137
+ loc: propLoc,
4138
+ });
4139
+ }
4140
+ return { stmts: out, value };
4141
+ }
4142
+ }
3886
4143
  if (init.type.kind !== "record") {
3887
4144
  L.unsupported("SC1031", blame, init.type.kind === "object"
3888
4145
  ? "destructuring assignment from class instances (read the fields directly — accessors make the desugar observable)"
@@ -3906,13 +4163,6 @@ function destructuringAssignInto(L, target, init, rhs, blame, loc) {
3906
4163
  let restTo = prop.expression;
3907
4164
  while (ts.isParenthesizedExpression(restTo))
3908
4165
  restTo = restTo.expression;
3909
- if (!ts.isIdentifier(restTo)) {
3910
- L.unsupported("SC1031", prop, "destructuring assignment of rest into non-variable targets");
3911
- }
3912
- const targetBinding = L.resolveWritable(restTo);
3913
- if (!targetBinding) {
3914
- L.rejectUnresolved(restTo, `assignment to '${restTo.text}' (not a writable local or module global)`);
3915
- }
3916
4166
  if (shape.indexValue) {
3917
4167
  L.unsupported("SC1031", prop, "rest bindings over index-signature shapes (the undeclared entries would need overflow packing)");
3918
4168
  }
@@ -3937,6 +4187,16 @@ function destructuringAssignInto(L, target, init, rhs, blame, loc) {
3937
4187
  type: { kind: "record", shapeId: restShapeId },
3938
4188
  loc: locOf(prop),
3939
4189
  };
4190
+ if (!ts.isIdentifier(restTo)) {
4191
+ // `({ a, ...box.rest } = src)` — the rest packs identically and
4192
+ // lands through the property/element write machinery.
4193
+ lowerAssignTargetInto(L, out, restTo, prop, null, () => packed);
4194
+ continue;
4195
+ }
4196
+ const targetBinding = L.resolveWritable(restTo);
4197
+ if (!targetBinding) {
4198
+ L.rejectUnresolved(restTo, `assignment to '${restTo.text}' (not a writable local or module global)`);
4199
+ }
3940
4200
  out.push({
3941
4201
  kind: "assign",
3942
4202
  localId: targetBinding.id,
@@ -3973,7 +4233,33 @@ function destructuringAssignInto(L, target, init, rhs, blame, loc) {
3973
4233
  while (ts.isParenthesizedExpression(bindTo))
3974
4234
  bindTo = bindTo.expression;
3975
4235
  if (!ts.isIdentifier(bindTo)) {
3976
- L.unsupported("SC1031", bindTo, "destructuring assignment to nested patterns or non-variable targets (assign a variable, then write the parts out)");
4236
+ // Nested patterns and property/element targets: the element's
4237
+ // value (default applied at the target's own type) lands through
4238
+ // the shared non-variable machinery.
4239
+ const fieldT = shape.fields.find((f) => f.name === fieldName)?.type;
4240
+ const dflt = dfltInit;
4241
+ lowerAssignTargetInto(L, out, bindTo, prop, dflt, (targetT) => {
4242
+ if (!fieldT) {
4243
+ // The defaulted read of a field the shape does not carry is
4244
+ // always undefined — the default IS the value (the identifier
4245
+ // path's rule below).
4246
+ if (dflt && targetT)
4247
+ return L.lowerExprExpecting(dflt, targetT);
4248
+ L.unsupported("SC1031", prop, `destructuring the field '${fieldName}' the source shape does not carry`);
4249
+ }
4250
+ let v = {
4251
+ kind: "recordGet",
4252
+ obj: { kind: "varRef", localId: tmp.id, type: srcType, loc: locOf(prop) },
4253
+ shapeId: srcType.shapeId,
4254
+ field: fieldName,
4255
+ type: fieldT,
4256
+ loc: locOf(prop),
4257
+ };
4258
+ if (dflt && targetT)
4259
+ v = undefArmDefault(L, prop, dflt, v, targetT);
4260
+ return v;
4261
+ });
4262
+ continue;
3977
4263
  }
3978
4264
  // Shorthand names resolve to the PROPERTY symbol at their location;
3979
4265
  // the VALUE symbol (the binding being assigned) comes from the
@@ -4029,6 +4315,102 @@ function destructuringAssignInto(L, target, init, rhs, blame, loc) {
4029
4315
  }
4030
4316
  return { stmts: out, value };
4031
4317
  }
4318
+ /** A destructuring-ASSIGNMENT target that is not a plain variable:
4319
+ * PROPERTY targets (`({ a: this.x } = o)`, `[c.x, c.y] = t` — the
4320
+ * receiver evaluates into a hidden temp at the element's pattern
4321
+ * position, BEFORE the element's value read, JS's get-the-reference-
4322
+ * then-GetV order), ELEMENT targets over arrays (runtime f64 index)
4323
+ * and tuples/records (literal keys) with the same receiver/key temp
4324
+ * discipline, and NESTED patterns (`({ p: { q } } = o)`, `[[a], b] = t`
4325
+ * — the element's value destructures through the same assignment
4326
+ * machinery, its own hidden temp included). `valueOf` builds the
4327
+ * (defaulted) source value at the target's own type; it receives null
4328
+ * exactly for nested patterns, which have no single binding type —
4329
+ * defaults there keep a fence (JS would need the pattern's implied
4330
+ * type to test against). Optional-chain targets are not assignment
4331
+ * targets in JS at all; index-signature runtime keys and island/dyn
4332
+ * receivers keep the fence below. */
4333
+ function lowerAssignTargetInto(L, out, targetNode, blame, dflt, valueOf) {
4334
+ let t = targetNode;
4335
+ while (ts.isParenthesizedExpression(t))
4336
+ t = t.expression;
4337
+ const loc = locOf(t);
4338
+ if (ts.isObjectLiteralExpression(t) || ts.isArrayLiteralExpression(t)) {
4339
+ if (dflt) {
4340
+ L.unsupported("SC1031", blame, "defaults on nested patterns in destructuring assignment (bind a variable with the default, then destructure it)");
4341
+ }
4342
+ const sub = destructuringAssignInto(L, t, valueOf(null), null, blame, loc);
4343
+ out.push(...sub.stmts);
4344
+ return;
4345
+ }
4346
+ if (ts.isPropertyAccessExpression(t) && !t.questionDotToken) {
4347
+ const ft = L.fieldTarget(t);
4348
+ if (ft) {
4349
+ // The receiver temps FIRST: JS evaluates the target reference
4350
+ // before reading the element's value, so a side-effecting
4351
+ // receiver (`f().x`) must run before the source read the
4352
+ // defaulted value machinery may emit.
4353
+ const recv = L.declareHiddenLocal("%dtRecv", ft.obj.type);
4354
+ out.push({ kind: "varDecl", localId: recv.id, init: ft.obj, loc });
4355
+ const value = L.coerceInto(blame, valueOf(ft.fieldType), ft.fieldType);
4356
+ out.push(L.fieldSetStmt({ ...ft, obj: { kind: "varRef", localId: recv.id, type: ft.obj.type, loc } }, value, loc, t));
4357
+ return;
4358
+ }
4359
+ }
4360
+ if (ts.isElementAccessExpression(t) && !t.questionDotToken) {
4361
+ const recvT = L.mapTypeOf(L.typeOf(t.expression));
4362
+ if (recvT?.kind === "array") {
4363
+ const arr = L.lowerExpr(t.expression);
4364
+ if (arr.type.kind === "array") {
4365
+ const recv = L.declareHiddenLocal("%dtRecv", arr.type);
4366
+ out.push({ kind: "varDecl", localId: recv.id, init: arr, loc });
4367
+ const index = L.lowerExpr(t.argumentExpression);
4368
+ if (index.type.kind !== "f64") {
4369
+ L.unsupported("SC1090", t.argumentExpression, "indexing with non-number keys");
4370
+ }
4371
+ const idx = L.declareHiddenLocal("%dtIdx", F64);
4372
+ out.push({ kind: "varDecl", localId: idx.id, init: index, loc });
4373
+ const value = L.coerceInto(blame, valueOf(recvT.elem), recvT.elem);
4374
+ out.push({
4375
+ kind: "arraySet",
4376
+ arr: { kind: "varRef", localId: recv.id, type: arr.type, loc },
4377
+ index: { kind: "varRef", localId: idx.id, type: F64, loc },
4378
+ value,
4379
+ loc,
4380
+ });
4381
+ return;
4382
+ }
4383
+ }
4384
+ else if (recvT?.kind === "record") {
4385
+ // Tuple positions and LITERAL declared record keys are field
4386
+ // writes (the element-write lowering's own rule); runtime keys
4387
+ // keep the fence below.
4388
+ const shape = L.shapes.get(recvT.shapeId);
4389
+ const litKey = ts.isStringLiteralLike(t.argumentExpression) || ts.isNumericLiteral(t.argumentExpression)
4390
+ ? L.foldedStringKeyOf(t.argumentExpression)
4391
+ : null;
4392
+ const field = litKey !== null ? shape?.fields.find((f) => f.name === litKey) : undefined;
4393
+ if (field) {
4394
+ const obj = L.lowerExpr(t.expression);
4395
+ if (obj.type.kind === "record") {
4396
+ const recv = L.declareHiddenLocal("%dtRecv", obj.type);
4397
+ out.push({ kind: "varDecl", localId: recv.id, init: obj, loc });
4398
+ const value = L.coerceInto(blame, valueOf(field.type), field.type);
4399
+ out.push({
4400
+ kind: "recordSet",
4401
+ obj: { kind: "varRef", localId: recv.id, type: obj.type, loc },
4402
+ shapeId: recvT.shapeId,
4403
+ field: field.name,
4404
+ value,
4405
+ loc,
4406
+ });
4407
+ return;
4408
+ }
4409
+ }
4410
+ }
4411
+ }
4412
+ L.unsupported("SC1031", targetNode, "destructuring assignment to targets with no static write form (assign a variable, then write the parts out)");
4413
+ }
4032
4414
  /** One object-pattern property's pieces for the checked-dynamic path:
4033
4415
  * the source FIELD name (or, over ISLAND sources, a runtime KEY
4034
4416
  * expression the engine indexes with) and the resolved writable target.
@@ -4176,32 +4558,28 @@ function lowerArrayAssignInto(L, out, target, tmpRef, blame, loc) {
4176
4558
  }
4177
4559
  while (ts.isParenthesizedExpression(targetNode))
4178
4560
  targetNode = targetNode.expression;
4179
- if (!ts.isIdentifier(targetNode)) {
4180
- L.unsupported("SC1031", targetNode, "destructuring assignment to nested patterns or non-variable targets (assign a variable, then write the parts out)");
4181
- }
4182
- const targetBinding = L.resolveWritable(targetNode);
4183
- if (!targetBinding) {
4184
- L.rejectUnresolved(targetNode, `assignment to '${targetNode.text}' (not a writable local or module global)`);
4185
- }
4186
- let read;
4187
- if (isRest) {
4188
- read = isTuple
4189
- ? tupleTailValue(L, el, tmpRef, srcType, shape, i, targetBinding.type)
4190
- : { kind: "arrIntrinsic", method: "slice", receiver: tmpRef(), args: [{ kind: "numLit", value: i, type: F64, loc: locOf(el) }], type: srcType, loc: locOf(el) };
4191
- }
4192
- else if (isTuple && defaultNode !== null && !shape.fields.some((f) => f.name === String(i))) {
4193
- // Past the tuple's end the read is always undefined — the default
4194
- // IS the assignment (JS evaluates it unconditionally there).
4195
- read = L.lowerExprExpecting(defaultNode, targetBinding.type);
4196
- }
4197
- else {
4561
+ // The element's (defaulted) value at the target's own type — shared
4562
+ // by variable, property/element, and nested-pattern targets (the
4563
+ // latter receive null: no single binding type exists there, and
4564
+ // defaults on nested targets fence in the shared machinery).
4565
+ const readOf = (targetT) => {
4566
+ if (isRest) {
4567
+ return isTuple
4568
+ ? tupleTailValue(L, el, tmpRef, srcType, shape, i, targetT)
4569
+ : { kind: "arrIntrinsic", method: "slice", receiver: tmpRef(), args: [{ kind: "numLit", value: i, type: F64, loc: locOf(el) }], type: srcType, loc: locOf(el) };
4570
+ }
4571
+ if (isTuple && defaultNode !== null && targetT !== null && !shape.fields.some((f) => f.name === String(i))) {
4572
+ // Past the tuple's end the read is always undefined — the default
4573
+ // IS the assignment (JS evaluates it unconditionally there).
4574
+ return L.lowerExprExpecting(defaultNode, targetT);
4575
+ }
4198
4576
  // Defaults: JS applies the default ONLY when the element reads
4199
4577
  // undefined. A checked-dynamic element tests at runtime; a tuple
4200
4578
  // position tests its undefined arm (a never-undefined field makes
4201
4579
  // the default dead — JS would not evaluate it either); an array
4202
4580
  // position carries the bounds test (past the end JS reads
4203
4581
  // undefined where the plain read would trap).
4204
- read = elemRead(i, el);
4582
+ let read = elemRead(i, el);
4205
4583
  if (defaultNode !== null) {
4206
4584
  if (read.type.kind === "dyn" || read.type.kind === "jsval") {
4207
4585
  const boundary = read.type;
@@ -4221,18 +4599,26 @@ function lowerArrayAssignInto(L, out, target, tmpRef, blame, loc) {
4221
4599
  loc: locOf(el),
4222
4600
  };
4223
4601
  }
4224
- else if (isTuple) {
4225
- read = undefArmDefault(L, el, defaultNode, read, targetBinding.type);
4226
- }
4227
- else {
4228
- read = arrayPositionDefaultValue(L, el, defaultNode, read, tmpRef, i, srcType.elem, targetBinding.type, out);
4602
+ else if (targetT !== null) {
4603
+ read = isTuple
4604
+ ? undefArmDefault(L, el, defaultNode, read, targetT)
4605
+ : arrayPositionDefaultValue(L, el, defaultNode, read, tmpRef, i, srcType.elem, targetT, out);
4229
4606
  }
4230
4607
  }
4608
+ return read;
4609
+ };
4610
+ if (!ts.isIdentifier(targetNode)) {
4611
+ lowerAssignTargetInto(L, out, targetNode, el, defaultNode, readOf);
4612
+ return;
4613
+ }
4614
+ const targetBinding = L.resolveWritable(targetNode);
4615
+ if (!targetBinding) {
4616
+ L.rejectUnresolved(targetNode, `assignment to '${targetNode.text}' (not a writable local or module global)`);
4231
4617
  }
4232
4618
  out.push({
4233
4619
  kind: "assign",
4234
4620
  localId: targetBinding.id,
4235
- value: L.coerceInto(el, read, targetBinding.type),
4621
+ value: L.coerceInto(el, readOf(targetBinding.type), targetBinding.type),
4236
4622
  loc: locOf(el),
4237
4623
  });
4238
4624
  });
@@ -4363,7 +4749,7 @@ export function lowerForOf(L, stmt) {
4363
4749
  if (iterable.type.kind !== "array") {
4364
4750
  // The lib types strings as iterable too, so those for-ofs typecheck;
4365
4751
  // only arrays, Maps, and Sets have a lowering. Named specifically —
4366
- // the generic SC2001 would blame the TYPE, which is itself supported.
4752
+ // the blanket type fence would blame the TYPE, which is itself supported.
4367
4753
  if (iterable.type.kind === "string") {
4368
4754
  // JS's string iterator walks code POINTS, not units: each pass
4369
4755
  // yields the whole character (two UTF-16 units for astral chars,