@scriptc/compiler 0.0.8 → 0.0.9

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 (66) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +13 -0
  2. package/dist/backend/cc.js +14 -0
  3. package/dist/backend/cc.js.map +1 -1
  4. package/dist/backend/emission/emit-exprs.js +108 -5
  5. package/dist/backend/emission/emit-exprs.js.map +1 -1
  6. package/dist/backend/emission/emit-types.d.ts +3 -0
  7. package/dist/backend/emission/emit-types.js +12 -0
  8. package/dist/backend/emission/emit-types.js.map +1 -1
  9. package/dist/backend/emission/emit-walkers.d.ts +7 -5
  10. package/dist/backend/emission/emit-walkers.js +23 -10
  11. package/dist/backend/emission/emit-walkers.js.map +1 -1
  12. package/dist/backend/emission/emitter.d.ts +1 -1
  13. package/dist/backend/emission/emitter.js +30 -9
  14. package/dist/backend/emission/emitter.js.map +1 -1
  15. package/dist/backend/llvm/emitter.js +206 -11
  16. package/dist/backend/llvm/emitter.js.map +1 -1
  17. package/dist/diagnostics/diagnostic.d.ts +55 -10
  18. package/dist/diagnostics/diagnostic.js +82 -20
  19. package/dist/diagnostics/diagnostic.js.map +1 -1
  20. package/dist/frontend/lowering/lower-assert.js +16 -0
  21. package/dist/frontend/lowering/lower-assert.js.map +1 -1
  22. package/dist/frontend/lowering/lower-builtins.d.ts +11 -0
  23. package/dist/frontend/lowering/lower-builtins.js +111 -3
  24. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  25. package/dist/frontend/lowering/lower-calls.d.ts +30 -11
  26. package/dist/frontend/lowering/lower-calls.js +418 -8
  27. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  28. package/dist/frontend/lowering/lower-classes.js +27 -1
  29. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  30. package/dist/frontend/lowering/lower-containers.d.ts +4 -2
  31. package/dist/frontend/lowering/lower-containers.js +193 -43
  32. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  33. package/dist/frontend/lowering/lower-emitter.js +54 -0
  34. package/dist/frontend/lowering/lower-emitter.js.map +1 -1
  35. package/dist/frontend/lowering/lower-exprs.js +52 -7
  36. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  37. package/dist/frontend/lowering/lower-stmts.js +445 -13
  38. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  39. package/dist/frontend/lowering/lower-stream.js +28 -2
  40. package/dist/frontend/lowering/lower-stream.js.map +1 -1
  41. package/dist/frontend/lowering/lowerer.d.ts +66 -14
  42. package/dist/frontend/lowering/lowerer.js +273 -28
  43. package/dist/frontend/lowering/lowerer.js.map +1 -1
  44. package/dist/frontend/lowering/surfaces.js +20 -3
  45. package/dist/frontend/lowering/surfaces.js.map +1 -1
  46. package/dist/frontend/npm-static-rewrite.d.ts +7 -2
  47. package/dist/frontend/npm-static-rewrite.js +566 -138
  48. package/dist/frontend/npm-static-rewrite.js.map +1 -1
  49. package/dist/frontend/npm-static.js +11 -2
  50. package/dist/frontend/npm-static.js.map +1 -1
  51. package/dist/frontend/program.js +5 -5
  52. package/dist/frontend/program.js.map +1 -1
  53. package/dist/frontend/shared.d.ts +6 -1
  54. package/dist/frontend/shared.js +46 -1
  55. package/dist/frontend/shared.js.map +1 -1
  56. package/dist/frontend/types.js +4 -2
  57. package/dist/frontend/types.js.map +1 -1
  58. package/dist/index.js +150 -25
  59. package/dist/index.js.map +1 -1
  60. package/dist/ir/nodes.d.ts +131 -7
  61. package/dist/ir/nodes.js +41 -3
  62. package/dist/ir/nodes.js.map +1 -1
  63. package/dist/ir/validate.js +58 -8
  64. package/dist/ir/validate.js.map +1 -1
  65. package/package.json +2 -2
  66. package/surface-manifest.json +10 -3
@@ -373,6 +373,36 @@ export declare const TIMER_MODULE_MEMBERS: ReadonlySet<string>;
373
373
  * timer function (the caller's fence machinery takes over). */
374
374
  export declare function lowerTimersMemberCall(L: Lowerer, expr: ts.CallExpression, member: string, loc: SrcLoc): IrExpr | null;
375
375
  export declare function lowerCall(L: Lowerer, expr: ts.CallExpression): IrExpr;
376
+ /** The RUNTIME-ARITY spread call — `f(...args)`, the rest-forwarding idiom
377
+ * (`const f = (...args) => from(...args)`): a spread whose length is a
378
+ * runtime fact has no home in the compile-time completion, so the call
379
+ * rides a dynamic boundary instead. Two lanes, picked by the spread
380
+ * source's tier:
381
+ *
382
+ * - CHECKED-DYNAMIC (dyn spread sources — a JS rest binding, a dyn
383
+ * value): box the callee (dynFrom; a dyn callee is already boxed),
384
+ * convert every argument into dyn, and emit the spread-marked dynCall
385
+ * — the emitters build one fresh DOM argument array (spreads flatten
386
+ * left-to-right, non-iterables throw V8's TypeError) and apply through
387
+ * it; the boxed thunk delivers JS arity exactly. Result dyn, checked
388
+ * per use like every any-origin value.
389
+ * - ISLAND (a jsval spread source — the --dynamic rest binding is the
390
+ * engine's own arguments array): marshal the callee in and emit jsOp
391
+ * callSpread — the prelude helper's REAL `f(...pre, ...s)`, so
392
+ * iterator protocols and the not-iterable TypeError are the engine's
393
+ * own. One trailing spread after the fixed arguments is the modeled
394
+ * shape (exactly the forwarding idiom).
395
+ *
396
+ * Answers null when neither lane fits (typed .ts spreads keep
397
+ * completeArgs' rest packing and its fences). JS sources only — the
398
+ * same guard as the over-arity dynCall precedent. */
399
+ export declare function lowerSpreadArgsCall(L: Lowerer, expr: ts.CallExpression, callee: IrExpr, loc: SrcLoc): IrExpr | null;
400
+ /** True when a spread argument lands where the compile-time completion
401
+ * cannot take it — a FIXED parameter position, or a dynamic rest slot
402
+ * (dynRest/islandRest, whose packs are built per-argument): the shapes
403
+ * the runtime-arity lane (lowerSpreadArgsCall) serves. Typed `rest`
404
+ * slots keep completeArgs' same-element spread packing. */
405
+ export declare function spreadNeedsRuntimeArity(shapes: readonly ParamShape[], argNodes: readonly ts.Expression[]): boolean;
376
406
  /** The SHARED prototype names scr_dyn_invoke dispatches at runtime (the
377
407
  * subset of DOM_PROTO_METHOD_NAMES with a receiver-kind dispatch): the
378
408
  * runtime runs the real method for the receiver's kind, throws Node's
@@ -600,17 +630,6 @@ export declare function bindingGenericFnAliasInfoOf(L: Lowerer, decl: ts.Variabl
600
630
  * structurally identical literal with a DIFFERENT body, which static
601
631
  * resolution would silently miss. */
602
632
  export declare function requireObjLitGenericReceiver(L: Lowerer, blame: ts.Node, recvExpr: ts.Expression, literal: ts.ObjectLiteralExpression, name: string): void;
603
- /** `o.m(args)` where `m` is an object-literal GENERIC method (own type
604
- * parameters — the member is excluded from the record shape, see
605
- * isGenericCallableMemberType): monomorphized per call site against the
606
- * DEFINING literal's declaration, exactly like top-level generic
607
- * functions. Resolution is static, so the receiver must provably BE the
608
- * defining literal: a const binding whose initializer is that literal,
609
- * read directly. The receiver read is pure and the compiled instance is
610
- * a plain module function (no `this`, fenced), so the call lowers to a
611
- * direct `call` of the instance with the receiver unevaluated. Claims
612
- * every call whose member is generic-callable — lowering it or fencing
613
- * with a named message. */
614
633
  export declare function lowerObjLitGenericMethodCall(L: Lowerer, call: ts.CallExpression, access: ts.PropertyAccessExpression): IrExpr | null;
615
634
  /** `obj.method(args)` — whole-program devirtualization decides the form:
616
635
  * a method some strict subclass of the receiver's STATIC class overrides
@@ -7,14 +7,14 @@ import { lowerGenMethodCall } from "./lower-generators.js";
7
7
  import { BOOL, CAUGHT, DYN, F64, JSVAL, STRING, SYMBOL_T, UNDEFINED_T, VOID, arrayOf, canBoxFuncIntoDyn, canConvertToDyn, canDynCheckTo, canMarshalTypedFuncIntoIsland, funcOf, isUnitType, shapeHasAccessorSlots, typeEquals } from "../../ir/nodes.js";
8
8
  import { isJsSourceFile, locOf } from "../program.js";
9
9
  import { isGenericCallableMemberType, typeKey } from "../types.js";
10
- import { PoisonError, dynFallbackType, dynUndefinedExpr, importCallHandleType, jsFuncNameOf, newFnCtx } from "./lowerer.js";
10
+ import { PoisonError, dynFallbackType, dynUndefinedExpr, importCallHandleType, jsFuncNameOf, newFnCtx, nodeThrowExpr } from "./lowerer.js";
11
11
  import { enforceLibBoundary } from "./lib-boundary.js";
12
- import { builtinFenceHintOf, builtinModuleFnOf } from "./surfaces.js";
12
+ import { NARROW_FIRST, builtinFenceHintOf, builtinModuleFnOf } from "./surfaces.js";
13
13
  import { requiresDynamicDiag } from "../../diagnostics/diagnostic.js";
14
14
  import { mixinFnShapeOf } from "./lower-mixins.js";
15
15
  import { bufEncoding, dynStringReceiver, lowerArrayFromCall, lowerDynArrayFilterCall, lowerDynArrayFlatMapCall, lowerGroupByStaticCall, lowerIteratorHelperCall, lowerObjectAssignIndexShape, lowerObjectFromEntriesCall, lowerObjectIterOverIndexShape, lowerRegexMethodCall, lowerStringMethodCall, lowerTupleReadMethodCall } from "./lower-containers.js";
16
16
  import { lowerChildStreamMethodCall, lowerDirentMethodCall, lowerPerfHooksCall, lowerProcStreamMethodCall, lowerReflectApplyCall, lowerWatcherMethodCall } from "./lower-builtins.js";
17
- import { lowerPromiseAllTupleCall, lowerPromiseRejectCall, probeLower, templateRawTextOf } from "./lower-exprs.js";
17
+ import { droppableStatic, lowerPromiseAllTupleCall, lowerPromiseRejectCall, probeLower, templateRawTextOf } from "./lower-exprs.js";
18
18
  import { httpClientFnBindingOf, isStreamUndefCallExpr, lowerHttpClientFnCall } from "./lower-server.js";
19
19
  import { EMITTER_API_MEMBERS, exactInstanceClassOf, findGenericMethodOn, lowerClassGenericMethodCall, lowerStaticMethodCall } from "./lower-classes.js";
20
20
  import { emitterRooted, lowerEmitterMethodCall } from "./lower-emitter.js";
@@ -2804,6 +2804,9 @@ export function lowerCall(L, expr) {
2804
2804
  const streamServed = lowerStreamModuleCall(L, expr, bi, loc);
2805
2805
  if (streamServed)
2806
2806
  return streamServed;
2807
+ const fsTs = L.lowerFsToUnixTimestampCall(expr, bi, loc);
2808
+ if (fsTs)
2809
+ return fsTs;
2807
2810
  const builtinFn = builtinModuleFnOf(L, bi.module, bi.member);
2808
2811
  if (!builtinFn) {
2809
2812
  // Typed by @types/node (the fallback declarations only declare
@@ -3033,15 +3036,24 @@ export function lowerCall(L, expr) {
3033
3036
  // closure object, plain C call). Generic functions route through
3034
3037
  // monomorphization (the call targets a per-instantiation instance).
3035
3038
  if (ts.isIdentifier(expr.expression) && !L.isSelfReference(expr.expression)) {
3039
+ // A JS spread argument the compile-time completion cannot take (a
3040
+ // fixed position, a dynamic rest slot) sends the call down the
3041
+ // VALUE path — the runtime-arity lane (lowerSpreadArgsCall) boxes
3042
+ // the declaration's value and applies through a runtime-built
3043
+ // argument list. Typed .ts spreads keep completeArgs' rest packing.
3044
+ const jsSpreadArgs = expr.arguments.some((a) => ts.isSpreadElement(a)) && isJsSourceFile(expr.getSourceFile());
3036
3045
  if (L.isTopLevelFnSymbol(expr.expression) && !L.peekLocal(expr.expression)) {
3037
3046
  // `import g = N.f; g()` — the alias's own source-order guards
3038
3047
  // (a no-op for every non-import= binding).
3039
3048
  fenceEarlyAliasUse(L, expr.expression, expr);
3040
3049
  const generic = L.genericFnOf(expr.expression);
3041
- if (generic)
3050
+ // An implicit-any JS function spread-forwarded into: per-site
3051
+ // monomorphization has no slot for a runtime-length argument
3052
+ // list — the value path's boxed thunk delivers JS arity instead.
3053
+ if (generic && !(jsSpreadArgs && generic.implicitParams))
3042
3054
  return L.lowerGenericCall(expr, generic);
3043
- const sig = L.fnSigOf(expr.expression);
3044
- if (sig) {
3055
+ const sig = generic ? null : L.fnSigOf(expr.expression);
3056
+ if (sig && !(jsSpreadArgs && spreadNeedsRuntimeArity(sig.params, expr.arguments))) {
3045
3057
  L.noteEdge(sig.name);
3046
3058
  const args = L.completeArgs(expr.arguments, sig.params, loc, expr);
3047
3059
  return reconcileOverloadReturn(L, expr, { kind: "call", callee: sig.name, args, type: sig.returnType, loc });
@@ -3065,10 +3077,12 @@ export function lowerCall(L, expr) {
3065
3077
  // the call monomorphizes against it exactly like a generic function
3066
3078
  // declaration and the binding is never read. Symbol identity does
3067
3079
  // the discrimination — a shadowing local has its own symbol, and
3068
- // registered bindings never declare locals or globals.
3080
+ // registered bindings never declare locals or globals. Implicit-any
3081
+ // JS bindings spread-forwarded into skip to the value path (the
3082
+ // runtime-arity lane), like the declaration form above.
3069
3083
  {
3070
3084
  const generic = L.genericFnOf(expr.expression);
3071
- if (generic)
3085
+ if (generic && !(jsSpreadArgs && generic.implicitParams))
3072
3086
  return L.lowerGenericCall(expr, generic);
3073
3087
  }
3074
3088
  }
@@ -3277,6 +3291,9 @@ export function lowerCall(L, expr) {
3277
3291
  // island path (bytes never cross the boundary).
3278
3292
  L.lowerBytesMethodCall(expr, expr.expression) ??
3279
3293
  L.lowerBufferStaticCall(expr, expr.expression) ??
3294
+ // URL.revokeObjectURL's zero-argument contract (the one-argument
3295
+ // form keeps the fence — createObjectURL does too).
3296
+ lowerUrlStaticCall(L, expr, expr.expression) ??
3280
3297
  // Readable.from — the stream classes' one static (before the
3281
3298
  // stdlib chokepoint claims the member).
3282
3299
  lowerStreamStaticCall(L, expr, expr.expression) ??
@@ -3312,6 +3329,9 @@ export function lowerCall(L, expr) {
3312
3329
  lowerSymbolStaticCall(L, expr, expr.expression) ??
3313
3330
  lowerSymbolMethodCall(L, expr, expr.expression) ??
3314
3331
  lowerRegExpStaticCall(L, expr, expr.expression) ??
3332
+ // The composed en-US Intl.NumberFormat form — before the member
3333
+ // fences (the receiver's Intl.NumberFormat type has no mapping).
3334
+ lowerIntlNumberFormatCall(L, expr, expr.expression) ??
3315
3335
  lowerGroupByStaticCall(L, expr, expr.expression) ??
3316
3336
  // Iterator-helper chains rooted at arr.values() — before the
3317
3337
  // array method paths (the terminal names collide with array
@@ -3420,6 +3440,12 @@ export function lowerCall(L, expr) {
3420
3440
  // any-origin value). Spread arguments keep their fence.
3421
3441
  if (callee.type.kind === "dyn") {
3422
3442
  if (expr.arguments.some((a) => ts.isSpreadElement(a))) {
3443
+ // The runtime-arity lane: a dyn callee is already boxed — the
3444
+ // spread-marked dynCall applies through a fresh DOM argument
3445
+ // array (lowerSpreadArgsCall). Sources outside it keep the fence.
3446
+ const spreadServed = lowerSpreadArgsCall(L, expr, callee, loc);
3447
+ if (spreadServed)
3448
+ return spreadServed;
3423
3449
  L.unsupported("SC1090", expr, "spread arguments in calls through 'unknown' values");
3424
3450
  }
3425
3451
  const args = expr.arguments.map((a) => L.lowerExprExpecting(a, DYN));
@@ -3433,6 +3459,42 @@ export function lowerCall(L, expr) {
3433
3459
  if (callee.type.kind !== "func") {
3434
3460
  L.badType(expr.expression, L.typeOf(expr.expression));
3435
3461
  }
3462
+ // A SPREAD argument on a func-typed callee — the rest-forwarding
3463
+ // idiom (`(...args) => from(...args)`): the runtime-arity lane boxes
3464
+ // or marshals the callee and applies through a runtime-built argument
3465
+ // list (lowerSpreadArgsCall). Shapes outside its lanes fall through
3466
+ // to the historical fences.
3467
+ if (expr.arguments.some((a) => ts.isSpreadElement(a))) {
3468
+ const spreadServed = lowerSpreadArgsCall(L, expr, callee, loc);
3469
+ if (spreadServed)
3470
+ return spreadServed;
3471
+ }
3472
+ // An ISLAND-REST func value called directly (`f(1, 2)` where f is the
3473
+ // --dynamic `(...args) =>` lambda): the type SPELLS its trailing
3474
+ // engine-array param — complete the call exactly like completeArgs'
3475
+ // island pack (fixed slots positionally, missing ones with the
3476
+ // engine's undefined, the surplus marshaled into one fresh engine
3477
+ // array). JS arity, no runtime machinery.
3478
+ if (callee.type.rest === true &&
3479
+ callee.type.restAbi === "jsval" &&
3480
+ callee.type.params.length >= 1 &&
3481
+ callee.type.params[callee.type.params.length - 1].kind === "jsval" &&
3482
+ !expr.arguments.some((a) => ts.isSpreadElement(a))) {
3483
+ const fixed = callee.type.params.slice(0, -1);
3484
+ const args = fixed.map((p, i) => {
3485
+ const a = expr.arguments[i];
3486
+ if (a)
3487
+ return L.lowerExprExpecting(a, p);
3488
+ const absent = omittedArgFor(L, p, loc);
3489
+ if (!absent) {
3490
+ L.unsupported("SC1090", expr, "calls omitting a non-optional parameter of the callee's type");
3491
+ }
3492
+ return absent;
3493
+ });
3494
+ const restArgs = expr.arguments.slice(fixed.length).map((a) => L.lowerExprExpecting(a, JSVAL));
3495
+ args.push({ kind: "jsOp", op: "arrLit", args: restArgs, type: JSVAL, loc });
3496
+ return { kind: "callValue", callee, args, type: callee.type.ret, loc };
3497
+ }
3436
3498
  // A JS call with MORE arguments than the callee's lowered signature
3437
3499
  // (`cb(1, 'x')` where the mustCall wrapper's inferred type declared
3438
3500
  // fewer params — tsc's JS world doesn't police arity): ride the
@@ -3470,6 +3532,101 @@ export function lowerCall(L, expr) {
3470
3532
  }
3471
3533
  return { kind: "callValue", callee, args, type: callee.type.ret, loc };
3472
3534
  }
3535
+ /** The RUNTIME-ARITY spread call — `f(...args)`, the rest-forwarding idiom
3536
+ * (`const f = (...args) => from(...args)`): a spread whose length is a
3537
+ * runtime fact has no home in the compile-time completion, so the call
3538
+ * rides a dynamic boundary instead. Two lanes, picked by the spread
3539
+ * source's tier:
3540
+ *
3541
+ * - CHECKED-DYNAMIC (dyn spread sources — a JS rest binding, a dyn
3542
+ * value): box the callee (dynFrom; a dyn callee is already boxed),
3543
+ * convert every argument into dyn, and emit the spread-marked dynCall
3544
+ * — the emitters build one fresh DOM argument array (spreads flatten
3545
+ * left-to-right, non-iterables throw V8's TypeError) and apply through
3546
+ * it; the boxed thunk delivers JS arity exactly. Result dyn, checked
3547
+ * per use like every any-origin value.
3548
+ * - ISLAND (a jsval spread source — the --dynamic rest binding is the
3549
+ * engine's own arguments array): marshal the callee in and emit jsOp
3550
+ * callSpread — the prelude helper's REAL `f(...pre, ...s)`, so
3551
+ * iterator protocols and the not-iterable TypeError are the engine's
3552
+ * own. One trailing spread after the fixed arguments is the modeled
3553
+ * shape (exactly the forwarding idiom).
3554
+ *
3555
+ * Answers null when neither lane fits (typed .ts spreads keep
3556
+ * completeArgs' rest packing and its fences). JS sources only — the
3557
+ * same guard as the over-arity dynCall precedent. */
3558
+ export function lowerSpreadArgsCall(L, expr, callee, loc) {
3559
+ if (!isJsSourceFile(expr.getSourceFile()))
3560
+ return null;
3561
+ if (!expr.arguments.some((a) => ts.isSpreadElement(a)))
3562
+ return null;
3563
+ if (callee.type.kind !== "dyn" && callee.type.kind !== "func" && callee.type.kind !== "jsval")
3564
+ return null;
3565
+ const calleeName = ts.isPropertyAccessExpression(expr.expression) || ts.isElementAccessExpression(expr.expression)
3566
+ ? expr.expression.getText()
3567
+ : ts.isIdentifier(expr.expression)
3568
+ ? expr.expression.text
3569
+ : "value";
3570
+ // Lower every argument ONCE, in source order (the IR nests them in
3571
+ // exactly this order, so runtime evaluation order is JS's).
3572
+ const parts = expr.arguments.map((a) => ts.isSpreadElement(a)
3573
+ ? { spreadOf: a.expression, node: null, v: L.lowerExpr(a.expression) }
3574
+ : { spreadOf: null, node: a, v: L.lowerExpr(a) });
3575
+ const spreadParts = parts.filter((p) => p.spreadOf !== null);
3576
+ const getR = (id) => L.shapes.get(id);
3577
+ const getU = (id) => L.unions.get(id);
3578
+ const anyJsvalSpread = spreadParts.some((p) => p.v.type.kind === "jsval");
3579
+ if (!anyJsvalSpread &&
3580
+ (callee.type.kind === "dyn" || (callee.type.kind === "func" && canBoxFuncIntoDyn(callee.type, getR, getU))) &&
3581
+ spreadParts.every((p) => p.v.type.kind === "dyn" || canConvertToDyn(p.v.type, getR, getU))) {
3582
+ const args = [];
3583
+ const spreads = [];
3584
+ for (const p of parts) {
3585
+ if (p.spreadOf !== null) {
3586
+ // The spelling rides along for V8's nullish spread-call
3587
+ // TypeError ("v is not iterable (cannot read property ...)").
3588
+ spreads.push({ arg: args.length, what: p.spreadOf.getText() });
3589
+ args.push(L.coerceInto(p.spreadOf, p.v, DYN));
3590
+ }
3591
+ else {
3592
+ args.push(L.coerceInto(p.node, p.v, DYN));
3593
+ }
3594
+ }
3595
+ const boxed = callee.type.kind === "dyn" ? callee : L.coerceInto(expr.expression, callee, DYN);
3596
+ return { kind: "dynCall", callee: boxed, calleeName, args, spreads, type: DYN, loc };
3597
+ }
3598
+ if (spreadParts.length > 0 &&
3599
+ spreadParts.every((p) => p.v.type.kind === "jsval") &&
3600
+ (callee.type.kind === "jsval" || callee.type.kind === "func")) {
3601
+ if (spreadParts.length !== 1 || parts[parts.length - 1].spreadOf === null) {
3602
+ L.unsupported("SC1090", expr, "spread arguments before positional arguments in island calls (one trailing spread after the fixed arguments is the supported form)");
3603
+ }
3604
+ const f = L.coerceInto(expr.expression, callee, JSVAL);
3605
+ const pre = parts
3606
+ .slice(0, -1)
3607
+ .map((p) => L.coerceInto(p.node, p.v, JSVAL));
3608
+ const preArr = { kind: "jsOp", op: "arrLit", args: pre, type: JSVAL, loc };
3609
+ const last = parts[parts.length - 1];
3610
+ // The spelling rides in `name` for V8's nullish spread-call
3611
+ // TypeError ("v is not iterable (cannot read property ...)").
3612
+ return { kind: "jsOp", op: "callSpread", name: last.spreadOf.getText(), args: [f, preArr, last.v], type: JSVAL, loc };
3613
+ }
3614
+ // No lane fits (a spread source outside both tiers, a callee neither
3615
+ // boxable nor marshalable, mixed dyn/jsval spreads): fence HERE — the
3616
+ // arguments are already lowered, and falling back to the historical
3617
+ // per-site fences would lower them a second time (duplicate lambda
3618
+ // lifts, duplicated diagnostics).
3619
+ L.unsupported("SC1090", expr, "spread arguments");
3620
+ }
3621
+ /** True when a spread argument lands where the compile-time completion
3622
+ * cannot take it — a FIXED parameter position, or a dynamic rest slot
3623
+ * (dynRest/islandRest, whose packs are built per-argument): the shapes
3624
+ * the runtime-arity lane (lowerSpreadArgsCall) serves. Typed `rest`
3625
+ * slots keep completeArgs' same-element spread packing. */
3626
+ export function spreadNeedsRuntimeArity(shapes, argNodes) {
3627
+ const restAt = shapes.findIndex((s) => s.mode === "rest" || s.mode === "dynRest" || s.mode === "islandRest");
3628
+ return argNodes.some((a, i) => ts.isSpreadElement(a) && (restAt < 0 || i < restAt || shapes[restAt].mode !== "rest"));
3629
+ }
3473
3630
  /** METHOD calls on dyn receivers (`pkg.name.replace(...)`, `rawName.split`,
3474
3631
  * `ws.packages.filter(...)` — JSON.parse-derived values): validate the
3475
3632
  * receiver's DOM kind, extract, and ride the STATIC method machinery — the
@@ -4008,6 +4165,32 @@ export function lowerPrimitiveProtoCall(L, call, recv, name, memberSym) {
4008
4165
  const fn = name === "toExponential" ? "num.toExponential" : "num.toFixed0";
4009
4166
  return { kind: "libCall", fn, args: [operand], type: STRING, loc };
4010
4167
  }
4168
+ // Number.prototype.toLocaleString("en-US") — the spec makes it
4169
+ // NumberFormat(locale).format(this), so the en-US embedded formatter
4170
+ // answers exactly. The unlowered forms fence by NAME: no locale (the
4171
+ // host environment's default, which a compiled binary cannot carry),
4172
+ // other locales (ICU data the binary does not embed), options bags.
4173
+ if (name === "toLocaleString" && recvKind === "f64") {
4174
+ if (call.arguments.length === 0) {
4175
+ L.noLowering("Number.prototype.toLocaleString without a locale", call, "the default locale is the host environment's, which a compiled binary cannot carry — " +
4176
+ 'pass it explicitly: x.toLocaleString("en-US")');
4177
+ }
4178
+ if (call.arguments.length > 1) {
4179
+ L.noLowering("Number.prototype.toLocaleString with an options bag", call, "the embedded data covers DEFAULT options only (decimal notation, up to 3 fraction " +
4180
+ 'digits, grouping) — x.toLocaleString("en-US")');
4181
+ }
4182
+ const locNode = call.arguments[0];
4183
+ if (ts.isSpreadElement(locNode) || !ts.isStringLiteralLike(locNode) || locNode.text !== "en-US") {
4184
+ L.noLowering(!ts.isSpreadElement(locNode) && ts.isStringLiteralLike(locNode)
4185
+ ? `Number.prototype.toLocaleString at locale "${locNode.text}"`
4186
+ : "Number.prototype.toLocaleString with a non-literal locale", locNode, '"en-US" (Node\'s default-build locale) is the one locale whose data the runtime embeds — ' +
4187
+ "everything else is ICU data the binary does not carry");
4188
+ }
4189
+ const operand = L.lowerExpr(recv);
4190
+ if (operand.type.kind !== "f64")
4191
+ return null;
4192
+ return { kind: "libCall", fn: "intl.numFormatEnUs", args: [operand], type: STRING, loc };
4193
+ }
4011
4194
  if (name === "charAt" && recvKind === "string" && call.arguments.length === 1 &&
4012
4195
  !ts.isSpreadElement(call.arguments[0])) {
4013
4196
  const receiver = L.lowerExpr(recv);
@@ -4602,6 +4785,15 @@ export function lowerLambda(L, node) {
4602
4785
  const first = captured[0];
4603
4786
  const pos = ts.getLineAndCharacterOfPosition(L.program.getSourceFile(first.loc.file) ?? node.getSourceFile(), first.loc.start);
4604
4787
  const params = funcType.params.map((t, i) => ({ localId: `%pf${i}`, name: `%pf${i}`, type: t }));
4788
+ // A REST-MARKED value type hides one synthetic trailing DOM-array
4789
+ // param in the lifted function (the boxed call thunk fills it) —
4790
+ // the fence lambda must spell that slot too or the validator's
4791
+ // closure-signature check trips (SC9001). Island rest types SPELL
4792
+ // their trailing engine-array param, so funcType.params already
4793
+ // covers those.
4794
+ if (funcType.rest === true && funcType.restAbi !== "jsval") {
4795
+ params.push({ localId: "%pfrest", name: "%pfrest", type: DYN });
4796
+ }
4605
4797
  const lifted = {
4606
4798
  name: fnName,
4607
4799
  params,
@@ -5782,12 +5974,184 @@ function lowerRegExpStaticCall(L, call, access) {
5782
5974
  L.badType(call.arguments[0], L.typeOf(call.arguments[0]));
5783
5975
  return { kind: "libCall", fn: "regexp.escape", args: [arg], type: STRING, loc: locOf(call) };
5784
5976
  }
5977
+ /** The composed en-US Intl.NumberFormat form: `new Intl.NumberFormat(
5978
+ * "en-US").format(x)` (and the callable spelling without `new` — the
5979
+ * spec makes them the same formatter). Only the COMPOSED form lowers —
5980
+ * formatter values have no representation — and only the one locale
5981
+ * whose data the runtime embeds, with default options: decimal
5982
+ * notation, 0–3 fraction digits rounded half-up on the shortest
5983
+ * round-tripping decimal (ICU's rounding input — format(1.0005) is
5984
+ * "1.001" though toFixed(3) answers "1.000"), "," grouping. The
5985
+ * unlowered forms fence by NAME (no locale — the host environment's
5986
+ * default, which a compiled binary cannot carry; other locales — ICU
5987
+ * data the binary does not embed; options bags; non-number arguments).
5988
+ * Null when the callee isn't a NumberFormat-construction .format. */
5989
+ function lowerIntlNumberFormatCall(L, call, access) {
5990
+ if (call.questionDotToken || access.questionDotToken)
5991
+ return null;
5992
+ if (access.name.text !== "format")
5993
+ return null;
5994
+ let recv = access.expression;
5995
+ while (ts.isParenthesizedExpression(recv))
5996
+ recv = recv.expression;
5997
+ let ctorArgs;
5998
+ if (ts.isNewExpression(recv) || (ts.isCallExpression(recv) && !recv.questionDotToken)) {
5999
+ const ctor = recv.expression;
6000
+ if (!ts.isPropertyAccessExpression(ctor) || ctor.questionDotToken ||
6001
+ ctor.name.text !== "NumberFormat" || !L.isStdlibGlobal(ctor.expression, "Intl")) {
6002
+ return null;
6003
+ }
6004
+ ctorArgs = recv.arguments ?? [];
6005
+ }
6006
+ else {
6007
+ return null;
6008
+ }
6009
+ const loc = locOf(call);
6010
+ if (ctorArgs.length === 0) {
6011
+ L.noLowering("Intl.NumberFormat without a locale", recv, "the default locale is the host environment's, which a compiled binary cannot carry — " +
6012
+ 'pass it explicitly: new Intl.NumberFormat("en-US").format(x)');
6013
+ }
6014
+ if (ctorArgs.length > 1) {
6015
+ L.noLowering("Intl.NumberFormat with an options bag", ctorArgs[1], "the embedded data covers DEFAULT options only (decimal notation, up to 3 fraction digits, " +
6016
+ 'grouping) — new Intl.NumberFormat("en-US").format(x)');
6017
+ }
6018
+ const locArg = ctorArgs[0];
6019
+ if (ts.isSpreadElement(locArg) || !ts.isStringLiteralLike(locArg) || locArg.text !== "en-US") {
6020
+ L.noLowering(!ts.isSpreadElement(locArg) && ts.isStringLiteralLike(locArg)
6021
+ ? `Intl.NumberFormat at locale "${locArg.text}"`
6022
+ : "Intl.NumberFormat with a non-literal locale", locArg, '"en-US" (Node\'s default-build locale) is the one locale whose data the runtime embeds — ' +
6023
+ "everything else is ICU data the binary does not carry");
6024
+ }
6025
+ if (call.arguments.length !== 1 || ts.isSpreadElement(call.arguments[0])) {
6026
+ L.noLowering(`Intl.NumberFormat("en-US").format with ${call.arguments.length} arguments`, call);
6027
+ }
6028
+ const argNode = call.arguments[0];
6029
+ if (L.mapTypeOf(L.typeOf(argNode))?.kind !== "f64") {
6030
+ L.noLowering(`Intl.NumberFormat("en-US").format over a '${L.checker.typeToString(L.typeOf(argNode))}'`, argNode, "a number argument is the lowered form (bigint and numeric-string inputs have no representation)");
6031
+ }
6032
+ const arg = L.lowerExprExpecting(argNode, F64);
6033
+ if (arg.type.kind !== "f64")
6034
+ L.badType(argNode, L.typeOf(argNode));
6035
+ return { kind: "libCall", fn: "intl.numFormatEnUs", args: [arg], type: STRING, loc };
6036
+ }
6037
+ /** Object.is over statically disjoint kinds: the constant false, with
6038
+ * both operands still evaluated for their effects (droppable statics
6039
+ * fold away — JS evaluates arguments, but nothing observes a pure one). */
6040
+ function objectIsDisjointFalse(left, right, loc) {
6041
+ const stmts = [];
6042
+ for (const e of [left, right]) {
6043
+ if (!droppableStatic(e))
6044
+ stmts.push({ kind: "exprStmt", expr: e, loc });
6045
+ }
6046
+ const answer = { kind: "boolLit", value: false, type: BOOL, loc };
6047
+ if (stmts.length === 0)
6048
+ return answer;
6049
+ return { kind: "seqExpr", stmts, result: answer, type: BOOL, loc };
6050
+ }
5785
6051
  function lowerObjectStaticCall(L, call, access) {
5786
6052
  if (call.questionDotToken || access.questionDotToken)
5787
6053
  return null;
5788
6054
  if (!L.isStdlibGlobal(access.expression, "Object"))
5789
6055
  return null;
5790
6056
  const member = access.name.text;
6057
+ // Object.is — the spec's SameValue over the static kinds. Number
6058
+ // pairs take the runtime SameValue (NaN equals NaN, +0 differs from
6059
+ // -0 — the two divergences from ===); every other supported pair
6060
+ // rides exactly the strict-equality machinery, whose answers
6061
+ // SameValue shares: strings by bytes, bools by value, unit literals
6062
+ // by tag, unions per arm (a number arm's payload compare upgrades to
6063
+ // SameValue via unionEq's flag), and the reference kinds by pointer
6064
+ // identity. Statically DISJOINT kind pairs answer the constant false
6065
+ // with the operands still evaluated (tsc admits any pair — Object.is
6066
+ // is (any, any) — and JS evaluates the arguments either way).
6067
+ // dyn/jsval operands keep strict equality's stance: validate first.
6068
+ if (member === "is") {
6069
+ if (call.arguments.length !== 2 || call.arguments.some((a) => ts.isSpreadElement(a))) {
6070
+ L.noLowering(`Object.is with ${call.arguments.length} arguments`, call, "exactly two arguments are the lowered form (JS treats a missing one as undefined — pass it explicitly)");
6071
+ }
6072
+ const loc = locOf(call);
6073
+ const leftNode = call.arguments[0];
6074
+ const rightNode = call.arguments[1];
6075
+ const left = L.lowerExpr(leftNode);
6076
+ const right = L.lowerExpr(rightNode);
6077
+ const lk = left.type.kind;
6078
+ const rk = right.type.kind;
6079
+ if (lk === "f64" && rk === "f64") {
6080
+ return { kind: "libCall", fn: "num.sameValue", args: [left, right], type: BOOL, loc };
6081
+ }
6082
+ if (left.type.kind === "string" && right.type.kind === "string") {
6083
+ return { kind: "strEq", negated: false, left, right, type: BOOL, loc };
6084
+ }
6085
+ if (lk === "bool" && rk === "bool") {
6086
+ return { kind: "bin", op: "===", left, right, type: BOOL, loc };
6087
+ }
6088
+ const unitTest = L.lowerUnitComparison(left, right, false, loc);
6089
+ if (unitTest)
6090
+ return unitTest;
6091
+ if (lk === "dyn" || rk === "dyn" || lk === "jsval" || rk === "jsval") {
6092
+ L.noLowering("Object.is over a dynamic operand", call, "validate/narrow the value first (strict equality's rule) — SameValue only differs from === on numbers (NaN, ±0)");
6093
+ }
6094
+ if (left.type.kind === "union" || right.type.kind === "union") {
6095
+ const ut = left.type.kind === "union" ? left.type : right.type;
6096
+ const bothUnion = left.type.kind === "union" && right.type.kind === "union";
6097
+ const sameUnion = bothUnion && typeEquals(left.type, right.type);
6098
+ if ((sameUnion || !bothUnion) && L.eqComparableUnion(ut.unionId)) {
6099
+ const plain = left.type.kind === "union" ? right : left;
6100
+ const arms = L.unions.get(ut.unionId)?.arms ?? [];
6101
+ // The plain side wraps into the union exactly like === when the
6102
+ // union holds its type; a plain PRIMITIVE the union has no arm
6103
+ // for is the disjoint constant false (coercing it would strand).
6104
+ if (bothUnion || arms.some((a) => typeEquals(a, plain.type))) {
6105
+ const sameValue = arms.some((a) => a.kind === "f64");
6106
+ return {
6107
+ kind: "unionEq",
6108
+ unionId: ut.unionId,
6109
+ negated: false,
6110
+ sameValue,
6111
+ left: L.coerceInto(leftNode, left, ut),
6112
+ right: L.coerceInto(rightNode, right, ut),
6113
+ type: BOOL,
6114
+ loc,
6115
+ };
6116
+ }
6117
+ if (plain.type.kind === "f64" || plain.type.kind === "string" ||
6118
+ plain.type.kind === "bool" || isUnitType(plain.type)) {
6119
+ return objectIsDisjointFalse(left, right, loc);
6120
+ }
6121
+ }
6122
+ L.noLowering("Object.is over these union operands", call, `union-typed comparisons need one comparable shape (${NARROW_FIRST})`);
6123
+ }
6124
+ // Reference kinds: pointer identity — exactly strict equality
6125
+ // (hierarchy-related classes widen the derived side first).
6126
+ let idLeft = left;
6127
+ let idRight = right;
6128
+ if (left.type.kind === "object" && right.type.kind === "object") {
6129
+ if (L.isSubclassOf(left.type.className, right.type.className)) {
6130
+ idLeft = L.upcastTo(left, right.type.className);
6131
+ }
6132
+ else if (L.isSubclassOf(right.type.className, left.type.className)) {
6133
+ idRight = L.upcastTo(right, left.type.className);
6134
+ }
6135
+ }
6136
+ if ((idLeft.type.kind === "func" && idRight.type.kind === "func") ||
6137
+ (idLeft.type.kind === "classval" && idRight.type.kind === "classval")) {
6138
+ return { kind: "bin", op: "===", left: idLeft, right: idRight, type: BOOL, loc };
6139
+ }
6140
+ if ((idLeft.type.kind === "array" || idLeft.type.kind === "map" ||
6141
+ idLeft.type.kind === "set" || idLeft.type.kind === "object" ||
6142
+ idLeft.type.kind === "record" || idLeft.type.kind === "symbol" ||
6143
+ idLeft.type.kind === "bytes" || idLeft.type.kind === "promise") &&
6144
+ typeEquals(idLeft.type, idRight.type)) {
6145
+ return { kind: "bin", op: "===", left: idLeft, right: idRight, type: BOOL, loc };
6146
+ }
6147
+ // Statically disjoint pairs with a primitive/unit side: SameValue
6148
+ // never crosses kinds, so the answer is the constant false.
6149
+ const disjoint = new Set(["f64", "string", "bool", "undefinedT", "nullT"]);
6150
+ if (lk !== rk && (disjoint.has(lk) || disjoint.has(rk))) {
6151
+ return objectIsDisjointFalse(left, right, loc);
6152
+ }
6153
+ L.noLowering(`Object.is over '${L.fmt(left.type)}' and '${L.fmt(right.type)}' operands`, call, "the operands must share one comparable kind (numbers, strings, booleans, units, one union shape, or one reference type)");
6154
+ }
5791
6155
  // `Object.assign(fn, { props })` whose RESULT type maps to the hybrid
5792
6156
  // (function-with-properties) record: the chalk-shape CONSTRUCTOR.
5793
6157
  if (member === "assign") {
@@ -6721,10 +7085,56 @@ export function requireObjLitGenericReceiver(L, blame, recvExpr, literal, name)
6721
7085
  * direct `call` of the instance with the receiver unevaluated. Claims
6722
7086
  * every call whose member is generic-callable — lowering it or fencing
6723
7087
  * with a named message. */
7088
+ /** URL.revokeObjectURL() with NO argument: Node's ERR_MISSING_ARGS
7089
+ * throws before the registry lookup, so the zero-argument contract is
7090
+ * exact without any blob machinery. The one-argument form (Node's
7091
+ * silent no-op for unregistered ids) and createObjectURL keep their
7092
+ * fences — a compiled program has no blob registry to consult. */
7093
+ function lowerUrlStaticCall(L, call, callee) {
7094
+ if (!ts.isPropertyAccessExpression(callee) || callee.questionDotToken !== undefined)
7095
+ return null;
7096
+ if (!ts.isIdentifier(callee.expression) || callee.expression.text !== "URL")
7097
+ return null;
7098
+ if (callee.name.text !== "revokeObjectURL" || call.arguments.length !== 0)
7099
+ return null;
7100
+ const sym = L.resolveValueSymbol(callee.expression);
7101
+ if (!sym || !L.isStdlibSymbol(sym))
7102
+ return null;
7103
+ return nodeThrowExpr(1, "ERR_MISSING_ARGS", 'The "url" argument must be specified', VOID, locOf(call));
7104
+ }
7105
+ const SP_BRAND_METHODS = new Set([
7106
+ "append", "delete", "get", "getAll", "has", "set", "sort",
7107
+ "forEach", "keys", "values", "entries", "toString",
7108
+ ]);
6724
7109
  export function lowerObjLitGenericMethodCall(L, call, access) {
6725
7110
  if (L.chainBlocked(access, call))
6726
7111
  return null;
6727
7112
  const name = access.name.text;
7113
+ // URLSearchParams method values through Function.prototype.call/apply
7114
+ // with a receiver that is provably NOT a URLSearchParams (the suite's
7115
+ // `params.append.call(undefined)` probes): the WHATWG brand check
7116
+ // throws ERR_INVALID_THIS before any argument conversion — the whole
7117
+ // call IS that throw. A receiver that IS searchParams-typed, or one
7118
+ // whose runtime kind is unknowable (dyn/'any'), keeps the fence.
7119
+ if ((name === "call" || name === "apply") &&
7120
+ ts.isPropertyAccessExpression(access.expression) &&
7121
+ L.mapTypeOf(L.typeOf(access.expression.expression))?.kind === "searchParams" &&
7122
+ SP_BRAND_METHODS.has(access.expression.name.text) &&
7123
+ L.isStdlibMember(access.expression)) {
7124
+ const thisArg = call.arguments[0];
7125
+ const thisT = thisArg ? L.mapTypeOf(L.typeOf(thisArg)) : { kind: "undefinedT" };
7126
+ const provablyNot = thisT !== null &&
7127
+ thisT.kind !== "searchParams" &&
7128
+ thisT.kind !== "dyn" &&
7129
+ thisT.kind !== "jsval" &&
7130
+ (!thisArg || ts.isIdentifier(thisArg) || ts.isLiteralExpression(thisArg) ||
7131
+ thisArg.kind === ts.SyntaxKind.UndefinedKeyword ||
7132
+ thisArg.kind === ts.SyntaxKind.NullKeyword ||
7133
+ isUnitType(thisT));
7134
+ if (provablyNot) {
7135
+ return nodeThrowExpr(1, "ERR_INVALID_THIS", 'Value of "this" must be of type URLSearchParams', L.mapTypeOf(L.typeOf(call)) ?? VOID, locOf(call));
7136
+ }
7137
+ }
6728
7138
  const recvT = L.typeOf(access.expression);
6729
7139
  const propSym = L.checker.getPropertyOfType(recvT, name);
6730
7140
  if (!propSym)