@scriptc/compiler 0.2.8 → 0.2.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 (189) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +1 -0
  2. package/dist/backend/cycle-analysis.js +50 -17
  3. package/dist/backend/cycle-analysis.js.map +1 -1
  4. package/dist/backend/cycle-analysis.test.js +138 -14
  5. package/dist/backend/cycle-analysis.test.js.map +1 -1
  6. package/dist/backend/cycle-mutability.d.ts +31 -6
  7. package/dist/backend/cycle-mutability.js +120 -18
  8. package/dist/backend/cycle-mutability.js.map +1 -1
  9. package/dist/backend/llvm/alloc.d.ts +13 -3
  10. package/dist/backend/llvm/alloc.js +24 -10
  11. package/dist/backend/llvm/alloc.js.map +1 -1
  12. package/dist/backend/llvm/alloc.test.js +5 -5
  13. package/dist/backend/llvm/alloc.test.js.map +1 -1
  14. package/dist/backend/llvm/byte-numbers.js +2 -3
  15. package/dist/backend/llvm/byte-numbers.js.map +1 -1
  16. package/dist/backend/llvm/classes.js +7 -6
  17. package/dist/backend/llvm/classes.js.map +1 -1
  18. package/dist/backend/llvm/common.d.ts +17 -0
  19. package/dist/backend/llvm/common.js +28 -0
  20. package/dist/backend/llvm/common.js.map +1 -1
  21. package/dist/backend/llvm/dense-array-access.js +1 -1
  22. package/dist/backend/llvm/dense-array-access.test.js +1 -1
  23. package/dist/backend/llvm/dyn.d.ts +4 -4
  24. package/dist/backend/llvm/dyn.js +27 -11
  25. package/dist/backend/llvm/dyn.js.map +1 -1
  26. package/dist/backend/llvm/emitter.d.ts +45 -3
  27. package/dist/backend/llvm/emitter.js +333 -74
  28. package/dist/backend/llvm/emitter.js.map +1 -1
  29. package/dist/backend/llvm/emitter.test.js +36 -3
  30. package/dist/backend/llvm/emitter.test.js.map +1 -1
  31. package/dist/backend/llvm/expr-bytes.js +35 -28
  32. package/dist/backend/llvm/expr-bytes.js.map +1 -1
  33. package/dist/backend/llvm/expr-containers.js +1 -1
  34. package/dist/backend/llvm/expr-context.d.ts +2 -0
  35. package/dist/backend/llvm/expr-control.js +46 -5
  36. package/dist/backend/llvm/expr-control.js.map +1 -1
  37. package/dist/backend/llvm/expr-dynamic.js +3 -0
  38. package/dist/backend/llvm/expr-dynamic.js.map +1 -1
  39. package/dist/backend/llvm/expr-stream-bridges.d.ts +3 -0
  40. package/dist/backend/llvm/expr-stream-bridges.js +73 -11
  41. package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
  42. package/dist/backend/llvm/lib-filesystem.js +18 -2
  43. package/dist/backend/llvm/lib-filesystem.js.map +1 -1
  44. package/dist/backend/llvm/lib-shared.js +1 -0
  45. package/dist/backend/llvm/lib-shared.js.map +1 -1
  46. package/dist/backend/llvm/local-array-reads.d.ts +17 -6
  47. package/dist/backend/llvm/local-array-reads.js +155 -43
  48. package/dist/backend/llvm/local-array-reads.js.map +1 -1
  49. package/dist/backend/llvm/reference-effects.js +34 -1
  50. package/dist/backend/llvm/reference-effects.js.map +1 -1
  51. package/dist/backend/llvm/shapes.d.ts +14 -3
  52. package/dist/backend/llvm/shapes.js +43 -12
  53. package/dist/backend/llvm/shapes.js.map +1 -1
  54. package/dist/backend/llvm/shapes.test.js +5 -5
  55. package/dist/backend/llvm/shapes.test.js.map +1 -1
  56. package/dist/backend/llvm/string-reads.js +29 -26
  57. package/dist/backend/llvm/string-reads.js.map +1 -1
  58. package/dist/backend/llvm/string-slices.js +1 -1
  59. package/dist/backend/llvm/trunc.d.ts +8 -0
  60. package/dist/backend/llvm/trunc.js +28 -0
  61. package/dist/backend/llvm/trunc.js.map +1 -0
  62. package/dist/backend/loop-polls.d.ts +14 -0
  63. package/dist/backend/loop-polls.js +100 -0
  64. package/dist/backend/loop-polls.js.map +1 -0
  65. package/dist/backend/loop-polls.test.d.ts +1 -0
  66. package/dist/backend/loop-polls.test.js +86 -0
  67. package/dist/backend/loop-polls.test.js.map +1 -0
  68. package/dist/backend/may-throw.js +20 -1
  69. package/dist/backend/may-throw.js.map +1 -1
  70. package/dist/backend/may-throw.test.js +23 -0
  71. package/dist/backend/may-throw.test.js.map +1 -1
  72. package/dist/backend/native-codegen.d.ts +1 -3
  73. package/dist/backend/native-codegen.js +4 -32
  74. package/dist/backend/native-codegen.js.map +1 -1
  75. package/dist/backend/runtime-abi.d.ts +2 -2
  76. package/dist/backend/runtime-abi.js +2 -2
  77. package/dist/backend/runtime-abi.js.map +1 -1
  78. package/dist/backend/runtime-pack-targets.test.js +1 -1
  79. package/dist/backend/runtime-pack-targets.test.js.map +1 -1
  80. package/dist/backend/runtime-pack.test.js +1 -1
  81. package/dist/backend/runtime-pack.test.js.map +1 -1
  82. package/dist/cli/command.js +8 -1
  83. package/dist/cli/command.js.map +1 -1
  84. package/dist/cli/output-options.d.ts +1 -0
  85. package/dist/cli/output-options.js +3 -0
  86. package/dist/cli/output-options.js.map +1 -1
  87. package/dist/cli/usage.d.ts +5 -1
  88. package/dist/cli/usage.js +6 -0
  89. package/dist/cli/usage.js.map +1 -1
  90. package/dist/compile-types.d.ts +5 -0
  91. package/dist/executable/executable-cache.d.ts +2 -0
  92. package/dist/executable/executable-cache.js +3 -0
  93. package/dist/executable/executable-cache.js.map +1 -1
  94. package/dist/executable/executable-cache.test.js +1 -0
  95. package/dist/executable/executable-cache.test.js.map +1 -1
  96. package/dist/frontend/lowering/array-values.js +37 -1
  97. package/dist/frontend/lowering/array-values.js.map +1 -1
  98. package/dist/frontend/lowering/builtins/strings.js +4 -1
  99. package/dist/frontend/lowering/builtins/strings.js.map +1 -1
  100. package/dist/frontend/lowering/call-arguments.js +96 -4
  101. package/dist/frontend/lowering/call-arguments.js.map +1 -1
  102. package/dist/frontend/lowering/class-method-mutations.js +88 -0
  103. package/dist/frontend/lowering/class-method-mutations.js.map +1 -1
  104. package/dist/frontend/lowering/containers/bytes.js +15 -4
  105. package/dist/frontend/lowering/containers/bytes.js.map +1 -1
  106. package/dist/frontend/lowering/expressions/union-fields.d.ts +19 -0
  107. package/dist/frontend/lowering/expressions/union-fields.js +236 -0
  108. package/dist/frontend/lowering/expressions/union-fields.js.map +1 -0
  109. package/dist/frontend/lowering/lower-calls.js +12 -2
  110. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  111. package/dist/frontend/lowering/lower-exprs.d.ts +6 -0
  112. package/dist/frontend/lowering/lower-exprs.js +162 -3
  113. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  114. package/dist/frontend/lowering/lower-island.js +5 -19
  115. package/dist/frontend/lowering/lower-island.js.map +1 -1
  116. package/dist/frontend/lowering/lower-stmts.js +11 -1
  117. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  118. package/dist/frontend/lowering/lowerer.d.ts +5 -0
  119. package/dist/frontend/lowering/lowerer.js +174 -8
  120. package/dist/frontend/lowering/lowerer.js.map +1 -1
  121. package/dist/frontend/lowering/nan-coded-reads.d.ts +161 -0
  122. package/dist/frontend/lowering/nan-coded-reads.js +1342 -0
  123. package/dist/frontend/lowering/nan-coded-reads.js.map +1 -0
  124. package/dist/frontend/lowering/runtime-optional-bounds.d.ts +14 -2
  125. package/dist/frontend/lowering/runtime-optional-bounds.js +17 -11
  126. package/dist/frontend/lowering/runtime-optional-bounds.js.map +1 -1
  127. package/dist/frontend/lowering/runtime-optional-bounds.test.js +28 -0
  128. package/dist/frontend/lowering/runtime-optional-bounds.test.js.map +1 -1
  129. package/dist/frontend/lowering/surfaces.js +8 -0
  130. package/dist/frontend/lowering/surfaces.js.map +1 -1
  131. package/dist/index.d.ts +1 -1
  132. package/dist/index.js +20 -8
  133. package/dist/index.js.map +1 -1
  134. package/dist/ir/builtin-signatures.d.ts +1 -1
  135. package/dist/ir/builtin-signatures.js +2 -0
  136. package/dist/ir/builtin-signatures.js.map +1 -1
  137. package/dist/ir/constant-globals.d.ts +14 -0
  138. package/dist/ir/constant-globals.js +81 -0
  139. package/dist/ir/constant-globals.js.map +1 -0
  140. package/dist/ir/constant-globals.test.d.ts +1 -0
  141. package/dist/ir/constant-globals.test.js +97 -0
  142. package/dist/ir/constant-globals.test.js.map +1 -0
  143. package/dist/ir/final-fields.d.ts +35 -0
  144. package/dist/ir/final-fields.js +187 -0
  145. package/dist/ir/final-fields.js.map +1 -0
  146. package/dist/ir/final-fields.test.d.ts +1 -0
  147. package/dist/ir/final-fields.test.js +106 -0
  148. package/dist/ir/final-fields.test.js.map +1 -0
  149. package/dist/ir/int32-slots.d.ts +9 -0
  150. package/dist/ir/int32-slots.js +70 -57
  151. package/dist/ir/int32-slots.js.map +1 -1
  152. package/dist/ir/int32-slots.test.js +27 -0
  153. package/dist/ir/int32-slots.test.js.map +1 -1
  154. package/dist/ir/integer-loops.d.ts +5 -0
  155. package/dist/ir/integer-loops.js +20 -4
  156. package/dist/ir/integer-loops.js.map +1 -1
  157. package/dist/ir/integer-loops.test.js +17 -0
  158. package/dist/ir/integer-loops.test.js.map +1 -1
  159. package/dist/ir/ir.d.ts +6 -1
  160. package/dist/ir/ir.js +4 -0
  161. package/dist/ir/ir.js.map +1 -1
  162. package/dist/ir/serialize.d.ts +12 -2
  163. package/dist/ir/serialize.js +190 -39
  164. package/dist/ir/serialize.js.map +1 -1
  165. package/dist/ir/serialize.test.js +65 -1
  166. package/dist/ir/serialize.test.js.map +1 -1
  167. package/dist/native/cache.d.ts +8 -1
  168. package/dist/native/cache.js +38 -1
  169. package/dist/native/cache.js.map +1 -1
  170. package/dist/native/cache.test.js +11 -0
  171. package/dist/native/cache.test.js.map +1 -1
  172. package/dist/native/compiler.js +4 -3
  173. package/dist/native/compiler.js.map +1 -1
  174. package/dist/native/prepare.d.ts +3 -1
  175. package/dist/native/prepare.js +63 -28
  176. package/dist/native/prepare.js.map +1 -1
  177. package/dist/native/prepare.test.js +23 -1
  178. package/dist/native/prepare.test.js.map +1 -1
  179. package/dist/native/text-file.d.ts +10 -0
  180. package/dist/native/text-file.js +41 -0
  181. package/dist/native/text-file.js.map +1 -0
  182. package/dist/text-parts.d.ts +5 -0
  183. package/dist/text-parts.js +36 -0
  184. package/dist/text-parts.js.map +1 -0
  185. package/package.json +20 -20
  186. package/surface-manifest.json +7 -1
  187. package/dist/frontend/lowering/expressions/union-field-write.d.ts +0 -8
  188. package/dist/frontend/lowering/expressions/union-field-write.js +0 -108
  189. package/dist/frontend/lowering/expressions/union-field-write.js.map +0 -1
@@ -164,6 +164,12 @@ export declare function lowerArrayLiteral(lowerer: Lowerer, expr: ts.ArrayLitera
164
164
  * reads preserve their tagged value; JavaScript's ToNumber(undefined) is NaN,
165
165
  * so compounds and binary arithmetic use this checked one-time extraction. */
166
166
  export declare function lowerOptionalNumber(lowerer: Lowerer, operand: IrExpr, loc: SrcLoc, narrowedNode?: ts.Expression): IrExpr;
167
+ /** A number argument of a builtin that converts it with ToNumber (Math
168
+ * functions, String.fromCharCode, the global isNaN/isFinite, DataView
169
+ * offsets and values). ToNumber(undefined) is NaN, so a missing value — an
170
+ * out-of-range read, a local or field holding one, or a hole — converts to
171
+ * NaN instead of trusting the checker's number type. */
172
+ export declare function lowerToNumberArgument(lowerer: Lowerer, node: ts.Expression): IrExpr;
167
173
  /** `a[i]` reads. Only f64 indices into array receivers are modeled; JS
168
174
  * string-key element access (`a["length"]`) and string indexing (`s[0]`
169
175
  * typechecks against the lib's index signature; use .charAt) stay out. */
@@ -20,7 +20,7 @@ import { dirname } from "node:path";
20
20
  import * as posix from "node:path/posix";
21
21
  import { checkedClassAssertion, checkedClassUnionAssertion } from "./class-assertions.js";
22
22
  import { narrowClassUnion, narrowStoredClassValue } from "./class-unions.js";
23
- import { lowerUnionFieldWrite } from "./expressions/union-field-write.js";
23
+ import { lowerUnionFieldRead, lowerUnionFieldWrite } from "./expressions/union-fields.js";
24
24
  import { captureContextArguments } from "./function-context.js";
25
25
  import { OBJECT_CALLABLE_VALUES } from "./surfaces.js";
26
26
  import { wasiGuestPath } from "../../wasi-paths.js";
@@ -167,7 +167,14 @@ export function lowerExpr(lowerer, expr) {
167
167
  }
168
168
  lowerExprDepth++;
169
169
  try {
170
- const lowered = lowerExprInner(lowerer, expr);
170
+ let lowered = lowerExprInner(lowerer, expr);
171
+ const nanCoded = lowerer.nanCodedReads;
172
+ if (nanCoded) {
173
+ if (lowered.type.kind === "f64" && nanCoded.reboxes(expr))
174
+ lowered = reboxNanCoded(lowerer, lowered, locOf(expr));
175
+ else if (lowered.type.kind === "union" && nanCoded.convertsSource(expr))
176
+ lowered = lowerOptionalNumber(lowerer, lowered, locOf(expr));
177
+ }
171
178
  if (lowered.type.kind === "void" && (lowerer.typeOf(expr).flags & ts.TypeFlags.Never) !== 0)
172
179
  lowerer.neverValued.add(lowered);
173
180
  return lowered;
@@ -176,6 +183,27 @@ export function lowerExpr(lowerer, expr) {
176
183
  lowerExprDepth--;
177
184
  }
178
185
  }
186
+ /** `number | undefined` from a NaN-coded double whose slot never holds a
187
+ * genuine NaN (nan-coded-reads.ts): NaN is exactly undefined. */
188
+ function reboxNanCoded(lowerer, value, loc) {
189
+ const optional = lowerer.withUndefinedArm(F64);
190
+ const stmts = [];
191
+ let stable = value;
192
+ if (value.kind !== "varRef") {
193
+ const tmp = lowerer.declareHiddenLocal("%nanCoded", F64);
194
+ stmts.push({ kind: "varDecl", localId: tmp.id, init: value, loc });
195
+ stable = varRef(tmp.id, F64, loc);
196
+ }
197
+ const result = {
198
+ kind: "ternary",
199
+ cond: { kind: "libCall", fn: "num.isNaN", args: [stable], type: BOOL, loc },
200
+ then: lowerer.wrappedUndefined(optional, loc),
201
+ else_: lowerer.coerceToExpected(stable, optional),
202
+ type: optional,
203
+ loc,
204
+ };
205
+ return stmts.length === 0 ? result : { kind: "seqExpr", stmts, result, type: optional, loc };
206
+ }
179
207
  function lowerExprInner(lowerer, expr) {
180
208
  const loc = locOf(expr);
181
209
  // An optional chain's guarded receiver: already evaluated by the
@@ -2209,6 +2237,11 @@ function lowerExprInner(lowerer, expr) {
2209
2237
  handled.kind === "recordGet" &&
2210
2238
  lowerer.isRuntimeOptionalField(handled.shapeId, handled.field))
2211
2239
  return handled;
2240
+ // A class field assigned a missing read: the checker narrows this
2241
+ // read to the assigned value's bare type, but the stored undefined
2242
+ // arm is real (`obj.n = bytes[i]; obj.n` is undefined in Node).
2243
+ if (runtimeOptionalClassField(lowerer, handled, expr))
2244
+ return handled;
2212
2245
  return lowerer.maybeNarrow(handled, expr);
2213
2246
  }
2214
2247
  // `globalThis.<name>` that no lowering above claimed
@@ -5582,6 +5615,34 @@ export function lowerOptionalNumber(lowerer, operand, loc, narrowedNode) {
5582
5615
  ? value
5583
5616
  : { kind: "seqExpr", stmts: prefix, result: value, type: F64, loc };
5584
5617
  }
5618
+ /** A number argument of a builtin that converts it with ToNumber (Math
5619
+ * functions, String.fromCharCode, the global isNaN/isFinite, DataView
5620
+ * offsets and values). ToNumber(undefined) is NaN, so a missing value — an
5621
+ * out-of-range read, a local or field holding one, or a hole — converts to
5622
+ * NaN instead of trusting the checker's number type. */
5623
+ export function lowerToNumberArgument(lowerer, node) {
5624
+ let inner = node;
5625
+ while (ts.isParenthesizedExpression(inner))
5626
+ inner = inner.expression;
5627
+ const loc = locOf(inner);
5628
+ const isOptionalNumber = (value) => value.type.kind === "union" &&
5629
+ lowerer.armTag(value.type.unionId, UNDEFINED_T) >= 0 &&
5630
+ lowerer.stripUndefinedArm(value.type).kind === "f64";
5631
+ const optional = ts.isIdentifier(inner)
5632
+ ? runtimeOptionalStorageOperand(lowerer, inner)
5633
+ : ts.isElementAccessExpression(inner)
5634
+ ? lowerAbsenceProbe(lowerer, inner)
5635
+ : null;
5636
+ if (optional && isOptionalNumber(optional))
5637
+ return lowerOptionalNumber(lowerer, optional, loc, inner);
5638
+ // A conditional builds each branch for the destination.
5639
+ if (ts.isConditionalExpression(inner))
5640
+ return lowerer.lowerExprExpecting(node, F64);
5641
+ const value = lowerer.lowerExpr(node);
5642
+ if (isOptionalNumber(value))
5643
+ return lowerOptionalNumber(lowerer, value, loc, inner);
5644
+ return lowerer.coerceInto(node, value, F64);
5645
+ }
5585
5646
  /** `a[i]` reads. Only f64 indices into array receivers are modeled; JS
5586
5647
  * string-key element access (`a["length"]`) and string indexing (`s[0]`
5587
5648
  * typechecks against the lib's index signature; use .charAt) stay out. */
@@ -5866,7 +5927,10 @@ export function lowerElementAccess(lowerer, expr) {
5866
5927
  // `t[i]!` asserts presence: the numeric element read (NaN when
5867
5928
  // invalid). Every other read answers undefined for an invalid index,
5868
5929
  // like Node.
5869
- if (ts.isNonNullExpression(expr.parent) && expr.parent.expression === expr) {
5930
+ // A read whose every consumer treats undefined like NaN (see
5931
+ // nan-coded-reads.ts) is the same plain numeric read.
5932
+ if ((ts.isNonNullExpression(expr.parent) && expr.parent.expression === expr) ||
5933
+ lowerer.nanCodedReads?.isNumericRead(expr) === true) {
5870
5934
  return {
5871
5935
  kind: "bytesIntrinsic",
5872
5936
  method: "get",
@@ -6529,6 +6593,23 @@ export function runtimeOptionalStorageOperand(lowerer, node) {
6529
6593
  return null;
6530
6594
  return optional.value;
6531
6595
  }
6596
+ /** A `T | undefined` class field read that the checker narrowed to `T`
6597
+ * where the field was assigned a value that may be missing (an unchecked
6598
+ * indexed read). TypeScript's assignment narrowing is not a runtime fact
6599
+ * there, so the read keeps the stored union instead of extracting `T`. */
6600
+ function runtimeOptionalClassField(lowerer, value, node) {
6601
+ if (value.kind !== "fieldGet" || value.type.kind !== "union")
6602
+ return false;
6603
+ if (lowerer.armTag(value.type.unionId, UNDEFINED_T) < 0)
6604
+ return false;
6605
+ if ((lowerer.unions.get(value.type.unionId)?.arms.length ?? 0) !== 2)
6606
+ return false;
6607
+ const symbol = lowerer.checker.getSymbolAtLocation(node.name);
6608
+ if (!symbol || !lowerer.runtimeOptionalAssignedSymbols.has(symbol))
6609
+ return false;
6610
+ const narrowed = lowerer.mapTypeOf(lowerer.typeOf(node));
6611
+ return narrowed !== null && typeEquals(narrowed, lowerer.stripUndefinedArm(value.type));
6612
+ }
6532
6613
  /** A presence-test operand: an absence-aware element or field read, an
6533
6614
  * identifier's runtime-optional storage, or the ordinary lowering. */
6534
6615
  export function lowerPresenceOperand(lowerer, node) {
@@ -8298,12 +8379,75 @@ export function lowerCompoundValueToTarget(lowerer, expr, compound, target) {
8298
8379
  }
8299
8380
  lowerer.unsupported("SC1043", expr);
8300
8381
  }
8382
+ /** Equality of two NaN-coded values (nan-coded-reads.ts), each of which may
8383
+ * stand for undefined and never holds a genuine NaN: Node's undefined equals
8384
+ * undefined, so two NaNs compare equal here. Operands evaluate once, in
8385
+ * order. */
8386
+ function lowerNanCodedEquality(lowerer, expr) {
8387
+ const loc = locOf(expr);
8388
+ const left = lowerer.lowerExpr(expr.left);
8389
+ const right = lowerer.lowerExpr(expr.right);
8390
+ if (left.type.kind !== "f64" || right.type.kind !== "f64") {
8391
+ throw new InternalCompilerError("NaN-coded equality operand is not a plain number");
8392
+ }
8393
+ const stmts = [];
8394
+ const stable = (value, name) => {
8395
+ if (value.kind === "varRef" || value.kind === "numLit")
8396
+ return value;
8397
+ const tmp = lowerer.declareHiddenLocal(name, F64);
8398
+ stmts.push({ kind: "varDecl", localId: tmp.id, init: value, loc });
8399
+ return varRef(tmp.id, F64, loc);
8400
+ };
8401
+ const a = stable(left, "%eqLeft");
8402
+ const b = stable(right, "%eqRight");
8403
+ const isNaN = (value) => ({
8404
+ kind: "libCall",
8405
+ fn: "num.isNaN",
8406
+ args: [value],
8407
+ type: BOOL,
8408
+ loc,
8409
+ });
8410
+ const bothUndefined = {
8411
+ kind: "logical",
8412
+ op: "&&",
8413
+ left: isNaN(a),
8414
+ right: isNaN(b),
8415
+ type: BOOL,
8416
+ loc,
8417
+ };
8418
+ const op = expr.operatorToken.kind;
8419
+ const negated = op === ts.SyntaxKind.ExclamationEqualsEqualsToken ||
8420
+ op === ts.SyntaxKind.ExclamationEqualsToken;
8421
+ const result = negated
8422
+ ? {
8423
+ kind: "logical",
8424
+ op: "&&",
8425
+ left: { kind: "bin", op: "!==", left: a, right: b, type: BOOL, loc },
8426
+ right: { kind: "unary", op: "!", operand: bothUndefined, type: BOOL, loc },
8427
+ type: BOOL,
8428
+ loc,
8429
+ }
8430
+ : {
8431
+ kind: "logical",
8432
+ op: "||",
8433
+ left: { kind: "bin", op: "===", left: a, right: b, type: BOOL, loc },
8434
+ right: bothUndefined,
8435
+ type: BOOL,
8436
+ loc,
8437
+ };
8438
+ return stmts.length === 0 ? result : { kind: "seqExpr", stmts, result, type: BOOL, loc };
8439
+ }
8301
8440
  export function lowerBinary(lowerer, expr) {
8302
8441
  const loc = locOf(expr);
8303
8442
  const op = expr.operatorToken.kind;
8304
8443
  const cacheHas = lowerRequireCacheHas(lowerer, expr);
8305
8444
  if (cacheHas)
8306
8445
  return cacheHas;
8446
+ if (lowerer.nanCodedReads?.needsUndefinedEquality(expr)) {
8447
+ const undefinedEquality = lowerNanCodedEquality(lowerer, expr);
8448
+ if (undefinedEquality)
8449
+ return undefinedEquality;
8450
+ }
8307
8451
  if (op === ts.SyntaxKind.EqualsToken ||
8308
8452
  (op >= ts.SyntaxKind.FirstCompoundAssignment && op <= ts.SyntaxKind.LastCompoundAssignment)) {
8309
8453
  const urlWrite = lowerUrlAssignment(lowerer, expr);
@@ -12246,6 +12390,11 @@ export function lowerUnionProperty(lowerer, expr) {
12246
12390
  const helper = lowerer.narrowedRetagHelper(expr.expression, value.type.unionId, receiverIr.unionId, locOf(expr));
12247
12391
  if (helper)
12248
12392
  value = { kind: "call", callee: helper, args: [value], type: receiverIr, loc: locOf(expr) };
12393
+ // Optional storage of a base class (a runtime-optional binding holding
12394
+ // `Base | undefined`) that `instanceof` narrowed to several subclasses:
12395
+ // extract the instance and test its class, as typed slots do.
12396
+ else
12397
+ value = narrowStoredClassValue(lowerer, value, receiverIr) ?? value;
12249
12398
  }
12250
12399
  // A checker-union receiver whose VALUE lowered to a plain RECORD (the
12251
12400
  // merged-signature fiction — `runner(cmd, args)` where runner joined
@@ -12368,6 +12517,11 @@ export function lowerUnionProperty(lowerer, expr) {
12368
12517
  const keyed = lowerUnionKeyedRead(lowerer, expr, value.type.unionId, value, key, field);
12369
12518
  if (keyed)
12370
12519
  return keyed;
12520
+ // Class arms declaring the field with compatible but different types
12521
+ // (`T | undefined` beside `T`): per-arm slot reads widened to one type.
12522
+ const widened = lowerUnionFieldRead(lowerer, expr, value);
12523
+ if (widened)
12524
+ return widened;
12371
12525
  // JavaScript overloads commonly inspect a class brand on a scalar-or-
12372
12526
  // instance argument (e.g. Matrix4.makeTranslation). Preserve primitive
12373
12527
  // missing-property semantics and dispatch class data through its native
@@ -12532,6 +12686,11 @@ function classFieldTarget(lowerer, receiverNode, receiverType, fieldName, saved)
12532
12686
  const lowerObjectReceiver = () => {
12533
12687
  let obj = represented;
12534
12688
  if (obj.type.kind === "union") {
12689
+ // A receiver that may hold a missing value (a field assigned an
12690
+ // unchecked read): Node's member-read TypeError for the unit arm.
12691
+ const present = lowerer.runtimeOptionalPropertyReceiver(receiverNode, obj, receiverType, fieldName);
12692
+ if (present)
12693
+ return present;
12535
12694
  const helper = lowerer.narrowedArmHelper(obj.type.unionId, receiverType, locOf(receiverNode));
12536
12695
  obj = helper
12537
12696
  ? lowerer.checkedNarrowCall(helper, obj, receiverType, locOf(receiverNode))