@scriptc/compiler 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +145 -9
  2. package/dist/backend/cc.d.ts +7 -0
  3. package/dist/backend/cc.js +1 -0
  4. package/dist/backend/cc.js.map +1 -1
  5. package/dist/backend/emission/emit-exprs.js +35 -0
  6. package/dist/backend/emission/emit-exprs.js.map +1 -1
  7. package/dist/backend/emission/emit-shapes.js +1 -0
  8. package/dist/backend/emission/emit-shapes.js.map +1 -1
  9. package/dist/backend/emission/emitter.js +15 -3
  10. package/dist/backend/emission/emitter.js.map +1 -1
  11. package/dist/backend/llvm/emitter.js +65 -11
  12. package/dist/backend/llvm/emitter.js.map +1 -1
  13. package/dist/backend/llvm/shapes.js +1 -0
  14. package/dist/backend/llvm/shapes.js.map +1 -1
  15. package/dist/coverage/surface-manifest.d.ts +22 -0
  16. package/dist/coverage/surface-manifest.js +295 -0
  17. package/dist/coverage/surface-manifest.js.map +1 -0
  18. package/dist/diagnostics/diagnostic.d.ts +16 -0
  19. package/dist/diagnostics/diagnostic.js +26 -2
  20. package/dist/diagnostics/diagnostic.js.map +1 -1
  21. package/dist/frontend/lowering/lib-boundary.js +76 -6
  22. package/dist/frontend/lowering/lib-boundary.js.map +1 -1
  23. package/dist/frontend/lowering/lower-builtins.d.ts +37 -4
  24. package/dist/frontend/lowering/lower-builtins.js +240 -6
  25. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  26. package/dist/frontend/lowering/lower-calls.js +122 -32
  27. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  28. package/dist/frontend/lowering/lower-classes.d.ts +34 -2
  29. package/dist/frontend/lowering/lower-classes.js +256 -33
  30. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  31. package/dist/frontend/lowering/lower-containers.js +25 -3
  32. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  33. package/dist/frontend/lowering/lower-exprs.d.ts +10 -0
  34. package/dist/frontend/lowering/lower-exprs.js +410 -9
  35. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  36. package/dist/frontend/lowering/lower-inspect.d.ts +16 -1
  37. package/dist/frontend/lowering/lower-inspect.js +132 -5
  38. package/dist/frontend/lowering/lower-inspect.js.map +1 -1
  39. package/dist/frontend/lowering/lower-island.js +24 -3
  40. package/dist/frontend/lowering/lower-island.js.map +1 -1
  41. package/dist/frontend/lowering/lower-modules.d.ts +6 -5
  42. package/dist/frontend/lowering/lower-modules.js +56 -18
  43. package/dist/frontend/lowering/lower-modules.js.map +1 -1
  44. package/dist/frontend/lowering/lower-stmts.js +56 -0
  45. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  46. package/dist/frontend/lowering/lowerer.d.ts +57 -1
  47. package/dist/frontend/lowering/lowerer.js +291 -10
  48. package/dist/frontend/lowering/lowerer.js.map +1 -1
  49. package/dist/frontend/lowering/surfaces.d.ts +9 -2
  50. package/dist/frontend/lowering/surfaces.js +44 -7
  51. package/dist/frontend/lowering/surfaces.js.map +1 -1
  52. package/dist/frontend/npm.js +2 -2
  53. package/dist/frontend/npm.js.map +1 -1
  54. package/dist/frontend/program.js +59 -8
  55. package/dist/frontend/program.js.map +1 -1
  56. package/dist/frontend/resolve.js +120 -5
  57. package/dist/frontend/resolve.js.map +1 -1
  58. package/dist/frontend/shared.d.ts +8 -2
  59. package/dist/frontend/shared.js +22 -6
  60. package/dist/frontend/shared.js.map +1 -1
  61. package/dist/frontend/ts7/checker.js +1 -1
  62. package/dist/frontend/ts7/checker.js.map +1 -1
  63. package/dist/frontend/types.d.ts +25 -1
  64. package/dist/frontend/types.js +121 -7
  65. package/dist/frontend/types.js.map +1 -1
  66. package/dist/index.d.ts +1 -0
  67. package/dist/index.js +5 -1
  68. package/dist/index.js.map +1 -1
  69. package/dist/ir/nodes.d.ts +43 -1
  70. package/dist/ir/nodes.js +41 -0
  71. package/dist/ir/nodes.js.map +1 -1
  72. package/dist/ir/validate.js +38 -6
  73. package/dist/ir/validate.js.map +1 -1
  74. package/package.json +6 -4
  75. package/surface-manifest.json +2117 -0
@@ -522,3 +522,13 @@ export declare function lowerPromiseAllTupleCall(L: Lowerer, call: ts.CallExpres
522
522
  * Restricted to side-effect-free receivers (identifier or `this`)
523
523
  * because the desugar evaluates the receiver twice. */
524
524
  export declare function lowerFieldCompound(L: Lowerer, access: ts.PropertyAccessExpression | ts.ElementAccessExpression, op: CompoundOp, rhsNode: ts.Expression | null, loc: SrcLoc): IrStmt;
525
+ /** The primitive-constructor closure (`String`/`Number`/`Boolean` as a
526
+ * VALUE): interns one synthesized module function per constructor —
527
+ * `%builtin.String` et al. — and returns the zero-capture closure over it.
528
+ * The body IS the string-coercion the type mapping promised
529
+ * (`(value: string) => primitive`): String is identity, Number the ECMA
530
+ * StringToNumber (num.fromString — the same runtime call the direct
531
+ * `Number(s)` lowering emits), Boolean the emptiness test. Interning makes
532
+ * every reference the SAME immortal closure, so `opt.type === String`
533
+ * compares like JS function identity. */
534
+ export declare function primitiveCtorClosure(L: Lowerer, name: "String" | "Number" | "Boolean", loc: SrcLoc): IrExpr;
@@ -6,11 +6,11 @@
6
6
  * (FieldTarget). */
7
7
  import * as ts from "../ts7/adapter.js";
8
8
  import { dirname } from "node:path";
9
- import { BOOL, CAUGHT, DYN, DYN_HANDLE_KINDS, F64, JSVAL, NULL_T, REF_TRUTHY_KINDS, REGEX, RUNTIME_ERROR_CLASSES, SEARCH_PARAMS_T, STRING, UNDEFINED_T, VOID, arrayOf, canAdaptDynFuncTo, canBoxFuncIntoDyn, canDynCheckTo, isJsonSafeType, isUnitType, jsOpResultKind, shapeHasAccessorSlots, typeEquals, typeKey } from "../../ir/nodes.js";
9
+ import { BOOL, CAUGHT, DYN, DYN_HANDLE_KINDS, F64, JSVAL, NULL_T, REF_TRUTHY_KINDS, REGEX, RUNTIME_ERROR_CLASSES, SEARCH_PARAMS_T, STRING, UNDEFINED_T, VOID, arrayOf, canAdaptDynFuncTo, canBoxFuncIntoDyn, canDynCheckTo, funcOf, isJsonSafeType, isUnitType, jsOpResultKind, shapeHasAccessorSlots, typeEquals, typeKey, unionFuncSetArmsOk } from "../../ir/nodes.js";
10
10
  import { cjsClassExprWholeExportOf, cjsExportAssignmentOf, cjsExportDiscardReason, isCjsExportTableLiteral, isCjsJsFile, isJsSourceFile, isModuleExportsAccess, isNodeEsmFile, locOf } from "../program.js";
11
11
  import { ARRAY_METHODS, builtinConstLit, builtinFenceHintOf, builtinModuleConstOf, builtinModuleFnOf, ISLAND_SURFACE, isChildSurfaceMember, MAP_METHODS, NARROW_FIRST, SET_METHODS, STR_METHODS, UNSUPPORTED_EXPR, sideEffectFreeOptionValue, stdlibGlobalNameOf } from "./surfaces.js";
12
12
  import { blockedBindingUseDiag, recordShapeMismatchDiag, requiresDynamicPackageDiag, unsupportedDiag } from "../../diagnostics/diagnostic.js";
13
- import { PoisonError, jsFuncNameOf, neverTaintedJsType, own } from "./lowerer.js";
13
+ import { PoisonError, dynUndefinedExpr, jsFuncNameOf, neverTaintedJsType, own } from "./lowerer.js";
14
14
  import { lowerIndexMergeHelper, lowerNpmStaticSafeIndexRead, strCharsCall } from "./lower-containers.js";
15
15
  import { npmStaticPackageOfPath } from "../npm-static.js";
16
16
  import { fenceEnumObjectValue, lowerEnumAccess } from "./lower-enums.js";
@@ -20,7 +20,7 @@ import { lowerSocketInstanceOf } from "./lower-server.js";
20
20
  import { findGenericMethodOn, lowerStaticFieldRead } from "./lower-classes.js";
21
21
  import { bindingNeverReassigned, implicitMonoFile, lowerTaggedTemplate, objLitGenericFnInfoOf, objLitGenericFnNodeOf, requireObjLitGenericReceiver } from "./lower-calls.js";
22
22
  import { mixinFnOfCallee } from "./lower-mixins.js";
23
- import { isGenericCallableMemberType, unitOnlyUnion } from "../types.js";
23
+ import { isConstAssertionTypeNode, isGenericCallableMemberType, underConstAssertion, unitOnlyUnion } from "../types.js";
24
24
  import { lowerYield } from "./lower-generators.js";
25
25
  import { lowerStreamProperty, lowerStreamStateProperty, streamSidesOf } from "./lower-stream.js";
26
26
  /** A template piece's RAW text (String.raw's contract: escapes stay
@@ -195,7 +195,15 @@ function lowerExprInner(L, expr) {
195
195
  (() => {
196
196
  const ctxTs = L.checker.getContextualType(expr);
197
197
  const mapped = (ctxTs ? L.mapTypeOf(ctxTs) : null) ?? L.mapTypeOf(L.typeOf(expr));
198
- return mapped?.kind === "jsval";
198
+ if (mapped?.kind !== "jsval")
199
+ return false;
200
+ // The tsgo readonly-[] panic repair (see lowerArrayLiteral): an
201
+ // EMPTY array literal under a const assertion is the empty tuple —
202
+ // its `any` answer is a panicked query, not an island slot.
203
+ if (ts.isArrayLiteralExpression(expr) && expr.elements.length === 0 && underConstAssertion(expr)) {
204
+ return false;
205
+ }
206
+ return true;
199
207
  })()) {
200
208
  if (ts.isObjectLiteralExpression(expr)) {
201
209
  // Two engine literals when a CONDITIONAL spread participates
@@ -537,6 +545,20 @@ function lowerExprInner(L, expr) {
537
545
  return { kind: "numLit", value: expr.text === "NaN" ? NaN : Infinity, type: F64, loc };
538
546
  }
539
547
  }
548
+ // The primitive constructors as VALUES (`const f = String`, an
549
+ // option table's `type: Boolean` field, `opt.type === Number`): the
550
+ // interned coercion closure — one synthesized module function per
551
+ // constructor per program, so every reference is the SAME zero-
552
+ // capture closure and `===` is JS identity (the type mapping in
553
+ // types.ts pins the one concrete signature `(value: string) =>
554
+ // primitive`; direct calls `String(x)` never reach here — the call
555
+ // lowering intercepts them with the wider static coercions).
556
+ // JavaScript sources keep the identity-token path below.
557
+ if ((expr.text === "String" || expr.text === "Number" || expr.text === "Boolean") &&
558
+ !isJsSourceFile(expr.getSourceFile()) &&
559
+ L.isStdlibSymbol(L.checker.getSymbolAtLocation(expr))) {
560
+ return primitiveCtorClosure(L, expr.text, loc);
561
+ }
540
562
  // The lib fence's IDENTIFIER chokepoint: the real standard library
541
563
  // resolves names the old minimal ambient world never declared
542
564
  // (Symbol, Reflect, Infinity, Date, ...) — and the adopted
@@ -938,6 +960,22 @@ function lowerExprInner(L, expr) {
938
960
  // bound handle).
939
961
  if (L.isIslandExpr(expr.expression)) {
940
962
  const receiver = L.lowerExpr(expr.expression);
963
+ // The checker said 'any' but the VALUE lives in the DOM (`this`
964
+ // in a plain JS function — dyn.this — or a checked-dynamic local
965
+ // behind an any-typed spelling): the property read is the DOM's
966
+ // own keyed read, dyn results and dyn chains exactly like every
967
+ // checked-dynamic member access (a nullish receiver throws V8's
968
+ // catchable TypeError, exactly Node). Never a jsOp over a dyn —
969
+ // the two dynamic worlds don't share a value representation.
970
+ if (receiver.type.kind === "dyn") {
971
+ return {
972
+ kind: "dynKeyGet",
973
+ key: { kind: "strLit", value: expr.name.text, type: STRING, loc },
974
+ value: receiver,
975
+ type: DYN,
976
+ loc,
977
+ };
978
+ }
941
979
  const read = { kind: "jsOp", op: "getProp", name: expr.name.text, args: [receiver], type: JSVAL, loc };
942
980
  // A member the .d.ts DECLARES as a primitive exits eagerly to that
943
981
  // static type (`f.mediaType` on a package handle IS a string):
@@ -2835,6 +2873,17 @@ export function lowerArrayLiteral(L, expr, expected) {
2835
2873
  mapped = arrayArms[0];
2836
2874
  }
2837
2875
  }
2876
+ // An EMPTY literal under a CONST ASSERTION whose type queries panicked
2877
+ // (tsgo's readonly-[] TupleType conversion — the facade answers `any`,
2878
+ // which maps to nothing statically and to an island value under
2879
+ // --dynamic): the value is provably the empty tuple; ride the
2880
+ // unit-element array, mapType's own `[] as const` rule.
2881
+ if ((mapped === null || mapped.kind === "jsval") &&
2882
+ expr.elements.length === 0 &&
2883
+ (tsType.flags & ts.TypeFlags.Any) !== 0 &&
2884
+ underConstAssertion(expr)) {
2885
+ mapped = arrayOf(unitOnlyUnion(L.unions));
2886
+ }
2838
2887
  // A TUPLE-typed slot (`const t: [string, number] = ["a", 1]`): the
2839
2888
  // literal constructs the tuple's record shape — one positional field
2840
2889
  // per element, source order (which IS index order, so evaluation order
@@ -3301,6 +3350,102 @@ function fenceClosureProbe(L, node, slotType, attempt) {
3301
3350
  return { kind: "closure", fnName: name, captures: [], type: fieldType, loc };
3302
3351
  }
3303
3352
  }
3353
+ /** The union arm an object LITERAL inhabits when its fields must widen
3354
+ * PER FIELD into exactly one record arm — the reducer-action pattern
3355
+ * (`{ kind: "a", parsed: 1 }` into `{ kind: "a"; parsed: number | null }
3356
+ * | { kind: "b" }`). The IR shapes erased the literal types tsc
3357
+ * discriminated on, so the probe runs against the contextual union's
3358
+ * CHECKER members: every literal field must exist on the member
3359
+ * (excess-property freshness — tsc already enforced it), every arm field
3360
+ * missing from the literal must be optional-flavored (an undefined-armed
3361
+ * union, or a dyn slot — the absent-completion rule), and each present
3362
+ * field's LITERAL type must fit the member's field type — literal against
3363
+ * literal decides by VALUE (the discriminant), everything else by the
3364
+ * widened IR pair under the width-lift relation. Exactly ONE fitting arm
3365
+ * answers it; zero or several answer null and the caller keeps its
3366
+ * fences. Plain property-assignment/shorthand literals only — spreads,
3367
+ * accessors, methods, and unfoldable computed keys keep their own paths. */
3368
+ function literalUnionArmOf(L, expr, tsType, recordArms) {
3369
+ if (!tsType.isUnionType())
3370
+ return null;
3371
+ const props = [];
3372
+ for (const p of expr.properties) {
3373
+ if (ts.isPropertyAssignment(p) && !ts.isComputedPropertyName(p.name)) {
3374
+ props.push({ name: propNameText(L, p.name), node: p.initializer });
3375
+ }
3376
+ else if (ts.isShorthandPropertyAssignment(p) && ts.isIdentifier(p.name)) {
3377
+ props.push({ name: p.name.text, node: p.name });
3378
+ }
3379
+ else {
3380
+ return null;
3381
+ }
3382
+ }
3383
+ /** litT fits ftT: unions per arm; literal-vs-literal by value; unit
3384
+ * types only into their own unit; otherwise the widened IR pair must be
3385
+ * equal or width-liftable. */
3386
+ const fits = (litT, ftT) => {
3387
+ if (ftT.isUnionType())
3388
+ return ftT.getTypes().some((a) => fits(litT, a));
3389
+ if (ftT.isStringLiteralType())
3390
+ return litT.isStringLiteralType() && litT.value === ftT.value;
3391
+ if (ftT.isNumberLiteralType())
3392
+ return litT.isNumberLiteralType() && litT.value === ftT.value;
3393
+ if (ftT.flags & ts.TypeFlags.BooleanLiteral) {
3394
+ return (litT.flags & ts.TypeFlags.BooleanLiteral) !== 0 && L.checker.typeToString(litT) === L.checker.typeToString(ftT);
3395
+ }
3396
+ if (ftT.flags & ts.TypeFlags.Null)
3397
+ return (litT.flags & ts.TypeFlags.Null) !== 0;
3398
+ if (ftT.flags & ts.TypeFlags.Undefined)
3399
+ return (litT.flags & ts.TypeFlags.Undefined) !== 0;
3400
+ if (litT.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined))
3401
+ return false;
3402
+ const li = L.mapTypeOf(L.checker.getBaseTypeOfLiteralType(litT));
3403
+ const fi = L.mapTypeOf(ftT);
3404
+ if (!li || !fi)
3405
+ return false;
3406
+ return typeEquals(li, fi) || L.widthLiftPlan(li, fi) !== null;
3407
+ };
3408
+ const armShapeIds = new Set(recordArms.map((a) => a.shapeId));
3409
+ const candidates = new Set();
3410
+ for (const member of tsType.getTypes()) {
3411
+ const mMapped = L.mapTypeOf(member);
3412
+ if (mMapped?.kind !== "record" || !armShapeIds.has(mMapped.shapeId) || candidates.has(mMapped.shapeId))
3413
+ continue;
3414
+ const shape = L.shapes.get(mMapped.shapeId);
3415
+ if (!shape || shape.tuple || shape.indexValue)
3416
+ continue;
3417
+ // Excess-property freshness: every literal field must exist on the
3418
+ // member (tsc rejected the others for a fresh literal).
3419
+ if (!props.every((p) => L.checker.getPropertyOfType(member, p.name) !== undefined))
3420
+ continue;
3421
+ // Arm fields the literal leaves unset must be optional-flavored (the
3422
+ // absent-completion rule: an undefined-armed union or a dyn slot).
3423
+ const names = new Set(props.map((p) => p.name));
3424
+ const absentOk = shape.fields.every((f) => {
3425
+ if (names.has(f.name))
3426
+ return true;
3427
+ if (f.type.kind === "dyn")
3428
+ return true;
3429
+ if (f.type.kind !== "union")
3430
+ return false;
3431
+ return L.unions.get(f.type.unionId)?.arms.some((a) => a.kind === "undefinedT") ?? false;
3432
+ });
3433
+ if (!absentOk)
3434
+ continue;
3435
+ const fieldsFit = props.every((p) => {
3436
+ const sym = L.checker.getPropertyOfType(member, p.name);
3437
+ if (!sym)
3438
+ return false;
3439
+ return fits(L.typeOf(p.node), L.checker.getTypeOfSymbol(sym));
3440
+ });
3441
+ if (fieldsFit)
3442
+ candidates.add(mMapped.shapeId);
3443
+ }
3444
+ if (candidates.size !== 1)
3445
+ return null;
3446
+ const [only] = candidates;
3447
+ return recordArms.find((a) => a.shapeId === only) ?? null;
3448
+ }
3304
3449
  export function lowerObjectLiteral(L, expr) {
3305
3450
  const loc = locOf(expr);
3306
3451
  // The RUNTIME-KEYED literal (JS): a computed key that doesn't fold to a
@@ -3491,6 +3636,26 @@ export function lowerObjectLiteral(L, expr) {
3491
3636
  mapped = recordArms[0];
3492
3637
  }
3493
3638
  }
3639
+ else if (recordArms.length > 1) {
3640
+ const ownShapeId = mapped?.kind === "record" ? mapped.shapeId : null;
3641
+ // SEVERAL record arms (the reducer-action / discriminated-message
3642
+ // pattern): the literal's own inferred shape re-tags into no arm
3643
+ // — its field types widened per field (`parsed: 1` against
3644
+ // `parsed: number | null`), so the IR-level candidate probe is
3645
+ // ambiguous (a narrower arm also admits the literal by dropping
3646
+ // fields). The LITERAL types tsc checked carry the discriminant
3647
+ // the shapes erased: match the literal's fields against each
3648
+ // union member's CHECKER types (literal-vs-literal field pairs
3649
+ // decide by value — the `kind: "a"` discriminant), and when
3650
+ // exactly ONE member fits, build AS that arm — its field types
3651
+ // drive every property's coercion, exactly the single-record-arm
3652
+ // rule above. Ambiguous literals keep the SC2003 fence.
3653
+ if (ownShapeId === null || !recordArms.some((a) => a.shapeId === ownShapeId)) {
3654
+ const arm = literalUnionArmOf(L, expr, tsType, recordArms);
3655
+ if (arm)
3656
+ mapped = arm;
3657
+ }
3658
+ }
3494
3659
  }
3495
3660
  }
3496
3661
  // The CJS EXPORT-TABLE literal in VALUE position (JS): importers reach
@@ -4177,9 +4342,13 @@ export function lowerObjectLiteral(L, expr) {
4177
4342
  for (const f of shape.fields) {
4178
4343
  if (provided.has(f.name))
4179
4344
  continue;
4180
- const absent = L.wrappedUndefined(f.type, loc);
4345
+ // 'unknown' fields complete with the DOM undefined — the absent
4346
+ // property reads as undefined in Node, and a dyn slot holds
4347
+ // exactly that (the options-record call shape against
4348
+ // `{ plugins: unknown, ... }` — a JS caller the checker admits).
4349
+ const absent = L.wrappedUndefined(f.type, loc) ?? (f.type.kind === "dyn" ? dynUndefinedExpr(loc) : null);
4181
4350
  if (!absent)
4182
- throw shapeMismatch(expr); // only optional (undefined-armed) fields may be omitted
4351
+ throw shapeMismatch(expr); // only optional (undefined-armed) and 'unknown' fields may be omitted
4183
4352
  fields.push({ name: f.name, value: absent });
4184
4353
  }
4185
4354
  }
@@ -5257,6 +5426,17 @@ export function lowerTemplate(L, expr) {
5257
5426
  /** `e as T` and the old-style assertion `<T>e` — one node shape (both
5258
5427
  * carry `.type` and `.expression`), one lowering. */
5259
5428
  export function lowerAsExpression(L, expr) {
5429
+ // `[] as const` — tsgo panics computing the expression's `readonly []`
5430
+ // type (the facade's fence answers `any`), but the syntax pins the
5431
+ // value exactly: the empty tuple, ridden as the unit-element array
5432
+ // (mapType's empty-tuple rule). Lower it directly; enclosing slots
5433
+ // coerce like any other empty-array source.
5434
+ if (ts.isAsExpression(expr) &&
5435
+ isConstAssertionTypeNode(expr.type) &&
5436
+ ts.isArrayLiteralExpression(expr.expression) &&
5437
+ expr.expression.elements.length === 0) {
5438
+ return { kind: "arrayLit", elems: [], type: arrayOf(unitOnlyUnion(L.unions)), loc: locOf(expr) };
5439
+ }
5260
5440
  // `e as C` on a CATCH BINDING (`(err as Error).message`): the checked
5261
5441
  // extraction — an instanceof match extracts the payload, anything else
5262
5442
  // throws the catchable TypeError (dynCheck's trust-but-verify stance,
@@ -5635,6 +5815,22 @@ export function lowerBinary(L, expr) {
5635
5815
  const nullTest = lowerLooseNullCompare(L, expr, loc);
5636
5816
  if (nullTest)
5637
5817
  return nullTest;
5818
+ // SAME-KIND loose equality IS strict equality (the spec's ==
5819
+ // dispatches to === when both operands share a type): `typeof v ==
5820
+ // 'object'`, `n != 0`, `flag == true` all lower exactly. Mixed
5821
+ // kinds (where == coerces) keep the fence.
5822
+ {
5823
+ const negated = op === ts.SyntaxKind.ExclamationEqualsToken;
5824
+ const left = L.lowerExpr(expr.left);
5825
+ const right = L.lowerExpr(expr.right);
5826
+ if (left.type.kind === "string" && right.type.kind === "string") {
5827
+ return { kind: "strEq", negated, left, right, type: BOOL, loc };
5828
+ }
5829
+ if ((left.type.kind === "f64" && right.type.kind === "f64") ||
5830
+ (left.type.kind === "bool" && right.type.kind === "bool")) {
5831
+ return { kind: "bin", op: negated ? "!==" : "===", left, right, type: BOOL, loc };
5832
+ }
5833
+ }
5638
5834
  L.unsupported("SC1040", expr);
5639
5835
  }
5640
5836
  if (op === ts.SyntaxKind.AmpersandAmpersandToken || op === ts.SyntaxKind.BarBarToken) {
@@ -6940,6 +7136,147 @@ export function lowerInstanceOf(L, expr, loc) {
6940
7136
  }
6941
7137
  L.unsupported("SC1090", expr, "statically-decided 'instanceof' on computed operands (bind the value to a variable first)");
6942
7138
  }
7139
+ /** `#name in obj` — the ergonomic brand check. In this closed world a
7140
+ * brand is held by exactly the instances of the declaring class
7141
+ * (subclasses included — construction always runs the declaring class's
7142
+ * own initializers), so the test IS `obj instanceof <declaring class>`:
7143
+ * statically decided when the receiver's class sits at/below the
7144
+ * declarer (true) or in a disjoint subtree (false) — both folds under
7145
+ * the instanceof purity rule — and a runtime interval test when the
7146
+ * declarer sits strictly BELOW the receiver's static class (the
7147
+ * narrowing use; tsc types the true branch at the class, and reads
7148
+ * bridge through maybeNarrow's downcast exactly like instanceof). tsc
7149
+ * confines the spelling to the declaring class's body and rejects
7150
+ * primitive/unknown receivers, so Node's in-operator TypeError is
7151
+ * unreachable in compilable programs. Timing residue: JS installs
7152
+ * brands DURING construction, so a check reachable from a base
7153
+ * constructor can observe false mid-construction where this answers
7154
+ * true (SEMANTICS.md). */
7155
+ function lowerPrivateIn(L, expr, priv, loc) {
7156
+ const pname = priv.text;
7157
+ // The declaring class: the nearest enclosing class declaring the name
7158
+ // (JS scoping — an inner class's spelling shadows an outer one's).
7159
+ let classDecl = null;
7160
+ let staticBrand = false;
7161
+ for (let n = priv.parent; n; n = n.parent) {
7162
+ if (ts.isClassDeclaration(n) || ts.isClassExpression(n)) {
7163
+ const owner = n.members.find((m) => {
7164
+ const name = m.name;
7165
+ return name !== undefined && ts.isPrivateIdentifier(name) && name.text === pname;
7166
+ });
7167
+ if (owner) {
7168
+ classDecl = n;
7169
+ staticBrand =
7170
+ ts.canHaveModifiers(owner) &&
7171
+ ts.getModifiers(owner)?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword) === true;
7172
+ break;
7173
+ }
7174
+ }
7175
+ }
7176
+ // tsc rejects the spelling outside a declaring class body; defensive.
7177
+ if (!classDecl)
7178
+ L.unsupported("SC1090", expr, `'${pname} in …' outside a class declaring '${pname}'`);
7179
+ if (staticBrand) {
7180
+ L.unsupported("SC1090", expr, `'${pname} in …' brand checks for private STATICS (JS brands the declaring class OBJECT, not instances — compare against the class value directly)`);
7181
+ }
7182
+ const info = L.currentClass?.decl === classDecl
7183
+ ? L.currentClass
7184
+ : (() => {
7185
+ const sym = classDecl.name ? L.checker.getSymbolAtLocation(classDecl.name) : undefined;
7186
+ return sym ? L.classBySymbol.get(sym) : undefined;
7187
+ })();
7188
+ if (!info) {
7189
+ L.unsupported("SC1090", expr, `'${pname} in …' where the declaring class has no lowering (see the class declaration's own diagnostic)`);
7190
+ }
7191
+ // A GENERIC family: JS has ONE runtime class and ONE brand for every
7192
+ // instantiation, while these layouts mint one class per instantiation
7193
+ // — a cross-instantiation check would answer false where Node says
7194
+ // true, so the family fences rather than silently splitting the brand.
7195
+ if (info.generic || info.genericInstance) {
7196
+ L.unsupported("SC1090", expr, `'${pname} in …' inside a generic class (JS shares one brand across every instantiation; these layouts mint one class per instantiation)`);
7197
+ }
7198
+ const recv = L.lowerExpr(expr.right);
7199
+ const declName = info.def.name;
7200
+ // A UNION of class instances (`o: Counter | Helper` — the
7201
+ // discriminating use): every object arm answers membership STATICALLY
7202
+ // (at/below the declarer → true, disjoint subtree → false), so the
7203
+ // whole test collapses to runtime TAG tests — the record-shape 'in'
7204
+ // discrimination, brand form. An arm ABOVE the declarer answers per
7205
+ // VALUE (its slot can hold branded and unbranded instances), and
7206
+ // non-object arms have no brand story — both fence.
7207
+ if (recv.type.kind === "union") {
7208
+ const unionId = recv.type.unionId;
7209
+ const arms = L.unions.get(unionId)?.arms ?? [];
7210
+ const answers = [];
7211
+ let staticAnswers = arms.length > 0;
7212
+ for (const arm of arms) {
7213
+ const tag = L.armTag(unionId, arm);
7214
+ if (tag < 0 || arm.kind !== "object" || L.isSubclassOf(declName, arm.className)) {
7215
+ staticAnswers = false;
7216
+ break;
7217
+ }
7218
+ answers.push({ tag, has: arm.className === declName || L.isSubclassOf(arm.className, declName) });
7219
+ }
7220
+ if (staticAnswers) {
7221
+ const pureRecv = recv.kind === "varRef" || recv.kind === "recordGet" || recv.kind === "fieldGet";
7222
+ const isTag = (tag, negated) => ({
7223
+ kind: "unionIsTag",
7224
+ unionId,
7225
+ tag,
7226
+ negated,
7227
+ value: recv,
7228
+ type: BOOL,
7229
+ loc,
7230
+ });
7231
+ const trues = answers.filter((a) => a.has);
7232
+ if (trues.length === answers.length || trues.length === 0) {
7233
+ const ans = trues.length !== 0;
7234
+ if (pureRecv)
7235
+ return { kind: "boolLit", value: ans, type: BOOL, loc };
7236
+ // Constant either way, receiver still evaluates once: one tag
7237
+ // test whose branches agree (the record-'in' rule verbatim).
7238
+ return {
7239
+ kind: "ternary",
7240
+ cond: isTag(answers[0].tag, false),
7241
+ then: { kind: "boolLit", value: ans, type: BOOL, loc },
7242
+ else_: { kind: "boolLit", value: ans, type: BOOL, loc },
7243
+ type: BOOL,
7244
+ loc,
7245
+ };
7246
+ }
7247
+ if (trues.length === 1)
7248
+ return isTag(trues[0].tag, false);
7249
+ const falses = answers.filter((a) => !a.has);
7250
+ if (falses.length === 1)
7251
+ return isTag(falses[0].tag, true);
7252
+ if (pureRecv) {
7253
+ let out = isTag(trues[0].tag, false);
7254
+ for (const t of trues.slice(1)) {
7255
+ out = { kind: "logical", op: "||", left: out, right: isTag(t.tag, false), type: BOOL, loc };
7256
+ }
7257
+ return out;
7258
+ }
7259
+ L.unsupported("SC1090", expr, `statically-decided '${pname} in …' on computed receivers (bind the value to a variable first)`);
7260
+ }
7261
+ }
7262
+ if (recv.type.kind !== "object") {
7263
+ L.unsupported("SC1090", expr, `'${pname} in …' on '${L.fmt(recv.type)}' receivers (only class-instance receivers — and unions of classes below or beside the declarer — have a static brand answer; narrow first)`);
7264
+ }
7265
+ const lhsName = recv.type.className;
7266
+ const lhsInfo = L.classes.get(lhsName);
7267
+ if (!lhsInfo)
7268
+ throw new Error(`lowerer bug: unknown class ${lhsName}`);
7269
+ if (L.isSubclassOf(declName, lhsName)) {
7270
+ // The narrowing direction: the declarer strictly below the
7271
+ // receiver's static class — a strict subclass relation puts both in
7272
+ // a hierarchy, so the vtable interval test always exists.
7273
+ return { kind: "instanceOf", value: recv, className: declName, type: BOOL, loc };
7274
+ }
7275
+ const value = lhsName === declName || L.isSubclassOf(lhsName, declName);
7276
+ if (recv.kind === "varRef")
7277
+ return { kind: "boolLit", value, type: BOOL, loc };
7278
+ L.unsupported("SC1090", expr, `statically-decided '${pname} in …' on computed operands (bind the value to a variable first)`);
7279
+ }
6943
7280
  /** `"key" in v` — the key-presence test. Three lowered receivers:
6944
7281
  * process.env (getenv(3) presence — Node-exact, an empty value still
6945
7282
  * counts as present), and monomorphic record shapes, where the answer is
@@ -6955,6 +7292,12 @@ export function lowerInstanceOf(L, expr, loc) {
6955
7292
  * and dyn/unknown stay fenced. Keys are literal strings — a computed key
6956
7293
  * over a shape would need the runtime key table. */
6957
7294
  export function lowerInExpression(L, expr, loc) {
7295
+ // `#name in obj` — the ergonomic brand check (ES2022) — resolves
7296
+ // before any string-key machinery: the left operand is a private
7297
+ // NAME, not a value.
7298
+ if (ts.isPrivateIdentifier(expr.left)) {
7299
+ return lowerPrivateIn(L, expr, expr.left, loc);
7300
+ }
6958
7301
  // Compile-time-known STRING keys fold — literals, and the same
6959
7302
  // const/enum-literal and template folding computed property keys get
6960
7303
  // (foldedStringKeyOf); runtime-valued keys keep the fence.
@@ -7522,6 +7865,16 @@ function lowerUnionKeyedRead(L, expr, unionId, value, key, literalField) {
7522
7865
  }
7523
7866
  if (joinArms.length === 0)
7524
7867
  return null;
7868
+ // The join must be a BUILDABLE union: arm kinds the union invariants
7869
+ // admit (no map/dyn/jsval/generator arms; func arms only beside
7870
+ // func/unit siblings — unionFuncSetArmsOk, the validator's rule). A
7871
+ // join mixing, say, a func answer with data answers has no union to
7872
+ // surface as; the caller owns the fence message.
7873
+ if (joinArms.length > 1 &&
7874
+ (!unionFuncSetArmsOk(joinArms) ||
7875
+ joinArms.some((a) => a.kind === "map" || a.kind === "dyn" || a.kind === "jsval" || a.kind === "generator" || a.kind === "caught"))) {
7876
+ return null;
7877
+ }
7525
7878
  joinArms.sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1));
7526
7879
  const type = joinArms.length === 1 ? joinArms[0] : { kind: "union", unionId: L.unions.intern(joinArms) };
7527
7880
  // Pass 2: every arm's answer must SURFACE as the join, and index arms
@@ -7824,9 +8177,23 @@ export function fieldGetExpr(L, target, loc, blame) {
7824
8177
  loc,
7825
8178
  };
7826
8179
  }
7827
- return target.container === "class"
7828
- ? { kind: "fieldGet", obj: target.obj, className: target.className, field: target.field, type: target.fieldType, loc }
7829
- : { kind: "recordGet", obj: target.obj, shapeId: target.shapeId, field: target.field, type: target.fieldType, loc };
8180
+ if (target.container === "class") {
8181
+ const read = { kind: "fieldGet", obj: target.obj, className: target.className, field: target.field, type: target.fieldType, loc };
8182
+ // DEFERRED-INIT fields (`stream!: T` assigned past the constructor's
8183
+ // top level — ClassInfo.deferredInitFields): the SLOT is the
8184
+ // undefined-armed union; the read CHECKED-extracts the declared type
8185
+ // — a genuinely unassigned read throws the catchable TypeError
8186
+ // where Node reads an undefined the declared type cannot hold.
8187
+ if (L.classes.get(target.className)?.deferredInitFields?.has(target.field) === true &&
8188
+ target.fieldType.kind === "union") {
8189
+ const inner = L.stripUndefinedArm(target.fieldType);
8190
+ const helper = L.deferredReadHelper(target.fieldType.unionId, inner, loc);
8191
+ if (helper)
8192
+ return { kind: "call", callee: helper, args: [read], type: inner, loc };
8193
+ }
8194
+ return read;
8195
+ }
8196
+ return { kind: "recordGet", obj: target.obj, shapeId: target.shapeId, field: target.field, type: target.fieldType, loc };
7830
8197
  }
7831
8198
  /** The write statement for a field target (fieldSet / recordSet / setter
7832
8199
  * call). A write to a getter-only property never gets here in a clean
@@ -8108,4 +8475,38 @@ function lowerStreamObjectProperty(L, expr) {
8108
8475
  return null;
8109
8476
  return lowerStreamProperty(L, expr, info);
8110
8477
  }
8478
+ /** The primitive-constructor closure (`String`/`Number`/`Boolean` as a
8479
+ * VALUE): interns one synthesized module function per constructor —
8480
+ * `%builtin.String` et al. — and returns the zero-capture closure over it.
8481
+ * The body IS the string-coercion the type mapping promised
8482
+ * (`(value: string) => primitive`): String is identity, Number the ECMA
8483
+ * StringToNumber (num.fromString — the same runtime call the direct
8484
+ * `Number(s)` lowering emits), Boolean the emptiness test. Interning makes
8485
+ * every reference the SAME immortal closure, so `opt.type === String`
8486
+ * compares like JS function identity. */
8487
+ export function primitiveCtorClosure(L, name, loc) {
8488
+ const ret = name === "String" ? STRING : name === "Number" ? F64 : BOOL;
8489
+ const fnT = funcOf([STRING], ret);
8490
+ let fnName = L.primitiveCtorFns.get(name);
8491
+ if (!fnName) {
8492
+ fnName = `%builtin.${name}`;
8493
+ L.primitiveCtorFns.set(name, fnName);
8494
+ const s = { kind: "varRef", localId: "v.0", type: STRING, loc };
8495
+ const value = name === "String"
8496
+ ? s
8497
+ : name === "Number"
8498
+ ? { kind: "libCall", fn: "num.fromString", args: [s], type: F64, loc }
8499
+ : { kind: "strEq", negated: true, left: s, right: { kind: "strLit", value: "", type: STRING, loc }, type: BOOL, loc };
8500
+ const fn = {
8501
+ name: fnName,
8502
+ params: [{ localId: "v.0", name: "value", type: STRING }],
8503
+ returnType: ret,
8504
+ locals: [{ id: "v.0", name: "value", type: STRING, mutable: false }],
8505
+ body: [{ kind: "return", value, loc }],
8506
+ loc,
8507
+ };
8508
+ L.liftedFns.push(fn);
8509
+ }
8510
+ return { kind: "closure", fnName, captures: [], type: fnT, loc };
8511
+ }
8111
8512
  //# sourceMappingURL=lower-exprs.js.map