@scriptc/compiler 0.0.3 → 0.0.5

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 (92) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +145 -9
  2. package/dist/backend/cc.d.ts +23 -0
  3. package/dist/backend/cc.js +89 -0
  4. package/dist/backend/cc.js.map +1 -1
  5. package/dist/backend/emission/emit-async.js +2 -2
  6. package/dist/backend/emission/emit-async.js.map +1 -1
  7. package/dist/backend/emission/emit-exprs.js +37 -2
  8. package/dist/backend/emission/emit-exprs.js.map +1 -1
  9. package/dist/backend/emission/emit-shapes.js +3 -2
  10. package/dist/backend/emission/emit-shapes.js.map +1 -1
  11. package/dist/backend/emission/emit-walkers.js +7 -8
  12. package/dist/backend/emission/emit-walkers.js.map +1 -1
  13. package/dist/backend/emission/emitter.d.ts +1 -0
  14. package/dist/backend/emission/emitter.js +123 -3
  15. package/dist/backend/emission/emitter.js.map +1 -1
  16. package/dist/backend/llvm/emitter.js +198 -15
  17. package/dist/backend/llvm/emitter.js.map +1 -1
  18. package/dist/backend/llvm/shapes.js +1 -0
  19. package/dist/backend/llvm/shapes.js.map +1 -1
  20. package/dist/diagnostics/diagnostic.d.ts +25 -0
  21. package/dist/diagnostics/diagnostic.js +88 -2
  22. package/dist/diagnostics/diagnostic.js.map +1 -1
  23. package/dist/frontend/lib-exports.d.ts +16 -0
  24. package/dist/frontend/lib-exports.js +35 -0
  25. package/dist/frontend/lib-exports.js.map +1 -0
  26. package/dist/frontend/lowering/lib-boundary.js +76 -6
  27. package/dist/frontend/lowering/lib-boundary.js.map +1 -1
  28. package/dist/frontend/lowering/lower-builtins.d.ts +37 -4
  29. package/dist/frontend/lowering/lower-builtins.js +240 -6
  30. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  31. package/dist/frontend/lowering/lower-calls.js +124 -34
  32. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  33. package/dist/frontend/lowering/lower-classes.d.ts +34 -2
  34. package/dist/frontend/lowering/lower-classes.js +257 -34
  35. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  36. package/dist/frontend/lowering/lower-containers.js +25 -3
  37. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  38. package/dist/frontend/lowering/lower-exprs.d.ts +10 -0
  39. package/dist/frontend/lowering/lower-exprs.js +411 -10
  40. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  41. package/dist/frontend/lowering/lower-inspect.d.ts +16 -1
  42. package/dist/frontend/lowering/lower-inspect.js +132 -5
  43. package/dist/frontend/lowering/lower-inspect.js.map +1 -1
  44. package/dist/frontend/lowering/lower-island.js +25 -4
  45. package/dist/frontend/lowering/lower-island.js.map +1 -1
  46. package/dist/frontend/lowering/lower-mixins.js +4 -2
  47. package/dist/frontend/lowering/lower-mixins.js.map +1 -1
  48. package/dist/frontend/lowering/lower-modules.d.ts +6 -5
  49. package/dist/frontend/lowering/lower-modules.js +56 -18
  50. package/dist/frontend/lowering/lower-modules.js.map +1 -1
  51. package/dist/frontend/lowering/lower-stmts.js +163 -0
  52. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  53. package/dist/frontend/lowering/lowerer.d.ts +72 -2
  54. package/dist/frontend/lowering/lowerer.js +339 -19
  55. package/dist/frontend/lowering/lowerer.js.map +1 -1
  56. package/dist/frontend/lowering/surfaces.d.ts +9 -2
  57. package/dist/frontend/lowering/surfaces.js +44 -7
  58. package/dist/frontend/lowering/surfaces.js.map +1 -1
  59. package/dist/frontend/npm-static-rewrite.d.ts +3 -0
  60. package/dist/frontend/npm-static-rewrite.js +677 -0
  61. package/dist/frontend/npm-static-rewrite.js.map +1 -0
  62. package/dist/frontend/npm-static.d.ts +8 -1
  63. package/dist/frontend/npm-static.js +159 -18
  64. package/dist/frontend/npm-static.js.map +1 -1
  65. package/dist/frontend/npm.js +2 -2
  66. package/dist/frontend/npm.js.map +1 -1
  67. package/dist/frontend/program.d.ts +6 -0
  68. package/dist/frontend/program.js +110 -22
  69. package/dist/frontend/program.js.map +1 -1
  70. package/dist/frontend/resolve.js +120 -5
  71. package/dist/frontend/resolve.js.map +1 -1
  72. package/dist/frontend/shared.d.ts +8 -2
  73. package/dist/frontend/shared.js +22 -6
  74. package/dist/frontend/shared.js.map +1 -1
  75. package/dist/frontend/ts7/checker.js +1 -1
  76. package/dist/frontend/ts7/checker.js.map +1 -1
  77. package/dist/frontend/types.d.ts +25 -1
  78. package/dist/frontend/types.js +121 -7
  79. package/dist/frontend/types.js.map +1 -1
  80. package/dist/index.d.ts +22 -0
  81. package/dist/index.js +256 -17
  82. package/dist/index.js.map +1 -1
  83. package/dist/ir/nodes.d.ts +86 -1
  84. package/dist/ir/nodes.js +183 -0
  85. package/dist/ir/nodes.js.map +1 -1
  86. package/dist/ir/validate.js +89 -6
  87. package/dist/ir/validate.js.map +1 -1
  88. package/dist/library/profile.d.ts +52 -0
  89. package/dist/library/profile.js +232 -0
  90. package/dist/library/profile.js.map +1 -0
  91. package/package.json +2 -2
  92. package/surface-manifest.json +67 -2
@@ -17,10 +17,10 @@ import * as ts from "../ts7/adapter.js";
17
17
  import { anyOpRequiresDynamicDiag, blockedBindingUseDiag, checkerPanicDiag, isCheckerPanic, genericSignatureTypeDiag, indexSignatureTypeDiag, noLoweringDiag, recordShapeMismatchDiag, requiresDynamicApiDiag, requiresDynamicPackageDiag, requiresDynamicTypeDiag, unionMismatchDiag, unsupportedDiag, unsupportedTypeDiag, } from "../../diagnostics/diagnostic.js";
18
18
  import { arrayOf, BOOL, canAdaptDynFuncTo, canConvertToDyn, canCrossIslandBoundary, canExitIslandToType, canMarshalTypedFuncIntoIsland, DYN, F64, isJsonSafeType, isUndefinedArmedUnion, isUnitType, JSVAL, RUNTIME_ERROR_CLASSES, STRING, typeEquals, UNDEFINED_T, VOID } from "../../ir/nodes.js";
19
19
  import { provenanceActive } from "../provenance-registry.js";
20
- import { ambientDtsPath, canonicalBuiltinModule, cjsExportAssignmentOf, cjsExportDiscardReason, fallbackDtsPath, isCjsExportTableLiteral, isJsSourceFile, isNodeEsmFile, isNodeTypesPath, locOf, overridesDtsPath, requireSpecOf, resolveImport, workspacePackageOfPath, } from "../program.js";
20
+ import { ambientDtsPath, canonicalBuiltinModule, cjsExportAssignmentOf, cjsExportDiscardReason, fallbackDtsPath, isCjsExportTableLiteral, isJsSourceFile, isNodeEsmFile, isNodeTypesPath, locOf, overridesDtsPath, npmStaticDepSf7, requireSpecOf, resolveImport, workspacePackageOfPath, } from "../program.js";
21
21
  import { containsRecord, containsUnion, formatIrType, ISLAND_AMBIENT_TYPES, isUnitOnlyTsType, mapType, ShapeRegistry, typeKey, UnionRegistry, withUndefinedArm as withUndefinedArmCanonical, } from "../types.js";
22
22
  import { boundaryIntoIslandMsg, boundaryOutOfIslandMsg, builtinConstLit, builtinModuleConstOf, builtinFenceHintOf, builtinModuleFnOf, stdlibMemberFence, isStdlibMember, isStdlibSymbol, isStdlibGlobal, stdlibGlobalMember, nodeTypesOnlySymbol } from "./surfaces.js";
23
- import { splitFiles, collectProgram, collectNpmImports, collectJsonImports, moduleArtifacts, collectGlobals, declSymbolOf, lowerFileInit, lowerDefaultExport, buildMain, appendDynamicImportModules } from "./lower-modules.js";
23
+ import { splitFiles, collectProgram, collectNpmImports, collectJsonImports, moduleArtifacts, collectGlobals, declSymbolOf, defaultExportSymbolOf, lowerFileInit, lowerDefaultExport, buildMain, appendDynamicImportModules } from "./lower-modules.js";
24
24
  import { registerBuiltinErrorClasses, registerBuiltinEmitterClass, registerBuiltinStreamClasses, builtinErrorInfoOf, builtinEmitterInfoOf, builtinStreamInfoOf, analyzeClassDecoration, classIteratorDrainCall, classIteratorNextCall, classIteratorOf, classIteratorOpenCall, classIteratorRestDrainCall, classMemberNameOf, classValueRef, collectClassShape, exactClassOfReceiver, collectClassShapeInner, ctorAbiEquals, findMethodOn, findStaticOn, findGenericMethodOn, findGenericStaticOn, genericClassInstanceType, isSubclassOf, inHierarchy, overrideBelow, staticShadowBelow, upcastTo, lowerClassMembers, lowerClassCtor, lowerClassExpression, lowerClassExpressionInfo, lowerClassMethodMember, lowerClassValueProperty, lowerStaticMethod, throwingSetterFn, fieldInitStmts, lowerStaticFieldInits, lowerStaticFieldRead, lowerDerivedCtorBody, superCallStmt, lowerSuperMethodCall, superThisRef, lowerSuperAccessorRead, lowerSuperAccessorWrite, inheritsBuiltinErrorCtor, inheritsBuiltinEmitterCtor, errorMessageArg, lowerNew, accessorCall } from "./lower-classes.js";
25
25
  import { mixinCallClassInfoOf, mixinIntersectionInstanceType } from "./lower-mixins.js";
26
26
  import { bindingNeverReassigned, bodyReadsArguments, isThisParameter, paramShape, paramShapes, checkDefaultParamBodyType, completeArgs, wrappedUndefined, undefinedArgFor, requireExactArityValue, bodyReturnType, declaredReturnType, collectSignature, collectSignatureInner, collectGenericSignature, genericFnOf, lowerGenericCall, lowerGenericFnValue, inferTypeParamBindings, lowerGenericInstance, lowerCall, lowerTimersMemberCall, lowerPromiseMethodCall, lowerFilterNarrowCall, isTopLevelFnSymbol, lowerNestedFunctionDecl, lambdaSignature, lowerLambda, lowerFunction } from "./lower-calls.js";
@@ -148,7 +148,7 @@ export function lowerToIr(program, entry, moduleOrder, options = {}) {
148
148
  const dynamic = options.dynamic ?? false;
149
149
  const targetPlatform = options.targetPlatform ?? process.platform;
150
150
  const startupCrash = options.startupCrash ?? null;
151
- const reachable = new Lowerer(program, entry, moduleOrder, dynamic, { targetPlatform }).discover();
151
+ const reachable = new Lowerer(program, entry, moduleOrder, dynamic, { targetPlatform }).discover(options.libRoots);
152
152
  const emit = new Lowerer(program, entry, moduleOrder, dynamic, { reachable, targetPlatform, startupCrash });
153
153
  const result = emit.run();
154
154
  if (options.coverage !== true)
@@ -568,6 +568,12 @@ export class Lowerer {
568
568
  /** Synthetic URLSearchParams.forEach loop functions, interned per
569
569
  * callback arity/return — Map's pattern over the sp index walk. */
570
570
  spHofHelpers = new Map();
571
+ /** The primitive-constructor VALUES (`String`/`Number`/`Boolean` as
572
+ * bare identifiers — CLI option tables store and compare them): one
573
+ * synthesized coercion function per constructor per program, interned
574
+ * here by name so every reference is the SAME zero-capture closure and
575
+ * `opt.type === String` is JS identity (see primitiveCtorClosure). */
576
+ primitiveCtorFns = new Map();
571
577
  /** Optional-chain lowering state. While a chain body lowers, the guarded
572
578
  * receiver NODE reads as a chainRecv (typed by the narrowed arm) instead
573
579
  * of re-lowering, its checker type reads non-nullish (typeOf), and the
@@ -918,6 +924,16 @@ export class Lowerer {
918
924
  this.flushDeferred(symbol);
919
925
  return symbol;
920
926
  }
927
+ // DEFAULT-SNAPSHOT storage lives on the EXPORTER'S default alias
928
+ // symbol (`export default someLet` — collectGlobals registers the
929
+ // Node-semantics snapshot there). getAliasedSymbol would resolve
930
+ // PAST it to the live let; walk the default-import hops and stop at
931
+ // the first default symbol carrying storage instead.
932
+ const snap = this.defaultSnapshotSymbolOf(symbol);
933
+ if (snap) {
934
+ this.flushDeferred(snap);
935
+ return snap;
936
+ }
921
937
  symbol = this.checker.getAliasedSymbol(symbol);
922
938
  }
923
939
  // CommonJS export plumbing: a binding that resolved to a PROPERTY of a
@@ -933,6 +949,51 @@ export class Lowerer {
933
949
  this.flushDeferred(symbol);
934
950
  return symbol;
935
951
  }
952
+ /** The default-snapshot storage symbol a DEFAULT-import alias chain
953
+ * lands on, or null. A mutable entity-name default (`export default
954
+ * someLet`) registers its Node-semantics snapshot global under the
955
+ * exporter's default ALIAS symbol; the checker's getAliasedSymbol
956
+ * resolves through that symbol to the live let, so this walk follows
957
+ * the default hops syntactically — default import clauses, `{ default
958
+ * as x }` specifiers, `export { default } from` re-exports — and stops
959
+ * at the first default symbol carrying registered storage. Local
960
+ * `export { x as default }` specifiers (no module specifier) are LIVE
961
+ * bindings in Node and fall through to ordinary alias resolution. */
962
+ defaultSnapshotSymbolOf(alias) {
963
+ let sym = alias;
964
+ for (let hop = 0; sym !== undefined && hop < 32; hop++) {
965
+ if (hop > 0 && sym.flags & ts.SymbolFlags.Alias && this.globalsBySymbol.has(sym))
966
+ return sym;
967
+ const d = this.checker
968
+ .declarationsOf(sym)
969
+ .find((x) => ts.isImportClause(x) || ts.isImportSpecifier(x) || ts.isExportSpecifier(x));
970
+ let spec;
971
+ let name;
972
+ if (d && ts.isImportClause(d) && ts.isImportDeclaration(d.parent)) {
973
+ spec = d.parent.moduleSpecifier;
974
+ name = "default";
975
+ }
976
+ else if (d && ts.isImportSpecifier(d)) {
977
+ const idecl = d.parent.parent.parent;
978
+ if (ts.isImportDeclaration(idecl))
979
+ spec = idecl.moduleSpecifier;
980
+ name = (d.propertyName ?? d.name).text;
981
+ }
982
+ else if (d && ts.isExportSpecifier(d)) {
983
+ const edecl = d.parent.parent;
984
+ if (ts.isExportDeclaration(edecl))
985
+ spec = edecl.moduleSpecifier;
986
+ name = (d.propertyName ?? d.name).text;
987
+ }
988
+ if (d === undefined || spec === undefined || !ts.isStringLiteral(spec) || name !== "default")
989
+ return null;
990
+ const dep = resolveImport(this.program, d.getSourceFile(), spec.text);
991
+ if (!dep)
992
+ return null;
993
+ sym = defaultExportSymbolOf(this, dep) ?? undefined;
994
+ }
995
+ return null;
996
+ }
936
997
  /** A module's CJS export-table member symbol by NAME (the checker's
937
998
  * module-symbol exports map — present in tsgo even where no expando
938
999
  * property symbol exists at the attachment/use sites). */
@@ -1018,9 +1079,13 @@ export class Lowerer {
1018
1079
  return null;
1019
1080
  }
1020
1081
  const spec = importDecl.moduleSpecifier.text;
1021
- if (!isRelativeSpecifier(spec))
1022
- return null;
1023
- const dep = resolveImport(this.program, importDecl.getSourceFile(), spec);
1082
+ // Bare specifiers resolve only for opted-in --npm-static packages —
1083
+ // their CJS entries take the same default-binding interop as a
1084
+ // relative require; every other bare import answers null and keeps
1085
+ // its own machinery.
1086
+ const dep = isRelativeSpecifier(spec)
1087
+ ? resolveImport(this.program, importDecl.getSourceFile(), spec)
1088
+ : npmStaticDepSf7(this.program, importDecl.getSourceFile(), spec);
1024
1089
  if (!dep || !isJsSourceFile(dep) || isNodeEsmFile(dep))
1025
1090
  return null;
1026
1091
  return dep;
@@ -1376,6 +1441,20 @@ export class Lowerer {
1376
1441
  // the emitted program references, flushing deferred class diagnostics
1377
1442
  // that a reached type makes relevant.
1378
1443
  const artifacts = this.moduleArtifacts(functions);
1444
+ // Types still naming a class that never REGISTERED after retention's
1445
+ // flush (JS graphs whose class fences deferred to runtime — the
1446
+ // sentence-walker's path params, printer tables whose func-typed
1447
+ // fields spell the fenced class): the emitter would name a struct
1448
+ // that does not exist — the compile-C escape family. No instance of
1449
+ // such a class can ever exist (every construction site fenced), so
1450
+ // the slots are inert by construction: rewrite each to the f64 dummy
1451
+ // placeholder (boxNewC's uncollected-class stance, applied to unboxed
1452
+ // slots), uniformly across params/locals/globals/fields/body types so
1453
+ // every producer and consumer agrees. Programs with no unregistered
1454
+ // reference are untouched — byte-stability holds.
1455
+ if (this.diags.length === 0) {
1456
+ this.sanitizeUnregisteredClassTypes([functions, this.globalsList, artifacts.classes, artifacts.records, artifacts.unions]);
1457
+ }
1379
1458
  const module = this.diags.length > 0
1380
1459
  ? null
1381
1460
  : {
@@ -1400,6 +1479,45 @@ export class Lowerer {
1400
1479
  ...(this.npmLazyTraps ? { npmLazyTraps: this.npmLazyTraps } : {}),
1401
1480
  };
1402
1481
  }
1482
+ /** The unregistered-class type sweep (run()'s last step before the
1483
+ * module assembles): every `{kind:"object"}` TYPE naming a class with
1484
+ * no registered ClassInfo is rewritten IN PLACE to the f64 dummy.
1485
+ * classval types are exempt (they emit the class-independent
1486
+ * `ScrClassObj *` — inert-but-valid storage, the validator's own
1487
+ * stance), and only type objects rewrite — node-level classNames
1488
+ * (`new`, upcasts) cannot reach here (their lowerings fence without a
1489
+ * registered class), so the validator still backstops those. */
1490
+ sanitizeUnregisteredClassTypes(roots) {
1491
+ const isUnregisteredObjectType = (v) => typeof v === "object" && v !== null &&
1492
+ v.kind === "object" &&
1493
+ typeof v.className === "string" &&
1494
+ !this.classes.has(v.className);
1495
+ const sweep = (node) => {
1496
+ if (node === null || typeof node !== "object")
1497
+ return;
1498
+ if (Array.isArray(node)) {
1499
+ node.forEach((item, i) => {
1500
+ if (isUnregisteredObjectType(item))
1501
+ node[i] = F64;
1502
+ else
1503
+ sweep(item);
1504
+ });
1505
+ return;
1506
+ }
1507
+ const rec = node;
1508
+ for (const key of Object.keys(rec)) {
1509
+ if (key === "loc")
1510
+ continue;
1511
+ const v = rec[key];
1512
+ if (isUnregisteredObjectType(v))
1513
+ rec[key] = F64;
1514
+ else
1515
+ sweep(v);
1516
+ }
1517
+ };
1518
+ for (const root of roots)
1519
+ sweep(root);
1520
+ }
1403
1521
  /** True when `t` (recursively) names a class instance type with no
1404
1522
  * registered ClassInfo — the shape of a JS class whose collection fenced.
1405
1523
  * Used by run()'s global pruning; shapes/unions recurse with a seen-set
@@ -1461,7 +1579,7 @@ export class Lowerer {
1461
1579
  * demand-driven) and lifted lambdas lower inline with their enclosing
1462
1580
  * body; both fire edges through the same hooks and are not units
1463
1581
  * themselves. */
1464
- discover() {
1582
+ discover(extraRoots) {
1465
1583
  const parts = this.splitFiles();
1466
1584
  this.collectProgram(parts);
1467
1585
  // Decorated classes analyze post-collection here too: the %init seeds
@@ -1569,6 +1687,12 @@ export class Lowerer {
1569
1687
  this.lowerFileInit(fp.sf, fp.topStmts, this.initNameOf.get(fp.sf));
1570
1688
  drainInstances();
1571
1689
  });
1690
+ // LIBRARY mode's extra reachability roots (LowerOptions.libRoots): the
1691
+ // profile-mapped exports are called from outside the graph, so they
1692
+ // seed the worklist beside the init bodies. Unknown names are inert
1693
+ // (the export-map resolution reports them as SC4002 later).
1694
+ for (const root of extraRoots ?? [])
1695
+ this.onEdge?.(root);
1572
1696
  while (queue.length > 0) {
1573
1697
  // A body-level poison outside the per-statement catches (a fenced
1574
1698
  // constructor/method parameter default lowered by declareParams):
@@ -2459,9 +2583,15 @@ export class Lowerer {
2459
2583
  }
2460
2584
  if (expected.kind === "array" && expr.type.kind === "array" && expected.elem.kind !== "jsval") {
2461
2585
  const helper = this.arrayWidthHelper(expr.type, expected, expr.loc);
2462
- if (!helper)
2586
+ if (helper)
2587
+ return { kind: "call", callee: helper, args: [expr], type: expected, loc: expr.loc };
2588
+ // The EMPTY-array lift (widthLiftPlan's emptyArr rule), top-level:
2589
+ // `cmd.aliases` typed `(null | undefined)[]` (an `aliases: []`
2590
+ // table) flowing into a `string[]` slot.
2591
+ const lift = this.widthLiftPlan(expr.type, expected);
2592
+ if (lift?.how !== "emptyArr")
2463
2593
  return null;
2464
- return { kind: "call", callee: helper, args: [expr], type: expected, loc: expr.loc };
2594
+ return this.applyWidthLift(lift, expr, expected, expr.loc);
2465
2595
  }
2466
2596
  // A TUPLE flowing into an array slot (`const NAMES = [...] as const`
2467
2597
  // assigned to a `readonly T[]` — the const-table pattern): TS erases
@@ -2547,10 +2677,39 @@ export class Lowerer {
2547
2677
  return this.recordToClassPlan(src.shapeId, dst.className) !== null ? { how: "clsWidth" } : null;
2548
2678
  }
2549
2679
  if (dst.kind === "array" && src.kind === "array") {
2550
- return this.widthLiftPlan(src.elem, dst.elem) !== null ? { how: "arr" } : null;
2680
+ if (this.widthLiftPlan(src.elem, dst.elem) !== null)
2681
+ return { how: "arr" };
2682
+ // The EMPTY-array lift: a unit-only element type (`readonly []`
2683
+ // mapped as the unit-element array, `(null | undefined)[]`) has no
2684
+ // per-element conversion into a data element — but the only value
2685
+ // such a slot honestly holds in the width family is EMPTY, so the
2686
+ // lift is a fresh empty array of the target type, guarded by a
2687
+ // runtime non-empty trap (the checked-extraction stance).
2688
+ if (this.unitOnlyElem(src.elem) && dst.elem.kind !== "jsval" && !this.unitOnlyElem(dst.elem)) {
2689
+ return { how: "emptyArr" };
2690
+ }
2691
+ return null;
2692
+ }
2693
+ // A TUPLE flowing into an array FIELD/ELEMENT (`aliases: ["ls"]` into
2694
+ // an `aliases: string[]` slot): per-position lifts, the top-level
2695
+ // tuple-into-array coercion applied recursively.
2696
+ if (dst.kind === "array" && src.kind === "record" && dst.elem.kind !== "jsval") {
2697
+ const from = this.shapes.get(src.shapeId);
2698
+ if (from?.tuple && from.fields.every((f) => this.widthLiftPlan(f.type, dst.elem) !== null)) {
2699
+ return { how: "tupleArr" };
2700
+ }
2701
+ return null;
2551
2702
  }
2552
2703
  return null;
2553
2704
  }
2705
+ /** True for the unit-only element types (`(null | undefined)[]`, the
2706
+ * `readonly []` mapping): a union whose every arm is a unit. */
2707
+ unitOnlyElem(t) {
2708
+ if (t.kind !== "union")
2709
+ return false;
2710
+ const def = this.unions.get(t.unionId);
2711
+ return def !== undefined && def.arms.every((a) => isUnitType(a));
2712
+ }
2554
2713
  /** The build side of widthLiftPlan: the IrExpr converting `value` into
2555
2714
  * `dst` under a plan the caller validated. Interns whatever helpers the
2556
2715
  * lift needs (planned first, so the interns cannot fail — a failure here
@@ -2597,6 +2756,20 @@ export class Lowerer {
2597
2756
  throw new Error("lowerer bug: planned arr lift failed to intern");
2598
2757
  return { kind: "call", callee: helper, args: [value], type: dst, loc };
2599
2758
  }
2759
+ case "tupleArr": {
2760
+ if (dst.kind !== "array" || value.type.kind !== "record")
2761
+ throw new Error("lowerer bug: tupleArr lift shape");
2762
+ const helper = this.tupleArrayWidthHelper(value.type.shapeId, dst, loc);
2763
+ if (!helper)
2764
+ throw new Error("lowerer bug: planned tupleArr lift failed to intern");
2765
+ return { kind: "call", callee: helper, args: [value], type: dst, loc };
2766
+ }
2767
+ case "emptyArr": {
2768
+ if (dst.kind !== "array" || value.type.kind !== "array")
2769
+ throw new Error("lowerer bug: emptyArr lift shape");
2770
+ const helper = this.emptyArrayLiftHelper(value.type, dst, loc);
2771
+ return { kind: "call", callee: helper, args: [value], type: dst, loc };
2772
+ }
2600
2773
  case "objWidth": {
2601
2774
  if (dst.kind !== "record" || value.type.kind !== "object")
2602
2775
  throw new Error("lowerer bug: objWidth lift shape");
@@ -2660,10 +2833,19 @@ export class Lowerer {
2660
2833
  if (!ff) {
2661
2834
  // A target field MISSING on the source: legal exactly when it is
2662
2835
  // optional-flavored (an undefined-armed union) — the unset field
2663
- // IS the undefined arm, the same rule literal completion applies.
2664
- // Never for tuples: a completed position would change .length
2665
- // and JSON where Node keeps the source arity.
2666
- if (from.tuple || tf.type.kind !== "union")
2836
+ // IS the undefined arm, the same rule literal completion applies
2837
+ // — or 'unknown' (a dyn slot holds the DOM undefined, exactly
2838
+ // the absent-property read: the options-record call shape
2839
+ // against `{ plugins: unknown, ... }`). Never for tuples: a
2840
+ // completed position would change .length and JSON where Node
2841
+ // keeps the source arity.
2842
+ if (from.tuple)
2843
+ return null;
2844
+ if (tf.type.kind === "dyn") {
2845
+ plan.set(tf.name, { absentDyn: true });
2846
+ continue;
2847
+ }
2848
+ if (tf.type.kind !== "union")
2667
2849
  return null;
2668
2850
  const def = this.unions.get(tf.type.unionId);
2669
2851
  const utag = def ? def.arms.findIndex((a) => a.kind === "undefinedT") : -1;
@@ -2717,6 +2899,11 @@ export class Lowerer {
2717
2899
  kind: "recordLit",
2718
2900
  fields: to.fields.map((f) => {
2719
2901
  const lift = plan.get(f.name);
2902
+ if ("absentDyn" in lift) {
2903
+ // The unset 'unknown' field: the DOM undefined — exactly
2904
+ // the absent-property read's answer.
2905
+ return { name: f.name, value: dynUndefinedExpr(loc) };
2906
+ }
2720
2907
  if ("absent" in lift) {
2721
2908
  if (f.type.kind !== "union")
2722
2909
  throw new Error("lowerer bug: absent lift against a non-union field");
@@ -2796,6 +2983,57 @@ export class Lowerer {
2796
2983
  });
2797
2984
  return name;
2798
2985
  }
2986
+ /** Interned `%arr.empty.<n>(a)` — the EMPTY-array lift's build side: a
2987
+ * unit-only-element array reshapes into any data-element array by
2988
+ * answering a FRESH empty array, after a runtime non-empty trap (a
2989
+ * genuinely inhabited `(null | undefined)[]` cannot reshape — the
2990
+ * catchable-TypeError stance every checked extraction takes). */
2991
+ emptyArrayLiftHelper(fromT, toT, loc) {
2992
+ const key = `emptyarr:${typeKey(fromT.elem)}:${typeKey(toT.elem)}`;
2993
+ const existing = this.widthHelpers.get(key);
2994
+ if (existing)
2995
+ return existing;
2996
+ const name = `%arr.empty.${this.widthHelpers.size}`;
2997
+ this.widthHelpers.set(key, name);
2998
+ const a = { kind: "varRef", localId: "a.0", type: fromT, loc };
2999
+ this.liftedFns.push({
3000
+ name,
3001
+ params: [{ localId: "a.0", name: "a", type: fromT }],
3002
+ returnType: toT,
3003
+ locals: [{ id: "a.0", name: "a", type: fromT, mutable: true }],
3004
+ body: [
3005
+ {
3006
+ kind: "if",
3007
+ cond: {
3008
+ kind: "bin",
3009
+ op: "!==",
3010
+ left: { kind: "arrIntrinsic", method: "length", receiver: a, args: [], type: F64, loc },
3011
+ right: { kind: "numLit", value: 0, type: F64, loc },
3012
+ type: BOOL,
3013
+ loc,
3014
+ },
3015
+ then: [
3016
+ {
3017
+ kind: "throw",
3018
+ value: {
3019
+ kind: "libCall",
3020
+ fn: "error.new",
3021
+ args: [{ kind: "strLit", value: `expected ${this.fmt(toT)} (a non-empty ${this.fmt(fromT)} has no elements the target can hold)`, type: STRING, loc }],
3022
+ type: { kind: "object", className: "%TypeError" },
3023
+ loc,
3024
+ },
3025
+ loc,
3026
+ },
3027
+ ],
3028
+ else_: [],
3029
+ loc,
3030
+ },
3031
+ { kind: "return", value: { kind: "arrayLit", elems: [], type: toT, loc }, loc },
3032
+ ],
3033
+ loc,
3034
+ });
3035
+ return name;
3036
+ }
2799
3037
  arrayWidthHelper(fromT, toT, loc) {
2800
3038
  const fromElem = fromT.elem;
2801
3039
  const toElem = toT.elem;
@@ -3932,6 +4170,59 @@ export class Lowerer {
3932
4170
  });
3933
4171
  return name;
3934
4172
  }
4173
+ /** The DEFERRED-INIT field read (`stream!: T` assigned past the
4174
+ * constructor's top level — the slot is `T | undefined`): interned
4175
+ * `%deferred.read.<n>(u)` extracting the declared type. SCALAR arms
4176
+ * whose JS-undefined behavior a unit default reproduces read that
4177
+ * default — bool false (conditions are exact: undefined and false are
4178
+ * both falsy; only printing/strict-equality could tell) and f64 NaN
4179
+ * (arithmetic and conditions exact) — while string and REF arms keep
4180
+ * the checked-extraction TRAP: JS itself TypeErrors the first member
4181
+ * use of such an undefined, so the catchable TypeError at the read is
4182
+ * the same failure, named earlier (SEMANTICS.md). */
4183
+ deferredReadHelper(fromId, target, loc) {
4184
+ if (target.kind !== "bool" && target.kind !== "f64") {
4185
+ return this.narrowedArmHelper(fromId, target, loc);
4186
+ }
4187
+ const from = this.unions.get(fromId);
4188
+ const tag = this.armTag(fromId, target);
4189
+ const utag = from ? from.arms.findIndex((a) => a.kind === "undefinedT") : -1;
4190
+ if (!from || tag < 0 || utag < 0)
4191
+ return null;
4192
+ const key = `deferred:${fromId}:${tag}`;
4193
+ const existing = this.narrowHelpers.get(key);
4194
+ if (existing)
4195
+ return existing;
4196
+ const name = `%deferred.read.${this.narrowHelpers.size}`;
4197
+ this.narrowHelpers.set(key, name);
4198
+ const fromT = { kind: "union", unionId: fromId };
4199
+ const u = { kind: "varRef", localId: "u.0", type: fromT, loc };
4200
+ const dflt = target.kind === "bool"
4201
+ ? { kind: "boolLit", value: false, type: BOOL, loc }
4202
+ : { kind: "numLit", value: NaN, type: F64, loc };
4203
+ this.liftedFns.push({
4204
+ name,
4205
+ params: [{ localId: "u.0", name: "u", type: fromT }],
4206
+ returnType: target,
4207
+ locals: [{ id: "u.0", name: "u", type: fromT, mutable: true }],
4208
+ body: [
4209
+ {
4210
+ kind: "if",
4211
+ cond: { kind: "unionIsTag", unionId: fromId, tag: utag, negated: false, value: u, type: BOOL, loc },
4212
+ then: [{ kind: "return", value: dflt, loc }],
4213
+ else_: null,
4214
+ loc,
4215
+ },
4216
+ {
4217
+ kind: "return",
4218
+ value: { kind: "unionNarrow", unionId: fromId, tag, value: u, type: target, loc },
4219
+ loc,
4220
+ },
4221
+ ],
4222
+ loc,
4223
+ });
4224
+ return name;
4225
+ }
3935
4226
  /** True when a static value can become ONE island value: jsval itself,
3936
4227
  * anything boundary-safe (the deep JSON marshal), a record whose fields
3937
4228
  * all can (built as an island OBJECT literal, field by field), or an
@@ -4851,7 +5142,7 @@ export class Lowerer {
4851
5142
  // unresolvable ones never collected, so they skip here too.
4852
5143
  const baseName = ts.isMethodDeclaration(fnLike)
4853
5144
  ? classMemberNameOf(this, fnLike.name)
4854
- : ts.isIdentifier(fnLike.name)
5145
+ : ts.isIdentifier(fnLike.name) || ts.isPrivateIdentifier(fnLike.name)
4855
5146
  ? fnLike.name.text
4856
5147
  : null;
4857
5148
  if (baseName === null)
@@ -5192,6 +5483,27 @@ export class Lowerer {
5192
5483
  resolveThis() {
5193
5484
  return this.resolveKey(THIS_BINDING);
5194
5485
  }
5486
+ /** READ-ONLY twin of resolveLocal for PROBES (isIslandExpr): answers
5487
+ * the nearest binding entry without boxing, threading, or predeclaring.
5488
+ * resolveKey mutates capture state as a side effect, and a speculative
5489
+ * island-ness query through a context that takes no captures (a plain
5490
+ * declared function between the origin and the reference) was an ICE —
5491
+ * the REAL lowering path still resolves (and diagnoses) the reference
5492
+ * itself. */
5493
+ peekLocal(ident) {
5494
+ let symbol = this.checker.getSymbolAtLocation(ident);
5495
+ if (ident.parent && ts.isShorthandPropertyAssignment(ident.parent) && ident.parent.name === ident) {
5496
+ symbol = this.checker.getShorthandAssignmentValueSymbol(ident.parent) ?? symbol;
5497
+ }
5498
+ if (!symbol)
5499
+ return null;
5500
+ for (let depth = this.fnStack.length - 1; depth >= 0; depth--) {
5501
+ const hit = this.bindingIn(this.fnStack[depth], symbol);
5502
+ if (hit)
5503
+ return hit;
5504
+ }
5505
+ return null;
5506
+ }
5195
5507
  resolveKey(symbol, blame) {
5196
5508
  const direct = this.bindingIn(this.ctx, symbol);
5197
5509
  if (direct)
@@ -5225,6 +5537,17 @@ export class Lowerer {
5225
5537
  const ctx = this.fnStack[j];
5226
5538
  let entry = ctx.captureBySymbol.get(symbol);
5227
5539
  if (!entry) {
5540
+ // A context that takes NO captures (a plain declared function —
5541
+ // monomorphized/implicit instances lower this way) cannot carry
5542
+ // the binding through: the shape is a module binding whose only
5543
+ // storage is the init function's LOCAL (a typed-but-unmappable
5544
+ // const — the file-scope `new Map()` ledger idiom) read from
5545
+ // inside a nested instance. Fence it — in JS the statement
5546
+ // defers to its runtime trap like every collection failure;
5547
+ // asserting here was an ICE on ordinary npm-static JS.
5548
+ if (ctx.captures === null) {
5549
+ this.unsupported("SC1090", blame ?? this.checker.declarationsOf(symbol)[0] ?? this.entry, `the binding '${origin.name}' captured through a plain nested function (the declaration has no static storage a capture can thread — bind the value through a typed const, or read it in the declaring scope)`);
5550
+ }
5228
5551
  const count = ctx.localCounters.get(origin.name) ?? 0;
5229
5552
  ctx.localCounters.set(origin.name, count + 1);
5230
5553
  entry = {
@@ -5239,9 +5562,6 @@ export class Lowerer {
5239
5562
  };
5240
5563
  ctx.locals.push(entry);
5241
5564
  ctx.captureBySymbol.set(symbol, entry);
5242
- if (ctx.captures === null) {
5243
- throw new Error("lowerer bug: capture threading through a non-lifted function");
5244
- }
5245
5565
  ctx.captures.push({ localId: entry.id, name: entry.name, type: entry.type });
5246
5566
  ctx.captureSources.push(parentEntry.id);
5247
5567
  }
@@ -5947,7 +6267,7 @@ export class Lowerer {
5947
6267
  if (module !== null)
5948
6268
  return { module, member: access.name.text };
5949
6269
  // The RE-EXPORT FACADE's namespace spelling: `import * as assert from
5950
- // "./facade.js"` over `export { ok } from "node:assert"` (prettier's
6270
+ // "./facade.js"` over `export { ok } from "node:assert"` (the formatter idiom's
5951
6271
  // universal/assert). The member's symbol is the facade's
5952
6272
  // ExportSpecifier, and builtinImportOf's alias chase answers the
5953
6273
  // builtin's own module/member — the same tables as a direct import;