@scriptc/compiler 0.0.32 → 0.0.34

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 (120) hide show
  1. package/dist/backend/cc-cache.test.js +430 -1
  2. package/dist/backend/cc-cache.test.js.map +1 -1
  3. package/dist/backend/cc.d.ts +25 -0
  4. package/dist/backend/cc.js +818 -88
  5. package/dist/backend/cc.js.map +1 -1
  6. package/dist/backend/emission/emit-exprs.js +79 -3
  7. package/dist/backend/emission/emit-exprs.js.map +1 -1
  8. package/dist/backend/emission/emit-shapes.js +37 -4
  9. package/dist/backend/emission/emit-shapes.js.map +1 -1
  10. package/dist/backend/emission/emitter.d.ts +23 -7
  11. package/dist/backend/emission/emitter.js +171 -27
  12. package/dist/backend/emission/emitter.js.map +1 -1
  13. package/dist/backend/emission/may-throw.js +7 -4
  14. package/dist/backend/emission/may-throw.js.map +1 -1
  15. package/dist/backend/ffi-callbacks.d.ts +38 -1
  16. package/dist/backend/ffi-callbacks.js +76 -3
  17. package/dist/backend/ffi-callbacks.js.map +1 -1
  18. package/dist/backend/library-identity.d.ts +22 -0
  19. package/dist/backend/library-identity.js +103 -0
  20. package/dist/backend/library-identity.js.map +1 -0
  21. package/dist/backend/llvm/emitter.d.ts +3 -0
  22. package/dist/backend/llvm/emitter.js +278 -17
  23. package/dist/backend/llvm/emitter.js.map +1 -1
  24. package/dist/backend/llvm/shapes.d.ts +1 -0
  25. package/dist/backend/llvm/shapes.js +25 -1
  26. package/dist/backend/llvm/shapes.js.map +1 -1
  27. package/dist/backend/mangle.d.ts +1 -0
  28. package/dist/backend/mangle.js +3 -0
  29. package/dist/backend/mangle.js.map +1 -1
  30. package/dist/backend/object-localize.d.ts +5 -5
  31. package/dist/backend/object-localize.js +19 -16
  32. package/dist/backend/object-localize.js.map +1 -1
  33. package/dist/backend/object-localize.test.js +37 -13
  34. package/dist/backend/object-localize.test.js.map +1 -1
  35. package/dist/diagnostics/diagnostic.js +2 -0
  36. package/dist/diagnostics/diagnostic.js.map +1 -1
  37. package/dist/ffi/profile.d.ts +17 -5
  38. package/dist/ffi/profile.js +169 -22
  39. package/dist/ffi/profile.js.map +1 -1
  40. package/dist/frontend/input-tracker.d.ts +82 -0
  41. package/dist/frontend/input-tracker.js +380 -0
  42. package/dist/frontend/input-tracker.js.map +1 -0
  43. package/dist/frontend/input-tracker.test.d.ts +1 -0
  44. package/dist/frontend/input-tracker.test.js +137 -0
  45. package/dist/frontend/input-tracker.test.js.map +1 -0
  46. package/dist/frontend/lowering/lower-builtins.js +2 -8
  47. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  48. package/dist/frontend/lowering/lower-calls.d.ts +1 -0
  49. package/dist/frontend/lowering/lower-calls.js +244 -163
  50. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  51. package/dist/frontend/lowering/lower-classes.js +3 -0
  52. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  53. package/dist/frontend/lowering/lower-containers.js +113 -28
  54. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  55. package/dist/frontend/lowering/lower-exprs.js +67 -1
  56. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  57. package/dist/frontend/lowering/lower-inspect.js +45 -26
  58. package/dist/frontend/lowering/lower-inspect.js.map +1 -1
  59. package/dist/frontend/lowering/lower-modules.js +1 -1
  60. package/dist/frontend/lowering/lower-modules.js.map +1 -1
  61. package/dist/frontend/lowering/lower-stmts.js +48 -3
  62. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  63. package/dist/frontend/lowering/lowerer.d.ts +79 -29
  64. package/dist/frontend/lowering/lowerer.js +429 -87
  65. package/dist/frontend/lowering/lowerer.js.map +1 -1
  66. package/dist/frontend/npm-static-rewrite.js +25 -26
  67. package/dist/frontend/npm-static-rewrite.js.map +1 -1
  68. package/dist/frontend/npm-static.js +29 -30
  69. package/dist/frontend/npm-static.js.map +1 -1
  70. package/dist/frontend/npm-static.test.d.ts +1 -0
  71. package/dist/frontend/npm-static.test.js +15 -0
  72. package/dist/frontend/npm-static.test.js.map +1 -0
  73. package/dist/frontend/npm.js +5 -17
  74. package/dist/frontend/npm.js.map +1 -1
  75. package/dist/frontend/program.js +2 -6
  76. package/dist/frontend/program.js.map +1 -1
  77. package/dist/frontend/resolve.js +8 -29
  78. package/dist/frontend/resolve.js.map +1 -1
  79. package/dist/frontend/ts7/checker.d.ts +2 -0
  80. package/dist/frontend/ts7/checker.js +20 -3
  81. package/dist/frontend/ts7/checker.js.map +1 -1
  82. package/dist/frontend/ts7/program.js +17 -18
  83. package/dist/frontend/ts7/program.js.map +1 -1
  84. package/dist/frontend/types.d.ts +41 -0
  85. package/dist/frontend/types.js +147 -8
  86. package/dist/frontend/types.js.map +1 -1
  87. package/dist/index.d.ts +1 -1
  88. package/dist/index.js +275 -41
  89. package/dist/index.js.map +1 -1
  90. package/dist/ir/nodes.d.ts +42 -12
  91. package/dist/ir/nodes.js +7 -1
  92. package/dist/ir/nodes.js.map +1 -1
  93. package/dist/ir/serialize.d.ts +1 -1
  94. package/dist/ir/serialize.js +1 -1
  95. package/dist/ir/validate.js +104 -1
  96. package/dist/ir/validate.js.map +1 -1
  97. package/dist/library/early-cache.d.ts +65 -0
  98. package/dist/library/early-cache.js +380 -0
  99. package/dist/library/early-cache.js.map +1 -0
  100. package/dist/library/early-cache.test.d.ts +1 -0
  101. package/dist/library/early-cache.test.js +528 -0
  102. package/dist/library/early-cache.test.js.map +1 -0
  103. package/dist/library/int-infer.js +14 -0
  104. package/dist/library/int-infer.js.map +1 -1
  105. package/dist/library/int-infer.test.js +21 -4
  106. package/dist/library/int-infer.test.js.map +1 -1
  107. package/dist/library/profile.d.ts +3 -0
  108. package/dist/library/profile.js +9 -1
  109. package/dist/library/profile.js.map +1 -1
  110. package/dist/library/semantic-source.d.ts +13 -0
  111. package/dist/library/semantic-source.js +257 -0
  112. package/dist/library/semantic-source.js.map +1 -0
  113. package/dist/library/semantic-source.test.d.ts +1 -0
  114. package/dist/library/semantic-source.test.js +113 -0
  115. package/dist/library/semantic-source.test.js.map +1 -0
  116. package/dist/library/sidecar.d.ts +6 -0
  117. package/dist/library/sidecar.js +11 -0
  118. package/dist/library/sidecar.js.map +1 -1
  119. package/package.json +2 -2
  120. package/surface-manifest.json +1 -1
@@ -428,6 +428,7 @@ export declare function spreadNeedsRuntimeArity(shapes: readonly ParamShape[], a
428
428
  * is-not-a-function where the kind's prototype lacks the name, and
429
429
  * fences LOUDLY on real-but-unimplemented pairs. */
430
430
  export declare const DYN_DISPATCH_METHODS: Set<string>;
431
+ export declare function lowerDynDispatchMethodCall(L: Lowerer, call: ts.CallExpression, access: ts.PropertyAccessExpression, recv: IrExpr, arrayReceiver: boolean): IrExpr | null;
431
432
  /** Radix-free `.toString()` on a PRIMITIVE receiver: numbers take the
432
433
  * STATIC JS-exact number formatter — the same `toString` node templates
433
434
  * and String(n) lower to (Number::toString with radix 10 IS that
@@ -4,7 +4,7 @@
4
4
  * generic instantiation (bounded by MAX_GENERIC_INSTANCES). */
5
5
  import * as ts from "../ts7/adapter.js";
6
6
  import { lowerGenMethodCall } from "./lower-generators.js";
7
- import { BOOL, CAUGHT, DYN, F64, JSVAL, STRING, SYMBOL_T, UNDEFINED_T, VOID, arrayOf, canBoxFuncIntoDyn, canConvertToDyn, canDynCheckTo, canMarshalTypedFuncIntoIsland, ffiClassType, ffiSourceParamTypes, funcOf, isFfiCallbackParam, isFfiContextParam, isUnitType, shapeHasAccessorSlots, typeEquals } from "../../ir/nodes.js";
7
+ import { BOOL, CAUGHT, DYN, F64, JSVAL, STRING, SYMBOL_T, UNDEFINED_T, VOID, arrayOf, canBoxFuncIntoDyn, canConvertToDyn, canDynCheckTo, canMarshalTypedFuncIntoIsland, ffiClassType, ffiSourceParamTypes, funcOf, isFfiCallbackParam, isFfiContextParam, isFfiReleaseParam, isUnitType, shapeHasAccessorSlots, typeEquals } from "../../ir/nodes.js";
8
8
  import { isJsSourceFile, locOf } from "../program.js";
9
9
  import { isGenericCallableMemberType, typeKey } from "../types.js";
10
10
  import { PoisonError, dynFallbackType, dynUndefinedExpr, importCallHandleType, jsFuncNameOf, newFnCtx, nodeThrowExpr } from "./lowerer.js";
@@ -931,6 +931,7 @@ export function internGenericInstance(L, blame, info, params, returnType, makeBi
931
931
  info.instances.set(key, inst);
932
932
  L.instantiationQueue.push({ info, inst });
933
933
  }
934
+ L.noteGenericInstanceDemand(inst);
934
935
  return inst;
935
936
  }
936
937
  /** The type parameters of `info` declared with a `keyof` CONSTRAINT
@@ -2276,7 +2277,11 @@ function ffiSourceParams(binding) {
2276
2277
  return binding.params.filter((param) => !isFfiContextParam(param));
2277
2278
  }
2278
2279
  function ffiParamDisplay(param) {
2279
- return isFfiCallbackParam(param) ? `callback '${param.callback.id}'` : `class '${param}'`;
2280
+ return isFfiCallbackParam(param)
2281
+ ? `callback '${param.callback.id}'`
2282
+ : isFfiReleaseParam(param)
2283
+ ? `release callback '${param.callback.release}'`
2284
+ : `class '${param}'`;
2280
2285
  }
2281
2286
  /** Callback arguments flow from native code into TypeScript. Their source
2282
2287
  * declarations therefore need to cover the whole scalar domain represented
@@ -2302,12 +2307,23 @@ function ffiCallbackInputDiagnostic(L, descriptor, callbackType) {
2302
2307
  !typeEquals(mapped, ffiClassType(nativeClass))) {
2303
2308
  continue;
2304
2309
  }
2305
- const domain = nativeClass === "bool" ? "boolean" : "number";
2310
+ if (nativeClass === "bytes")
2311
+ continue;
2312
+ const domain = nativeClass === "bool"
2313
+ ? "boolean"
2314
+ : nativeClass === "cstring" || nativeClass === "string"
2315
+ ? "string"
2316
+ : "number";
2306
2317
  const coversDomain = nativeClass === "bool"
2307
2318
  ? (paramType.flags & ts.TypeFlags.Boolean) !== 0
2308
- : (paramType.flags & ts.TypeFlags.Number) !== 0;
2319
+ : nativeClass === "cstring" || nativeClass === "string"
2320
+ ? (paramType.flags & ts.TypeFlags.String) !== 0
2321
+ : (paramType.flags & ts.TypeFlags.Number) !== 0;
2309
2322
  if (!coversDomain) {
2310
- return (`callback '${descriptor.callback.id}' parameter ${i + 1} is '${L.checker.typeToString(paramType)}', ` +
2323
+ const descriptorName = isFfiCallbackParam(descriptor)
2324
+ ? descriptor.callback.id
2325
+ : descriptor.callback.release;
2326
+ return (`callback '${descriptorName}' parameter ${i + 1} is '${L.checker.typeToString(paramType)}', ` +
2311
2327
  `but native class '${nativeClass}' may supply any ${domain}; declare it as '${domain}'`);
2312
2328
  }
2313
2329
  }
@@ -2379,7 +2395,7 @@ function ffiDeclarationDiagnostic(L, binding, symbol, loc) {
2379
2395
  if ((paramType.flags & ts.TypeFlags.Never) !== 0) {
2380
2396
  return signatureDiag(binding.name, `parameter ${i + 1} is 'never', an uninhabited TypeScript type that cannot describe a native ABI parameter`, loc);
2381
2397
  }
2382
- if (isFfiCallbackParam(sourceParam)) {
2398
+ if (isFfiCallbackParam(sourceParam) || isFfiReleaseParam(sourceParam)) {
2383
2399
  const callbackDiagnostic = ffiCallbackInputDiagnostic(L, sourceParam, paramType);
2384
2400
  if (callbackDiagnostic !== null) {
2385
2401
  return signatureDiag(binding.name, callbackDiagnostic, loc);
@@ -2602,7 +2618,45 @@ export function lowerFfiCall(L, expr) {
2602
2618
  signatureError(`this call passes ${expr.arguments.length} argument(s), but the native binding requires exactly ${expectedParams.length}`);
2603
2619
  }
2604
2620
  const expectedReturn = ffiClassType(binding.returns);
2605
- const args = expr.arguments.map((arg, i) => L.lowerExprExpecting(arg, expectedParams[i]));
2621
+ const sourceParams = ffiSourceParams(binding);
2622
+ const args = expr.arguments.map((arg, i) => {
2623
+ const sourceParam = sourceParams[i];
2624
+ const expected = expectedParams[i];
2625
+ const lowered = L.lowerExprExpecting(arg, expected);
2626
+ // Retained identity is the runtime closure pointer. A coercion adapter
2627
+ // would be freshly allocated at registration and release sites, so an
2628
+ // assignable-but-different function shape (notably `() => number` into
2629
+ // `() => void`) cannot honestly participate in explicit release. The
2630
+ // adapter set comes from the mint sites themselves (Lowerer's
2631
+ // freshClosureAdapters), not name-prefix matching, so a new coercion
2632
+ // helper cannot silently slip past this guard.
2633
+ if (isFfiReleaseParam(sourceParam) ||
2634
+ (isFfiCallbackParam(sourceParam) && sourceParam.callback.lifetime === "retained")) {
2635
+ if (lowered.kind === "dynCheck" ||
2636
+ (lowered.kind === "call" && L.freshClosureAdapters.has(lowered.callee))) {
2637
+ signatureError(`retained callback argument ${i + 1} must have the exact manifest function type; ` +
2638
+ `an implicit function adapter would change its release identity`);
2639
+ }
2640
+ // An inline function value at a RELEASE site can never match:
2641
+ // lifted lambdas always carry a captures list (even an empty one),
2642
+ // so both backends mint a fresh closure per evaluation of the
2643
+ // expression — the release argument is a pointer no registration
2644
+ // holds, a guaranteed runtime trap. Declared functions stay valid
2645
+ // here — their value is the interned immortal closure (captures
2646
+ // undefined), one pointer for every mention. Registration sites
2647
+ // still accept literals: an unnameable registration is simply
2648
+ // permanent, released by the exit teardown (the live-at-exit
2649
+ // fixture shape), and hides no matching failure.
2650
+ if (isFfiReleaseParam(sourceParam) &&
2651
+ lowered.kind === "closure" &&
2652
+ L.liftedFns.some((f) => f.name === lowered.fnName && f.captures !== undefined)) {
2653
+ signatureError(`retained callback argument ${i + 1} cannot be an inline function value; ` +
2654
+ `each evaluation creates a fresh closure no registration holds — ` +
2655
+ `pass the same named value used to register`);
2656
+ }
2657
+ }
2658
+ return lowered;
2659
+ });
2606
2660
  return {
2607
2661
  kind: "ffiCall",
2608
2662
  import: binding.name,
@@ -4155,7 +4209,8 @@ function lowerDynReceiverMethodCall(L, call, access) {
4155
4209
  // take the ordinary typed paths, but there is no static home for an
4156
4210
  // any-elemented array). Typed receivers keep their own lowerings.
4157
4211
  const recvTs = L.typeOf(access.expression);
4158
- const anyArray = L.checker.isArrayType(recvTs) &&
4212
+ const arrayReceiver = L.checker.isArrayType(recvTs);
4213
+ const anyArray = arrayReceiver &&
4159
4214
  ((L.checker.getTypeArguments(recvTs)[0]?.flags ?? 0) &
4160
4215
  (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0;
4161
4216
  let recv;
@@ -4234,21 +4289,9 @@ function lowerDynReceiverMethodCall(L, call, access) {
4234
4289
  // unimplemented methods throw a LOUD not-supported Error; names the
4235
4290
  // kind's prototype lacks throw Node's "x.y is not a function"; OBJ
4236
4291
  // receivers call the own member.
4237
- if (DYN_DISPATCH_METHODS.has(access.name.text) && !call.questionDotToken && !access.questionDotToken) {
4238
- if (call.arguments.some((a) => ts.isSpreadElement(a))) {
4239
- L.unsupported("SC1090", call, "spread arguments in calls through 'unknown' values");
4240
- }
4241
- const args = call.arguments.map((a) => L.lowerExprExpecting(a, DYN));
4242
- return {
4243
- kind: "dynInvoke",
4244
- recv,
4245
- method: access.name.text,
4246
- calleeName: access.getText(),
4247
- args,
4248
- type: DYN,
4249
- loc: locOf(call),
4250
- };
4251
- }
4292
+ const dispatched = lowerDynDispatchMethodCall(L, call, access, recv, arrayReceiver);
4293
+ if (dispatched)
4294
+ return dispatched;
4252
4295
  // Names NO dyn-representable prototype declares: the member can only
4253
4296
  // be an OWN property, so "read the member, call it" IS Node's
4254
4297
  // semantics for every possible dyn value — `handlers.onDone(x)` on a
@@ -4359,6 +4402,30 @@ export const DYN_DISPATCH_METHODS = new Set([
4359
4402
  "request", "sendTrailers", "priority", "settings", "goaway", "ping",
4360
4403
  "additionalHeaders", "altsvc", "origin",
4361
4404
  ]);
4405
+ export function lowerDynDispatchMethodCall(L, call, access, recv, arrayReceiver) {
4406
+ const method = access.name.text;
4407
+ if (!DYN_DISPATCH_METHODS.has(method) || call.questionDotToken || access.questionDotToken)
4408
+ return null;
4409
+ if (call.arguments.some((arg) => ts.isSpreadElement(arg))) {
4410
+ L.unsupported("SC1090", call, "spread arguments in calls through 'unknown' values");
4411
+ }
4412
+ const predicate = method === "filter" && call.arguments[0]
4413
+ ? L.lowerExpr(call.arguments[0])
4414
+ : null;
4415
+ if (arrayReceiver && predicate?.type.kind === "func" && predicate.type.ret.kind === "void") {
4416
+ L.unsupported("SC1090", call.arguments[0], "'.filter()' with a void-returning predicate (the callback return value is erased before its truthiness can be tested)");
4417
+ }
4418
+ const args = call.arguments.map((arg, i) => i === 0 && predicate ? L.coerceInto(arg, predicate, DYN) : L.lowerExprExpecting(arg, DYN));
4419
+ return {
4420
+ kind: "dynInvoke",
4421
+ recv,
4422
+ method,
4423
+ calleeName: access.getText(),
4424
+ args,
4425
+ type: DYN,
4426
+ loc: locOf(call),
4427
+ };
4428
+ }
4362
4429
  /** STR_METHODS ∪ the regex-form names, MINUS everything Array (or any
4363
4430
  * other dyn kind's prototype) also declares. */
4364
4431
  const DYN_STRING_ONLY_METHODS = new Set([
@@ -6343,48 +6410,8 @@ export function recordKeysArrayCall(L, receiver, argIr, shape, loc) {
6343
6410
  helper = `%obj.keys.${L.arrHofHelpers.size}`;
6344
6411
  const ref = { kind: "varRef", localId: "r.0", type: argIr, loc };
6345
6412
  const outRef = { kind: "varRef", localId: "out.0", type: resultT, loc };
6346
- const body = [
6347
- { kind: "varDecl", localId: "out.0", init: { kind: "arrayLit", elems: [], type: resultT, loc }, loc },
6348
- ];
6349
- const order = shape.declaredOrder ?? shape.fields.map((f) => f.name);
6350
- for (const name of order) {
6351
- const f = shape.fields.find((x) => x.name === name);
6352
- const pushStmt = {
6353
- kind: "exprStmt",
6354
- expr: {
6355
- kind: "arrIntrinsic",
6356
- method: "push",
6357
- receiver: outRef,
6358
- args: [{ kind: "strLit", value: f.name, type: STRING, loc }],
6359
- type: F64,
6360
- loc,
6361
- },
6362
- loc,
6363
- };
6364
- // Undefined-armed fields: the push is guarded by a tag test (the
6365
- // key exists exactly when the arm is not undefined).
6366
- const utag = f.type.kind === "union" ? L.armTag(f.type.unionId, UNDEFINED_T) : -1;
6367
- body.push(utag >= 0 && f.type.kind === "union"
6368
- ? {
6369
- kind: "if",
6370
- cond: {
6371
- kind: "unionIsTag",
6372
- unionId: f.type.unionId,
6373
- tag: utag,
6374
- negated: true,
6375
- value: { kind: "recordGet", obj: ref, shapeId: argIr.shapeId, field: f.name, type: f.type, loc },
6376
- type: BOOL,
6377
- loc,
6378
- },
6379
- then: [pushStmt],
6380
- else_: null,
6381
- loc,
6382
- }
6383
- : pushStmt);
6384
- }
6385
- body.push({ kind: "return", value: outRef, loc });
6386
6413
  L.arrHofHelpers.set(key, helper);
6387
- L.liftedFns.push({
6414
+ const fn = {
6388
6415
  name: helper,
6389
6416
  params: [{ localId: "r.0", name: "r", type: argIr }],
6390
6417
  returnType: resultT,
@@ -6392,9 +6419,56 @@ export function recordKeysArrayCall(L, receiver, argIr, shape, loc) {
6392
6419
  { id: "r.0", name: "r", type: argIr, mutable: true },
6393
6420
  { id: "out.0", name: "out", type: resultT, mutable: false },
6394
6421
  ],
6395
- body,
6422
+ body: [],
6396
6423
  loc,
6397
- });
6424
+ };
6425
+ const finalize = () => {
6426
+ const current = L.shapes.get(argIr.shapeId) ?? shape;
6427
+ const body = [
6428
+ { kind: "varDecl", localId: "out.0", init: { kind: "arrayLit", elems: [], type: resultT, loc }, loc },
6429
+ ];
6430
+ const order = current.declaredOrder ?? current.fields.map((f) => f.name);
6431
+ for (const name of order) {
6432
+ const f = current.fields.find((x) => x.name === name);
6433
+ const pushStmt = {
6434
+ kind: "exprStmt",
6435
+ expr: {
6436
+ kind: "arrIntrinsic",
6437
+ method: "push",
6438
+ receiver: outRef,
6439
+ args: [{ kind: "strLit", value: f.name, type: STRING, loc }],
6440
+ type: F64,
6441
+ loc,
6442
+ },
6443
+ loc,
6444
+ };
6445
+ // Undefined-armed fields: the push is guarded by a tag test (the
6446
+ // key exists exactly when Object.keys would list it).
6447
+ const utag = f.type.kind === "union" ? L.armTag(f.type.unionId, UNDEFINED_T) : -1;
6448
+ body.push(utag >= 0 && f.type.kind === "union"
6449
+ ? {
6450
+ kind: "if",
6451
+ cond: {
6452
+ kind: "unionIsTag",
6453
+ unionId: f.type.unionId,
6454
+ tag: utag,
6455
+ negated: true,
6456
+ value: { kind: "recordGet", obj: ref, shapeId: argIr.shapeId, field: f.name, type: f.type, loc },
6457
+ type: BOOL,
6458
+ loc,
6459
+ },
6460
+ then: [pushStmt],
6461
+ else_: null,
6462
+ loc,
6463
+ }
6464
+ : pushStmt);
6465
+ }
6466
+ body.push({ kind: "return", value: outRef, loc });
6467
+ fn.body = body;
6468
+ };
6469
+ finalize();
6470
+ L.shapeOrderHelperFinalizers.push(finalize);
6471
+ L.liftedFns.push(fn);
6398
6472
  }
6399
6473
  return { kind: "call", callee: helper, args: [receiver], type: resultT, loc };
6400
6474
  }
@@ -7104,109 +7178,15 @@ function lowerObjectStaticCall(L, call, access) {
7104
7178
  L.badType(call, L.typeOf(call));
7105
7179
  valueT = tupleShape.fields.find((f) => f.name === "1").type;
7106
7180
  }
7107
- const order = shape.declaredOrder ?? shape.fields.map((f) => f.name);
7108
7181
  const key = `obj.${member}:${argIr.shapeId}:${typeKey(resultT)}`;
7109
7182
  let helper = L.arrHofHelpers.get(key);
7110
7183
  if (!helper) {
7111
7184
  helper = `%obj.${member}.${L.arrHofHelpers.size}`;
7112
7185
  const recT = argIr;
7113
7186
  const ref = { kind: "varRef", localId: "r.0", type: recT, loc };
7114
- const body = [
7115
- { kind: "varDecl", localId: "out.0", init: { kind: "arrayLit", elems: [], type: resultT, loc }, loc },
7116
- ];
7117
7187
  const outRef = { kind: "varRef", localId: "out.0", type: resultT, loc };
7118
- for (const name of order) {
7119
- const f = shape.fields.find((x) => x.name === name);
7120
- const raw = { kind: "recordGet", obj: ref, shapeId: argIr.shapeId, field: f.name, type: f.type, loc };
7121
- // The pushed element per member; null when the field's value
7122
- // cannot flow into the result element type.
7123
- const elemOf = (value, vt) => {
7124
- if (!valueT)
7125
- return null;
7126
- if (typeEquals(vt, valueT))
7127
- return value;
7128
- if (valueT.kind === "union" && vt.kind !== "union") {
7129
- const tag = L.armTag(valueT.unionId, vt);
7130
- if (tag >= 0) {
7131
- return { kind: "unionWrap", unionId: valueT.unionId, tag, value, type: valueT, loc };
7132
- }
7133
- }
7134
- return null;
7135
- };
7136
- // Undefined-armed fields: the push is guarded by a tag test, and
7137
- // the pushed value is the narrowed non-undefined arm.
7138
- let guardUndefTag = null;
7139
- let value = raw;
7140
- let vt = f.type;
7141
- if (f.type.kind === "union") {
7142
- const undefTag = L.armTag(f.type.unionId, UNDEFINED_T);
7143
- if (undefTag >= 0) {
7144
- guardUndefTag = undefTag;
7145
- const arms = L.unions.get(f.type.unionId)?.arms ?? [];
7146
- const others = arms.filter((a) => a.kind !== "undefinedT");
7147
- if (typeEquals(f.type, valueT ?? f.type)) {
7148
- // The field union IS the result union (single-field shapes):
7149
- // push the raw box — but then the undefined skip must NOT
7150
- // narrow. Handled below via vt === valueT.
7151
- value = raw;
7152
- vt = f.type;
7153
- }
7154
- else if (others.length === 1) {
7155
- vt = others[0];
7156
- // A UNIT other arm (`null | undefined` fields — the mixed-
7157
- // defaults spread idiom; undefined was filtered above, so
7158
- // the unit is null): units carry no payload, so the guarded
7159
- // push writes the unit LITERAL — unionNarrow to a unit arm
7160
- // (and unionWrap of a narrowed unit) is malformed IR; the
7161
- // literal is the one legal unit spelling.
7162
- value = isUnitType(vt)
7163
- ? { kind: "unitLit", unit: "null", type: vt, loc }
7164
- : { kind: "unionNarrow", unionId: f.type.unionId, tag: L.armTag(f.type.unionId, vt), value: raw, type: vt, loc };
7165
- }
7166
- else {
7167
- L.unsupported("SC1090", call, `Object.${member} over '${L.fmt(argIr)}' (field '${f.name}' is a multi-arm union that ` +
7168
- "cannot re-tag into the result element type — read the fields directly)");
7169
- }
7170
- }
7171
- else if (!typeEquals(f.type, valueT ?? f.type)) {
7172
- L.unsupported("SC1090", call, `Object.${member} over '${L.fmt(argIr)}' (field '${f.name}' is a union that cannot ` +
7173
- "re-tag into the result element type — read the fields directly)");
7174
- }
7175
- }
7176
- const coerced = elemOf(value, vt);
7177
- if (!coerced) {
7178
- L.unsupported("SC1090", call, `Object.${member} over '${L.fmt(argIr)}' (field '${f.name}' of type '${L.fmt(f.type)}' ` +
7179
- `cannot flow into the '${L.fmt(valueT)}' result element — read the fields directly)`);
7180
- }
7181
- const pushed = member === "values"
7182
- ? coerced
7183
- : {
7184
- kind: "recordLit",
7185
- fields: [
7186
- { name: "0", value: { kind: "strLit", value: f.name, type: STRING, loc } },
7187
- { name: "1", value: coerced },
7188
- ],
7189
- type: tupleT,
7190
- loc,
7191
- };
7192
- const pushStmt = {
7193
- kind: "exprStmt",
7194
- expr: { kind: "arrIntrinsic", method: "push", receiver: outRef, args: [pushed], type: F64, loc },
7195
- loc,
7196
- };
7197
- body.push(guardUndefTag !== null && f.type.kind === "union"
7198
- ? {
7199
- kind: "if",
7200
- cond: { kind: "unionIsTag", unionId: f.type.unionId, tag: guardUndefTag, negated: true, value: raw, type: BOOL, loc },
7201
- then: [pushStmt],
7202
- else_: null,
7203
- loc,
7204
- }
7205
- : pushStmt);
7206
- }
7207
- body.push({ kind: "return", value: outRef, loc });
7208
7188
  L.arrHofHelpers.set(key, helper);
7209
- L.liftedFns.push({
7189
+ const fn = {
7210
7190
  name: helper,
7211
7191
  params: [{ localId: "r.0", name: "r", type: recT }],
7212
7192
  returnType: resultT,
@@ -7214,9 +7194,110 @@ function lowerObjectStaticCall(L, call, access) {
7214
7194
  { id: "r.0", name: "r", type: recT, mutable: true },
7215
7195
  { id: "out.0", name: "out", type: resultT, mutable: false },
7216
7196
  ],
7217
- body,
7197
+ body: [],
7218
7198
  loc,
7219
- });
7199
+ };
7200
+ const finalize = () => {
7201
+ const current = L.shapes.get(argIr.shapeId) ?? shape;
7202
+ const body = [
7203
+ { kind: "varDecl", localId: "out.0", init: { kind: "arrayLit", elems: [], type: resultT, loc }, loc },
7204
+ ];
7205
+ const order = current.declaredOrder ?? current.fields.map((f) => f.name);
7206
+ for (const name of order) {
7207
+ const f = current.fields.find((x) => x.name === name);
7208
+ const raw = { kind: "recordGet", obj: ref, shapeId: argIr.shapeId, field: f.name, type: f.type, loc };
7209
+ // The pushed element per member; null when the field's value
7210
+ // cannot flow into the result element type.
7211
+ const elemOf = (value, vt) => {
7212
+ if (!valueT)
7213
+ return null;
7214
+ if (typeEquals(vt, valueT))
7215
+ return value;
7216
+ if (valueT.kind === "union" && vt.kind !== "union") {
7217
+ const tag = L.armTag(valueT.unionId, vt);
7218
+ if (tag >= 0) {
7219
+ return { kind: "unionWrap", unionId: valueT.unionId, tag, value, type: valueT, loc };
7220
+ }
7221
+ }
7222
+ return null;
7223
+ };
7224
+ // Undefined-armed fields: the push is guarded by a tag test, and
7225
+ // the pushed value is the narrowed non-undefined arm.
7226
+ let guardUndefTag = null;
7227
+ let value = raw;
7228
+ let vt = f.type;
7229
+ if (f.type.kind === "union") {
7230
+ const undefTag = L.armTag(f.type.unionId, UNDEFINED_T);
7231
+ if (undefTag >= 0) {
7232
+ guardUndefTag = undefTag;
7233
+ const arms = L.unions.get(f.type.unionId)?.arms ?? [];
7234
+ const others = arms.filter((a) => a.kind !== "undefinedT");
7235
+ if (typeEquals(f.type, valueT ?? f.type)) {
7236
+ // The field union IS the result union (single-field shapes):
7237
+ // push the raw box — but then the undefined skip must NOT
7238
+ // narrow. Handled below via vt === valueT.
7239
+ value = raw;
7240
+ vt = f.type;
7241
+ }
7242
+ else if (others.length === 1) {
7243
+ vt = others[0];
7244
+ // A UNIT other arm (`null | undefined` fields — the mixed-
7245
+ // defaults spread idiom; undefined was filtered above, so
7246
+ // the unit is null): units carry no payload, so the guarded
7247
+ // push writes the unit LITERAL — unionNarrow to a unit arm
7248
+ // (and unionWrap of a narrowed unit) is malformed IR; the
7249
+ // literal is the one legal unit spelling.
7250
+ value = isUnitType(vt)
7251
+ ? { kind: "unitLit", unit: "null", type: vt, loc }
7252
+ : { kind: "unionNarrow", unionId: f.type.unionId, tag: L.armTag(f.type.unionId, vt), value: raw, type: vt, loc };
7253
+ }
7254
+ else {
7255
+ L.unsupported("SC1090", call, `Object.${member} over '${L.fmt(argIr)}' (field '${f.name}' is a multi-arm union that ` +
7256
+ "cannot re-tag into the result element type — read the fields directly)");
7257
+ }
7258
+ }
7259
+ else if (!typeEquals(f.type, valueT ?? f.type)) {
7260
+ L.unsupported("SC1090", call, `Object.${member} over '${L.fmt(argIr)}' (field '${f.name}' is a union that cannot ` +
7261
+ "re-tag into the result element type — read the fields directly)");
7262
+ }
7263
+ }
7264
+ const coerced = elemOf(value, vt);
7265
+ if (!coerced) {
7266
+ L.unsupported("SC1090", call, `Object.${member} over '${L.fmt(argIr)}' (field '${f.name}' of type '${L.fmt(f.type)}' ` +
7267
+ `cannot flow into the '${L.fmt(valueT)}' result element — read the fields directly)`);
7268
+ }
7269
+ const pushed = member === "values"
7270
+ ? coerced
7271
+ : {
7272
+ kind: "recordLit",
7273
+ fields: [
7274
+ { name: "0", value: { kind: "strLit", value: f.name, type: STRING, loc } },
7275
+ { name: "1", value: coerced },
7276
+ ],
7277
+ type: tupleT,
7278
+ loc,
7279
+ };
7280
+ const pushStmt = {
7281
+ kind: "exprStmt",
7282
+ expr: { kind: "arrIntrinsic", method: "push", receiver: outRef, args: [pushed], type: F64, loc },
7283
+ loc,
7284
+ };
7285
+ body.push(guardUndefTag !== null && f.type.kind === "union"
7286
+ ? {
7287
+ kind: "if",
7288
+ cond: { kind: "unionIsTag", unionId: f.type.unionId, tag: guardUndefTag, negated: true, value: raw, type: BOOL, loc },
7289
+ then: [pushStmt],
7290
+ else_: null,
7291
+ loc,
7292
+ }
7293
+ : pushStmt);
7294
+ }
7295
+ body.push({ kind: "return", value: outRef, loc });
7296
+ fn.body = body;
7297
+ };
7298
+ finalize();
7299
+ L.shapeOrderHelperFinalizers.push(finalize);
7300
+ L.liftedFns.push(fn);
7220
7301
  }
7221
7302
  return { kind: "call", callee: helper, args: [receiver], type: resultT, loc };
7222
7303
  }