@scriptc/compiler 0.2.8 → 0.2.10

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 (275) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +1 -0
  2. package/dist/backend/cycle-analysis.d.ts +56 -0
  3. package/dist/backend/cycle-analysis.js +222 -28
  4. package/dist/backend/cycle-analysis.js.map +1 -1
  5. package/dist/backend/cycle-analysis.test.js +232 -15
  6. package/dist/backend/cycle-analysis.test.js.map +1 -1
  7. package/dist/backend/cycle-mutability.d.ts +27 -6
  8. package/dist/backend/cycle-mutability.js +110 -17
  9. package/dist/backend/cycle-mutability.js.map +1 -1
  10. package/dist/backend/immutability.d.ts +57 -0
  11. package/dist/backend/immutability.js +178 -0
  12. package/dist/backend/immutability.js.map +1 -0
  13. package/dist/backend/immutability.test.d.ts +1 -0
  14. package/dist/backend/immutability.test.js +131 -0
  15. package/dist/backend/immutability.test.js.map +1 -0
  16. package/dist/backend/llvm/alloc.d.ts +13 -3
  17. package/dist/backend/llvm/alloc.js +24 -10
  18. package/dist/backend/llvm/alloc.js.map +1 -1
  19. package/dist/backend/llvm/alloc.test.js +5 -5
  20. package/dist/backend/llvm/alloc.test.js.map +1 -1
  21. package/dist/backend/llvm/borrowed-inputs.d.ts +9 -2
  22. package/dist/backend/llvm/borrowed-inputs.js +31 -3
  23. package/dist/backend/llvm/borrowed-inputs.js.map +1 -1
  24. package/dist/backend/llvm/byte-numbers.js +2 -3
  25. package/dist/backend/llvm/byte-numbers.js.map +1 -1
  26. package/dist/backend/llvm/classes.d.ts +28 -0
  27. package/dist/backend/llvm/classes.js +51 -10
  28. package/dist/backend/llvm/classes.js.map +1 -1
  29. package/dist/backend/llvm/classes.test.js +129 -1
  30. package/dist/backend/llvm/classes.test.js.map +1 -1
  31. package/dist/backend/llvm/common.d.ts +30 -0
  32. package/dist/backend/llvm/common.js +41 -0
  33. package/dist/backend/llvm/common.js.map +1 -1
  34. package/dist/backend/llvm/dense-array-access.js +6 -5
  35. package/dist/backend/llvm/dense-array-access.js.map +1 -1
  36. package/dist/backend/llvm/dense-array-access.test.js +10 -2
  37. package/dist/backend/llvm/dense-array-access.test.js.map +1 -1
  38. package/dist/backend/llvm/dyn.d.ts +6 -4
  39. package/dist/backend/llvm/dyn.js +30 -14
  40. package/dist/backend/llvm/dyn.js.map +1 -1
  41. package/dist/backend/llvm/emitter.d.ts +109 -5
  42. package/dist/backend/llvm/emitter.js +716 -112
  43. package/dist/backend/llvm/emitter.js.map +1 -1
  44. package/dist/backend/llvm/emitter.test.js +54 -3
  45. package/dist/backend/llvm/emitter.test.js.map +1 -1
  46. package/dist/backend/llvm/expr-bytes.js +35 -28
  47. package/dist/backend/llvm/expr-bytes.js.map +1 -1
  48. package/dist/backend/llvm/expr-calls.js +14 -6
  49. package/dist/backend/llvm/expr-calls.js.map +1 -1
  50. package/dist/backend/llvm/expr-containers.js +31 -8
  51. package/dist/backend/llvm/expr-containers.js.map +1 -1
  52. package/dist/backend/llvm/expr-context.d.ts +2 -0
  53. package/dist/backend/llvm/expr-control.js +46 -5
  54. package/dist/backend/llvm/expr-control.js.map +1 -1
  55. package/dist/backend/llvm/expr-dynamic.js +3 -0
  56. package/dist/backend/llvm/expr-dynamic.js.map +1 -1
  57. package/dist/backend/llvm/expr-primitives.js +30 -3
  58. package/dist/backend/llvm/expr-primitives.js.map +1 -1
  59. package/dist/backend/llvm/expr-primitives.test.js +53 -0
  60. package/dist/backend/llvm/expr-primitives.test.js.map +1 -1
  61. package/dist/backend/llvm/expr-stream-bridges.d.ts +3 -0
  62. package/dist/backend/llvm/expr-stream-bridges.js +76 -14
  63. package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
  64. package/dist/backend/llvm/lib-filesystem.js +18 -2
  65. package/dist/backend/llvm/lib-filesystem.js.map +1 -1
  66. package/dist/backend/llvm/lib-shared.js +1 -0
  67. package/dist/backend/llvm/lib-shared.js.map +1 -1
  68. package/dist/backend/llvm/local-array-reads.d.ts +17 -6
  69. package/dist/backend/llvm/local-array-reads.js +157 -45
  70. package/dist/backend/llvm/local-array-reads.js.map +1 -1
  71. package/dist/backend/llvm/loop-array-borrows.d.ts +5 -2
  72. package/dist/backend/llvm/loop-array-borrows.js +27 -8
  73. package/dist/backend/llvm/loop-array-borrows.js.map +1 -1
  74. package/dist/backend/llvm/map-number-lookup.d.ts +7 -0
  75. package/dist/backend/llvm/map-number-lookup.js +18 -1
  76. package/dist/backend/llvm/map-number-lookup.js.map +1 -1
  77. package/dist/backend/llvm/map-read-lifetimes.d.ts +5 -0
  78. package/dist/backend/llvm/map-read-lifetimes.js +14 -4
  79. package/dist/backend/llvm/map-read-lifetimes.js.map +1 -1
  80. package/dist/backend/llvm/map-read-lifetimes.test.js +5 -0
  81. package/dist/backend/llvm/map-read-lifetimes.test.js.map +1 -1
  82. package/dist/backend/llvm/reference-effects.js +34 -1
  83. package/dist/backend/llvm/reference-effects.js.map +1 -1
  84. package/dist/backend/llvm/reference-writes.d.ts +124 -0
  85. package/dist/backend/llvm/reference-writes.js +1090 -0
  86. package/dist/backend/llvm/reference-writes.js.map +1 -0
  87. package/dist/backend/llvm/shapes.d.ts +22 -8
  88. package/dist/backend/llvm/shapes.js +79 -23
  89. package/dist/backend/llvm/shapes.js.map +1 -1
  90. package/dist/backend/llvm/shapes.test.js +5 -5
  91. package/dist/backend/llvm/shapes.test.js.map +1 -1
  92. package/dist/backend/llvm/string-lifetimes.test.js +39 -10
  93. package/dist/backend/llvm/string-lifetimes.test.js.map +1 -1
  94. package/dist/backend/llvm/string-reads.d.ts +3 -0
  95. package/dist/backend/llvm/string-reads.js +45 -11
  96. package/dist/backend/llvm/string-reads.js.map +1 -1
  97. package/dist/backend/llvm/string-slices.js +1 -1
  98. package/dist/backend/llvm/switch-dispatch.d.ts +2 -2
  99. package/dist/backend/llvm/switch-dispatch.js +35 -22
  100. package/dist/backend/llvm/switch-dispatch.js.map +1 -1
  101. package/dist/backend/llvm/switch-dispatch.test.js +28 -10
  102. package/dist/backend/llvm/switch-dispatch.test.js.map +1 -1
  103. package/dist/backend/llvm/trunc.d.ts +8 -0
  104. package/dist/backend/llvm/trunc.js +28 -0
  105. package/dist/backend/llvm/trunc.js.map +1 -0
  106. package/dist/backend/llvm/walk-borrows.d.ts +8 -1
  107. package/dist/backend/llvm/walk-borrows.js +15 -4
  108. package/dist/backend/llvm/walk-borrows.js.map +1 -1
  109. package/dist/backend/loop-polls.d.ts +14 -0
  110. package/dist/backend/loop-polls.js +100 -0
  111. package/dist/backend/loop-polls.js.map +1 -0
  112. package/dist/backend/loop-polls.test.d.ts +1 -0
  113. package/dist/backend/loop-polls.test.js +86 -0
  114. package/dist/backend/loop-polls.test.js.map +1 -0
  115. package/dist/backend/may-throw.d.ts +36 -2
  116. package/dist/backend/may-throw.js +328 -13
  117. package/dist/backend/may-throw.js.map +1 -1
  118. package/dist/backend/may-throw.test.js +169 -1
  119. package/dist/backend/may-throw.test.js.map +1 -1
  120. package/dist/backend/native-codegen.d.ts +1 -3
  121. package/dist/backend/native-codegen.js +4 -32
  122. package/dist/backend/native-codegen.js.map +1 -1
  123. package/dist/backend/publication.d.ts +5 -0
  124. package/dist/backend/publication.js +10 -0
  125. package/dist/backend/publication.js.map +1 -1
  126. package/dist/backend/runtime-abi.d.ts +2 -2
  127. package/dist/backend/runtime-abi.js +2 -2
  128. package/dist/backend/runtime-abi.js.map +1 -1
  129. package/dist/backend/runtime-pack-targets.test.js +1 -1
  130. package/dist/backend/runtime-pack-targets.test.js.map +1 -1
  131. package/dist/backend/runtime-pack.test.js +1 -1
  132. package/dist/backend/runtime-pack.test.js.map +1 -1
  133. package/dist/cli/command.js +8 -1
  134. package/dist/cli/command.js.map +1 -1
  135. package/dist/cli/output-options.d.ts +1 -0
  136. package/dist/cli/output-options.js +3 -0
  137. package/dist/cli/output-options.js.map +1 -1
  138. package/dist/cli/usage.d.ts +5 -1
  139. package/dist/cli/usage.js +6 -0
  140. package/dist/cli/usage.js.map +1 -1
  141. package/dist/compile-types.d.ts +5 -0
  142. package/dist/contracts/check.d.ts +8 -0
  143. package/dist/contracts/check.js +1577 -0
  144. package/dist/contracts/check.js.map +1 -0
  145. package/dist/executable/executable-cache.d.ts +2 -0
  146. package/dist/executable/executable-cache.js +3 -0
  147. package/dist/executable/executable-cache.js.map +1 -1
  148. package/dist/executable/executable-cache.test.js +1 -0
  149. package/dist/executable/executable-cache.test.js.map +1 -1
  150. package/dist/executable/prepare.js +9 -0
  151. package/dist/executable/prepare.js.map +1 -1
  152. package/dist/frontend/contract-types.d.ts +50 -0
  153. package/dist/frontend/contract-types.js +232 -0
  154. package/dist/frontend/contract-types.js.map +1 -0
  155. package/dist/frontend/lowering/array-values.js +37 -1
  156. package/dist/frontend/lowering/array-values.js.map +1 -1
  157. package/dist/frontend/lowering/builtins/strings.js +4 -1
  158. package/dist/frontend/lowering/builtins/strings.js.map +1 -1
  159. package/dist/frontend/lowering/call-arguments.js +96 -4
  160. package/dist/frontend/lowering/call-arguments.js.map +1 -1
  161. package/dist/frontend/lowering/class-method-mutations.js +88 -0
  162. package/dist/frontend/lowering/class-method-mutations.js.map +1 -1
  163. package/dist/frontend/lowering/coercions/unions.d.ts +12 -0
  164. package/dist/frontend/lowering/coercions/unions.js +63 -0
  165. package/dist/frontend/lowering/coercions/unions.js.map +1 -1
  166. package/dist/frontend/lowering/containers/bytes.js +15 -4
  167. package/dist/frontend/lowering/containers/bytes.js.map +1 -1
  168. package/dist/frontend/lowering/expressions/optional-chains.js +4 -1
  169. package/dist/frontend/lowering/expressions/optional-chains.js.map +1 -1
  170. package/dist/frontend/lowering/expressions/union-fields.d.ts +19 -0
  171. package/dist/frontend/lowering/expressions/union-fields.js +236 -0
  172. package/dist/frontend/lowering/expressions/union-fields.js.map +1 -0
  173. package/dist/frontend/lowering/lower-calls.js +21 -4
  174. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  175. package/dist/frontend/lowering/lower-exprs.d.ts +6 -0
  176. package/dist/frontend/lowering/lower-exprs.js +245 -4
  177. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  178. package/dist/frontend/lowering/lower-island.js +5 -19
  179. package/dist/frontend/lowering/lower-island.js.map +1 -1
  180. package/dist/frontend/lowering/lower-stmts.js +11 -1
  181. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  182. package/dist/frontend/lowering/lowerer.d.ts +14 -0
  183. package/dist/frontend/lowering/lowerer.js +221 -8
  184. package/dist/frontend/lowering/lowerer.js.map +1 -1
  185. package/dist/frontend/lowering/nan-coded-reads.d.ts +161 -0
  186. package/dist/frontend/lowering/nan-coded-reads.js +1342 -0
  187. package/dist/frontend/lowering/nan-coded-reads.js.map +1 -0
  188. package/dist/frontend/lowering/narrowing-stability.d.ts +100 -0
  189. package/dist/frontend/lowering/narrowing-stability.js +1174 -0
  190. package/dist/frontend/lowering/narrowing-stability.js.map +1 -0
  191. package/dist/frontend/lowering/narrowing-stability.test.d.ts +1 -0
  192. package/dist/frontend/lowering/narrowing-stability.test.js +150 -0
  193. package/dist/frontend/lowering/narrowing-stability.test.js.map +1 -0
  194. package/dist/frontend/lowering/runtime-optional-bounds.d.ts +14 -2
  195. package/dist/frontend/lowering/runtime-optional-bounds.js +17 -11
  196. package/dist/frontend/lowering/runtime-optional-bounds.js.map +1 -1
  197. package/dist/frontend/lowering/runtime-optional-bounds.test.js +28 -0
  198. package/dist/frontend/lowering/runtime-optional-bounds.test.js.map +1 -1
  199. package/dist/frontend/lowering/surfaces.js +8 -0
  200. package/dist/frontend/lowering/surfaces.js.map +1 -1
  201. package/dist/frontend/program.js +1 -1
  202. package/dist/frontend/type-mapper.js +43 -0
  203. package/dist/frontend/type-mapper.js.map +1 -1
  204. package/dist/index.d.ts +1 -1
  205. package/dist/index.js +20 -8
  206. package/dist/index.js.map +1 -1
  207. package/dist/ir/builtin-signatures.d.ts +1 -1
  208. package/dist/ir/builtin-signatures.js +2 -0
  209. package/dist/ir/builtin-signatures.js.map +1 -1
  210. package/dist/ir/constant-globals.d.ts +14 -0
  211. package/dist/ir/constant-globals.js +81 -0
  212. package/dist/ir/constant-globals.js.map +1 -0
  213. package/dist/ir/constant-globals.test.d.ts +1 -0
  214. package/dist/ir/constant-globals.test.js +97 -0
  215. package/dist/ir/constant-globals.test.js.map +1 -0
  216. package/dist/ir/final-fields.d.ts +35 -0
  217. package/dist/ir/final-fields.js +187 -0
  218. package/dist/ir/final-fields.js.map +1 -0
  219. package/dist/ir/final-fields.test.d.ts +1 -0
  220. package/dist/ir/final-fields.test.js +106 -0
  221. package/dist/ir/final-fields.test.js.map +1 -0
  222. package/dist/ir/int32-slots.d.ts +35 -0
  223. package/dist/ir/int32-slots.js +217 -104
  224. package/dist/ir/int32-slots.js.map +1 -1
  225. package/dist/ir/int32-slots.test.js +27 -0
  226. package/dist/ir/int32-slots.test.js.map +1 -1
  227. package/dist/ir/integer-loops.d.ts +5 -0
  228. package/dist/ir/integer-loops.js +23 -4
  229. package/dist/ir/integer-loops.js.map +1 -1
  230. package/dist/ir/integer-loops.test.js +43 -1
  231. package/dist/ir/integer-loops.test.js.map +1 -1
  232. package/dist/ir/integer-ranges.d.ts +20 -0
  233. package/dist/ir/integer-ranges.js +178 -21
  234. package/dist/ir/integer-ranges.js.map +1 -1
  235. package/dist/ir/integer-ranges.test.js +148 -3
  236. package/dist/ir/integer-ranges.test.js.map +1 -1
  237. package/dist/ir/ir.d.ts +38 -1
  238. package/dist/ir/ir.js +4 -0
  239. package/dist/ir/ir.js.map +1 -1
  240. package/dist/ir/serialize.d.ts +17 -3
  241. package/dist/ir/serialize.js +198 -39
  242. package/dist/ir/serialize.js.map +1 -1
  243. package/dist/ir/serialize.test.js +65 -1
  244. package/dist/ir/serialize.test.js.map +1 -1
  245. package/dist/ir/unshare.d.ts +14 -0
  246. package/dist/ir/unshare.js +60 -0
  247. package/dist/ir/unshare.js.map +1 -0
  248. package/dist/ir/unshare.test.d.ts +1 -0
  249. package/dist/ir/unshare.test.js +115 -0
  250. package/dist/ir/unshare.test.js.map +1 -0
  251. package/dist/library/prepare.js +9 -0
  252. package/dist/library/prepare.js.map +1 -1
  253. package/dist/native/cache.d.ts +8 -1
  254. package/dist/native/cache.js +38 -1
  255. package/dist/native/cache.js.map +1 -1
  256. package/dist/native/cache.test.js +11 -0
  257. package/dist/native/cache.test.js.map +1 -1
  258. package/dist/native/compiler.js +4 -3
  259. package/dist/native/compiler.js.map +1 -1
  260. package/dist/native/prepare.d.ts +3 -1
  261. package/dist/native/prepare.js +63 -28
  262. package/dist/native/prepare.js.map +1 -1
  263. package/dist/native/prepare.test.js +23 -1
  264. package/dist/native/prepare.test.js.map +1 -1
  265. package/dist/native/text-file.d.ts +10 -0
  266. package/dist/native/text-file.js +41 -0
  267. package/dist/native/text-file.js.map +1 -0
  268. package/dist/text-parts.d.ts +5 -0
  269. package/dist/text-parts.js +36 -0
  270. package/dist/text-parts.js.map +1 -0
  271. package/package.json +20 -20
  272. package/surface-manifest.json +7 -1
  273. package/dist/frontend/lowering/expressions/union-field-write.d.ts +0 -8
  274. package/dist/frontend/lowering/expressions/union-field-write.js +0 -108
  275. 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. */
@@ -19,8 +19,9 @@ import { constituentTypes } from "../ts7/checker.js";
19
19
  import { dirname } from "node:path";
20
20
  import * as posix from "node:path/posix";
21
21
  import { checkedClassAssertion, checkedClassUnionAssertion } from "./class-assertions.js";
22
+ import { checkedArmExtraction } from "./coercions/unions.js";
22
23
  import { narrowClassUnion, narrowStoredClassValue } from "./class-unions.js";
23
- import { lowerUnionFieldWrite } from "./expressions/union-field-write.js";
24
+ import { lowerUnionFieldRead, lowerUnionFieldWrite } from "./expressions/union-fields.js";
24
25
  import { captureContextArguments } from "./function-context.js";
25
26
  import { OBJECT_CALLABLE_VALUES } from "./surfaces.js";
26
27
  import { wasiGuestPath } from "../../wasi-paths.js";
@@ -167,7 +168,14 @@ export function lowerExpr(lowerer, expr) {
167
168
  }
168
169
  lowerExprDepth++;
169
170
  try {
170
- const lowered = lowerExprInner(lowerer, expr);
171
+ let lowered = lowerExprInner(lowerer, expr);
172
+ const nanCoded = lowerer.nanCodedReads;
173
+ if (nanCoded) {
174
+ if (lowered.type.kind === "f64" && nanCoded.reboxes(expr))
175
+ lowered = reboxNanCoded(lowerer, lowered, locOf(expr));
176
+ else if (lowered.type.kind === "union" && nanCoded.convertsSource(expr))
177
+ lowered = lowerOptionalNumber(lowerer, lowered, locOf(expr));
178
+ }
171
179
  if (lowered.type.kind === "void" && (lowerer.typeOf(expr).flags & ts.TypeFlags.Never) !== 0)
172
180
  lowerer.neverValued.add(lowered);
173
181
  return lowered;
@@ -176,6 +184,27 @@ export function lowerExpr(lowerer, expr) {
176
184
  lowerExprDepth--;
177
185
  }
178
186
  }
187
+ /** `number | undefined` from a NaN-coded double whose slot never holds a
188
+ * genuine NaN (nan-coded-reads.ts): NaN is exactly undefined. */
189
+ function reboxNanCoded(lowerer, value, loc) {
190
+ const optional = lowerer.withUndefinedArm(F64);
191
+ const stmts = [];
192
+ let stable = value;
193
+ if (value.kind !== "varRef") {
194
+ const tmp = lowerer.declareHiddenLocal("%nanCoded", F64);
195
+ stmts.push({ kind: "varDecl", localId: tmp.id, init: value, loc });
196
+ stable = varRef(tmp.id, F64, loc);
197
+ }
198
+ const result = {
199
+ kind: "ternary",
200
+ cond: { kind: "libCall", fn: "num.isNaN", args: [stable], type: BOOL, loc },
201
+ then: lowerer.wrappedUndefined(optional, loc),
202
+ else_: lowerer.coerceToExpected(stable, optional),
203
+ type: optional,
204
+ loc,
205
+ };
206
+ return stmts.length === 0 ? result : { kind: "seqExpr", stmts, result, type: optional, loc };
207
+ }
179
208
  function lowerExprInner(lowerer, expr) {
180
209
  const loc = locOf(expr);
181
210
  // An optional chain's guarded receiver: already evaluated by the
@@ -797,6 +826,13 @@ function lowerExprInner(lowerer, expr) {
797
826
  // tests, so only side-effect-free reads compose — and when every arm
798
827
  // agrees the whole expression folds to that one string (dropping only
799
828
  // the pure read, the trust-the-checker bet lowerUnitComparison makes).
829
+ // An effectful producer (a getter) evaluates once into a hidden local.
830
+ const prelude = [];
831
+ if (operand.type.kind === "union" && !isSafeToRepeat(operand)) {
832
+ const local = lowerer.declareHiddenLocal("%typeofOperand", operand.type);
833
+ prelude.push({ kind: "varDecl", localId: local.id, init: operand, loc });
834
+ operand = varRef(local.id, operand.type, loc);
835
+ }
800
836
  if (operand.type.kind === "union" && isSafeToRepeat(operand)) {
801
837
  const def = lowerer.unions.get(operand.type.unionId);
802
838
  const answers = def?.arms.map(typeofAnswer);
@@ -847,7 +883,9 @@ function lowerExprInner(lowerer, expr) {
847
883
  loc,
848
884
  };
849
885
  }
850
- return result;
886
+ return prelude.length > 0
887
+ ? { kind: "seqExpr", stmts: prelude, result, type: STRING, loc }
888
+ : result;
851
889
  }
852
890
  }
853
891
  if (operand.type.kind === "dyn") {
@@ -2209,6 +2247,11 @@ function lowerExprInner(lowerer, expr) {
2209
2247
  handled.kind === "recordGet" &&
2210
2248
  lowerer.isRuntimeOptionalField(handled.shapeId, handled.field))
2211
2249
  return handled;
2250
+ // A class field assigned a missing read: the checker narrows this
2251
+ // read to the assigned value's bare type, but the stored undefined
2252
+ // arm is real (`obj.n = bytes[i]; obj.n` is undefined in Node).
2253
+ if (runtimeOptionalClassField(lowerer, handled, expr))
2254
+ return handled;
2212
2255
  return lowerer.maybeNarrow(handled, expr);
2213
2256
  }
2214
2257
  // `globalThis.<name>` that no lowering above claimed
@@ -3116,6 +3159,11 @@ export function maybeNarrow(lowerer, expr, node) {
3116
3159
  if (narrowed?.kind === "object" &&
3117
3160
  narrowed.className !== expr.type.className &&
3118
3161
  lowerer.isSubclassOf(narrowed.className, expr.type.className)) {
3162
+ if (lowerer.narrowing.untrusted(node, narrowed)) {
3163
+ const checked = checkedClassAssertion(lowerer, expr, narrowed, expr.loc);
3164
+ if (checked)
3165
+ return checked;
3166
+ }
3119
3167
  return { kind: "downcast", value: expr, type: narrowed, loc: expr.loc };
3120
3168
  }
3121
3169
  return expr;
@@ -3158,6 +3206,9 @@ export function maybeNarrow(lowerer, expr, node) {
3158
3206
  const arrayTags = lowerer.arrayValueTags(expr.type.unionId);
3159
3207
  if (arrayTags.length === 1) {
3160
3208
  const arm = def.arms[arrayTags[0]];
3209
+ const checked = checkedStaleArm(lowerer, node, expr, arrayTags[0], arm);
3210
+ if (checked)
3211
+ return checked;
3161
3212
  return {
3162
3213
  kind: "unionNarrow",
3163
3214
  unionId: expr.type.unionId,
@@ -3191,6 +3242,9 @@ export function maybeNarrow(lowerer, expr, node) {
3191
3242
  const values = literalValues(lowerer.checker.getTypeOfSymbol(property));
3192
3243
  const arm = values && literalUnionArm(def, values, (id) => lowerer.shapes.get(id));
3193
3244
  if (arm) {
3245
+ const checked = checkedStaleArm(lowerer, node, expr, lowerer.armTag(expr.type.unionId, arm), arm);
3246
+ if (checked)
3247
+ return checked;
3194
3248
  return {
3195
3249
  kind: "unionNarrow",
3196
3250
  unionId: expr.type.unionId,
@@ -3216,6 +3270,10 @@ export function maybeNarrow(lowerer, expr, node) {
3216
3270
  arm.kind !== "object" ||
3217
3271
  !lowerer.isSubclassOf(narrowed.className, arm.className)) &&
3218
3272
  lowerer.isSubclassOf(narrowed.className, valueType.className)) {
3273
+ const checked = checkedStaleArm(lowerer, node, expr, valueTag, valueType, narrowed);
3274
+ if (checked) {
3275
+ return checkedClassAssertion(lowerer, checked, narrowed, expr.loc) ?? checked;
3276
+ }
3219
3277
  return {
3220
3278
  kind: "downcast",
3221
3279
  value: {
@@ -3232,6 +3290,9 @@ export function maybeNarrow(lowerer, expr, node) {
3232
3290
  }
3233
3291
  return expr;
3234
3292
  }
3293
+ const checked = checkedStaleArm(lowerer, node, expr, tag, narrowed);
3294
+ if (checked)
3295
+ return checked;
3235
3296
  return {
3236
3297
  kind: "unionNarrow",
3237
3298
  unionId: expr.type.unionId,
@@ -3241,6 +3302,60 @@ export function maybeNarrow(lowerer, expr, node) {
3241
3302
  loc: expr.loc,
3242
3303
  };
3243
3304
  }
3305
+ /** The checked form of maybeNarrow's arm extraction for a narrowing the
3306
+ * checker may hold past code that replaced the value (a call clearing the
3307
+ * property; see NarrowingStability). Every other arm throws the TypeError
3308
+ * Node raises for the operation consuming the read (`Cannot read
3309
+ * properties of undefined (reading 'x')`, `o.f is not a function`), or the
3310
+ * stranded-value TypeError where Node would carry the value on. Null when
3311
+ * the narrowing is provably current. `narrowed` is the checker's type when
3312
+ * it refines the arm (a subclass view). */
3313
+ function checkedStaleArm(lowerer, node, value, tag, arm, narrowed = arm) {
3314
+ if (value.type.kind !== "union" || tag < 0)
3315
+ return null;
3316
+ if (!lowerer.narrowing.untrusted(node, narrowed))
3317
+ return null;
3318
+ const inPlace = checkedArmExtraction(lowerer, value, tag, arm, (found) => staleReadMessage(node, found));
3319
+ if (inPlace)
3320
+ return inPlace;
3321
+ const helper = lowerer.narrowedArmHelper(value.type.unionId, arm, value.loc);
3322
+ return helper ? lowerer.checkedNarrowCall(helper, value, arm, value.loc) : null;
3323
+ }
3324
+ /** Node's TypeError for the operation consuming `node` when it holds a
3325
+ * value of type `found`, or null where Node would not throw. */
3326
+ function staleReadMessage(node, found) {
3327
+ let child = node;
3328
+ let parent = node.parent;
3329
+ while (ts.isParenthesizedExpression(parent)) {
3330
+ child = parent;
3331
+ parent = parent.parent;
3332
+ }
3333
+ if (ts.isCallExpression(parent) && parent.expression === child && !parent.questionDotToken) {
3334
+ return found.kind === "func" ? null : `${node.getText()} is not a function`;
3335
+ }
3336
+ const unit = found.kind === "undefinedT" ? "undefined" : found.kind === "nullT" ? "null" : null;
3337
+ if (unit === null)
3338
+ return null;
3339
+ if (!(ts.isPropertyAccessExpression(parent) || ts.isElementAccessExpression(parent)) ||
3340
+ parent.expression !== child ||
3341
+ parent.questionDotToken)
3342
+ return null;
3343
+ let key = null;
3344
+ if (ts.isPropertyAccessExpression(parent))
3345
+ key = parent.name.text;
3346
+ else if (ts.isStringLiteral(parent.argumentExpression))
3347
+ key = parent.argumentExpression.text;
3348
+ else if (ts.isNumericLiteral(parent.argumentExpression))
3349
+ key = String(Number(parent.argumentExpression.text));
3350
+ if (key === null)
3351
+ return null;
3352
+ const store = parent.parent;
3353
+ return ts.isBinaryExpression(store) &&
3354
+ store.left === parent &&
3355
+ store.operatorToken.kind === ts.SyntaxKind.EqualsToken
3356
+ ? `Cannot set properties of ${unit} (setting '${key}')`
3357
+ : `Cannot read properties of ${unit} (reading '${key}')`;
3358
+ }
3244
3359
  /** A runtime-optional slot can be assigned while a truthy guard is in
3245
3360
  * force. TypeScript then types a direct member receiver as the plain arm,
3246
3361
  * even though an OOB-safe assignment may have restored undefined. Check
@@ -5582,6 +5697,34 @@ export function lowerOptionalNumber(lowerer, operand, loc, narrowedNode) {
5582
5697
  ? value
5583
5698
  : { kind: "seqExpr", stmts: prefix, result: value, type: F64, loc };
5584
5699
  }
5700
+ /** A number argument of a builtin that converts it with ToNumber (Math
5701
+ * functions, String.fromCharCode, the global isNaN/isFinite, DataView
5702
+ * offsets and values). ToNumber(undefined) is NaN, so a missing value — an
5703
+ * out-of-range read, a local or field holding one, or a hole — converts to
5704
+ * NaN instead of trusting the checker's number type. */
5705
+ export function lowerToNumberArgument(lowerer, node) {
5706
+ let inner = node;
5707
+ while (ts.isParenthesizedExpression(inner))
5708
+ inner = inner.expression;
5709
+ const loc = locOf(inner);
5710
+ const isOptionalNumber = (value) => value.type.kind === "union" &&
5711
+ lowerer.armTag(value.type.unionId, UNDEFINED_T) >= 0 &&
5712
+ lowerer.stripUndefinedArm(value.type).kind === "f64";
5713
+ const optional = ts.isIdentifier(inner)
5714
+ ? runtimeOptionalStorageOperand(lowerer, inner)
5715
+ : ts.isElementAccessExpression(inner)
5716
+ ? lowerAbsenceProbe(lowerer, inner)
5717
+ : null;
5718
+ if (optional && isOptionalNumber(optional))
5719
+ return lowerOptionalNumber(lowerer, optional, loc, inner);
5720
+ // A conditional builds each branch for the destination.
5721
+ if (ts.isConditionalExpression(inner))
5722
+ return lowerer.lowerExprExpecting(node, F64);
5723
+ const value = lowerer.lowerExpr(node);
5724
+ if (isOptionalNumber(value))
5725
+ return lowerOptionalNumber(lowerer, value, loc, inner);
5726
+ return lowerer.coerceInto(node, value, F64);
5727
+ }
5585
5728
  /** `a[i]` reads. Only f64 indices into array receivers are modeled; JS
5586
5729
  * string-key element access (`a["length"]`) and string indexing (`s[0]`
5587
5730
  * typechecks against the lib's index signature; use .charAt) stay out. */
@@ -5866,7 +6009,10 @@ export function lowerElementAccess(lowerer, expr) {
5866
6009
  // `t[i]!` asserts presence: the numeric element read (NaN when
5867
6010
  // invalid). Every other read answers undefined for an invalid index,
5868
6011
  // like Node.
5869
- if (ts.isNonNullExpression(expr.parent) && expr.parent.expression === expr) {
6012
+ // A read whose every consumer treats undefined like NaN (see
6013
+ // nan-coded-reads.ts) is the same plain numeric read.
6014
+ if ((ts.isNonNullExpression(expr.parent) && expr.parent.expression === expr) ||
6015
+ lowerer.nanCodedReads?.isNumericRead(expr) === true) {
5870
6016
  return {
5871
6017
  kind: "bytesIntrinsic",
5872
6018
  method: "get",
@@ -6529,6 +6675,23 @@ export function runtimeOptionalStorageOperand(lowerer, node) {
6529
6675
  return null;
6530
6676
  return optional.value;
6531
6677
  }
6678
+ /** A `T | undefined` class field read that the checker narrowed to `T`
6679
+ * where the field was assigned a value that may be missing (an unchecked
6680
+ * indexed read). TypeScript's assignment narrowing is not a runtime fact
6681
+ * there, so the read keeps the stored union instead of extracting `T`. */
6682
+ function runtimeOptionalClassField(lowerer, value, node) {
6683
+ if (value.kind !== "fieldGet" || value.type.kind !== "union")
6684
+ return false;
6685
+ if (lowerer.armTag(value.type.unionId, UNDEFINED_T) < 0)
6686
+ return false;
6687
+ if ((lowerer.unions.get(value.type.unionId)?.arms.length ?? 0) !== 2)
6688
+ return false;
6689
+ const symbol = lowerer.checker.getSymbolAtLocation(node.name);
6690
+ if (!symbol || !lowerer.runtimeOptionalAssignedSymbols.has(symbol))
6691
+ return false;
6692
+ const narrowed = lowerer.mapTypeOf(lowerer.typeOf(node));
6693
+ return narrowed !== null && typeEquals(narrowed, lowerer.stripUndefinedArm(value.type));
6694
+ }
6532
6695
  /** A presence-test operand: an absence-aware element or field read, an
6533
6696
  * identifier's runtime-optional storage, or the ordinary lowering. */
6534
6697
  export function lowerPresenceOperand(lowerer, node) {
@@ -8298,12 +8461,75 @@ export function lowerCompoundValueToTarget(lowerer, expr, compound, target) {
8298
8461
  }
8299
8462
  lowerer.unsupported("SC1043", expr);
8300
8463
  }
8464
+ /** Equality of two NaN-coded values (nan-coded-reads.ts), each of which may
8465
+ * stand for undefined and never holds a genuine NaN: Node's undefined equals
8466
+ * undefined, so two NaNs compare equal here. Operands evaluate once, in
8467
+ * order. */
8468
+ function lowerNanCodedEquality(lowerer, expr) {
8469
+ const loc = locOf(expr);
8470
+ const left = lowerer.lowerExpr(expr.left);
8471
+ const right = lowerer.lowerExpr(expr.right);
8472
+ if (left.type.kind !== "f64" || right.type.kind !== "f64") {
8473
+ throw new InternalCompilerError("NaN-coded equality operand is not a plain number");
8474
+ }
8475
+ const stmts = [];
8476
+ const stable = (value, name) => {
8477
+ if (value.kind === "varRef" || value.kind === "numLit")
8478
+ return value;
8479
+ const tmp = lowerer.declareHiddenLocal(name, F64);
8480
+ stmts.push({ kind: "varDecl", localId: tmp.id, init: value, loc });
8481
+ return varRef(tmp.id, F64, loc);
8482
+ };
8483
+ const a = stable(left, "%eqLeft");
8484
+ const b = stable(right, "%eqRight");
8485
+ const isNaN = (value) => ({
8486
+ kind: "libCall",
8487
+ fn: "num.isNaN",
8488
+ args: [value],
8489
+ type: BOOL,
8490
+ loc,
8491
+ });
8492
+ const bothUndefined = {
8493
+ kind: "logical",
8494
+ op: "&&",
8495
+ left: isNaN(a),
8496
+ right: isNaN(b),
8497
+ type: BOOL,
8498
+ loc,
8499
+ };
8500
+ const op = expr.operatorToken.kind;
8501
+ const negated = op === ts.SyntaxKind.ExclamationEqualsEqualsToken ||
8502
+ op === ts.SyntaxKind.ExclamationEqualsToken;
8503
+ const result = negated
8504
+ ? {
8505
+ kind: "logical",
8506
+ op: "&&",
8507
+ left: { kind: "bin", op: "!==", left: a, right: b, type: BOOL, loc },
8508
+ right: { kind: "unary", op: "!", operand: bothUndefined, type: BOOL, loc },
8509
+ type: BOOL,
8510
+ loc,
8511
+ }
8512
+ : {
8513
+ kind: "logical",
8514
+ op: "||",
8515
+ left: { kind: "bin", op: "===", left: a, right: b, type: BOOL, loc },
8516
+ right: bothUndefined,
8517
+ type: BOOL,
8518
+ loc,
8519
+ };
8520
+ return stmts.length === 0 ? result : { kind: "seqExpr", stmts, result, type: BOOL, loc };
8521
+ }
8301
8522
  export function lowerBinary(lowerer, expr) {
8302
8523
  const loc = locOf(expr);
8303
8524
  const op = expr.operatorToken.kind;
8304
8525
  const cacheHas = lowerRequireCacheHas(lowerer, expr);
8305
8526
  if (cacheHas)
8306
8527
  return cacheHas;
8528
+ if (lowerer.nanCodedReads?.needsUndefinedEquality(expr)) {
8529
+ const undefinedEquality = lowerNanCodedEquality(lowerer, expr);
8530
+ if (undefinedEquality)
8531
+ return undefinedEquality;
8532
+ }
8307
8533
  if (op === ts.SyntaxKind.EqualsToken ||
8308
8534
  (op >= ts.SyntaxKind.FirstCompoundAssignment && op <= ts.SyntaxKind.LastCompoundAssignment)) {
8309
8535
  const urlWrite = lowerUrlAssignment(lowerer, expr);
@@ -12246,6 +12472,11 @@ export function lowerUnionProperty(lowerer, expr) {
12246
12472
  const helper = lowerer.narrowedRetagHelper(expr.expression, value.type.unionId, receiverIr.unionId, locOf(expr));
12247
12473
  if (helper)
12248
12474
  value = { kind: "call", callee: helper, args: [value], type: receiverIr, loc: locOf(expr) };
12475
+ // Optional storage of a base class (a runtime-optional binding holding
12476
+ // `Base | undefined`) that `instanceof` narrowed to several subclasses:
12477
+ // extract the instance and test its class, as typed slots do.
12478
+ else
12479
+ value = narrowStoredClassValue(lowerer, value, receiverIr) ?? value;
12249
12480
  }
12250
12481
  // A checker-union receiver whose VALUE lowered to a plain RECORD (the
12251
12482
  // merged-signature fiction — `runner(cmd, args)` where runner joined
@@ -12368,6 +12599,11 @@ export function lowerUnionProperty(lowerer, expr) {
12368
12599
  const keyed = lowerUnionKeyedRead(lowerer, expr, value.type.unionId, value, key, field);
12369
12600
  if (keyed)
12370
12601
  return keyed;
12602
+ // Class arms declaring the field with compatible but different types
12603
+ // (`T | undefined` beside `T`): per-arm slot reads widened to one type.
12604
+ const widened = lowerUnionFieldRead(lowerer, expr, value);
12605
+ if (widened)
12606
+ return widened;
12371
12607
  // JavaScript overloads commonly inspect a class brand on a scalar-or-
12372
12608
  // instance argument (e.g. Matrix4.makeTranslation). Preserve primitive
12373
12609
  // missing-property semantics and dispatch class data through its native
@@ -12532,6 +12768,11 @@ function classFieldTarget(lowerer, receiverNode, receiverType, fieldName, saved)
12532
12768
  const lowerObjectReceiver = () => {
12533
12769
  let obj = represented;
12534
12770
  if (obj.type.kind === "union") {
12771
+ // A receiver that may hold a missing value (a field assigned an
12772
+ // unchecked read): Node's member-read TypeError for the unit arm.
12773
+ const present = lowerer.runtimeOptionalPropertyReceiver(receiverNode, obj, receiverType, fieldName);
12774
+ if (present)
12775
+ return present;
12535
12776
  const helper = lowerer.narrowedArmHelper(obj.type.unionId, receiverType, locOf(receiverNode));
12536
12777
  obj = helper
12537
12778
  ? lowerer.checkedNarrowCall(helper, obj, receiverType, locOf(receiverNode))