@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
@@ -1,8 +1,11 @@
1
1
  import { type CycleInitFlag } from "./cycle-initialization.js";
2
2
  import type { FieldLift } from "./coercions/structural-plans.js";
3
3
  import { CoercionState } from "./coercions/state.js";
4
+ import { NarrowingStability } from "./narrowing-stability.js";
4
5
  import { StringIndexBounds } from "./string-index-bounds.js";
6
+ import { NanCodedReads } from "./nan-coded-reads.js";
5
7
  import { RuntimeOptionalLocals } from "./runtime-optional-locals.js";
8
+ import { type ContractAnnotations } from "../contract-types.js";
6
9
  import type { WidthLift } from "./width-lift.js";
7
10
  import { type FnCtx } from "./function-context.js";
8
11
  import type { FrontendServices } from "../services.js";
@@ -87,6 +90,9 @@ export interface LowerResult {
87
90
  /** Present iff diagnostics is empty. */
88
91
  module: IrModule | null;
89
92
  diagnostics: ScrDiagnostic[];
93
+ /** The program's written contract types (@scriptc/types), keyed by the
94
+ * source locations the IR carries; absent when it writes none. */
95
+ contracts?: ContractAnnotations;
90
96
  /** JS statements whose compile fences DEFERRED to runtime (runtimeFence
91
97
  * statements in the module) — off the build, on the coverage report. */
92
98
  runtimeFences: ScrDiagnostic[];
@@ -361,6 +367,10 @@ export declare class Lowerer {
361
367
  /** Per-symbol result of the never-reassigned file scan
362
368
  * (bindingNeverReassigned — object-literal generic-method receivers). */
363
369
  readonly neverReassignedCache: Map<ts.Symbol, boolean>;
370
+ /** Typed-array element reads whose every consumer treats undefined like
371
+ * NaN, which lower to plain doubles (see nan-coded-reads.ts). Settled by
372
+ * analyzeRuntimeOptionalArrayReads before any body lowers. */
373
+ nanCodedReads: NanCodedReads | null;
364
374
  private stringIndexBoundsCache;
365
375
  /** Proofs that a string element read names an existing code unit. The
366
376
  * indexed-read analysis and expression lowering share one instance, so a
@@ -471,6 +481,10 @@ export declare class Lowerer {
471
481
  * discriminant was captured. A case match narrows the captured value,
472
482
  * not that mutable binding; preserve its tagged representation on reads. */
473
483
  readonly unstableSwitchBindings: Set<ts.Symbol>;
484
+ /** Whether a checker narrowing of a mutable member or closure-assigned
485
+ * binding still holds at its use (calls do not reset TypeScript's
486
+ * narrowing). Stale ones read the declared storage or check it. */
487
+ readonly narrowing: NarrowingStability;
474
488
  /** All storage slots widened for runtime absence, including slots whose
475
489
  * current control-flow branch has temporarily narrowed the value. */
476
490
  readonly runtimeOptionalStorageLocals: Set<IrLocal>;
@@ -19,11 +19,14 @@ import { lowerUtilTypeValue, pathModuleValue, utilTypesModuleValue, } from "./lo
19
19
  import { everyExprChild, everyStmtChild, transformStmtList } from "../../ir/traverse.js";
20
20
  import { RuntimeOptionalProvenance } from "./runtime-optional-provenance.js";
21
21
  import { presentAtUse, reassignedBindings } from "./runtime-optional-guards.js";
22
+ import { NarrowingStability } from "./narrowing-stability.js";
22
23
  import { ArrayElementStates } from "./runtime-optional-elements.js";
23
24
  import { indexReadInBounds } from "./runtime-optional-bounds.js";
24
25
  import { ArrayOwnership } from "./runtime-optional-ownership.js";
25
26
  import { StringIndexBounds } from "./string-index-bounds.js";
27
+ import { NanCodedReads } from "./nan-coded-reads.js";
26
28
  import { RuntimeOptionalLocals } from "./runtime-optional-locals.js";
29
+ import { collectContractAnnotations } from "../contract-types.js";
27
30
  import { sanitizeUnregisteredClassTypes } from "./sanitize-class-types.js";
28
31
  import { UnregisteredClassTypes } from "./unregistered-class-types.js";
29
32
  import { bindingInContext, captureContextBinding, declareContextLocal, declareContextThis, } from "./function-context.js";
@@ -394,6 +397,39 @@ export function lowerToIr(program, entry, moduleOrder, options = {}) {
394
397
  timing("divergence-scan", { found: divergences.length });
395
398
  if (divergences.length > 0)
396
399
  result = { ...result, divergences };
400
+ // Contract types (@scriptc/types): written annotations, checked against
401
+ // the IR by the compile pipeline (contracts/check.ts).
402
+ // Acyclic object types name the IR unit the program lowered them to; a
403
+ // type the module never uses has no unit (mapping it now only touches the
404
+ // lowerer's tables, never the finished module).
405
+ const lowered = result.module;
406
+ const contracts = collectContractAnnotations(program.getTypeChecker(), moduleOrder, (type) => {
407
+ if (lowered === null)
408
+ return null;
409
+ const mapped = panicSafe(() => resultLowerer.mapTypeOf(type), null);
410
+ if (mapped?.kind === "object")
411
+ return lowered.classes?.some((c) => c.name === mapped.className)
412
+ ? `object:${mapped.className}`
413
+ : null;
414
+ if (mapped?.kind === "record")
415
+ return lowered.records?.some((r) => r.id === mapped.shapeId)
416
+ ? `record:${mapped.shapeId}`
417
+ : null;
418
+ if (mapped?.kind === "union")
419
+ return lowered.unions?.some((u) => u.id === mapped.unionId)
420
+ ? `union:${mapped.unionId}`
421
+ : null;
422
+ return null;
423
+ });
424
+ timing("contract-annotations", { found: contracts.count });
425
+ if (contracts.diagnostics.length > 0)
426
+ result = {
427
+ ...result,
428
+ module: null,
429
+ diagnostics: [...result.diagnostics, ...contracts.diagnostics],
430
+ };
431
+ else if (contracts.count > 0)
432
+ result = { ...result, contracts };
397
433
  if (options.coverage !== true)
398
434
  return result;
399
435
  const remainder = new Lowerer(program, entry, moduleOrder, dynamic, {
@@ -976,6 +1012,10 @@ export class Lowerer {
976
1012
  /** Per-symbol result of the never-reassigned file scan
977
1013
  * (bindingNeverReassigned — object-literal generic-method receivers). */
978
1014
  neverReassignedCache = new Map();
1015
+ /** Typed-array element reads whose every consumer treats undefined like
1016
+ * NaN, which lower to plain doubles (see nan-coded-reads.ts). Settled by
1017
+ * analyzeRuntimeOptionalArrayReads before any body lowers. */
1018
+ nanCodedReads = null;
979
1019
  stringIndexBoundsCache = null;
980
1020
  /** Proofs that a string element read names an existing code unit. The
981
1021
  * indexed-read analysis and expression lowering share one instance, so a
@@ -1214,6 +1254,10 @@ export class Lowerer {
1214
1254
  * discriminant was captured. A case match narrows the captured value,
1215
1255
  * not that mutable binding; preserve its tagged representation on reads. */
1216
1256
  unstableSwitchBindings = new Set();
1257
+ /** Whether a checker narrowing of a mutable member or closure-assigned
1258
+ * binding still holds at its use (calls do not reset TypeScript's
1259
+ * narrowing). Stale ones read the declared storage or check it. */
1260
+ narrowing = new NarrowingStability(this);
1217
1261
  /** All storage slots widened for runtime absence, including slots whose
1218
1262
  * current control-flow branch has temporarily narrowed the value. */
1219
1263
  runtimeOptionalStorageLocals = new Set();
@@ -3133,6 +3177,32 @@ export class Lowerer {
3133
3177
  }
3134
3178
  }
3135
3179
  }
3180
+ const mappedOrNull = (node) => panicSafe(() => this.mapTypeOf(this.typeOf(node)), null);
3181
+ const nanCoded = new NanCodedReads({
3182
+ symbolOf,
3183
+ declarationOf: (symbol) => this.checker.valueDeclarationOf(symbol),
3184
+ bytesElemOf: (receiver) => {
3185
+ const t = mappedOrNull(receiver);
3186
+ return t?.kind === "bytes" ? t.elem : null;
3187
+ },
3188
+ isNumber: (node) => mappedOrNull(node)?.kind === "f64",
3189
+ isString: (node) => mappedOrNull(node)?.kind === "string",
3190
+ hasLength: (node) => {
3191
+ const kind = mappedOrNull(node)?.kind;
3192
+ return kind === "array" || kind === "bytes";
3193
+ },
3194
+ isStdlibGlobal: (node, name) => this.isStdlibGlobal(node, name),
3195
+ paramIsNumber: (fn, index) => {
3196
+ const param = signatureBySymbol.get(fn)?.params[index];
3197
+ return (param !== undefined &&
3198
+ param.type.kind === "f64" &&
3199
+ (param.mode === "required" || param.mode === "omittable"));
3200
+ },
3201
+ returnIsNumber: (fn) => signatureBySymbol.get(fn)?.returnType.kind === "f64",
3202
+ methodOverridden: (fn) => (familyBySymbol.get(fn)?.length ?? 1) > 1,
3203
+ }, sourceFiles);
3204
+ nanCoded.analyze();
3205
+ this.nanCodedReads = nanCoded;
3136
3206
  const peel = (node) => {
3137
3207
  let e = node;
3138
3208
  while (ts.isParenthesizedExpression(e) ||
@@ -3252,8 +3322,11 @@ export class Lowerer {
3252
3322
  const kind = this.mapTypeOf(this.typeOf(e.expression))?.kind;
3253
3323
  if (kind === "string")
3254
3324
  return !this.stringIndexBounds.inBounds(e);
3255
- // Typed-array reads answer undefined for an invalid index too.
3256
- return kind === "array" || kind === "bytes";
3325
+ // Typed-array reads answer undefined for an invalid index too, unless
3326
+ // every consumer treats undefined like NaN.
3327
+ if (kind === "bytes")
3328
+ return !nanCoded.isNumericRead(e);
3329
+ return kind === "array";
3257
3330
  };
3258
3331
  const isDynamicObjectEntryRead = (node) => {
3259
3332
  const read = peel(node);
@@ -3337,6 +3410,13 @@ export class Lowerer {
3337
3410
  this.mapTypeOf(this.typeOf(e))?.kind === "union" &&
3338
3411
  this.armTag(this.mapTypeOf(this.typeOf(e)).unionId, UNDEFINED_T) >= 0)
3339
3412
  return true;
3413
+ // A NaN-coded value that reaches a consumer able to observe undefined
3414
+ // is rebuilt as the ordinary optional value there; every other read
3415
+ // of a NaN-coded slot is a plain number.
3416
+ if (nanCoded.reboxes(e))
3417
+ return true;
3418
+ if (nanCoded.readsNanCodedSlot(e))
3419
+ return false;
3340
3420
  if (ts.isIdentifier(e)) {
3341
3421
  const symbol = symbolOf(e);
3342
3422
  return (symbol !== null &&
@@ -3429,6 +3509,25 @@ export class Lowerer {
3429
3509
  return false;
3430
3510
  return noteFieldSymbol(symbol, name);
3431
3511
  };
3512
+ /** An instance field of a TypeScript class whose declared storage is
3513
+ * exactly `T | undefined`: an assignment from a missing read stores the
3514
+ * undefined arm while the checker narrows later reads to `T`. */
3515
+ const optionalInstanceField = (symbol) => {
3516
+ const decl = this.checker.valueDeclarationOf(symbol);
3517
+ if (!decl || !ts.isPropertyDeclaration(decl) || !ts.isIdentifier(decl.name))
3518
+ return false;
3519
+ if (ts.getCombinedModifierFlags(decl) & ts.ModifierFlags.Static)
3520
+ return false;
3521
+ const sf = decl.getSourceFile();
3522
+ if (isJsSourceFile(sf) || sf.isDeclarationFile)
3523
+ return false;
3524
+ const declared = typeOrNull(decl.name);
3525
+ const mapped = declared ? this.mapTypeOf(declared) : null;
3526
+ if (mapped?.kind !== "union" || this.armTag(mapped.unionId, UNDEFINED_T) < 0)
3527
+ return false;
3528
+ const arms = this.unions.get(mapped.unionId)?.arms ?? [];
3529
+ return (arms.length === 2 && arms.every((arm) => arm.kind === "undefinedT" || !isUnitType(arm)));
3530
+ };
3432
3531
  const noteFieldSymbol = (symbol, name) => {
3433
3532
  const set = optionalFields.get(symbol) ?? new Set();
3434
3533
  const before = set.size;
@@ -3567,14 +3666,31 @@ export class Lowerer {
3567
3666
  return found;
3568
3667
  };
3569
3668
  const jsLambdas = [];
3669
+ // TypeScript lambdas keep the checker's inferred return type, which
3670
+ // spells a missing read as its bare element type and `read ?? "text"`
3671
+ // as a number. Their closures return the runtime result instead.
3672
+ const tsLambdas = [];
3570
3673
  for (const sf of sourceFiles) {
3571
- if (!isJsSourceFile(sf))
3674
+ const js = isJsSourceFile(sf);
3675
+ if (!js && sf.isDeclarationFile)
3572
3676
  continue;
3573
3677
  ts.walkPreorder(sf, (node) => {
3574
3678
  if (ts.isArrowFunction(node) || ts.isFunctionExpression(node))
3575
- jsLambdas.push(node);
3679
+ (js ? jsLambdas : tsLambdas).push(node);
3576
3680
  });
3577
3681
  }
3682
+ /** The primitive result union of a lambda's returns (see
3683
+ * optionalPrimitiveResultType), as declared functions record it. */
3684
+ const lambdaArithmeticReturn = (fn) => {
3685
+ if (fn.body && !ts.isBlock(fn.body))
3686
+ return optionalPrimitiveResultType(fn.body);
3687
+ for (const expression of returnsOf(fn)) {
3688
+ const result = optionalPrimitiveResultType(expression);
3689
+ if (result)
3690
+ return result;
3691
+ }
3692
+ return null;
3693
+ };
3578
3694
  for (const [symbol, signature] of signatureBySymbol) {
3579
3695
  const declaration = functionDeclBySymbol.get(symbol);
3580
3696
  if (!declaration || !isJsSourceFile(declaration.getSourceFile()))
@@ -4262,6 +4378,14 @@ export class Lowerer {
4262
4378
  provenance?.note("static-field", node.left, node.right);
4263
4379
  changed = true;
4264
4380
  }
4381
+ else if (field && !optionalSymbols.has(field) && optionalInstanceField(field)) {
4382
+ // The checker narrows the field to the assigned read's bare
4383
+ // type, but the stored value can be undefined: reads keep the
4384
+ // stored union (runtimeOptionalClassField).
4385
+ optionalSymbols.add(field);
4386
+ provenance?.note("assigned-field", node.left, node.right);
4387
+ changed = true;
4388
+ }
4265
4389
  else if (ts.isIdentifier(node.left.expression)) {
4266
4390
  const symbol = symbolOf(node.left.expression);
4267
4391
  if (symbol && noteFieldSymbol(symbol, node.left.name.text)) {
@@ -4575,6 +4699,52 @@ export class Lowerer {
4575
4699
  for (const [symbol, decl] of functionDeclBySymbol)
4576
4700
  if (scanReturns(symbol, decl))
4577
4701
  changed = true;
4702
+ for (const fn of tsLambdas) {
4703
+ // An annotated return is the author's contract, as for parameters.
4704
+ if (fn.type)
4705
+ continue;
4706
+ const mapped = this.mapTypeOf(this.typeOf(fn));
4707
+ if (mapped?.kind !== "func")
4708
+ continue;
4709
+ const previous = this.runtimeOptionalFunctionReturnType(fn, mapped.ret);
4710
+ // Async, generator and void bodies keep their own result shapes.
4711
+ if (previous.kind === "promise" ||
4712
+ previous.kind === "generator" ||
4713
+ previous.kind === "void")
4714
+ continue;
4715
+ const arithmetic = lambdaArithmeticReturn(fn);
4716
+ const next = arithmetic &&
4717
+ (previous.kind === "f64" || previous.kind === "string" || previous.kind === "bool")
4718
+ ? arithmetic
4719
+ : bodyReturnsOptional(fn)
4720
+ ? this.runtimeOptionalType(previous)
4721
+ : previous;
4722
+ if (!typeEquals(previous, next)) {
4723
+ this.runtimeOptionalFunctionReturns.set(fn, next);
4724
+ provenance?.note("lambda-return", fn, fn.body);
4725
+ changed = true;
4726
+ }
4727
+ // Calls through the lambda's own binding (`const get = () => xs[i];
4728
+ // get()`) produce the same result as calls of a declared function.
4729
+ const binding = fn.parent;
4730
+ if (next !== previous &&
4731
+ ts.isVariableDeclaration(binding) &&
4732
+ binding.initializer === fn &&
4733
+ !binding.type &&
4734
+ ts.isIdentifier(binding.name)) {
4735
+ const symbol = symbolOf(binding.name);
4736
+ if (symbol && next.kind === "union" && this.armTag(next.unionId, UNDEFINED_T) >= 0) {
4737
+ if (!optionalReturns.has(symbol)) {
4738
+ optionalReturns.add(symbol);
4739
+ changed = true;
4740
+ }
4741
+ }
4742
+ else if (symbol && !arithmeticReturns.has(symbol)) {
4743
+ arithmeticReturns.set(symbol, next);
4744
+ changed = true;
4745
+ }
4746
+ }
4747
+ }
4578
4748
  for (const fn of jsLambdas) {
4579
4749
  if (!bodyReturnsOptional(fn))
4580
4750
  continue;
@@ -4606,6 +4776,25 @@ export class Lowerer {
4606
4776
  }
4607
4777
  }
4608
4778
  }
4779
+ // NaN-coded slots convert optional values on entry instead of holding
4780
+ // the union, so promotion never applies to them.
4781
+ for (const symbol of [...optionalSymbols])
4782
+ if (nanCoded.nanCodedBinding(symbol))
4783
+ optionalSymbols.delete(symbol);
4784
+ for (const symbol of [...optionalReturns])
4785
+ if (nanCoded.nanCodedReturn(symbol))
4786
+ optionalReturns.delete(symbol);
4787
+ for (const [symbol, params] of optionalParams) {
4788
+ const decl = functionDeclBySymbol.get(symbol);
4789
+ for (const index of [...params]) {
4790
+ const name = decl?.parameters[index]?.name;
4791
+ const param = name && ts.isIdentifier(name) ? symbolOf(name) : null;
4792
+ if (param && nanCoded.nanCodedBinding(param))
4793
+ params.delete(index);
4794
+ }
4795
+ if (params.size === 0)
4796
+ optionalParams.delete(symbol);
4797
+ }
4609
4798
  for (const [site, present] of presentSites)
4610
4799
  if (present)
4611
4800
  this.presentElementCalls.add(site);
@@ -4678,11 +4867,11 @@ export class Lowerer {
4678
4867
  const symbol = symbolOf(node.name);
4679
4868
  if (!symbol)
4680
4869
  return;
4681
- if (isJsSourceFile(sf) &&
4682
- !node.type &&
4870
+ if (!node.type &&
4683
4871
  !hasJsTypeAnnotation(node) &&
4684
4872
  node.initializer &&
4685
- (ts.isArrowFunction(node.initializer) || ts.isFunctionExpression(node.initializer))) {
4873
+ (ts.isArrowFunction(node.initializer) || ts.isFunctionExpression(node.initializer)) &&
4874
+ (isJsSourceFile(sf) || this.runtimeOptionalFunctionReturns.has(node.initializer))) {
4686
4875
  const global = this.globalsBySymbol.get(symbol);
4687
4876
  if (global?.type.kind === "func")
4688
4877
  global.type = {
@@ -4791,6 +4980,7 @@ export class Lowerer {
4791
4980
  }
4792
4981
  run() {
4793
4982
  const parts = this.splitFiles();
4983
+ this.narrowing.setFiles(parts.map((fp) => fp.sf));
4794
4984
  // The coverage remainder deliberately visits every body that reachable
4795
4985
  // emit skipped. Those files are already phase-managed, so an ordinary
4796
4986
  // checker miss would stay a one-node IPC query instead of falling back
@@ -5170,6 +5360,7 @@ export class Lowerer {
5170
5360
  emitReachable(extraRoots) {
5171
5361
  this.deferredModuleInitializers = new DeferredModuleInitializers();
5172
5362
  const parts = this.splitFiles();
5363
+ this.narrowing.setFiles(parts.map((fp) => fp.sf));
5173
5364
  // Direct lowering callers do not necessarily run program preflight.
5174
5365
  // Establish the same managed header/top-level batch here before
5175
5366
  // collection, while the production path simply finds warm memos.
@@ -5995,6 +6186,12 @@ export class Lowerer {
5995
6186
  const narrowed = this.chainNarrowedType.get(node);
5996
6187
  if (narrowed)
5997
6188
  return narrowed;
6189
+ // A stale narrowing of a mutable member or closure-assigned binding in
6190
+ // an absence/type test (or a slot accepting the declared type) reads
6191
+ // as declared, so the test inspects the stored value.
6192
+ const widened = this.narrowing.widenedType(node);
6193
+ if (widened)
6194
+ return widened;
5998
6195
  const t = this.checker.getTypeAtLocation(node);
5999
6196
  // ALIASED-TYPEOF narrowing: inside a branch a `type === 'string'`
6000
6197
  // test proves (type = typeof val, both never reassigned), references
@@ -8224,7 +8421,23 @@ export class Lowerer {
8224
8421
  }
8225
8422
  /* ── scoping and captures ─────────────────────────────────────────── */
8226
8423
  declareLocal(nameNode, name, type, mutable) {
8227
- return declareContextLocal(this.ctx, name, type, mutable, this.checker.getSymbolAtLocation(nameNode), bindingSource(nameNode));
8424
+ const symbol = this.checker.getSymbolAtLocation(nameNode);
8425
+ const local = declareContextLocal(this.ctx, name, type, mutable, symbol, bindingSource(nameNode));
8426
+ // A binding declared `T | undefined` that receives a missing read: the
8427
+ // checker narrows it to `T` after the assignment, but the stored value
8428
+ // can be undefined. Track it like widened storage so bare reads keep
8429
+ // the union until a guard proves presence.
8430
+ if (symbol &&
8431
+ this.runtimeOptionalAssignedSymbols.has(symbol) &&
8432
+ type.kind === "union" &&
8433
+ this.armTag(type.unionId, UNDEFINED_T) >= 0 &&
8434
+ this.unions.get(type.unionId)?.arms.length === 2 &&
8435
+ !isUnitType(this.stripUndefinedArm(type))) {
8436
+ const root = this.runtimeOptionalRootOf(local);
8437
+ this.runtimeOptionalStorageLocals.add(root);
8438
+ this.runtimeOptionalLocals.add(root);
8439
+ }
8440
+ return local;
8228
8441
  }
8229
8442
  /** A never-returning call in a value position of type `expected`: the
8230
8443
  * call runs for its effects (it throws or exits), and the expression ends