@scriptc/compiler 0.1.6 → 0.2.0

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 (1060) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +73 -25
  2. package/ambient/scriptc-overrides.d.ts +10 -6
  3. package/ambient/scriptc.d.ts +28 -5
  4. package/dist/backend/build-cache.d.ts +1 -3
  5. package/dist/backend/build-cache.js +3 -22
  6. package/dist/backend/build-cache.js.map +1 -1
  7. package/dist/backend/cache-root.d.ts +3 -0
  8. package/dist/backend/cache-root.js +23 -0
  9. package/dist/backend/cache-root.js.map +1 -0
  10. package/dist/backend/cycle-analysis.d.ts +15 -0
  11. package/dist/backend/cycle-analysis.js +107 -0
  12. package/dist/backend/cycle-analysis.js.map +1 -0
  13. package/dist/backend/cycle-analysis.test.js +124 -0
  14. package/dist/backend/cycle-analysis.test.js.map +1 -0
  15. package/dist/backend/debug-symbols.d.ts +8 -0
  16. package/dist/backend/debug-symbols.js +52 -0
  17. package/dist/backend/debug-symbols.js.map +1 -0
  18. package/dist/backend/executable-features.d.ts +4 -0
  19. package/dist/backend/executable-features.js +42 -0
  20. package/dist/backend/executable-features.js.map +1 -0
  21. package/dist/backend/external-c.d.ts +5 -27
  22. package/dist/backend/external-c.js +5 -40
  23. package/dist/backend/external-c.js.map +1 -1
  24. package/dist/backend/ffi-callbacks.d.ts +2 -3
  25. package/dist/backend/ffi-callbacks.js +2 -3
  26. package/dist/backend/ffi-callbacks.js.map +1 -1
  27. package/dist/backend/library-identity-markers.d.ts +4 -9
  28. package/dist/backend/library-identity-markers.js +11 -49
  29. package/dist/backend/library-identity-markers.js.map +1 -1
  30. package/dist/backend/library-pack.d.ts +5 -0
  31. package/dist/backend/library-pack.js +123 -0
  32. package/dist/backend/library-pack.js.map +1 -0
  33. package/dist/backend/link-plan-core.d.ts +20 -0
  34. package/dist/backend/link-plan-core.js +18 -0
  35. package/dist/backend/link-plan-core.js.map +1 -0
  36. package/dist/backend/link-plan.d.ts +2 -1
  37. package/dist/backend/link-plan.js +18 -17
  38. package/dist/backend/link-plan.js.map +1 -1
  39. package/dist/backend/link-trace.d.ts +2 -0
  40. package/dist/backend/link-trace.js +24 -0
  41. package/dist/backend/link-trace.js.map +1 -0
  42. package/dist/backend/linker.js +9 -1
  43. package/dist/backend/linker.js.map +1 -1
  44. package/dist/backend/literals.d.ts +9 -0
  45. package/dist/backend/literals.js +35 -0
  46. package/dist/backend/literals.js.map +1 -0
  47. package/dist/backend/literals.test.d.ts +1 -0
  48. package/dist/backend/literals.test.js +53 -0
  49. package/dist/backend/literals.test.js.map +1 -0
  50. package/dist/backend/llvm/blocks.d.ts +4 -0
  51. package/dist/backend/llvm/blocks.js +25 -4
  52. package/dist/backend/llvm/blocks.js.map +1 -1
  53. package/dist/backend/llvm/blocks.test.d.ts +1 -0
  54. package/dist/backend/llvm/blocks.test.js +91 -0
  55. package/dist/backend/llvm/blocks.test.js.map +1 -0
  56. package/dist/backend/llvm/classes.d.ts +14 -8
  57. package/dist/backend/llvm/classes.js +70 -28
  58. package/dist/backend/llvm/classes.js.map +1 -1
  59. package/dist/backend/llvm/common.d.ts +5 -1
  60. package/dist/backend/llvm/common.js +45 -4
  61. package/dist/backend/llvm/common.js.map +1 -1
  62. package/dist/backend/llvm/debug-info.d.ts +32 -0
  63. package/dist/backend/llvm/debug-info.js +198 -0
  64. package/dist/backend/llvm/debug-info.js.map +1 -0
  65. package/dist/backend/llvm/debug-info.test.d.ts +1 -0
  66. package/dist/backend/llvm/debug-info.test.js +110 -0
  67. package/dist/backend/llvm/debug-info.test.js.map +1 -0
  68. package/dist/backend/llvm/dyn.d.ts +16 -2
  69. package/dist/backend/llvm/dyn.js +498 -175
  70. package/dist/backend/llvm/dyn.js.map +1 -1
  71. package/dist/backend/llvm/emitter.d.ts +598 -1
  72. package/dist/backend/llvm/emitter.js +739 -409
  73. package/dist/backend/llvm/emitter.js.map +1 -1
  74. package/dist/backend/llvm/emitter.test.d.ts +1 -0
  75. package/dist/backend/llvm/emitter.test.js +179 -0
  76. package/dist/backend/llvm/emitter.test.js.map +1 -0
  77. package/dist/backend/llvm/expr-async.js +23 -14
  78. package/dist/backend/llvm/expr-async.js.map +1 -1
  79. package/dist/backend/llvm/expr-bytes.d.ts +1 -1
  80. package/dist/backend/llvm/expr-bytes.js +32 -14
  81. package/dist/backend/llvm/expr-bytes.js.map +1 -1
  82. package/dist/backend/llvm/expr-callbacks.d.ts +1 -1
  83. package/dist/backend/llvm/expr-callbacks.js +25 -22
  84. package/dist/backend/llvm/expr-callbacks.js.map +1 -1
  85. package/dist/backend/llvm/expr-calls.js +136 -35
  86. package/dist/backend/llvm/expr-calls.js.map +1 -1
  87. package/dist/backend/llvm/expr-containers.d.ts +1 -1
  88. package/dist/backend/llvm/expr-containers.js +43 -14
  89. package/dist/backend/llvm/expr-containers.js.map +1 -1
  90. package/dist/backend/llvm/expr-context.d.ts +5 -260
  91. package/dist/backend/llvm/expr-control.js +9 -5
  92. package/dist/backend/llvm/expr-control.js.map +1 -1
  93. package/dist/backend/llvm/expr-dispatch.d.ts +1 -1
  94. package/dist/backend/llvm/expr-dispatch.js +2 -0
  95. package/dist/backend/llvm/expr-dispatch.js.map +1 -1
  96. package/dist/backend/llvm/expr-dynamic.js +72 -74
  97. package/dist/backend/llvm/expr-dynamic.js.map +1 -1
  98. package/dist/backend/llvm/expr-island.d.ts +1 -1
  99. package/dist/backend/llvm/expr-island.js +11 -7
  100. package/dist/backend/llvm/expr-island.js.map +1 -1
  101. package/dist/backend/llvm/expr-primitives.d.ts +1 -1
  102. package/dist/backend/llvm/expr-primitives.js +51 -30
  103. package/dist/backend/llvm/expr-primitives.js.map +1 -1
  104. package/dist/backend/llvm/expr-primitives.test.js +1 -1
  105. package/dist/backend/llvm/expr-records.d.ts +5 -1
  106. package/dist/backend/llvm/expr-records.js +43 -4
  107. package/dist/backend/llvm/expr-records.js.map +1 -1
  108. package/dist/backend/llvm/expr-stream-bridges.d.ts +4 -4
  109. package/dist/backend/llvm/expr-stream-bridges.js +102 -121
  110. package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
  111. package/dist/backend/llvm/expr-stream-bridges.test.d.ts +1 -0
  112. package/dist/backend/llvm/expr-stream-bridges.test.js +55 -0
  113. package/dist/backend/llvm/expr-stream-bridges.test.js.map +1 -0
  114. package/dist/backend/llvm/expr-stream-callbacks.d.ts +1 -1
  115. package/dist/backend/llvm/expr-stream-callbacks.js +12 -12
  116. package/dist/backend/llvm/expr-stream-callbacks.js.map +1 -1
  117. package/dist/backend/llvm/layouts.d.ts +19 -0
  118. package/dist/backend/llvm/layouts.js +12 -0
  119. package/dist/backend/llvm/layouts.js.map +1 -0
  120. package/dist/backend/llvm/lib-abi.d.ts +19 -0
  121. package/dist/backend/llvm/lib-abi.js +82 -0
  122. package/dist/backend/llvm/lib-abi.js.map +1 -0
  123. package/dist/backend/llvm/lib-connect.d.ts +2 -0
  124. package/dist/backend/llvm/lib-connect.js +51 -0
  125. package/dist/backend/llvm/lib-connect.js.map +1 -0
  126. package/dist/backend/llvm/lib-datagram.d.ts +2 -0
  127. package/dist/backend/llvm/lib-datagram.js +135 -0
  128. package/dist/backend/llvm/lib-datagram.js.map +1 -0
  129. package/dist/backend/llvm/lib-dispatch.js +50 -6
  130. package/dist/backend/llvm/lib-dispatch.js.map +1 -1
  131. package/dist/backend/llvm/lib-filesystem.js +59 -18
  132. package/dist/backend/llvm/lib-filesystem.js.map +1 -1
  133. package/dist/backend/llvm/lib-http2.d.ts +2 -0
  134. package/dist/backend/llvm/lib-http2.js +215 -0
  135. package/dist/backend/llvm/lib-http2.js.map +1 -0
  136. package/dist/backend/llvm/lib-lookup.d.ts +2 -0
  137. package/dist/backend/llvm/lib-lookup.js +39 -0
  138. package/dist/backend/llvm/lib-lookup.js.map +1 -0
  139. package/dist/backend/llvm/lib-network.js +25 -2
  140. package/dist/backend/llvm/lib-network.js.map +1 -1
  141. package/dist/backend/llvm/lib-process.js +21 -16
  142. package/dist/backend/llvm/lib-process.js.map +1 -1
  143. package/dist/backend/llvm/lib-shared.js +125 -1
  144. package/dist/backend/llvm/lib-shared.js.map +1 -1
  145. package/dist/backend/llvm/lib-stream.js +3 -2
  146. package/dist/backend/llvm/lib-stream.js.map +1 -1
  147. package/dist/backend/llvm/lib-test.d.ts +2 -0
  148. package/dist/backend/llvm/lib-test.js +34 -0
  149. package/dist/backend/llvm/lib-test.js.map +1 -0
  150. package/dist/backend/llvm/lib-tls.d.ts +2 -0
  151. package/dist/backend/llvm/lib-tls.js +82 -0
  152. package/dist/backend/llvm/lib-tls.js.map +1 -0
  153. package/dist/backend/llvm/shapes.d.ts +10 -9
  154. package/dist/backend/llvm/shapes.js +35 -130
  155. package/dist/backend/llvm/shapes.js.map +1 -1
  156. package/dist/backend/llvm/split.js +4 -1
  157. package/dist/backend/llvm/split.js.map +1 -1
  158. package/dist/backend/llvm/split.test.js +26 -1
  159. package/dist/backend/llvm/split.test.js.map +1 -1
  160. package/dist/backend/llvm/unsupported.d.ts +1 -0
  161. package/dist/backend/llvm/unsupported.js +1 -2
  162. package/dist/backend/llvm/unsupported.js.map +1 -1
  163. package/dist/backend/llvm/walkers.js +1 -7
  164. package/dist/backend/llvm/walkers.js.map +1 -1
  165. package/dist/backend/mangle.d.ts +2 -2
  166. package/dist/backend/mangle.js +16 -5
  167. package/dist/backend/mangle.js.map +1 -1
  168. package/dist/backend/{c/may-throw.d.ts → may-throw.d.ts} +1 -1
  169. package/dist/backend/{c/may-throw.js → may-throw.js} +51 -46
  170. package/dist/backend/may-throw.js.map +1 -0
  171. package/dist/backend/may-throw.test.d.ts +1 -0
  172. package/dist/backend/may-throw.test.js +58 -0
  173. package/dist/backend/may-throw.test.js.map +1 -0
  174. package/dist/backend/native-codegen-core.d.ts +22 -0
  175. package/dist/backend/native-codegen-core.js +44 -0
  176. package/dist/backend/native-codegen-core.js.map +1 -0
  177. package/dist/backend/native-codegen.d.ts +3 -19
  178. package/dist/backend/native-codegen.js +5 -44
  179. package/dist/backend/native-codegen.js.map +1 -1
  180. package/dist/backend/native-codegen.test.js +2 -2
  181. package/dist/backend/native-codegen.test.js.map +1 -1
  182. package/dist/backend/native-link-info-core.d.ts +94 -0
  183. package/dist/backend/native-link-info-core.js +62 -0
  184. package/dist/backend/native-link-info-core.js.map +1 -0
  185. package/dist/backend/native-link-info.d.ts +3 -88
  186. package/dist/backend/native-link-info.js +12 -243
  187. package/dist/backend/native-link-info.js.map +1 -1
  188. package/dist/backend/native-link-info.test.js +41 -146
  189. package/dist/backend/native-link-info.test.js.map +1 -1
  190. package/dist/backend/native-toolchain.d.ts +10 -25
  191. package/dist/backend/native-toolchain.js +89 -219
  192. package/dist/backend/native-toolchain.js.map +1 -1
  193. package/dist/backend/native-toolchain.test.js +15 -5
  194. package/dist/backend/native-toolchain.test.js.map +1 -1
  195. package/dist/backend/native-tools.d.ts +24 -0
  196. package/dist/backend/native-tools.js +33 -0
  197. package/dist/backend/native-tools.js.map +1 -0
  198. package/dist/backend/object-localize.js +19 -8
  199. package/dist/backend/object-localize.js.map +1 -1
  200. package/dist/backend/object-localize.test.js +22 -0
  201. package/dist/backend/object-localize.test.js.map +1 -1
  202. package/dist/backend/runtime-abi.d.ts +2 -2
  203. package/dist/backend/runtime-abi.js +2 -2
  204. package/dist/backend/runtime-pack-core.d.ts +101 -0
  205. package/dist/backend/runtime-pack-core.js +172 -0
  206. package/dist/backend/runtime-pack-core.js.map +1 -0
  207. package/dist/backend/runtime-pack-native.d.ts +21 -0
  208. package/dist/backend/runtime-pack-native.js +70 -0
  209. package/dist/backend/runtime-pack-native.js.map +1 -0
  210. package/dist/backend/runtime-pack-targets.test.js +1 -1
  211. package/dist/backend/runtime-pack.d.ts +3 -83
  212. package/dist/backend/runtime-pack.js +8 -151
  213. package/dist/backend/runtime-pack.js.map +1 -1
  214. package/dist/backend/runtime-pack.test.js +24 -1
  215. package/dist/backend/runtime-pack.test.js.map +1 -1
  216. package/dist/backend/source-locations.d.ts +12 -0
  217. package/dist/backend/source-locations.js +32 -0
  218. package/dist/backend/source-locations.js.map +1 -0
  219. package/dist/backend/source-locations.test.d.ts +1 -0
  220. package/dist/backend/source-locations.test.js +16 -0
  221. package/dist/backend/source-locations.test.js.map +1 -0
  222. package/dist/backend/target-diagnostics.d.ts +22 -0
  223. package/dist/backend/target-diagnostics.js +108 -0
  224. package/dist/backend/target-diagnostics.js.map +1 -0
  225. package/dist/backend/target-platform.d.ts +15 -0
  226. package/dist/backend/target-platform.js +72 -0
  227. package/dist/backend/target-platform.js.map +1 -0
  228. package/dist/backend/targets.d.ts +13 -4
  229. package/dist/backend/targets.js +95 -30
  230. package/dist/backend/targets.js.map +1 -1
  231. package/dist/backend/targets.test.js +65 -4
  232. package/dist/backend/targets.test.js.map +1 -1
  233. package/dist/backend/toolchain-environment.d.ts +6 -0
  234. package/dist/backend/toolchain-environment.js +99 -0
  235. package/dist/backend/toolchain-environment.js.map +1 -0
  236. package/dist/backend/vendor-archives.d.ts +1 -9
  237. package/dist/backend/vendor-archives.js +2 -9
  238. package/dist/backend/vendor-archives.js.map +1 -1
  239. package/dist/backend/vendor-inputs.d.ts +9 -0
  240. package/dist/backend/vendor-inputs.js +10 -0
  241. package/dist/backend/vendor-inputs.js.map +1 -0
  242. package/dist/backend/warm-cache.d.ts +3 -0
  243. package/dist/backend/warm-cache.js +41 -0
  244. package/dist/backend/warm-cache.js.map +1 -0
  245. package/dist/cli/command.d.ts +3 -0
  246. package/dist/cli/command.js +328 -0
  247. package/dist/cli/command.js.map +1 -0
  248. package/dist/cli/host.d.ts +24 -0
  249. package/dist/cli/host.js +2 -0
  250. package/dist/cli/host.js.map +1 -0
  251. package/dist/cli/output-options.d.ts +26 -0
  252. package/dist/cli/output-options.js +60 -0
  253. package/dist/cli/output-options.js.map +1 -0
  254. package/dist/cli/paths.d.ts +12 -0
  255. package/dist/cli/paths.js +42 -0
  256. package/dist/cli/paths.js.map +1 -0
  257. package/dist/cli/usage.d.ts +74 -0
  258. package/dist/cli/usage.js +86 -0
  259. package/dist/cli/usage.js.map +1 -0
  260. package/dist/cli/wasi-paths.d.ts +8 -0
  261. package/dist/cli/wasi-paths.js +41 -0
  262. package/dist/cli/wasi-paths.js.map +1 -0
  263. package/dist/cli/wasi-runner.d.ts +2 -0
  264. package/dist/cli/wasi-runner.js +28 -0
  265. package/dist/cli/wasi-runner.js.map +1 -0
  266. package/dist/compat/fetch-profile.js +3 -3
  267. package/dist/compat/fetch-profile.js.map +1 -1
  268. package/dist/compile-types.d.ts +162 -0
  269. package/dist/compile-types.js +2 -0
  270. package/dist/compile-types.js.map +1 -0
  271. package/dist/coverage/report.js +11 -5
  272. package/dist/coverage/report.js.map +1 -1
  273. package/dist/coverage/surface-manifest.js +5 -2
  274. package/dist/coverage/surface-manifest.js.map +1 -1
  275. package/dist/diagnostics/diagnostic.d.ts +4 -0
  276. package/dist/diagnostics/diagnostic.js +15 -3
  277. package/dist/diagnostics/diagnostic.js.map +1 -1
  278. package/dist/executable/executable-cache.d.ts +3 -4
  279. package/dist/executable/executable-cache.js +33 -10
  280. package/dist/executable/executable-cache.js.map +1 -1
  281. package/dist/executable/executable-cache.test.js +38 -16
  282. package/dist/executable/executable-cache.test.js.map +1 -1
  283. package/dist/executable/prepare.d.ts +14 -0
  284. package/dist/executable/prepare.js +66 -0
  285. package/dist/executable/prepare.js.map +1 -0
  286. package/dist/ffi/ffi-manifest.d.ts +11 -4
  287. package/dist/ffi/ffi-manifest.js +170 -58
  288. package/dist/ffi/ffi-manifest.js.map +1 -1
  289. package/dist/ffi/ffi-manifest.test.d.ts +1 -0
  290. package/dist/ffi/ffi-manifest.test.js +111 -0
  291. package/dist/ffi/ffi-manifest.test.js.map +1 -0
  292. package/dist/format-integer.d.ts +3 -0
  293. package/dist/format-integer.js +14 -0
  294. package/dist/format-integer.js.map +1 -0
  295. package/dist/frontend/analysis.d.ts +5 -0
  296. package/dist/frontend/analysis.js +88 -0
  297. package/dist/frontend/analysis.js.map +1 -0
  298. package/dist/frontend/arguments-usage.d.ts +4 -0
  299. package/dist/frontend/arguments-usage.js +40 -0
  300. package/dist/frontend/arguments-usage.js.map +1 -0
  301. package/dist/frontend/binding-source.d.ts +4 -0
  302. package/dist/frontend/binding-source.js +18 -0
  303. package/dist/frontend/binding-source.js.map +1 -0
  304. package/dist/frontend/builtin-modules.d.ts +1 -1
  305. package/dist/frontend/builtin-modules.js +1 -1
  306. package/dist/frontend/builtin-modules.js.map +1 -1
  307. package/dist/frontend/cjs-lexer.d.ts +4 -25
  308. package/dist/frontend/cjs-lexer.js +6 -806
  309. package/dist/frontend/cjs-lexer.js.map +1 -1
  310. package/dist/frontend/cjs-resolve.d.ts +5 -0
  311. package/dist/frontend/cjs-resolve.js +429 -0
  312. package/dist/frontend/cjs-resolve.js.map +1 -0
  313. package/dist/frontend/cjs-syntax.d.ts +26 -0
  314. package/dist/frontend/cjs-syntax.js +806 -0
  315. package/dist/frontend/cjs-syntax.js.map +1 -0
  316. package/dist/frontend/comptime-node.d.ts +4 -0
  317. package/dist/frontend/comptime-node.js +16 -0
  318. package/dist/frontend/comptime-node.js.map +1 -0
  319. package/dist/frontend/comptime-node.test.d.ts +1 -0
  320. package/dist/frontend/comptime-node.test.js +37 -0
  321. package/dist/frontend/comptime-node.test.js.map +1 -0
  322. package/dist/frontend/dts-paths.d.ts +3 -4
  323. package/dist/frontend/dts-paths.js +15 -8
  324. package/dist/frontend/dts-paths.js.map +1 -1
  325. package/dist/frontend/fork-target.js +2 -2
  326. package/dist/frontend/fork-target.js.map +1 -1
  327. package/dist/frontend/helper-tokens.d.ts +1 -0
  328. package/dist/frontend/helper-tokens.js +147 -0
  329. package/dist/frontend/helper-tokens.js.map +1 -0
  330. package/dist/frontend/helper-tokens.test.d.ts +1 -0
  331. package/dist/frontend/helper-tokens.test.js +35 -0
  332. package/dist/frontend/helper-tokens.test.js.map +1 -0
  333. package/dist/frontend/inferred-js-diagnostics.d.ts +5 -0
  334. package/dist/frontend/inferred-js-diagnostics.js +69 -0
  335. package/dist/frontend/inferred-js-diagnostics.js.map +1 -0
  336. package/dist/frontend/inferred-js-diagnostics.test.d.ts +1 -0
  337. package/dist/frontend/inferred-js-diagnostics.test.js +58 -0
  338. package/dist/frontend/inferred-js-diagnostics.test.js.map +1 -0
  339. package/dist/frontend/input-tracker.d.ts +5 -2
  340. package/dist/frontend/input-tracker.js +24 -3
  341. package/dist/frontend/input-tracker.js.map +1 -1
  342. package/dist/frontend/input-tracker.test.js +23 -0
  343. package/dist/frontend/input-tracker.test.js.map +1 -1
  344. package/dist/frontend/lib-contract.js +32 -24
  345. package/dist/frontend/lib-contract.js.map +1 -1
  346. package/dist/frontend/literal-values.d.ts +4 -0
  347. package/dist/frontend/literal-values.js +21 -0
  348. package/dist/frontend/literal-values.js.map +1 -0
  349. package/dist/frontend/literal-values.test.d.ts +1 -0
  350. package/dist/frontend/literal-values.test.js +91 -0
  351. package/dist/frontend/literal-values.test.js.map +1 -0
  352. package/dist/frontend/lowering/abstract-equality.d.ts +1 -1
  353. package/dist/frontend/lowering/abstract-equality.js +3 -0
  354. package/dist/frontend/lowering/abstract-equality.js.map +1 -1
  355. package/dist/frontend/lowering/array-indexed-mutation.js +11 -6
  356. package/dist/frontend/lowering/array-indexed-mutation.js.map +1 -1
  357. package/dist/frontend/lowering/array-values.d.ts +4 -1
  358. package/dist/frontend/lowering/array-values.js +43 -1
  359. package/dist/frontend/lowering/array-values.js.map +1 -1
  360. package/dist/frontend/lowering/callback-arg.js +1 -0
  361. package/dist/frontend/lowering/callback-arg.js.map +1 -1
  362. package/dist/frontend/lowering/class-callbacks.d.ts +12 -0
  363. package/dist/frontend/lowering/class-callbacks.js +63 -0
  364. package/dist/frontend/lowering/class-callbacks.js.map +1 -0
  365. package/dist/frontend/lowering/class-descriptors.d.ts +9 -0
  366. package/dist/frontend/lowering/class-descriptors.js +73 -0
  367. package/dist/frontend/lowering/class-descriptors.js.map +1 -0
  368. package/dist/frontend/lowering/class-dynamic-dispatch.d.ts +41 -0
  369. package/dist/frontend/lowering/class-dynamic-dispatch.js +803 -0
  370. package/dist/frontend/lowering/class-dynamic-dispatch.js.map +1 -0
  371. package/dist/frontend/lowering/class-dynamic-dispatch.test.d.ts +1 -0
  372. package/dist/frontend/lowering/class-dynamic-dispatch.test.js +92 -0
  373. package/dist/frontend/lowering/class-dynamic-dispatch.test.js.map +1 -0
  374. package/dist/frontend/lowering/class-instance-constructor.d.ts +7 -0
  375. package/dist/frontend/lowering/class-instance-constructor.js +64 -0
  376. package/dist/frontend/lowering/class-instance-constructor.js.map +1 -0
  377. package/dist/frontend/lowering/class-method-values.d.ts +10 -0
  378. package/dist/frontend/lowering/class-method-values.js +170 -0
  379. package/dist/frontend/lowering/class-method-values.js.map +1 -0
  380. package/dist/frontend/lowering/class-prototypes.d.ts +16 -0
  381. package/dist/frontend/lowering/class-prototypes.js +134 -0
  382. package/dist/frontend/lowering/class-prototypes.js.map +1 -0
  383. package/dist/frontend/lowering/class-static-data.d.ts +6 -0
  384. package/dist/frontend/lowering/class-static-data.js +56 -0
  385. package/dist/frontend/lowering/class-static-data.js.map +1 -0
  386. package/dist/frontend/lowering/containers/buffer-numeric-methods.d.ts +4 -0
  387. package/dist/frontend/lowering/containers/buffer-numeric-methods.js +26 -0
  388. package/dist/frontend/lowering/containers/buffer-numeric-methods.js.map +1 -0
  389. package/dist/frontend/lowering/containers/bytes.d.ts +11 -7
  390. package/dist/frontend/lowering/containers/bytes.js +164 -155
  391. package/dist/frontend/lowering/containers/bytes.js.map +1 -1
  392. package/dist/frontend/lowering/containers/string-and-regexp.d.ts +2 -2
  393. package/dist/frontend/lowering/containers/string-and-regexp.js +39 -16
  394. package/dist/frontend/lowering/containers/string-and-regexp.js.map +1 -1
  395. package/dist/frontend/lowering/containers/string-replacement.d.ts +7 -0
  396. package/dist/frontend/lowering/containers/string-replacement.js +217 -0
  397. package/dist/frontend/lowering/containers/string-replacement.js.map +1 -0
  398. package/dist/frontend/lowering/error-methods.d.ts +11 -0
  399. package/dist/frontend/lowering/error-methods.js +78 -0
  400. package/dist/frontend/lowering/error-methods.js.map +1 -0
  401. package/dist/frontend/lowering/expressions/global-symbols.d.ts +6 -0
  402. package/dist/frontend/lowering/expressions/global-symbols.js +11 -0
  403. package/dist/frontend/lowering/expressions/global-symbols.js.map +1 -0
  404. package/dist/frontend/lowering/expressions/native-proxy.d.ts +7 -0
  405. package/dist/frontend/lowering/expressions/native-proxy.js +49 -0
  406. package/dist/frontend/lowering/expressions/native-proxy.js.map +1 -0
  407. package/dist/frontend/lowering/expressions/nullish-assignment.d.ts +6 -0
  408. package/dist/frontend/lowering/expressions/nullish-assignment.js +124 -0
  409. package/dist/frontend/lowering/expressions/nullish-assignment.js.map +1 -0
  410. package/dist/frontend/lowering/expressions/object-literals.d.ts +8 -8
  411. package/dist/frontend/lowering/expressions/object-literals.js +485 -185
  412. package/dist/frontend/lowering/expressions/object-literals.js.map +1 -1
  413. package/dist/frontend/lowering/expressions/optional-chains.d.ts +5 -6
  414. package/dist/frontend/lowering/expressions/optional-chains.js +27 -14
  415. package/dist/frontend/lowering/expressions/optional-chains.js.map +1 -1
  416. package/dist/frontend/lowering/expressions/try-lower-expression.js +1 -0
  417. package/dist/frontend/lowering/expressions/try-lower-expression.js.map +1 -1
  418. package/dist/frontend/lowering/expressions/union-field-write.d.ts +8 -0
  419. package/dist/frontend/lowering/expressions/union-field-write.js +71 -0
  420. package/dist/frontend/lowering/expressions/union-field-write.js.map +1 -0
  421. package/dist/frontend/lowering/function-context.d.ts +93 -0
  422. package/dist/frontend/lowering/function-context.js +103 -0
  423. package/dist/frontend/lowering/function-context.js.map +1 -0
  424. package/dist/frontend/lowering/generator-delegation.d.ts +7 -0
  425. package/dist/frontend/lowering/generator-delegation.js +85 -0
  426. package/dist/frontend/lowering/generator-delegation.js.map +1 -0
  427. package/dist/frontend/lowering/generator-dynamic-dispatch.d.ts +10 -0
  428. package/dist/frontend/lowering/generator-dynamic-dispatch.js +105 -0
  429. package/dist/frontend/lowering/generator-dynamic-dispatch.js.map +1 -0
  430. package/dist/frontend/lowering/lib-boundary.d.ts +2 -1
  431. package/dist/frontend/lowering/lib-boundary.js +59 -20
  432. package/dist/frontend/lowering/lib-boundary.js.map +1 -1
  433. package/dist/frontend/lowering/lib-boundary.test.d.ts +1 -0
  434. package/dist/frontend/lowering/lib-boundary.test.js +45 -0
  435. package/dist/frontend/lowering/lib-boundary.test.js.map +1 -0
  436. package/dist/frontend/lowering/lower-array-sort.d.ts +1 -1
  437. package/dist/frontend/lowering/lower-assert.d.ts +1 -1
  438. package/dist/frontend/lowering/lower-assert.js.map +1 -1
  439. package/dist/frontend/lowering/lower-async-iteration.d.ts +1 -1
  440. package/dist/frontend/lowering/lower-async-iteration.js.map +1 -1
  441. package/dist/frontend/lowering/lower-builtin-values.d.ts +12 -0
  442. package/dist/frontend/lowering/lower-builtin-values.js +139 -0
  443. package/dist/frontend/lowering/lower-builtin-values.js.map +1 -0
  444. package/dist/frontend/lowering/lower-builtins.d.ts +27 -41
  445. package/dist/frontend/lowering/lower-builtins.js +553 -346
  446. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  447. package/dist/frontend/lowering/lower-calls.d.ts +22 -26
  448. package/dist/frontend/lowering/lower-calls.js +1373 -380
  449. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  450. package/dist/frontend/lowering/lower-classes.d.ts +83 -45
  451. package/dist/frontend/lowering/lower-classes.js +952 -339
  452. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  453. package/dist/frontend/lowering/lower-comptime.d.ts +1 -1
  454. package/dist/frontend/lowering/lower-comptime.js +23 -32
  455. package/dist/frontend/lowering/lower-comptime.js.map +1 -1
  456. package/dist/frontend/lowering/lower-containers.d.ts +27 -32
  457. package/dist/frontend/lowering/lower-containers.js +796 -151
  458. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  459. package/dist/frontend/lowering/lower-dgram.d.ts +1 -1
  460. package/dist/frontend/lowering/lower-dgram.js.map +1 -1
  461. package/dist/frontend/lowering/lower-enums.d.ts +1 -1
  462. package/dist/frontend/lowering/lower-enums.js.map +1 -1
  463. package/dist/frontend/lowering/lower-event-emitter.d.ts +30 -1
  464. package/dist/frontend/lowering/lower-event-emitter.js +58 -26
  465. package/dist/frontend/lowering/lower-event-emitter.js.map +1 -1
  466. package/dist/frontend/lowering/lower-expando.d.ts +7 -1
  467. package/dist/frontend/lowering/lower-expando.js +14 -6
  468. package/dist/frontend/lowering/lower-expando.js.map +1 -1
  469. package/dist/frontend/lowering/lower-exprs.d.ts +35 -11
  470. package/dist/frontend/lowering/lower-exprs.js +1785 -790
  471. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  472. package/dist/frontend/lowering/lower-generators.d.ts +2 -5
  473. package/dist/frontend/lowering/lower-generators.js +77 -46
  474. package/dist/frontend/lowering/lower-generators.js.map +1 -1
  475. package/dist/frontend/lowering/lower-global-value.d.ts +7 -0
  476. package/dist/frontend/lowering/lower-global-value.js +17 -0
  477. package/dist/frontend/lowering/lower-global-value.js.map +1 -0
  478. package/dist/frontend/lowering/lower-inspect.d.ts +1 -1
  479. package/dist/frontend/lowering/lower-inspect.js +17 -8
  480. package/dist/frontend/lowering/lower-inspect.js.map +1 -1
  481. package/dist/frontend/lowering/lower-island.d.ts +5 -8
  482. package/dist/frontend/lowering/lower-island.js +34 -23
  483. package/dist/frontend/lowering/lower-island.js.map +1 -1
  484. package/dist/frontend/lowering/lower-mixins.d.ts +2 -2
  485. package/dist/frontend/lowering/lower-mixins.js +5 -3
  486. package/dist/frontend/lowering/lower-mixins.js.map +1 -1
  487. package/dist/frontend/lowering/lower-modules.d.ts +1 -1
  488. package/dist/frontend/lowering/lower-modules.js +252 -38
  489. package/dist/frontend/lowering/lower-modules.js.map +1 -1
  490. package/dist/frontend/lowering/lower-namespaces.d.ts +1 -1
  491. package/dist/frontend/lowering/lower-namespaces.js.map +1 -1
  492. package/dist/frontend/lowering/lower-node-module.d.ts +1 -1
  493. package/dist/frontend/lowering/lower-node-module.js +13 -10
  494. package/dist/frontend/lowering/lower-node-module.js.map +1 -1
  495. package/dist/frontend/lowering/lower-server.d.ts +1 -1
  496. package/dist/frontend/lowering/lower-server.js +6 -10
  497. package/dist/frontend/lowering/lower-server.js.map +1 -1
  498. package/dist/frontend/lowering/lower-stmts.d.ts +10 -14
  499. package/dist/frontend/lowering/lower-stmts.js +731 -532
  500. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  501. package/dist/frontend/lowering/lower-stream.d.ts +1 -1
  502. package/dist/frontend/lowering/lower-stream.js +19 -10
  503. package/dist/frontend/lowering/lower-stream.js.map +1 -1
  504. package/dist/frontend/lowering/lower-test.d.ts +1 -1
  505. package/dist/frontend/lowering/lower-test.js.map +1 -1
  506. package/dist/frontend/lowering/lower-url.d.ts +6 -0
  507. package/dist/frontend/lowering/lower-url.js +77 -0
  508. package/dist/frontend/lowering/lower-url.js.map +1 -0
  509. package/dist/frontend/lowering/lowerer.d.ts +113 -189
  510. package/dist/frontend/lowering/lowerer.js +1033 -663
  511. package/dist/frontend/lowering/lowerer.js.map +1 -1
  512. package/dist/frontend/lowering/lowerer.test.d.ts +1 -0
  513. package/dist/frontend/lowering/lowerer.test.js +26 -0
  514. package/dist/frontend/lowering/lowerer.test.js.map +1 -0
  515. package/dist/frontend/lowering/native-ffi.d.ts +5 -0
  516. package/dist/frontend/lowering/native-ffi.js +40 -0
  517. package/dist/frontend/lowering/native-ffi.js.map +1 -0
  518. package/dist/frontend/lowering/object-enumeration-receiver.d.ts +7 -0
  519. package/dist/frontend/lowering/object-enumeration-receiver.js +33 -0
  520. package/dist/frontend/lowering/object-enumeration-receiver.js.map +1 -0
  521. package/dist/frontend/lowering/object-factory-new.d.ts +8 -0
  522. package/dist/frontend/lowering/object-factory-new.js +68 -0
  523. package/dist/frontend/lowering/object-factory-new.js.map +1 -0
  524. package/dist/frontend/lowering/optional-arguments.js +6 -1
  525. package/dist/frontend/lowering/optional-arguments.js.map +1 -1
  526. package/dist/frontend/lowering/pairs-snapshot.js +2 -1
  527. package/dist/frontend/lowering/pairs-snapshot.js.map +1 -1
  528. package/dist/frontend/lowering/runtime-optional-locals.d.ts +14 -0
  529. package/dist/frontend/lowering/runtime-optional-locals.js +41 -0
  530. package/dist/frontend/lowering/runtime-optional-locals.js.map +1 -0
  531. package/dist/frontend/lowering/runtime-optional-locals.test.d.ts +1 -0
  532. package/dist/frontend/lowering/runtime-optional-locals.test.js +85 -0
  533. package/dist/frontend/lowering/runtime-optional-locals.test.js.map +1 -0
  534. package/dist/frontend/lowering/sanitize-class-types.d.ts +8 -0
  535. package/dist/frontend/lowering/sanitize-class-types.js +79 -0
  536. package/dist/frontend/lowering/sanitize-class-types.js.map +1 -0
  537. package/dist/frontend/lowering/sanitize-class-types.test.d.ts +1 -0
  538. package/dist/frontend/lowering/sanitize-class-types.test.js +60 -0
  539. package/dist/frontend/lowering/sanitize-class-types.test.js.map +1 -0
  540. package/dist/frontend/lowering/static-this.d.ts +5 -1
  541. package/dist/frontend/lowering/static-this.js +28 -2
  542. package/dist/frontend/lowering/static-this.js.map +1 -1
  543. package/dist/frontend/lowering/string-index.d.ts +7 -0
  544. package/dist/frontend/lowering/string-index.js +39 -0
  545. package/dist/frontend/lowering/string-index.js.map +1 -0
  546. package/dist/frontend/lowering/surfaces.d.ts +9 -4
  547. package/dist/frontend/lowering/surfaces.js +98 -35
  548. package/dist/frontend/lowering/surfaces.js.map +1 -1
  549. package/dist/frontend/lowering/symbol-fields.d.ts +22 -0
  550. package/dist/frontend/lowering/symbol-fields.js +183 -0
  551. package/dist/frontend/lowering/symbol-fields.js.map +1 -0
  552. package/dist/frontend/lowering/symbol-methods.d.ts +7 -0
  553. package/dist/frontend/lowering/symbol-methods.js +30 -0
  554. package/dist/frontend/lowering/symbol-methods.js.map +1 -0
  555. package/dist/frontend/lowering/text-decoder-encoding.d.ts +10 -0
  556. package/dist/frontend/lowering/text-decoder-encoding.js +77 -0
  557. package/dist/frontend/lowering/text-decoder-encoding.js.map +1 -0
  558. package/dist/frontend/lowering/text-decoder-encoding.test.d.ts +1 -0
  559. package/dist/frontend/lowering/text-decoder-encoding.test.js +38 -0
  560. package/dist/frontend/lowering/text-decoder-encoding.test.js.map +1 -0
  561. package/dist/frontend/lowering/union-narrow.d.ts +9 -0
  562. package/dist/frontend/lowering/union-narrow.js +59 -0
  563. package/dist/frontend/lowering/union-narrow.js.map +1 -0
  564. package/dist/frontend/lowering/union-narrow.test.d.ts +1 -0
  565. package/dist/frontend/lowering/union-narrow.test.js +72 -0
  566. package/dist/frontend/lowering/union-narrow.test.js.map +1 -0
  567. package/dist/frontend/lowering/union-retag.d.ts +43 -0
  568. package/dist/frontend/lowering/union-retag.js +259 -0
  569. package/dist/frontend/lowering/union-retag.js.map +1 -0
  570. package/dist/frontend/lowering/union-retag.test.d.ts +1 -0
  571. package/dist/frontend/lowering/union-retag.test.js +405 -0
  572. package/dist/frontend/lowering/union-retag.test.js.map +1 -0
  573. package/dist/frontend/lowering/unregistered-class-types.d.ts +14 -0
  574. package/dist/frontend/lowering/unregistered-class-types.js +94 -0
  575. package/dist/frontend/lowering/unregistered-class-types.js.map +1 -0
  576. package/dist/frontend/lowering/unregistered-class-types.test.d.ts +1 -0
  577. package/dist/frontend/lowering/unregistered-class-types.test.js +65 -0
  578. package/dist/frontend/lowering/unregistered-class-types.test.js.map +1 -0
  579. package/dist/frontend/lowering/width-lift.d.ts +40 -0
  580. package/dist/frontend/lowering/width-lift.js +2 -0
  581. package/dist/frontend/lowering/width-lift.js.map +1 -0
  582. package/dist/frontend/module-syntax.d.ts +58 -0
  583. package/dist/frontend/module-syntax.js +123 -0
  584. package/dist/frontend/module-syntax.js.map +1 -0
  585. package/dist/frontend/module-syntax.test.d.ts +1 -0
  586. package/dist/frontend/module-syntax.test.js +78 -0
  587. package/dist/frontend/module-syntax.test.js.map +1 -0
  588. package/dist/frontend/npm-fetch-analysis.d.ts +17 -0
  589. package/dist/frontend/npm-fetch-analysis.js +100 -0
  590. package/dist/frontend/npm-fetch-analysis.js.map +1 -0
  591. package/dist/frontend/npm-fetch-analysis.test.d.ts +1 -0
  592. package/dist/frontend/npm-fetch-analysis.test.js +107 -0
  593. package/dist/frontend/npm-fetch-analysis.test.js.map +1 -0
  594. package/dist/frontend/npm-fetch-node.d.ts +2 -0
  595. package/dist/frontend/npm-fetch-node.js +12 -0
  596. package/dist/frontend/npm-fetch-node.js.map +1 -0
  597. package/dist/frontend/npm-fetch-syntax.d.ts +6 -0
  598. package/dist/frontend/npm-fetch-syntax.js +207 -0
  599. package/dist/frontend/npm-fetch-syntax.js.map +1 -0
  600. package/dist/frontend/npm-node.d.ts +10 -0
  601. package/dist/frontend/npm-node.js +14 -0
  602. package/dist/frontend/npm-node.js.map +1 -0
  603. package/dist/frontend/npm-static-bundled-cjs.d.ts +8 -0
  604. package/dist/frontend/npm-static-bundled-cjs.js +236 -0
  605. package/dist/frontend/npm-static-bundled-cjs.js.map +1 -0
  606. package/dist/frontend/npm-static-bundled-cjs.test.d.ts +1 -0
  607. package/dist/frontend/npm-static-bundled-cjs.test.js +69 -0
  608. package/dist/frontend/npm-static-bundled-cjs.test.js.map +1 -0
  609. package/dist/frontend/npm-static-declaration-syntax.d.ts +57 -0
  610. package/dist/frontend/npm-static-declaration-syntax.js +944 -0
  611. package/dist/frontend/npm-static-declaration-syntax.js.map +1 -0
  612. package/dist/frontend/npm-static-declarations.d.ts +11 -39
  613. package/dist/frontend/npm-static-declarations.js +22 -561
  614. package/dist/frontend/npm-static-declarations.js.map +1 -1
  615. package/dist/frontend/npm-static-declarations.test.js +245 -6
  616. package/dist/frontend/npm-static-declarations.test.js.map +1 -1
  617. package/dist/frontend/npm-static-prune.d.ts +1 -1
  618. package/dist/frontend/npm-static-prune.js +5 -4
  619. package/dist/frontend/npm-static-prune.js.map +1 -1
  620. package/dist/frontend/npm-static-prune.test.js +1 -1
  621. package/dist/frontend/npm-static-prune.test.js.map +1 -1
  622. package/dist/frontend/npm-static-rewrite-host.d.ts +4 -0
  623. package/dist/frontend/npm-static-rewrite-host.js +179 -0
  624. package/dist/frontend/npm-static-rewrite-host.js.map +1 -0
  625. package/dist/frontend/npm-static-rewrite-syntax.d.ts +16 -0
  626. package/dist/frontend/npm-static-rewrite-syntax.js +905 -0
  627. package/dist/frontend/npm-static-rewrite-syntax.js.map +1 -0
  628. package/dist/frontend/npm-static-rewrite.d.ts +0 -5
  629. package/dist/frontend/npm-static-rewrite.js +2 -1079
  630. package/dist/frontend/npm-static-rewrite.js.map +1 -1
  631. package/dist/frontend/npm-static-subtyping.d.ts +5 -0
  632. package/dist/frontend/npm-static-subtyping.js +55 -0
  633. package/dist/frontend/npm-static-subtyping.js.map +1 -0
  634. package/dist/frontend/npm-static-subtyping.test.d.ts +1 -0
  635. package/dist/frontend/npm-static-subtyping.test.js +58 -0
  636. package/dist/frontend/npm-static-subtyping.test.js.map +1 -0
  637. package/dist/frontend/npm-static.d.ts +7 -15
  638. package/dist/frontend/npm-static.js +19 -28
  639. package/dist/frontend/npm-static.js.map +1 -1
  640. package/dist/frontend/npm.d.ts +8 -69
  641. package/dist/frontend/npm.js +21 -361
  642. package/dist/frontend/npm.js.map +1 -1
  643. package/dist/frontend/npm.test.js +2 -2
  644. package/dist/frontend/npm.test.js.map +1 -1
  645. package/dist/frontend/pipeline-native.d.ts +6 -0
  646. package/dist/frontend/pipeline-native.js +32 -0
  647. package/dist/frontend/pipeline-native.js.map +1 -0
  648. package/dist/frontend/pipeline.d.ts +54 -0
  649. package/dist/frontend/pipeline.js +382 -0
  650. package/dist/frontend/pipeline.js.map +1 -0
  651. package/dist/frontend/pipeline.test.d.ts +1 -0
  652. package/dist/frontend/pipeline.test.js +77 -0
  653. package/dist/frontend/pipeline.test.js.map +1 -0
  654. package/dist/frontend/program-analysis.d.ts +10 -0
  655. package/dist/frontend/program-analysis.js +14 -0
  656. package/dist/frontend/program-analysis.js.map +1 -0
  657. package/dist/frontend/program-node.d.ts +9 -0
  658. package/dist/frontend/program-node.js +48 -0
  659. package/dist/frontend/program-node.js.map +1 -0
  660. package/dist/frontend/program.d.ts +43 -19
  661. package/dist/frontend/program.js +556 -197
  662. package/dist/frontend/program.js.map +1 -1
  663. package/dist/frontend/program.test.d.ts +1 -0
  664. package/dist/frontend/program.test.js +202 -0
  665. package/dist/frontend/program.test.js.map +1 -0
  666. package/dist/frontend/provenance-core.d.ts +9 -0
  667. package/dist/frontend/provenance-core.js +435 -0
  668. package/dist/frontend/provenance-core.js.map +1 -0
  669. package/dist/frontend/provenance.d.ts +0 -4
  670. package/dist/frontend/provenance.js +4 -459
  671. package/dist/frontend/provenance.js.map +1 -1
  672. package/dist/frontend/resolve.d.ts +3 -1
  673. package/dist/frontend/resolve.js +6 -3
  674. package/dist/frontend/resolve.js.map +1 -1
  675. package/dist/frontend/runtime-resolve.d.ts +4 -3
  676. package/dist/frontend/runtime-resolve.js +22 -18
  677. package/dist/frontend/runtime-resolve.js.map +1 -1
  678. package/dist/frontend/runtime-resolve.test.js +53 -0
  679. package/dist/frontend/runtime-resolve.test.js.map +1 -1
  680. package/dist/frontend/services-node.d.ts +5 -0
  681. package/dist/frontend/services-node.js +19 -0
  682. package/dist/frontend/services-node.js.map +1 -0
  683. package/dist/frontend/services-node.test.d.ts +1 -0
  684. package/dist/frontend/services-node.test.js +70 -0
  685. package/dist/frontend/services-node.test.js.map +1 -0
  686. package/dist/frontend/services.d.ts +44 -0
  687. package/dist/frontend/services.js +90 -0
  688. package/dist/frontend/services.js.map +1 -0
  689. package/dist/frontend/services.test.d.ts +1 -0
  690. package/dist/frontend/services.test.js +174 -0
  691. package/dist/frontend/services.test.js.map +1 -0
  692. package/dist/frontend/ts7/adapter.d.ts +3 -3
  693. package/dist/frontend/ts7/adapter.js +14 -31
  694. package/dist/frontend/ts7/adapter.js.map +1 -1
  695. package/dist/frontend/ts7/api.d.ts +16 -0
  696. package/dist/frontend/ts7/api.js +24 -0
  697. package/dist/frontend/ts7/api.js.map +1 -0
  698. package/dist/frontend/ts7/ast-bytes.d.ts +31 -0
  699. package/dist/frontend/ts7/ast-bytes.js +137 -0
  700. package/dist/frontend/ts7/ast-bytes.js.map +1 -0
  701. package/dist/frontend/ts7/ast-bytes.test.d.ts +1 -0
  702. package/dist/frontend/ts7/ast-bytes.test.js +73 -0
  703. package/dist/frontend/ts7/ast-bytes.test.js.map +1 -0
  704. package/dist/frontend/ts7/ast-guards.generated.d.ts +353 -0
  705. package/dist/frontend/ts7/ast-guards.generated.js +1951 -0
  706. package/dist/frontend/ts7/ast-guards.generated.js.map +1 -0
  707. package/dist/frontend/ts7/ast-guards.test.d.ts +1 -0
  708. package/dist/frontend/ts7/ast-guards.test.js +43 -0
  709. package/dist/frontend/ts7/ast-guards.test.js.map +1 -0
  710. package/dist/frontend/ts7/ast-node.d.ts +194 -0
  711. package/dist/frontend/ts7/ast-node.js +448 -0
  712. package/dist/frontend/ts7/ast-node.js.map +1 -0
  713. package/dist/frontend/ts7/ast-node.test.d.ts +1 -0
  714. package/dist/frontend/ts7/ast-node.test.js +266 -0
  715. package/dist/frontend/ts7/ast-node.test.js.map +1 -0
  716. package/dist/frontend/ts7/ast-schema.generated.d.ts +503 -0
  717. package/dist/frontend/ts7/ast-schema.generated.js +1446 -0
  718. package/dist/frontend/ts7/ast-schema.generated.js.map +1 -0
  719. package/dist/frontend/ts7/ast-text.d.ts +6 -0
  720. package/dist/frontend/ts7/ast-text.js +124 -0
  721. package/dist/frontend/ts7/ast-text.js.map +1 -0
  722. package/dist/frontend/ts7/ast-text.test.d.ts +1 -0
  723. package/dist/frontend/ts7/ast-text.test.js +38 -0
  724. package/dist/frontend/ts7/ast-text.test.js.map +1 -0
  725. package/dist/frontend/ts7/ast-tokens.generated.d.ts +2 -0
  726. package/dist/frontend/ts7/ast-tokens.generated.js +155 -0
  727. package/dist/frontend/ts7/ast-tokens.generated.js.map +1 -0
  728. package/dist/frontend/ts7/ast-types.d.ts +42 -0
  729. package/dist/frontend/ts7/ast-types.generated.d.ts +1615 -0
  730. package/dist/frontend/ts7/ast-types.generated.js +5 -0
  731. package/dist/frontend/ts7/ast-types.generated.js.map +1 -0
  732. package/dist/frontend/ts7/ast-types.js +3 -0
  733. package/dist/frontend/ts7/ast-types.js.map +1 -0
  734. package/dist/frontend/ts7/ast-wire.d.ts +58 -0
  735. package/dist/frontend/ts7/ast-wire.js +267 -0
  736. package/dist/frontend/ts7/ast-wire.js.map +1 -0
  737. package/dist/frontend/ts7/ast-wire.test.d.ts +1 -0
  738. package/dist/frontend/ts7/ast-wire.test.js +151 -0
  739. package/dist/frontend/ts7/ast-wire.test.js.map +1 -0
  740. package/dist/frontend/ts7/ast.d.ts +14 -14
  741. package/dist/frontend/ts7/ast.js +21 -15
  742. package/dist/frontend/ts7/ast.js.map +1 -1
  743. package/dist/frontend/ts7/census-check.js +1 -0
  744. package/dist/frontend/ts7/census-check.js.map +1 -1
  745. package/dist/frontend/ts7/checker-cache.d.ts +48 -0
  746. package/dist/frontend/ts7/checker-cache.js +92 -0
  747. package/dist/frontend/ts7/checker-cache.js.map +1 -0
  748. package/dist/frontend/ts7/checker.d.ts +19 -46
  749. package/dist/frontend/ts7/checker.js +231 -161
  750. package/dist/frontend/ts7/checker.js.map +1 -1
  751. package/dist/frontend/ts7/checker.test.d.ts +1 -0
  752. package/dist/frontend/ts7/checker.test.js +335 -0
  753. package/dist/frontend/ts7/checker.test.js.map +1 -0
  754. package/dist/frontend/ts7/enums.d.ts +4 -27
  755. package/dist/frontend/ts7/enums.generated.d.ts +878 -0
  756. package/dist/frontend/ts7/enums.generated.js +1313 -0
  757. package/dist/frontend/ts7/enums.generated.js.map +1 -0
  758. package/dist/frontend/ts7/enums.js +4 -45
  759. package/dist/frontend/ts7/enums.js.map +1 -1
  760. package/dist/frontend/ts7/enums.test.d.ts +1 -0
  761. package/dist/frontend/ts7/enums.test.js +48 -0
  762. package/dist/frontend/ts7/enums.test.js.map +1 -0
  763. package/dist/frontend/ts7/native-api.d.ts +10 -0
  764. package/dist/frontend/ts7/native-api.js +10 -0
  765. package/dist/frontend/ts7/native-api.js.map +1 -0
  766. package/dist/frontend/ts7/native-host.d.ts +2 -0
  767. package/dist/frontend/ts7/native-host.js +11 -0
  768. package/dist/frontend/ts7/native-host.js.map +1 -0
  769. package/dist/frontend/ts7/native-process.d.ts +14 -0
  770. package/dist/frontend/ts7/native-process.js +62 -0
  771. package/dist/frontend/ts7/native-process.js.map +1 -0
  772. package/dist/frontend/ts7/program-adapter.d.ts +10 -98
  773. package/dist/frontend/ts7/program-adapter.js +16 -310
  774. package/dist/frontend/ts7/program-adapter.js.map +1 -1
  775. package/dist/frontend/ts7/program-host.d.ts +115 -0
  776. package/dist/frontend/ts7/program-host.js +347 -0
  777. package/dist/frontend/ts7/program-host.js.map +1 -0
  778. package/dist/frontend/ts7/program-host.test.d.ts +1 -0
  779. package/dist/frontend/ts7/program-host.test.js +207 -0
  780. package/dist/frontend/ts7/program-host.test.js.map +1 -0
  781. package/dist/frontend/ts7/rpc-api.d.ts +12 -0
  782. package/dist/frontend/ts7/rpc-api.js +23 -0
  783. package/dist/frontend/ts7/rpc-api.js.map +1 -0
  784. package/dist/frontend/ts7/rpc-client.d.ts +27 -0
  785. package/dist/frontend/ts7/rpc-client.js +102 -0
  786. package/dist/frontend/ts7/rpc-client.js.map +1 -0
  787. package/dist/frontend/ts7/rpc-client.test.d.ts +1 -0
  788. package/dist/frontend/ts7/rpc-client.test.js +133 -0
  789. package/dist/frontend/ts7/rpc-client.test.js.map +1 -0
  790. package/dist/frontend/ts7/rpc-filesystem.d.ts +16 -0
  791. package/dist/frontend/ts7/rpc-filesystem.js +42 -0
  792. package/dist/frontend/ts7/rpc-filesystem.js.map +1 -0
  793. package/dist/frontend/ts7/rpc-filesystem.test.d.ts +1 -0
  794. package/dist/frontend/ts7/rpc-filesystem.test.js +89 -0
  795. package/dist/frontend/ts7/rpc-filesystem.test.js.map +1 -0
  796. package/dist/frontend/ts7/rpc-process.d.ts +2 -0
  797. package/dist/frontend/ts7/rpc-process.js +148 -0
  798. package/dist/frontend/ts7/rpc-process.js.map +1 -0
  799. package/dist/frontend/ts7/rpc-process.test.d.ts +1 -0
  800. package/dist/frontend/ts7/rpc-process.test.js +29 -0
  801. package/dist/frontend/ts7/rpc-process.test.js.map +1 -0
  802. package/dist/frontend/ts7/rpc-wire.d.ts +46 -0
  803. package/dist/frontend/ts7/rpc-wire.js +195 -0
  804. package/dist/frontend/ts7/rpc-wire.js.map +1 -0
  805. package/dist/frontend/ts7/rpc-wire.test.d.ts +1 -0
  806. package/dist/frontend/ts7/rpc-wire.test.js +168 -0
  807. package/dist/frontend/ts7/rpc-wire.test.js.map +1 -0
  808. package/dist/frontend/ts7/semantic-checker.d.ts +137 -0
  809. package/dist/frontend/ts7/semantic-checker.js +221 -0
  810. package/dist/frontend/ts7/semantic-checker.js.map +1 -0
  811. package/dist/frontend/ts7/semantic-checker.test.d.ts +1 -0
  812. package/dist/frontend/ts7/semantic-checker.test.js +119 -0
  813. package/dist/frontend/ts7/semantic-checker.test.js.map +1 -0
  814. package/dist/frontend/ts7/semantic-json.d.ts +5 -0
  815. package/dist/frontend/ts7/semantic-json.js +52 -0
  816. package/dist/frontend/ts7/semantic-json.js.map +1 -0
  817. package/dist/frontend/ts7/semantic-json.test.d.ts +1 -0
  818. package/dist/frontend/ts7/semantic-json.test.js +42 -0
  819. package/dist/frontend/ts7/semantic-json.test.js.map +1 -0
  820. package/dist/frontend/ts7/semantic-model.d.ts +222 -0
  821. package/dist/frontend/ts7/semantic-model.js +423 -0
  822. package/dist/frontend/ts7/semantic-model.js.map +1 -0
  823. package/dist/frontend/ts7/semantic-model.test.d.ts +1 -0
  824. package/dist/frontend/ts7/semantic-model.test.js +322 -0
  825. package/dist/frontend/ts7/semantic-model.test.js.map +1 -0
  826. package/dist/frontend/ts7/semantic-schema.generated.d.ts +283 -0
  827. package/dist/frontend/ts7/semantic-schema.generated.js +222 -0
  828. package/dist/frontend/ts7/semantic-schema.generated.js.map +1 -0
  829. package/dist/frontend/ts7/semantic-types.d.ts +5 -0
  830. package/dist/frontend/ts7/semantic-types.js +2 -0
  831. package/dist/frontend/ts7/semantic-types.js.map +1 -0
  832. package/dist/frontend/ts7/session-cache.d.ts +17 -0
  833. package/dist/frontend/ts7/session-cache.js +97 -0
  834. package/dist/frontend/ts7/session-cache.js.map +1 -0
  835. package/dist/frontend/ts7/session-cache.test.d.ts +1 -0
  836. package/dist/frontend/ts7/session-cache.test.js +87 -0
  837. package/dist/frontend/ts7/session-cache.test.js.map +1 -0
  838. package/dist/frontend/ts7/session-path.d.ts +18 -0
  839. package/dist/frontend/ts7/session-path.js +203 -0
  840. package/dist/frontend/ts7/session-path.js.map +1 -0
  841. package/dist/frontend/ts7/session-path.test.d.ts +1 -0
  842. package/dist/frontend/ts7/session-path.test.js +34 -0
  843. package/dist/frontend/ts7/session-path.test.js.map +1 -0
  844. package/dist/frontend/ts7/session-schema.generated.d.ts +167 -0
  845. package/dist/frontend/ts7/session-schema.generated.js +5 -0
  846. package/dist/frontend/ts7/session-schema.generated.js.map +1 -0
  847. package/dist/frontend/ts7/session-timing.d.ts +50 -0
  848. package/dist/frontend/ts7/session-timing.js +61 -0
  849. package/dist/frontend/ts7/session-timing.js.map +1 -0
  850. package/dist/frontend/ts7/session-timing.test.d.ts +1 -0
  851. package/dist/frontend/ts7/session-timing.test.js +52 -0
  852. package/dist/frontend/ts7/session-timing.test.js.map +1 -0
  853. package/dist/frontend/ts7/session.d.ts +105 -0
  854. package/dist/frontend/ts7/session.js +233 -0
  855. package/dist/frontend/ts7/session.js.map +1 -0
  856. package/dist/frontend/ts7/session.test.d.ts +1 -0
  857. package/dist/frontend/ts7/session.test.js +215 -0
  858. package/dist/frontend/ts7/session.test.js.map +1 -0
  859. package/dist/frontend/ts7/source-parser-node.d.ts +6 -0
  860. package/dist/frontend/ts7/source-parser-node.js +15 -0
  861. package/dist/frontend/ts7/source-parser-node.js.map +1 -0
  862. package/dist/frontend/ts7/source-parser.d.ts +25 -0
  863. package/dist/frontend/ts7/source-parser.js +96 -0
  864. package/dist/frontend/ts7/source-parser.js.map +1 -0
  865. package/dist/frontend/ts7/source-parser.test.d.ts +1 -0
  866. package/dist/frontend/ts7/source-parser.test.js +151 -0
  867. package/dist/frontend/ts7/source-parser.test.js.map +1 -0
  868. package/dist/frontend/ts7/syntax.d.ts +3 -0
  869. package/dist/frontend/ts7/syntax.js +4 -0
  870. package/dist/frontend/ts7/syntax.js.map +1 -0
  871. package/dist/frontend/tsc-codes.js +3 -0
  872. package/dist/frontend/tsc-codes.js.map +1 -1
  873. package/dist/frontend/type-mapper.d.ts +29 -19
  874. package/dist/frontend/type-mapper.js +713 -306
  875. package/dist/frontend/type-mapper.js.map +1 -1
  876. package/dist/frontend/type-mapper.test.d.ts +1 -0
  877. package/dist/frontend/type-mapper.test.js +207 -0
  878. package/dist/frontend/type-mapper.test.js.map +1 -0
  879. package/dist/frontend/union-discriminants.d.ts +16 -0
  880. package/dist/frontend/union-discriminants.js +98 -0
  881. package/dist/frontend/union-discriminants.js.map +1 -0
  882. package/dist/frontend/union-discriminants.test.d.ts +1 -0
  883. package/dist/frontend/union-discriminants.test.js +182 -0
  884. package/dist/frontend/union-discriminants.test.js.map +1 -0
  885. package/dist/index.d.ts +7 -197
  886. package/dist/index.js +369 -1567
  887. package/dist/index.js.map +1 -1
  888. package/dist/ir/analysis.d.ts +16 -4
  889. package/dist/ir/analysis.js +25 -6
  890. package/dist/ir/analysis.js.map +1 -1
  891. package/dist/ir/analysis.test.js +15 -2
  892. package/dist/ir/analysis.test.js.map +1 -1
  893. package/dist/ir/constant-tables.js +67 -49
  894. package/dist/ir/constant-tables.js.map +1 -1
  895. package/dist/ir/constant-tables.test.js +1 -1
  896. package/dist/ir/control-flow.d.ts +6 -0
  897. package/dist/ir/control-flow.js +142 -0
  898. package/dist/ir/control-flow.js.map +1 -0
  899. package/dist/ir/control-flow.test.d.ts +1 -0
  900. package/dist/ir/control-flow.test.js +113 -0
  901. package/dist/ir/control-flow.test.js.map +1 -0
  902. package/dist/ir/integer-loops.js +7 -18
  903. package/dist/ir/integer-loops.js.map +1 -1
  904. package/dist/ir/integer-ranges.js +53 -27
  905. package/dist/ir/integer-ranges.js.map +1 -1
  906. package/dist/ir/ir.d.ts +309 -164
  907. package/dist/ir/ir.js +588 -879
  908. package/dist/ir/ir.js.map +1 -1
  909. package/dist/ir/ir.test.js +127 -1
  910. package/dist/ir/ir.test.js.map +1 -1
  911. package/dist/ir/scalar-records.js +165 -134
  912. package/dist/ir/scalar-records.js.map +1 -1
  913. package/dist/ir/scalar-records.test.js +1 -1
  914. package/dist/ir/serialize.d.ts +4 -2
  915. package/dist/ir/serialize.js +19 -12
  916. package/dist/ir/serialize.js.map +1 -1
  917. package/dist/ir/serialize.test.d.ts +1 -0
  918. package/dist/ir/serialize.test.js +53 -0
  919. package/dist/ir/serialize.test.js.map +1 -0
  920. package/dist/ir/traverse.d.ts +41 -0
  921. package/dist/ir/traverse.js +660 -0
  922. package/dist/ir/traverse.js.map +1 -0
  923. package/dist/ir/traverse.test.d.ts +1 -0
  924. package/dist/ir/traverse.test.js +242 -0
  925. package/dist/ir/traverse.test.js.map +1 -0
  926. package/dist/ir/validate.js +1956 -1496
  927. package/dist/ir/validate.js.map +1 -1
  928. package/dist/ir/validate.test.js +476 -2
  929. package/dist/ir/validate.test.js.map +1 -1
  930. package/dist/library/cache-primitives.d.ts +2 -3
  931. package/dist/library/cache-primitives.js +4 -6
  932. package/dist/library/cache-primitives.js.map +1 -1
  933. package/dist/library/compiler-self-identity.d.ts +1 -1
  934. package/dist/library/compiler-self-identity.js +31 -15
  935. package/dist/library/compiler-self-identity.js.map +1 -1
  936. package/dist/library/compiler-self-identity.test.d.ts +1 -0
  937. package/dist/library/compiler-self-identity.test.js +93 -0
  938. package/dist/library/compiler-self-identity.test.js.map +1 -0
  939. package/dist/library/fence-eval.js +2 -2
  940. package/dist/library/fence-eval.js.map +1 -1
  941. package/dist/library/int-infer.js +7 -6
  942. package/dist/library/int-infer.js.map +1 -1
  943. package/dist/library/int-infer.test.js +4 -4
  944. package/dist/library/library-cache.d.ts +3 -2
  945. package/dist/library/library-cache.js +5 -17
  946. package/dist/library/library-cache.js.map +1 -1
  947. package/dist/library/library-cache.test.js +54 -41
  948. package/dist/library/library-cache.test.js.map +1 -1
  949. package/dist/library/library-profile.d.ts +3 -1
  950. package/dist/library/library-profile.js +15 -6
  951. package/dist/library/library-profile.js.map +1 -1
  952. package/dist/library/prepare.d.ts +15 -0
  953. package/dist/library/prepare.js +584 -0
  954. package/dist/library/prepare.js.map +1 -0
  955. package/dist/library/semantic-source.d.ts +1 -1
  956. package/dist/library/semantic-source.js +8 -6
  957. package/dist/library/semantic-source.js.map +1 -1
  958. package/dist/library/sidecar-validate.js +4 -4
  959. package/dist/library/sidecar-validate.js.map +1 -1
  960. package/dist/library/sidecar.d.ts +2 -0
  961. package/dist/library/sidecar.js +10 -3
  962. package/dist/library/sidecar.js.map +1 -1
  963. package/dist/native/cache.d.ts +11 -0
  964. package/dist/native/cache.js +113 -0
  965. package/dist/native/cache.js.map +1 -0
  966. package/dist/native/cache.test.d.ts +1 -0
  967. package/dist/native/cache.test.js +66 -0
  968. package/dist/native/cache.test.js.map +1 -0
  969. package/dist/native/cli.d.ts +1 -0
  970. package/dist/native/cli.js +112 -0
  971. package/dist/native/cli.js.map +1 -0
  972. package/dist/native/compiler.d.ts +15 -0
  973. package/dist/native/compiler.js +336 -0
  974. package/dist/native/compiler.js.map +1 -0
  975. package/dist/native/compiler.test.d.ts +1 -0
  976. package/dist/native/compiler.test.js +174 -0
  977. package/dist/native/compiler.test.js.map +1 -0
  978. package/dist/native/comptime.d.ts +5 -0
  979. package/dist/native/comptime.js +73 -0
  980. package/dist/native/comptime.js.map +1 -0
  981. package/dist/native/comptime.test.d.ts +1 -0
  982. package/dist/native/comptime.test.js +65 -0
  983. package/dist/native/comptime.test.js.map +1 -0
  984. package/dist/native/executable-cache.d.ts +26 -0
  985. package/dist/native/executable-cache.js +228 -0
  986. package/dist/native/executable-cache.js.map +1 -0
  987. package/dist/native/executable-cache.test.d.ts +1 -0
  988. package/dist/native/executable-cache.test.js +151 -0
  989. package/dist/native/executable-cache.test.js.map +1 -0
  990. package/dist/native/file-identity.d.ts +4 -0
  991. package/dist/native/file-identity.js +14 -0
  992. package/dist/native/file-identity.js.map +1 -0
  993. package/dist/native/file-identity.test.d.ts +1 -0
  994. package/dist/native/file-identity.test.js +43 -0
  995. package/dist/native/file-identity.test.js.map +1 -0
  996. package/dist/native/library.d.ts +22 -0
  997. package/dist/native/library.js +83 -0
  998. package/dist/native/library.js.map +1 -0
  999. package/dist/native/prepare.d.ts +21 -0
  1000. package/dist/native/prepare.js +174 -0
  1001. package/dist/native/prepare.js.map +1 -0
  1002. package/dist/native/prepare.test.d.ts +1 -0
  1003. package/dist/native/prepare.test.js +70 -0
  1004. package/dist/native/prepare.test.js.map +1 -0
  1005. package/dist/native/sanitizer.d.ts +11 -0
  1006. package/dist/native/sanitizer.js +135 -0
  1007. package/dist/native/sanitizer.js.map +1 -0
  1008. package/dist/native/sanitizer.test.d.ts +1 -0
  1009. package/dist/native/sanitizer.test.js +58 -0
  1010. package/dist/native/sanitizer.test.js.map +1 -0
  1011. package/dist/native/task.d.ts +4 -0
  1012. package/dist/native/task.js +16 -0
  1013. package/dist/native/task.js.map +1 -0
  1014. package/dist/native/task.test.d.ts +1 -0
  1015. package/dist/native/task.test.js +17 -0
  1016. package/dist/native/task.test.js.map +1 -0
  1017. package/dist/native/toolchain.d.ts +45 -0
  1018. package/dist/native/toolchain.js +74 -0
  1019. package/dist/native/toolchain.js.map +1 -0
  1020. package/dist/native/toolchain.test.d.ts +1 -0
  1021. package/dist/native/toolchain.test.js +33 -0
  1022. package/dist/native/toolchain.test.js.map +1 -0
  1023. package/dist/startup-cache.d.ts +1 -1
  1024. package/dist/startup-cache.js +2 -5
  1025. package/dist/startup-cache.js.map +1 -1
  1026. package/dist/timing.d.ts +4 -0
  1027. package/dist/timing.js +19 -0
  1028. package/dist/timing.js.map +1 -0
  1029. package/dist/wasi-paths.js +10 -7
  1030. package/dist/wasi-paths.js.map +1 -1
  1031. package/package.json +28 -19
  1032. package/surface-manifest.json +171 -18
  1033. package/dist/backend/c/async.d.ts +0 -171
  1034. package/dist/backend/c/async.js +0 -1317
  1035. package/dist/backend/c/async.js.map +0 -1
  1036. package/dist/backend/c/c-emitter.d.ts +0 -531
  1037. package/dist/backend/c/c-emitter.js +0 -1917
  1038. package/dist/backend/c/c-emitter.js.map +0 -1
  1039. package/dist/backend/c/exprs.d.ts +0 -15
  1040. package/dist/backend/c/exprs.js +0 -8803
  1041. package/dist/backend/c/exprs.js.map +0 -1
  1042. package/dist/backend/c/island.d.ts +0 -45
  1043. package/dist/backend/c/island.js +0 -281
  1044. package/dist/backend/c/island.js.map +0 -1
  1045. package/dist/backend/c/may-throw.js.map +0 -1
  1046. package/dist/backend/c/shapes.d.ts +0 -142
  1047. package/dist/backend/c/shapes.js +0 -603
  1048. package/dist/backend/c/shapes.js.map +0 -1
  1049. package/dist/backend/c/stmts.d.ts +0 -76
  1050. package/dist/backend/c/stmts.js +0 -1084
  1051. package/dist/backend/c/stmts.js.map +0 -1
  1052. package/dist/backend/c/types.d.ts +0 -71
  1053. package/dist/backend/c/types.js +0 -459
  1054. package/dist/backend/c/types.js.map +0 -1
  1055. package/dist/backend/c/types.test.js +0 -32
  1056. package/dist/backend/c/types.test.js.map +0 -1
  1057. package/dist/backend/c/walkers.d.ts +0 -134
  1058. package/dist/backend/c/walkers.js +0 -1773
  1059. package/dist/backend/c/walkers.js.map +0 -1
  1060. /package/dist/backend/{c/types.test.d.ts → cycle-analysis.test.d.ts} +0 -0
@@ -1,4 +1,6 @@
1
1
  import { InternalCompilerError } from "../../errors.js";
2
+ import { literalValues } from "../literal-values.js";
3
+ import { literalUnionArm } from "../union-discriminants.js";
2
4
  /* Expression lowering: the expression dispatch (lowerExpr), literals
3
5
  * (array/object/regex/template), operators (binary incl. compound targets,
4
6
  * unary, instanceof, caught-typeof tests), union narrowing and unit
@@ -6,16 +8,19 @@ import { InternalCompilerError } from "../../errors.js";
6
8
  * ToBoolean/ToString coercion helpers, and field/element reads and writes
7
9
  * (FieldTarget). */
8
10
  import * as ts from "../ts7/adapter.js";
9
- import { dirname, posix } from "node:path";
11
+ import { dirname } from "node:path";
12
+ import * as posix from "node:path/posix";
10
13
  import { pathToFileURL } from "node:url";
14
+ import { OBJECT_CALLABLE_VALUES } from "./surfaces.js";
11
15
  import { wasiGuestPath } from "../../wasi-paths.js";
12
- import { BIGINT_T, BOOL, CAUGHT, DYN, DYN_HANDLE_KINDS, F64, JSVAL, NULL_T, REF_TRUTHY_KINDS, REGEX, RUNTIME_ERROR_CLASSES, SEARCH_PARAMS_T, STRING, UNDEFINED_T, VOID, arrayOf, canAdaptDynFuncTo, canBoxFuncIntoDyn, funcOf, isDynTypedRefType, isJsonSafeType, isSupportedArrayElem, isUnitType, jsOpResultKind, shapeHasAccessorSlots, typeEquals, typeKey, unionFuncSetArmsOk } from "../../ir/ir.js";
16
+ import { BIGINT_T, BYTES_ELEMENT_NAME, BOOL, CAUGHT, DYN, DYN_HANDLE_KINDS, F64, JSVAL, NULL_T, REF_TRUTHY_KINDS, REGEX, RUNTIME_ERROR_CLASSES, SEARCH_PARAMS_T, STRING, UNDEFINED_T, VOID, arrayOf, canAdaptDynFuncTo, canDynCheckTo, canBoxFuncIntoDyn, funcOf, isDynTypedRefType, isSupportedArrayElem, isUnitType, jsOpResultKind, shapeHasAccessorSlots, typeEquals, typeKey, unionContainerArmsOk } from "../../ir/ir.js";
13
17
  import { cjsClassExprWholeExportOf, cjsExportAssignmentOf, cjsExportDiscardReason, isCjsExportTableLiteral, isCjsJsFile, isJsSourceFile, isModuleExportsAccess, isNodeEsmFile, locOf } from "../program.js";
14
18
  import { ARRAY_METHODS, builtinConstLit, builtinFenceHintOf, builtinModuleConstOf, builtinModulesArrayLit, builtinModuleFnOf, COMPOUND_ASSIGN_OPS, ISLAND_SURFACE, isChildSurfaceMember, MAP_METHODS, NARROW_FIRST, SET_METHODS, STRING_INDEX_METHODS, STR_METHODS, UNSUPPORTED_EXPR, sideEffectFreeOptionValue, stdlibGlobalNameOf } from "./surfaces.js";
15
- import { blockedBindingUseDiag, requiresDynamicPackageDiag, unsupportedDiag } from "../../diagnostics/diagnostic.js";
19
+ import { UNSUPPORTED, blockedBindingUseDiag, requiresDynamicPackageDiag, unsupportedDiag } from "../../diagnostics/diagnostic.js";
16
20
  import { PoisonError, dynUndefinedExpr, jsFuncNameOf, neverTaintedJsType, nodeThrowExpr, own } from "./lowerer.js";
17
- import { lowerNpmStaticSafeIndexRead, lowerSafeIndexRead, strCharsCall, tryLowerNumericIndexRead } from "./lower-containers.js";
18
- import { arrayValueRead, arrayValueStore } from "./array-values.js";
21
+ import { lowerCollectionSpread, lowerNpmStaticSafeIndexRead, lowerSafeIndexRead, strCharsCall, tryLowerNumericIndexRead } from "./lower-containers.js";
22
+ import { arrayValueRead, arrayValueStore, arrayValueType } from "./array-values.js";
23
+ import { lowerOptionalStringIndex } from "./string-index.js";
19
24
  import { tryLowerIndexedComparison } from "./indexed-comparison.js";
20
25
  import { npmStaticPackageOfPath } from "../npm-static.js";
21
26
  import { unsupportedModuleFeatureOf } from "../builtin-modules.js";
@@ -23,20 +28,32 @@ import { fenceEnumObjectValue, lowerEnumAccess } from "./lower-enums.js";
23
28
  import { ambientNsRootOf, ambientUndefReadType, ambientUndefVarRootOf, ambientUndefinedFnSymbolOf, contextualUndefReadType, fenceEarlyAliasUse, fenceEarlyNsMemberRef, lowerNsIdentifierValue, nsMemberIdentOf, nsUndefRead, nsWritableTarget } from "./lower-namespaces.js";
24
29
  import { expandoMemberRead, expandoWritableTarget } from "./lower-expando.js";
25
30
  import { lowerSocketInstanceOf, lowerTlsRootCertificates } from "./lower-server.js";
26
- import { findGenericMethodOn, lowerStaticFieldRead } from "./lower-classes.js";
31
+ import { findGenericMethodOn, lowerStaticFieldRead, staticFieldWriteTarget, storedClassValueType } from "./lower-classes.js";
27
32
  import { bindingNeverReassigned, funcTypeFromParamShapes, implicitMonoFile, lowerTaggedTemplate, nullishGenericBindingUnitOf, objLitGenericFnInfoOf, objLitGenericFnNodeOf, requireObjLitGenericReceiver } from "./lower-calls.js";
28
33
  import { mixinFnOfCallee } from "./lower-mixins.js";
29
- import { isConstAssertionTypeNode, isGenericCallableMemberType, isParseArgsDynTypeName, underConstAssertion, unitOnlyUnion } from "../type-mapper.js";
34
+ import { isConstAssertionTypeNode, isGenericCallableMemberType, isParseArgsDynTypeName, underConstAssertion, unitOnlyUnion, withUnitArm } from "../type-mapper.js";
30
35
  import { lowerYield } from "./lower-generators.js";
36
+ import { errorToStringCall } from "./error-methods.js";
31
37
  import { lowerStreamProperty, lowerStreamStateProperty, streamSidesOf } from "./lower-stream.js";
32
38
  import { countedFor, numLit, varRef } from "../../ir/build.js";
39
+ import { unionWideningTags } from "../../ir/analysis.js";
33
40
  import { isSafeToDiscard, isSafeToMoveConditionEarlier, isSafeToRepeat } from "./expressions/evaluation-safety.js";
41
+ import { globalSymbolKey } from "./expressions/global-symbols.js";
42
+ import { lowerShortCircuitAssignment } from "./expressions/nullish-assignment.js";
34
43
  import { hasOptionalChainGuard, isOptionalChainTail, isRequireMainFilename } from "./expressions/optional-chains.js";
35
44
  import { conditionalSpreadOf, foldedStringKeyOf } from "./expressions/object-literals.js";
36
45
  import { tryLowerExpression } from "./expressions/try-lower-expression.js";
37
46
  import { fenceNodeModuleMutation, isNodeModuleValue, lowerNodeModuleIdentifier, lowerNodeModuleProperty, lowerRequireCacheElement, lowerRequireCacheHas, lowerRequireMainProperty } from "./lower-node-module.js";
38
47
  import { lowerAbstractEquality } from "./abstract-equality.js";
39
48
  import { coerceStringSearchValue, defaultAfterUndefined, lowerStaticallyUndefinedArgument } from "./optional-arguments.js";
49
+ import { recordTextCodecClass } from "../../ir/ir.js";
50
+ import { classSymbolKeyOf } from "./symbol-fields.js";
51
+ import { lowerClassMethodValue } from "./class-method-values.js";
52
+ import { classInstanceOf } from "./class-dynamic-dispatch.js";
53
+ import { lowerGlobalValue } from "./lower-global-value.js";
54
+ import { lowerClassPrototypeData } from "./class-prototypes.js";
55
+ import { lowerArrayIsArrayValue } from "./lower-builtin-values.js";
56
+ import { lowerPerfHooksTypeof } from "./lower-builtins.js";
40
57
  /** A template piece's RAW text (String.raw's contract: escapes stay
41
58
  * characters). 7's client AST ships no rawText at runtime (the typing
42
59
  * declares it; the serialized node data omits it), so the raw span comes
@@ -313,6 +330,12 @@ function lowerExprInner(lowerer, expr) {
313
330
  return !sf.isDeclarationFile && !sf.fileName.includes("/node_modules/");
314
331
  });
315
332
  if (projectDeclared) {
333
+ // A spread already stored in the island must copy there too.
334
+ // Later overrides can make the literal's own inferred shape
335
+ // appear static without making that source a native record.
336
+ if (ts.isObjectLiteralExpression(expr) && expr.properties.some((p) => ts.isSpreadAssignment(p) && lowerer.mapTypeOf(lowerer.typeOf(p.expression))?.kind === "jsval")) {
337
+ return true;
338
+ }
316
339
  const own = lowerer.mapTypeOf(lowerer.typeOf(expr));
317
340
  if (own?.kind === "record" || own?.kind === "array")
318
341
  return false;
@@ -350,7 +373,7 @@ function lowerExprInner(lowerer, expr) {
350
373
  // null keeps the closure shape (function-shaped members, and the
351
374
  // conditional-spread arms where getters cannot combine).
352
375
  const islandMemberFence = (diagsBefore, err, valueNode, getterName = null) => {
353
- if (!(err instanceof PoisonError) || !isJsSourceFile(expr.getSourceFile()))
376
+ if (!isJsSourceFile(expr.getSourceFile()))
354
377
  throw err;
355
378
  const fence = lowerer.deferToRuntimeFence(diagsBefore, valueNode, {
356
379
  kind: "closure",
@@ -398,6 +421,8 @@ function lowerExprInner(lowerer, expr) {
398
421
  // instance): func-typed values keep the call-time closure;
399
422
  // data-shaped values fence at the read (the getter), unless a
400
423
  // conditional spread owns the literal (getters cannot combine).
424
+ if (!(err instanceof PoisonError))
425
+ throw err;
401
426
  const asGetter = value.type.kind !== "func" && spread === null;
402
427
  const fence = islandMemberFence(diagsBefore, err, valueNode, asGetter ? name.text : null);
403
428
  if (fence === null)
@@ -486,6 +511,8 @@ function lowerExprInner(lowerer, expr) {
486
511
  : null;
487
512
  }
488
513
  catch (err) {
514
+ if (!(err instanceof PoisonError))
515
+ throw err;
489
516
  const nameText = name && (ts.isIdentifier(name) || ts.isStringLiteral(name)) ? name.text : null;
490
517
  const asGetter = nameText !== null && spread === null && !funcShapedMember(prop);
491
518
  value = islandMemberFence(valueDiagsBefore, err, prop, asGetter ? nameText : null);
@@ -551,6 +578,11 @@ function lowerExprInner(lowerer, expr) {
551
578
  return { kind: "jsOp", op: "arrLit", args, type: JSVAL, loc };
552
579
  }
553
580
  if (ts.isTypeOfExpression(expr)) {
581
+ if (lowerer.isStdlibGlobal(expr.expression, "process"))
582
+ return { kind: "strLit", value: "object", type: STRING, loc };
583
+ const performance = lowerPerfHooksTypeof(lowerer, expr.expression);
584
+ if (performance)
585
+ return performance;
554
586
  if (isNodeModuleValue(lowerer, expr.expression)) {
555
587
  return { kind: "strLit", value: "object", type: STRING, loc };
556
588
  }
@@ -580,8 +612,14 @@ function lowerExprInner(lowerer, expr) {
580
612
  // every function and constructor global is "function" (the harness's
581
613
  // `typeof queueMicrotask === 'function'` probes). Shadowing locals
582
614
  // have non-stdlib symbols and keep the ordinary path.
583
- if (ts.isIdentifier(expr.expression)) {
584
- const sym = lowerer.checker.getSymbolAtLocation(expr.expression);
615
+ let tested = expr.expression;
616
+ while (ts.isParenthesizedExpression(tested))
617
+ tested = tested.expression;
618
+ if (ts.isIdentifier(tested)) {
619
+ const sym = lowerer.checker.getSymbolAtLocation(tested);
620
+ if (!sym && !lowerer.dynamic) {
621
+ return { kind: "strLit", value: "undefined", type: STRING, loc };
622
+ }
585
623
  if (lowerer.isStdlibSymbol(sym)) {
586
624
  const t = lowerer.typeOf(expr.expression);
587
625
  if (lowerer.checker.getCallSignatures(t).length > 0 ||
@@ -590,11 +628,9 @@ function lowerExprInner(lowerer, expr) {
590
628
  }
591
629
  }
592
630
  }
593
- // Island values ask the engine; static primitives constant-fold to
594
- // the JS answer (the operand still evaluates — JS evaluates typeof
595
- // operands too — but primitives here are side-effect-free varRefs/
596
- // literals after lowering, so folding away the value is safe only
597
- // when the operand is trivial; otherwise keep it simple and reject).
631
+ // A known static result type fixes typeof's answer, but its producer
632
+ // still evaluates: calls, getters, and checked reads can have effects
633
+ // or throw. Only trivial operands may disappear.
598
634
  let operand = lowerer.lowerExpr(expr.expression);
599
635
  if (operand.type.kind === "jsval") {
600
636
  return { kind: "jsOp", op: "typeof", args: [operand], type: STRING, loc };
@@ -612,13 +648,16 @@ function lowerExprInner(lowerer, expr) {
612
648
  array: "object", object: "object", record: "object",
613
649
  symbol: "symbol",
614
650
  map: "object", set: "object", promise: "object", bytes: "object",
615
- regexp: "object", generator: "object", classval: "function",
651
+ regex: "object", generator: "object", classval: "function",
616
652
  moduleNs: "object",
617
653
  undefinedT: "undefined", nullT: "object",
618
654
  };
619
655
  const folded = FOLD[operand.type.kind];
620
- if (folded && isSafeToDiscard(operand)) {
621
- return { kind: "strLit", value: folded, type: STRING, loc };
656
+ if (folded) {
657
+ const result = { kind: "strLit", value: folded, type: STRING, loc };
658
+ return isSafeToDiscard(operand) ? result : {
659
+ kind: "seqExpr", stmts: [{ kind: "exprStmt", expr: operand, loc }], result, type: STRING, loc,
660
+ };
622
661
  }
623
662
  // A union operand: every arm's typeof answer is static, so the value
624
663
  // form is a ternary chain over runtime TAG tests (arms grouped by
@@ -653,6 +692,9 @@ function lowerExprInner(lowerer, expr) {
653
692
  lowerer.unsupported("SC1090", expr, "typeof expressions on statically-typed values");
654
693
  }
655
694
  if (ts.isIdentifier(expr)) {
695
+ if (lowerer.isStdlibGlobal(expr, "console")) {
696
+ return { kind: "libCall", fn: "console.native", args: [], type: DYN, loc };
697
+ }
656
698
  const moduleValue = lowerNodeModuleIdentifier(lowerer, expr);
657
699
  if (moduleValue)
658
700
  return moduleValue;
@@ -676,7 +718,7 @@ function lowerExprInner(lowerer, expr) {
676
718
  }
677
719
  if (projection?.kind === "builtin-function") {
678
720
  if (!isJsSourceFile(expr.getSourceFile())) {
679
- const callable = lowerer.lowerBuiltinCallableValue(projection, loc);
721
+ const callable = lowerer.lowerBuiltinCallableValue({ module: projection.module, member: projection.member }, loc);
680
722
  if (callable)
681
723
  return callable;
682
724
  }
@@ -688,6 +730,21 @@ function lowerExprInner(lowerer, expr) {
688
730
  // bridges the tagged representation with a unionNarrow.
689
731
  const local = lowerer.resolveLocal(expr);
690
732
  if (local) {
733
+ // A switch can dispatch past a lexical declaration. Keep this
734
+ // previously refused codec form out until switch-scope TDZ boxes
735
+ // model that skipped initialization, including value aliases.
736
+ if (local.type.kind === "record" && recordTextCodecClass(lowerer.shapes.get(local.type.shapeId)) !== null) {
737
+ const symbol = lowerer.resolveValueSymbol(expr);
738
+ const decl = symbol ? lowerer.checker.valueDeclarationOf(symbol) : undefined;
739
+ if (decl && ts.isVariableDeclaration(decl) && ts.isVariableDeclarationList(decl.parent)
740
+ && (decl.parent.flags & ts.NodeFlags.BlockScoped) !== 0 && ts.isVariableStatement(decl.parent.parent)) {
741
+ const clause = decl.parent.parent.parent;
742
+ if ((ts.isCaseClause(clause) || ts.isDefaultClause(clause))
743
+ && !(expr.getStart() >= clause.getStart() && expr.end <= clause.end)) {
744
+ lowerer.unsupported("SC1090", expr, "codec bindings read from another switch clause before initialization is proven");
745
+ }
746
+ }
747
+ }
691
748
  if (local.type.kind === "caught")
692
749
  return lowerer.caughtRead(expr, local, loc);
693
750
  const symbol = lowerer.resolveValueSymbol(expr);
@@ -696,7 +753,7 @@ function lowerExprInner(lowerer, expr) {
696
753
  const arithmetic = lowerer.runtimeOptionalArithmeticLocals.has(runtimeOptionalRoot);
697
754
  const captured = !!symbol &&
698
755
  lowerer.runtimeOptionalStorageLocals.has(runtimeOptionalRoot) &&
699
- (lowerer.ctx.captureBySymbol.get(symbol) === local || runtimeOptionalRoot !== local);
756
+ (local.boxed === true || lowerer.ctx.captureBySymbol.get(symbol) === local || runtimeOptionalRoot !== local);
700
757
  if (arithmetic && !runtimeOptionalUseOf(expr)) {
701
758
  return { kind: "varRef", localId: local.id, type: local.type, loc };
702
759
  }
@@ -737,7 +794,24 @@ function lowerExprInner(lowerer, expr) {
737
794
  }
738
795
  return { kind: "varRef", localId: local.id, type: local.type, loc };
739
796
  }
740
- return lowerer.maybeNarrow({ kind: "varRef", localId: local.id, type: local.type, loc }, expr);
797
+ const narrowed = lowerer.maybeNarrow({ kind: "varRef", localId: local.id, type: local.type, loc }, expr);
798
+ if (narrowed.type.kind === "union" && lowerer.runtimeOptionalStorageLocals.has(runtimeOptionalRoot) &&
799
+ lowerer.mapTypeOf(lowerer.typeOf(expr))?.kind === "record") {
800
+ // A presence guard can be followed by a predicate strengthening
801
+ // the record's fields. Its refined shape is not a stored union
802
+ // arm: extract the original layout before reading a member.
803
+ const use = runtimeOptionalUseOf(expr);
804
+ if (use && !use.complex && !use.optional) {
805
+ if (use.kind === "property")
806
+ return runtimeOptionalReceiverRead(lowerer, expr, local, use.access.name.text, loc);
807
+ if (use.kind === "element") {
808
+ const key = runtimeOptionalElementKey(use.access.argumentExpression);
809
+ if (key !== null)
810
+ return runtimeOptionalReceiverRead(lowerer, expr, local, key, loc);
811
+ }
812
+ }
813
+ }
814
+ return narrowed;
741
815
  }
742
816
  // `import x = N.y` aliases resolve transparently through globalOf/
743
817
  // fnSigOf below; their source-order guards live here (a no-op for
@@ -774,6 +848,7 @@ function lowerExprInner(lowerer, expr) {
774
848
  lowerer.unions.get(g.type.unionId).arms.some((a) => a.kind === "string");
775
849
  if (!arithmeticUnion && g.type.kind === "union" && narrowed && narrowed.kind !== "union" &&
776
850
  !isUnitType(narrowed) &&
851
+ !(narrowed.kind === "record" && recordTextCodecClass(lowerer.shapes.get(narrowed.shapeId)) !== null) &&
777
852
  narrowed.kind !== "f64" && narrowed.kind !== "string" && narrowed.kind !== "bool" &&
778
853
  lowerer.armTag(g.type.unionId, narrowed) >= 0) {
779
854
  const helper = lowerer.narrowedArmHelper(g.type.unionId, narrowed, loc);
@@ -817,7 +892,7 @@ function lowerExprInner(lowerer, expr) {
817
892
  if ((expr.text === "__dirname" || expr.text === "__filename") &&
818
893
  lowerer.isStdlibGlobal(expr, expr.text)) {
819
894
  const sf = expr.getSourceFile();
820
- if (isNodeEsmFile(sf)) {
895
+ if (isNodeEsmFile(sf, lowerer.program)) {
821
896
  lowerer.unsupported("SC1090", expr, `'${expr.text}' in an ES module (Node defines the CJS module globals only in CommonJS modules)`);
822
897
  }
823
898
  // The WASI runner mounts the host cwd at guest `/` and the host temp
@@ -899,17 +974,17 @@ function lowerExprInner(lowerer, expr) {
899
974
  if (roots)
900
975
  return roots;
901
976
  }
902
- // JavaScript sources: a builtin member taken as a bare VALUE is
903
- // the same identity-token story as stdlib globals above (the
904
- // harness adds worker_threads.Worker to its identity Set).
905
- if (isJsSourceFile(expr.getSourceFile())) {
906
- return { kind: "strLit", value: `[builtin ${bi.module}.${bi.member}]`, type: STRING, loc };
907
- }
908
977
  {
909
978
  const callable = lowerer.lowerBuiltinCallableValue(bi, loc);
910
979
  if (callable)
911
980
  return callable;
912
981
  }
982
+ // JavaScript sources: an otherwise unsupported builtin VALUE is
983
+ // the same identity-token story as stdlib globals above (the
984
+ // harness adds worker_threads.Worker to its identity Set).
985
+ if (isJsSourceFile(expr.getSourceFile())) {
986
+ return { kind: "strLit", value: `[builtin ${bi.module}.${bi.member}]`, type: STRING, loc };
987
+ }
913
988
  if (builtinModuleFnOf(lowerer, bi.module, bi.member)) {
914
989
  lowerer.unsupported("SC1090", expr, `library functions as values (call '${expr.text}' directly)`);
915
990
  }
@@ -999,6 +1074,9 @@ function lowerExprInner(lowerer, expr) {
999
1074
  // global, one token; what a token cannot do (be called, answer
1000
1075
  // typeof "function") meets per-site fences/divergences, and
1001
1076
  // SEMANTICS.md documents the stance.
1077
+ if (!lowerer.dynamic && stdlibGlobalNameOf(lowerer, expr) === "globalThis") {
1078
+ return lowerGlobalValue(lowerer, expr);
1079
+ }
1002
1080
  {
1003
1081
  const sym = lowerer.checker.getSymbolAtLocation(expr);
1004
1082
  if (lowerer.isStdlibSymbol(sym) || expr.text === "globalThis") {
@@ -1006,14 +1084,21 @@ function lowerExprInner(lowerer, expr) {
1006
1084
  const canonical = stdlibGlobalNameOf(lowerer, expr) ?? expr.text;
1007
1085
  return { kind: "strLit", value: `[builtin ${canonical}]`, type: STRING, loc };
1008
1086
  }
1087
+ // In a dynamic TypeScript build, the real global object is the
1088
+ // escape hatch for host capabilities without static lowering.
1089
+ // Direct globalThis.member uses still take their static surface
1090
+ // paths; an explicit `as any` or `: any` can reach engine ops.
1091
+ if (lowerer.dynamic && expr.text === "globalThis") {
1092
+ return { kind: "jsOp", op: "globalGet", name: "globalThis", args: [], type: JSVAL, loc };
1093
+ }
1009
1094
  // The families with a WHY: each hint states what makes the
1010
1095
  // surface genuinely non-static (or what to use instead).
1011
1096
  const globalHints = {
1012
- Proxy: "property-access metaprogramming has no static lowering (every property read must resolve at compile time)",
1097
+ Proxy: "Proxy constructor values have no native lowering; use a direct new Proxy with checked-native plain objects",
1013
1098
  Reflect: "reflective property access has no static lowering — read and call members directly",
1014
- Intl: "locale- and ICU-backed behavior lives outside the static runtime (the localeCompare stance: code-unit order, no collation/locale data) — what lowers: the composed new Intl.NumberFormat(\"en-US\").format(x) and x.toLocaleString(\"en-US\") with default options",
1099
+ Intl: "native Intl supports default Unicode grapheme segmentation and the composed new Intl.NumberFormat(\"en-US\").format(x); locale negotiation and other ICU-backed operations remain unsupported",
1015
1100
  SharedArrayBuffer: "no shared-memory threads exist in a compiled program — Uint8Array is the byte storage",
1016
- ArrayBuffer: "no free-standing ArrayBuffer value exists — typed arrays own their storage (new Uint8Array(n) allocates; new Uint8Array(new ArrayBuffer(n)) erases the buffer into the view)",
1101
+ ArrayBuffer: "ArrayBuffer constructor values have no native lowering; use new ArrayBuffer(length) for fixed-length shared storage",
1017
1102
  WeakRef: "deref()-after-collect exposes GC timing — genuinely dynamic; hold a strong reference instead",
1018
1103
  FinalizationRegistry: "finalization callbacks expose GC timing — genuinely dynamic; release resources explicitly instead",
1019
1104
  eval: "runtime code evaluation cannot be compiled ahead of time",
@@ -1363,6 +1448,14 @@ function lowerExprInner(lowerer, expr) {
1363
1448
  if (ts.isConditionalExpression(expr))
1364
1449
  return lowerTernary(lowerer, expr);
1365
1450
  if (ts.isPropertyAccessExpression(expr)) {
1451
+ // Expando statics may still be absent even when JS inference sees
1452
+ // the eventual assignment's scalar type. Preserve the checked value.
1453
+ const staticField = lowerStaticFieldRead(lowerer, expr);
1454
+ if (staticField)
1455
+ return staticField;
1456
+ const prototypeData = lowerClassPrototypeData(lowerer, expr);
1457
+ if (prototypeData)
1458
+ return prototypeData;
1366
1459
  // `super.x`: the base chain's GETTER, called directly (super
1367
1460
  // dispatch is static in JS — never through the dynamic class).
1368
1461
  // super.method() calls are routed at the call site; a bare super
@@ -1404,6 +1497,25 @@ function lowerExprInner(lowerer, expr) {
1404
1497
  const moduleProperty = lowerNodeModuleProperty(lowerer, expr);
1405
1498
  if (moduleProperty)
1406
1499
  return moduleProperty;
1500
+ if (stdlibGlobalNameOf(lowerer, expr.expression) === "Symbol" &&
1501
+ ["iterator", "asyncIterator", "toStringTag", "toPrimitive", "hasInstance", "isConcatSpreadable", "match", "matchAll", "replace", "search", "split", "species", "unscopables", "dispose", "asyncDispose"].includes(expr.name.text)) {
1502
+ return { kind: "libCall", fn: "sym.wellKnown", args: [{ kind: "strLit", value: expr.name.text, type: STRING, loc }], type: { kind: "symbol" }, loc };
1503
+ }
1504
+ if (lowerer.stdlibGlobalMember(expr, "globalThis") === "console") {
1505
+ return { kind: "libCall", fn: "console.native", args: [], type: DYN, loc };
1506
+ }
1507
+ if (lowerer.stdlibGlobalMember(expr, "console") !== null) {
1508
+ return { kind: "dynKeyGet", value: { kind: "libCall", fn: "console.native", args: [], type: DYN, loc },
1509
+ key: { kind: "strLit", value: expr.name.text, type: STRING, loc }, type: DYN, loc };
1510
+ }
1511
+ // Missing host globals (Bun/Deno capability probes) have no binding
1512
+ // in a native program. Declared globals retain their own lowering;
1513
+ // arbitrary global-object mutation remains fenced. Preserve a dyn
1514
+ // undefined value so stored probes compose with optional calls.
1515
+ if (!lowerer.dynamic && stdlibGlobalNameOf(lowerer, expr.expression) === "globalThis" &&
1516
+ !lowerer.checker.getPropertyOfType(lowerer.typeOf(expr.expression), expr.name.text)) {
1517
+ return { kind: "dynFrom", value: { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc }, type: DYN, loc };
1518
+ }
1407
1519
  if (expr.questionDotToken && expr.name.text === "electron") {
1408
1520
  const processProperty = lowerer.lowerProcessProperty(expr);
1409
1521
  if (processProperty)
@@ -1420,6 +1532,12 @@ function lowerExprInner(lowerer, expr) {
1420
1532
  if (!expr.questionDotToken && isOptionalChainTail(lowerer, expr)) {
1421
1533
  return lowerer.lowerOptionalChain(expr);
1422
1534
  }
1535
+ // Known Web handles retain their declared surface even when their
1536
+ // storage uses checked values. Apply this before generic reads.
1537
+ lowerer.fenceStaticAbortControllerMemberRead(expr);
1538
+ lowerer.fenceStaticResponseMember(expr, "read");
1539
+ lowerer.fenceStaticHeadersMember(expr, "read");
1540
+ lowerer.fenceStaticReadableStreamMember(expr, "read");
1423
1541
  // Island receiver: o.x is an engine property read (getProp may throw
1424
1542
  // — reading off null/undefined — bridged catchably like everything
1425
1543
  // at the boundary). `o?.x` arrives here too, through the chain
@@ -1559,11 +1677,11 @@ function lowerExprInner(lowerer, expr) {
1559
1677
  // resolve unconditionally — they run after the module evaluated,
1560
1678
  // the same approximation identifier exports already make. Every
1561
1679
  // other module.exports read keeps its existing story.
1562
- if (!expr.questionDotToken && isModuleExportsAccess(expr) && isCjsJsFile(expr.getSourceFile())) {
1680
+ if (!expr.questionDotToken && isModuleExportsAccess(expr) && isCjsJsFile(expr.getSourceFile(), lowerer.program)) {
1563
1681
  const whole = cjsClassExprWholeExportOf(expr.getSourceFile());
1564
1682
  if (whole) {
1565
1683
  let inBody = false;
1566
- for (let p = expr.parent; !ts.isSourceFile(p); p = p.parent) {
1684
+ for (let p = expr.parent; p !== undefined && !ts.isSourceFile(p); p = p.parent) {
1567
1685
  if (ts.isFunctionLike(p) || ts.isClassDeclaration(p) || ts.isClassExpression(p)) {
1568
1686
  inBody = true;
1569
1687
  break;
@@ -1591,7 +1709,7 @@ function lowerExprInner(lowerer, expr) {
1591
1709
  const writePos = ts.isBinaryExpression(expr.parent) &&
1592
1710
  expr.parent.left === expr &&
1593
1711
  expr.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken;
1594
- if (!expr.questionDotToken && cjsMemberRecv && !writePos && isCjsJsFile(sf)) {
1712
+ if (!expr.questionDotToken && cjsMemberRecv && !writePos && isCjsJsFile(sf, lowerer.program)) {
1595
1713
  const sym = lowerer.checker.getSymbolAtLocation(expr.name) ??
1596
1714
  lowerer.cjsModuleExportSymbol(sf, expr.name.text);
1597
1715
  const resolved = sym !== undefined && sym.flags & ts.SymbolFlags.Alias
@@ -1713,6 +1831,8 @@ function lowerExprInner(lowerer, expr) {
1713
1831
  isJsSourceFile(expr.getSourceFile())) {
1714
1832
  const canonical = stdlibGlobalNameOf(lowerer, expr);
1715
1833
  if (canonical !== null) {
1834
+ if (!lowerer.dynamic && canonical === "globalThis")
1835
+ return lowerGlobalValue(lowerer, expr);
1716
1836
  return { kind: "strLit", value: `[builtin ${canonical}]`, type: STRING, loc };
1717
1837
  }
1718
1838
  }
@@ -1736,6 +1856,7 @@ function lowerExprInner(lowerer, expr) {
1736
1856
  // instead.
1737
1857
  if ((isJsSourceFile(expr.getSourceFile()) &&
1738
1858
  (lowerer.mapTypeOf(lowerer.typeOf(expr.expression)) === null ||
1859
+ (lowerer.typeOf(expr.expression).flags & ts.TypeFlags.Never) !== 0 ||
1739
1860
  // A never-tainted receiver type maps (never rides as f64) but
1740
1861
  // its VALUE lowered checked-dynamic — same dyn read.
1741
1862
  neverTaintedJsType(lowerer, expr.expression, lowerer.typeOf(expr.expression)))) ||
@@ -1749,6 +1870,11 @@ function lowerExprInner(lowerer, expr) {
1749
1870
  const opt = hasOptionalChainGuard(expr.expression);
1750
1871
  return lowerer.maybeNarrow({ kind: "dynKeyGet", key, ...(opt ? { optional: true } : {}), value: recv, type: DYN, loc }, expr);
1751
1872
  }
1873
+ if (expr.name.text === "length" && (recv.type.kind === "array" || recv.type.kind === "string")) {
1874
+ return recv.type.kind === "array"
1875
+ ? { kind: "arrIntrinsic", method: "length", receiver: recv, args: [], type: F64, loc }
1876
+ : { kind: "strIntrinsic", method: "length", receiver: recv, args: [], type: F64, loc };
1877
+ }
1752
1878
  }
1753
1879
  // `f.name` / `f.length` / own properties on a FUNCTION-typed JS
1754
1880
  // value (the mustCall wrapper's function-instance members): read
@@ -1757,11 +1883,24 @@ function lowerExprInner(lowerer, expr) {
1757
1883
  // best-effort static name and the declared arity; anything else is
1758
1884
  // the own-property answer, undefined. Function.prototype METHOD
1759
1885
  // names stay fenced (a stored-member undefined would mis-answer
1760
- // `f.call` as a value). JS files only; TS keeps the fence.
1761
- if (!["apply", "bind", "call", "toString", "constructor", "prototype", "caller", "arguments"].includes(expr.name.text) &&
1762
- isJsSourceFile(expr.getSourceFile())) {
1886
+ // `f.call` as a value). Static callable values keep the existing TS fence; checked callable
1887
+ // storage can expose its own name and length in both source languages.
1888
+ if (!["apply", "bind", "call", "toString", "caller", "arguments"].includes(expr.name.text) &&
1889
+ (isJsSourceFile(expr.getSourceFile()) || expr.name.text === "name" || expr.name.text === "length" || expr.name.text === "constructor")) {
1763
1890
  const probed = tryLowerExpression(lowerer, expr.expression);
1764
- if (probed?.type.kind === "func" &&
1891
+ // Inferred callable returns can use checked storage even when the
1892
+ // checker still describes a function. Read its actual dyn value;
1893
+ // the ambient Function member must not hide its own properties.
1894
+ const receiverType = lowerer.typeOf(expr.expression);
1895
+ const receiverSymbol = receiverType.getSymbol();
1896
+ const checkedFunction = lowerer.checker.getCallSignatures(receiverType).length > 0 ||
1897
+ (receiverSymbol?.name === "Function" && lowerer.isStdlibSymbol(receiverSymbol));
1898
+ if (probed?.type.kind === "dyn" && checkedFunction) {
1899
+ const key = { kind: "strLit", value: expr.name.text, type: STRING, loc: locOf(expr.name) };
1900
+ const opt = hasOptionalChainGuard(expr.expression);
1901
+ return lowerer.maybeNarrow({ kind: "dynKeyGet", key, value: probed, ...(opt ? { optional: true } : {}), type: DYN, loc }, expr);
1902
+ }
1903
+ if ((isJsSourceFile(expr.getSourceFile()) || expr.name.text === "constructor") && probed?.type.kind === "func" &&
1765
1904
  canBoxFuncIntoDyn(probed.type, (id) => lowerer.shapes.get(id), (id) => lowerer.unions.get(id))) {
1766
1905
  const fnName = jsFuncNameOf(expr.expression);
1767
1906
  const boxed = {
@@ -1816,7 +1955,10 @@ function lowerExprInner(lowerer, expr) {
1816
1955
  // the generic dyn receiver path below. Provenance is limited to the
1817
1956
  // node:util parseArgs family; every other stdlib member keeps its
1818
1957
  // ordinary surface fence.
1819
- if (isParseArgsDynProperty(lowerer, expr)) {
1958
+ const segmentDataSymbol = lowerer.typeOf(expr.expression).getSymbol();
1959
+ const segmentDataProperty = segmentDataSymbol?.name === "SegmentData" && lowerer.isStdlibSymbol(segmentDataSymbol) &&
1960
+ ["segment", "index", "input", "isWordLike"].includes(expr.name.text);
1961
+ if (isParseArgsDynProperty(lowerer, expr) || segmentDataProperty) {
1820
1962
  const recv = lowerer.lowerExpr(expr.expression);
1821
1963
  if (recv.type.kind === "dyn") {
1822
1964
  const key = {
@@ -1850,10 +1992,6 @@ function lowerExprInner(lowerer, expr) {
1850
1992
  // The lib fence's PROPERTY chokepoint: a stdlib-declared member that
1851
1993
  // no lowering above claimed ([1,2].entries, Math.SQRT2, Promise.all,
1852
1994
  // re.exec as a value, ...) reports SC2020 here.
1853
- lowerer.fenceStaticAbortControllerMemberRead(expr);
1854
- lowerer.fenceStaticResponseMember(expr, "read");
1855
- lowerer.fenceStaticHeadersMember(expr, "read");
1856
- lowerer.fenceStaticReadableStreamMember(expr, "read");
1857
1995
  lowerer.stdlibMemberFence(expr);
1858
1996
  // The npm chokepoint: a member on a package-typed receiver in a
1859
1997
  // static build — attributed to the package, like every other site.
@@ -1912,6 +2050,17 @@ function lowerExprInner(lowerer, expr) {
1912
2050
  loc,
1913
2051
  }, expr);
1914
2052
  }
2053
+ // An inferred package-JS call specializes a parameter to the
2054
+ // caller's own record shape. Optional option names omitted by that
2055
+ // caller read undefined; the missing field is not a failed type
2056
+ // assertion. Known prototype members were handled/fenced above.
2057
+ if (recvShape && !recvShape.tuple && lowerer.implicitParamTypes !== null &&
2058
+ isJsSourceFile(expr.getSourceFile()) && npmStaticPackageOfPath(expr.getSourceFile().fileName) !== null) {
2059
+ return {
2060
+ kind: "seqExpr", stmts: [{ kind: "exprStmt", expr: recvLowered, loc }],
2061
+ result: dynUndefinedExpr(loc), type: DYN, loc,
2062
+ };
2063
+ }
1915
2064
  }
1916
2065
  // An ABSTRACT property through an abstract-typed receiver: the
1917
2066
  // declaration is erased at runtime — Node defines no field for it,
@@ -1921,6 +2070,19 @@ function lowerExprInner(lowerer, expr) {
1921
2070
  if (recvLowered.type.kind === "object" && abstractPropertyDeclOf(lowerer, expr)) {
1922
2071
  lowerer.unsupported("SC1090", expr, `reading the abstract property '${expr.name.text}' through a '${lowerer.checker.typeToString(lowerer.typeOf(expr.expression))}'-typed receiver (abstract property declarations are erased at runtime, so no shared slot exists — type the receiver as the concrete class, or declare an abstract getter instead)`);
1923
2072
  }
2073
+ // Array reads may add an undefined arm even when the checker still
2074
+ // reports a class. Its property bag must remain reachable, while
2075
+ // an absent element takes the normal checked-value TypeError.
2076
+ const nativeReceiver = lowerer.stripUndefinedArm(recvLowered.type);
2077
+ if (isDynTypedRefType(nativeReceiver)) {
2078
+ const info = lowerer.classes.get(nativeReceiver.className);
2079
+ if (info && !info.def.runtime && !info.builtinError && !info.builtinEmitter && !info.builtinStream) {
2080
+ return lowerer.maybeNarrow({
2081
+ kind: "dynKeyGet", value: lowerer.coerceToExpected(recvLowered, DYN),
2082
+ key: { kind: "strLit", value: expr.name.text, type: STRING, loc }, type: DYN, loc,
2083
+ }, expr);
2084
+ }
2085
+ }
1924
2086
  lowerer.unsupported("SC1090", expr, `reading '${expr.name.text}' from a value of type '${lowerer.checker.typeToString(lowerer.typeOf(expr.expression))}'`);
1925
2087
  }
1926
2088
  // Meta-properties, named: `new.target` reflects HOW a function was
@@ -1937,7 +2099,7 @@ function lowerExprInner(lowerer, expr) {
1937
2099
  const entry = UNSUPPORTED_EXPR[expr.kind];
1938
2100
  if (entry)
1939
2101
  lowerer.unsupported(entry.code, expr, entry.feature);
1940
- lowerer.unsupported("SC1090", expr, `syntax '${ts.SyntaxKind[expr.kind]}'`);
2102
+ lowerer.unsupported("SC1090", expr, `syntax '${ts.syntaxKindName(expr.kind)}'`);
1941
2103
  }
1942
2104
  function moduleFileName(lowerer, sf) {
1943
2105
  return lowerer.targetPlatform === "wasi"
@@ -1976,11 +2138,10 @@ function importMetaProperty(lowerer, expr) {
1976
2138
  return null;
1977
2139
  }
1978
2140
  }
1979
- /** `c ? a : b` — see the inline comments. `expected` plays the contextual
1980
- * array type's role when the caller knows the slot's array type and tsc's
1981
- * API doesn't surface it (a ternary as a SPREAD source — lowerArrayLiteral
1982
- * threads the literal's own type in). */
1983
- function lowerTernary(lowerer, expr, expected) {
2141
+ /** `c ? a : b`. An explicit destination constructs each branch at that
2142
+ * layout before joining it. Without one, retain the expression's inferred
2143
+ * type and the contextual/sibling rules below. */
2144
+ export function lowerTernary(lowerer, expr, expected) {
1984
2145
  const loc = locOf(expr);
1985
2146
  // `Array.isArray(x) ? x : [x]` over a `T | readonly T[]` union: tsc
1986
2147
  // narrows the TRUE branch to `any[]` (maybeNarrow's isArray bridge
@@ -2034,7 +2195,16 @@ function lowerTernary(lowerer, expr, expected) {
2034
2195
  // literals) sit untaken in compiled JS. A boolLit carries no
2035
2196
  // effects, so dropping the condition read loses nothing.
2036
2197
  if (cond.kind === "boolLit") {
2037
- return lowerer.lowerExpr(cond.value ? expr.whenTrue : expr.whenFalse);
2198
+ return lowerer.lowerExprExpecting(cond.value ? expr.whenTrue : expr.whenFalse, expected);
2199
+ }
2200
+ if (expected) {
2201
+ // Construct fresh literals in their actual destination. Joining
2202
+ // inferred layouts first can erase the literal discriminator or
2203
+ // choose an empty array's uninhabited element representation.
2204
+ // Runtime-optional proofs apply only within the selected true arm.
2205
+ const then = withRuntimeOptionalNarrowed(lowerer, runtimeOptionalTrueIds(lowerer, expr.condition), () => lowerer.lowerExprExpecting(expr.whenTrue, expected));
2206
+ const else_ = lowerer.lowerExprExpecting(expr.whenFalse, expected);
2207
+ return { kind: "ternary", cond, then, else_, type: expected, loc };
2038
2208
  }
2039
2209
  const ctxTs = lowerer.checker.getContextualType(expr);
2040
2210
  const ctxMapped = ctxTs ? lowerer.mapTypeOf(ctxTs) : null;
@@ -2051,7 +2221,7 @@ function lowerTernary(lowerer, expr, expected) {
2051
2221
  // lowerArrayLiteral, and just as unambiguous — tsc already typed the
2052
2222
  // whole ternary by the filled arm alone. Both arms empty stays
2053
2223
  // fenced (no element type exists anywhere).
2054
- const ctxArray = expected ?? (ctxMapped?.kind === "array" ? ctxMapped : null);
2224
+ const ctxArray = ctxMapped?.kind === "array" ? ctxMapped : null;
2055
2225
  const armLiteral = (e) => {
2056
2226
  let x = e;
2057
2227
  while (ts.isParenthesizedExpression(x))
@@ -2075,7 +2245,21 @@ function lowerTernary(lowerer, expr, expected) {
2075
2245
  }
2076
2246
  return false;
2077
2247
  };
2248
+ // Inferred signature tables often join a populated argument array
2249
+ // with nested empty-array branches. Carry the whole record layout
2250
+ // through those branches before constructing their fresh fields.
2251
+ const ownJoin = lowerer.mapTypeOf(lowerer.typeOf(expr));
2252
+ const recordJoin = ctxMapped?.kind === "record" ? ctxMapped :
2253
+ ownJoin?.kind === "record" ? ownJoin : undefined;
2078
2254
  const lowerArm = (e, siblingType) => {
2255
+ let fresh = e;
2256
+ while (ts.isParenthesizedExpression(fresh))
2257
+ fresh = fresh.expression;
2258
+ if (recordJoin && ts.isConditionalExpression(fresh))
2259
+ return lowerTernary(lowerer, fresh, recordJoin);
2260
+ if (recordJoin && ts.isObjectLiteralExpression(fresh)) {
2261
+ return lowerer.lowerObjectLiteral(fresh, recordJoin);
2262
+ }
2079
2263
  const lit = armLiteral(e);
2080
2264
  if (lit && ctxArray)
2081
2265
  return lowerer.lowerArrayLiteral(lit, ctxArray);
@@ -2128,11 +2312,14 @@ function lowerTernary(lowerer, expr, expected) {
2128
2312
  const lowerTrueArm = () => withRuntimeOptionalNarrowed(lowerer, runtimeTrueIds, () => lowerArm(expr.whenTrue, ownArrayJoin ?? undefined));
2129
2313
  if (!ctxArray && emptyUntypedArrayArm(expr.whenTrue) && !emptyUntypedArrayArm(expr.whenFalse)) {
2130
2314
  elseRaw = lowerer.lowerExpr(expr.whenFalse);
2131
- thenRaw = withRuntimeOptionalNarrowed(lowerer, runtimeTrueIds, () => lowerArm(expr.whenTrue, elseRaw.type));
2315
+ thenRaw = withRuntimeOptionalNarrowed(lowerer, runtimeTrueIds, () => lowerArm(expr.whenTrue, elseRaw.type.kind === "dyn" && ownArrayJoin ? ownArrayJoin : elseRaw.type));
2132
2316
  }
2133
2317
  else {
2134
2318
  thenRaw = lowerTrueArm();
2135
- elseRaw = lowerArm(expr.whenFalse, thenRaw.type);
2319
+ // A checked-dynamic producer (Object.entries(...).filter(...))
2320
+ // can still have a precise checker array type. Empty literal arms
2321
+ // must adopt that element layout before the checked array boundary.
2322
+ elseRaw = lowerArm(expr.whenFalse, thenRaw.type.kind === "dyn" && ownArrayJoin ? ownArrayJoin : thenRaw.type);
2136
2323
  }
2137
2324
  // The ternary's IR type is normally the checker's own: it collapses
2138
2325
  // same-kind literal unions ("a" | "b" → string) and forms tagged
@@ -2204,14 +2391,22 @@ function lowerTernary(lowerer, expr, expected) {
2204
2391
  }
2205
2392
  }
2206
2393
  }
2207
- const type = thenRaw.type.kind === "array" && typeEquals(thenRaw.type, elseRaw.type)
2208
- ? thenRaw.type
2209
- : dynJoin
2210
- ? DYN
2211
- : (anyJoin ??
2212
- (useCtx && ctxMapped
2213
- ? ctxMapped
2214
- : lowerer.irTypeOf(expr)));
2394
+ // JavaScript function selection preserves the chosen closure's full
2395
+ // call ABI. A checker join can erase a rest/arguments pack even though
2396
+ // a later call supplies additional observable arguments.
2397
+ const callableJoin = isJsSourceFile(expr.getSourceFile()) &&
2398
+ thenRaw.type.kind === "func" && elseRaw.type.kind === "func" &&
2399
+ !typeEquals(thenRaw.type, elseRaw.type) &&
2400
+ lowerer.dynConvertible(thenRaw.type) && lowerer.dynConvertible(elseRaw.type);
2401
+ const type = callableJoin ? DYN :
2402
+ thenRaw.type.kind === "array" && typeEquals(thenRaw.type, elseRaw.type)
2403
+ ? thenRaw.type
2404
+ : dynJoin
2405
+ ? DYN
2406
+ : (anyJoin ??
2407
+ (useCtx && ctxMapped
2408
+ ? ctxMapped
2409
+ : lowerer.irTypeOf(expr)));
2215
2410
  // Each arm flows into the ternary's type through the slot-coercion
2216
2411
  // path: union arms wrap, dyn slots reject the non-dyn arm with
2217
2412
  // SC1101, mismatched record shapes get SC2002; coerceInto is
@@ -2240,6 +2435,16 @@ function lowerTernary(lowerer, expr, expected) {
2240
2435
  * unrepresentable without a re-tag), or to nothing (`never` in an
2241
2436
  * exhaustive default) leaves the expression union-typed. */
2242
2437
  export function maybeNarrow(lowerer, expr, node) {
2438
+ if ((expr.type.kind === "union" || expr.type.kind === "dyn") &&
2439
+ isJsSourceFile(node.getSourceFile()) && ts.isPropertyAccessExpression(node)) {
2440
+ const symbol = lowerer.checker.getSymbolAtLocation(node.name);
2441
+ if (symbol && lowerer.checker.declarationsOf(symbol).some((decl) => ts.isPropertyDeclaration(decl) && !decl.initializer && !decl.type &&
2442
+ isJsSourceFile(decl.getSourceFile()))) {
2443
+ // Inference describes later writes, not the initial undefined.
2444
+ // A use that requires the inferred type must validate the value.
2445
+ return expr.type.kind === "dyn" ? expr : { kind: "dynFrom", value: expr, type: DYN, loc: expr.loc };
2446
+ }
2447
+ }
2243
2448
  // A dyn read tsc narrowed to a SCALAR (a typeof test proved the kind):
2244
2449
  // bridge with a VALIDATED extraction — dynCheck, the checked-cast
2245
2450
  // machinery — rather than a trusted one. After the guard the check
@@ -2247,15 +2452,40 @@ export function maybeNarrow(lowerer, expr, node) {
2247
2452
  // instead of misreading the payload (the dyn boundary's usual stance).
2248
2453
  // Object/array narrowings stay dyn-typed and keep their fences.
2249
2454
  if (expr.type.kind === "dyn") {
2455
+ if (isJsSourceFile(node.getSourceFile()) && ts.isIdentifier(node)) {
2456
+ const symbol = lowerer.resolveValueSymbol(node);
2457
+ if (symbol && lowerer.checker.declarationsOf(symbol).some((decl) => {
2458
+ if (!ts.isParameter(decl) || decl.type)
2459
+ return false;
2460
+ const fn = decl.parent;
2461
+ if (!ts.isFunctionExpression(fn) && !ts.isArrowFunction(fn))
2462
+ return false;
2463
+ const assignment = fn.parent;
2464
+ return ts.isBinaryExpression(assignment) && assignment.right === fn &&
2465
+ ts.isPropertyAccessExpression(assignment.left) && assignment.left.name.text === "write" &&
2466
+ lowerer.isStdlibMember(assignment.left);
2467
+ }))
2468
+ return expr;
2469
+ }
2470
+ // Node's TTY declarations promise booleans/numbers even when a pipe
2471
+ // has no such property. Preserve the checked read for JS capability
2472
+ // probes instead of treating a declaration as a runtime type guard.
2473
+ if (isJsSourceFile(node.getSourceFile()) && ts.isPropertyAccessExpression(node) &&
2474
+ ["isTTY", "columns", "rows"].includes(node.name.text) && lowerer.isStdlibMember(node))
2475
+ return expr;
2476
+ // A null-initialized JS field can later receive an object even when
2477
+ // checker flow calls its non-null branch never. The live checked
2478
+ // value remains authoritative; never is not a numeric guard.
2479
+ if (isJsSourceFile(node.getSourceFile()) && (lowerer.typeOf(node).flags & ts.TypeFlags.Never) !== 0)
2480
+ return expr;
2250
2481
  const narrowed = lowerer.mapTypeOf(lowerer.typeOf(node));
2251
2482
  if (narrowed &&
2252
- (narrowed.kind === "f64" || narrowed.kind === "bool" || narrowed.kind === "string")) {
2483
+ (narrowed.kind === "f64" || narrowed.kind === "bool" || narrowed.kind === "string" || narrowed.kind === "bigint")) {
2253
2484
  return { kind: "dynCheck", value: expr, type: narrowed, loc: expr.loc };
2254
2485
  }
2255
- // An `instanceof Uint8Array` narrow: the checked-dynamic tree's bytes kind, extracted
2256
- // with the same validated copy the checked cast uses (a Buffer that
2257
- // crossed in rides the kind too — it IS a Uint8Array in Node).
2258
- if (narrowed?.kind === "bytes" && narrowed.elem === "u8") {
2486
+ // An instanceof narrow retains the native view after validating its
2487
+ // exact element kind. Buffer also matches Uint8Array.
2488
+ if (narrowed?.kind === "bytes" || narrowed?.kind === "url") {
2259
2489
  return { kind: "dynCheck", value: expr, type: narrowed, loc: expr.loc };
2260
2490
  }
2261
2491
  // An `instanceof Error` narrow: the checked-dynamic tree's error encoding rebuilds a
@@ -2282,6 +2512,11 @@ export function maybeNarrow(lowerer, expr, node) {
2282
2512
  }
2283
2513
  if (expr.type.kind !== "union")
2284
2514
  return expr;
2515
+ if (ts.isIdentifier(node)) {
2516
+ const symbol = lowerer.resolveValueSymbol(node);
2517
+ if (symbol && lowerer.unstableSwitchBindings.has(symbol))
2518
+ return expr;
2519
+ }
2285
2520
  const narrowedTs = lowerer.typeOf(node);
2286
2521
  const narrowed = lowerer.mapTypeOf(narrowedTs);
2287
2522
  // `Array.isArray(u)` on a union with one readonly array/tuple arm: the
@@ -2307,8 +2542,43 @@ export function maybeNarrow(lowerer, expr, node) {
2307
2542
  return expr;
2308
2543
  }
2309
2544
  const tag = lowerer.armTag(expr.type.unionId, narrowed);
2310
- if (tag < 0)
2545
+ if (tag < 0) {
2546
+ // The checker may refine fields inside a discriminated record too.
2547
+ // That creates a different mapped shape, but does not change the
2548
+ // stored payload layout. A required literal discriminator proves
2549
+ // which original arm to read; retain all of that arm's field types.
2550
+ const def = lowerer.unions.get(expr.type.unionId);
2551
+ if (narrowed.kind === "record" && def?.discriminant) {
2552
+ const property = lowerer.checker.getPropertyOfType(narrowedTs, def.discriminant.field);
2553
+ if (property && !(property.flags & (ts.SymbolFlags.Optional | ts.SymbolFlags.GetAccessor | ts.SymbolFlags.SetAccessor))) {
2554
+ const values = literalValues(lowerer.checker.getTypeOfSymbol(property));
2555
+ const arm = values && literalUnionArm(def, values, (id) => lowerer.shapes.get(id));
2556
+ if (arm) {
2557
+ return { kind: "unionNarrow", unionId: expr.type.unionId,
2558
+ tag: lowerer.armTag(expr.type.unionId, arm), value: expr, type: arm, loc: expr.loc };
2559
+ }
2560
+ }
2561
+ }
2562
+ // A base-class arm can narrow to a subclass after instanceof. The
2563
+ // payload retains its base-class tag even when the union also has
2564
+ // records or other classes. Require one unambiguous containing arm.
2565
+ const arms = lowerer.unions.get(expr.type.unionId)?.arms ?? [];
2566
+ const valueTag = narrowed.kind === "object"
2567
+ ? arms.findIndex((arm) => arm.kind === "object" && lowerer.isSubclassOf(narrowed.className, arm.className))
2568
+ : -1;
2569
+ const valueType = arms[valueTag];
2570
+ if (narrowed.kind === "object" && valueType?.kind === "object" &&
2571
+ arms.every((arm, i) => i === valueTag || arm.kind !== "object" || !lowerer.isSubclassOf(narrowed.className, arm.className)) &&
2572
+ lowerer.isSubclassOf(narrowed.className, valueType.className)) {
2573
+ return {
2574
+ kind: "downcast",
2575
+ value: { kind: "unionNarrow", unionId: expr.type.unionId, tag: valueTag, value: expr, type: valueType, loc: expr.loc },
2576
+ type: narrowed,
2577
+ loc: expr.loc,
2578
+ };
2579
+ }
2311
2580
  return expr;
2581
+ }
2312
2582
  return {
2313
2583
  kind: "unionNarrow",
2314
2584
  unionId: expr.type.unionId,
@@ -2365,12 +2635,11 @@ function runtimeOptionalReceiverRead(lowerer, expr, local, property, loc) {
2365
2635
  }
2366
2636
  return { kind: "call", callee: helper, args: [varRef(local.id, local.type, loc)], type: narrowed, loc };
2367
2637
  }
2368
- const valueTag = lowerer.armTag(local.type.unionId, narrowed);
2369
2638
  const undefTag = lowerer.armTag(local.type.unionId, UNDEFINED_T);
2370
- if (valueTag < 0 || undefTag < 0)
2371
- throw new InternalCompilerError("runtime-optional local is missing its value or undefined arm");
2372
2639
  const def = lowerer.unions.get(local.type.unionId);
2373
- if (!def || def.arms.length !== 2) {
2640
+ if (!def || undefTag < 0)
2641
+ throw new InternalCompilerError("runtime-optional local is missing its undefined arm");
2642
+ if (def.arms.length !== 2) {
2374
2643
  // The checker may narrow a runtime-optional capture to one arm of a
2375
2644
  // value union (`Circle | Square | undefined` -> `Circle`). The
2376
2645
  // two-arm fast path below cannot extract that arm safely: a later
@@ -2383,10 +2652,19 @@ function runtimeOptionalReceiverRead(lowerer, expr, local, property, loc) {
2383
2652
  }
2384
2653
  return { kind: "call", callee: helper, args: [varRef(local.id, local.type, loc)], type: narrowed, loc };
2385
2654
  }
2655
+ // A predicate can strengthen a record's fields or narrow a base class
2656
+ // to a subclass without changing the slot's stored value arm. Extract
2657
+ // that actual arm first: record reads need its original field layout,
2658
+ // and class reads apply the ordinary downcast bridge afterward.
2659
+ const valueTag = undefTag === 0 ? 1 : 0;
2660
+ const valueType = def.arms[valueTag];
2661
+ if (!valueType || isUnitType(valueType)) {
2662
+ lowerer.unsupported("SC1090", expr, "a runtime-optional receiver without a representable value arm");
2663
+ }
2386
2664
  const message = property === null
2387
2665
  ? `${expr.text} is not a function`
2388
2666
  : `Cannot read properties of undefined (reading '${property}')`;
2389
- return {
2667
+ return lowerer.maybeNarrow({
2390
2668
  kind: "ternary",
2391
2669
  cond: {
2392
2670
  kind: "unionIsTag",
@@ -2397,18 +2675,18 @@ function runtimeOptionalReceiverRead(lowerer, expr, local, property, loc) {
2397
2675
  type: BOOL,
2398
2676
  loc,
2399
2677
  },
2400
- then: nodeThrowExpr(1, "", message, narrowed, loc),
2678
+ then: nodeThrowExpr(1, "", message, valueType, loc),
2401
2679
  else_: {
2402
2680
  kind: "unionNarrow",
2403
2681
  unionId: local.type.unionId,
2404
2682
  tag: valueTag,
2405
2683
  value: varRef(local.id, local.type, loc),
2406
- type: narrowed,
2684
+ type: valueType,
2407
2685
  loc,
2408
2686
  },
2409
- type: narrowed,
2687
+ type: valueType,
2410
2688
  loc,
2411
- };
2689
+ }, narrowedNode);
2412
2690
  }
2413
2691
  function runtimeOptionalUseOf(expr) {
2414
2692
  let receiver = expr;
@@ -2669,7 +2947,26 @@ function lowerLooseNullCompare(lowerer, expr, loc) {
2669
2947
  * results (several non-unit arms) and defaults that change the result
2670
2948
  * type are fenced with narrow-first hints. */
2671
2949
  export function lowerNullishCoalesce(lowerer, expr, loc) {
2672
- const left = lowerAbsenceProbe(lowerer, expr.left) ?? lowerer.lowerExpr(expr.left);
2950
+ // `a ?? b ?? c` is a left-nested AST. Lower its spine bottom-up so
2951
+ // long dispatch chains do not retain a full expression/binary lowering
2952
+ // frame per operand in a native compiler. The pair lowering still
2953
+ // decides each result layout and preserves the lazy right operand.
2954
+ const parents = [];
2955
+ let first = expr;
2956
+ while (ts.isBinaryExpression(first.left) &&
2957
+ first.left.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken &&
2958
+ !lowerer.chainRecvByNode.has(first.left)) {
2959
+ parents.push(first);
2960
+ first = first.left;
2961
+ }
2962
+ let result = lowerNullishPair(lowerer, first, first === expr ? loc : locOf(first), lowerAbsenceProbe(lowerer, first.left) ?? lowerer.lowerExpr(first.left));
2963
+ for (let i = parents.length - 1; i >= 0; i--) {
2964
+ const parent = parents[i];
2965
+ result = lowerNullishPair(lowerer, parent, parent === expr ? loc : locOf(parent), result);
2966
+ }
2967
+ return result;
2968
+ }
2969
+ function lowerNullishPair(lowerer, expr, loc, left) {
2673
2970
  if (left.type.kind === "dyn") {
2674
2971
  // `a ?? b` on a CHECKED-DYNAMIC left: the deciding test is the
2675
2972
  // runtime kind (scr_dyn_is_nullish — UNDEF/NULL take the default;
@@ -2698,8 +2995,14 @@ export function lowerNullishCoalesce(lowerer, expr, loc) {
2698
2995
  lowerer.badType(expr.left, lowerer.typeOf(expr.left));
2699
2996
  if (!def.arms.some(isUnitType))
2700
2997
  return left;
2701
- const type = lowerer.irTypeOf(expr);
2702
2998
  const rest = def.arms.filter((a) => !isUnitType(a));
2999
+ // `optionalMap ?? new Map()` carries its element contract on the left;
3000
+ // the empty fallback may use it without constructing a Map<any, any>
3001
+ // arm that has no first-class union representation.
3002
+ const fresh = rest.length === 1 ? lowerer.emptyCollectionFor(expr.right, rest[0]) : null;
3003
+ if (fresh)
3004
+ return { kind: "nullish", left, right: fresh, type: fresh.type, loc };
3005
+ const type = lowerer.irTypeOf(expr);
2703
3006
  if (typeEquals(type, left.type) || (rest.length === 1 && typeEquals(type, rest[0]))) {
2704
3007
  const right = lowerer.lowerExprExpecting(expr.right, type);
2705
3008
  return { kind: "nullish", left, right, type, loc };
@@ -2707,180 +3010,49 @@ export function lowerNullishCoalesce(lowerer, expr, loc) {
2707
3010
  if (type.kind !== "union" && rest.length > 1 && rest.some((arm) => typeEquals(arm, type))) {
2708
3011
  const helper = lowerer.narrowedArmHelper(left.type.unionId, type, loc);
2709
3012
  if (helper !== null) {
2710
- const stmts = [];
2711
- let stable = left;
2712
- if (!isSafeToRepeat(left)) {
2713
- const local = lowerer.declareHiddenLocal("%nullish", left.type);
2714
- stmts.push({ kind: "varDecl", localId: local.id, init: left, loc });
2715
- stable = varRef(local.id, left.type, loc);
2716
- }
2717
- const unitTags = def.arms
2718
- .map((arm, tag) => ({ arm, tag }))
2719
- .filter(({ arm }) => isUnitType(arm))
2720
- .map(({ tag }) => tag);
2721
- let isNullish = { kind: "unionIsTag", unionId: left.type.unionId, tag: unitTags[0], negated: false, value: stable, type: BOOL, loc };
2722
- for (const tag of unitTags.slice(1)) {
2723
- isNullish = {
2724
- kind: "logical",
2725
- op: "||",
2726
- left: isNullish,
2727
- right: { kind: "unionIsTag", unionId: left.type.unionId, tag, negated: false, value: stable, type: BOOL, loc },
2728
- type: BOOL,
2729
- loc,
2730
- };
2731
- }
2732
- const result = {
2733
- kind: "ternary",
2734
- cond: isNullish,
2735
- then: lowerer.lowerExprExpecting(expr.right, type),
2736
- else_: { kind: "call", callee: helper, args: [stable], type, loc },
2737
- type,
2738
- loc,
2739
- };
2740
- return stmts.length === 0 ? result : { kind: "seqExpr", stmts, result, type, loc };
3013
+ return nullishBranches(lowerer, left, left.type, def.arms, type, loc, () => lowerer.lowerExprExpecting(expr.right, type), (stable) => ({ kind: "call", callee: helper, args: [stable], type, loc }));
2741
3014
  }
2742
3015
  }
2743
- // The RETAGGED shape — the default changes the result union (`s ??
2744
- // null` over `string | undefined` answers `null | string`): every
2745
- // non-unit arm of the left has a home in the result, so an interned
2746
- // helper tests the unit tags and re-wraps the payload per arm. The
2747
- // helper call evaluates the default EAGERLY where JS is lazy, so only
2748
- // effect-free defaults (the literal null/0/"" spellings) qualify —
2749
- // anything effectful keeps the fence.
3016
+ // A default may change the union or widen its records. Test the
3017
+ // original tag before coercing: converting the whole left first can
3018
+ // lose nullish arms, and evaluating the default eagerly loses laziness.
2750
3019
  if (type.kind === "union") {
2751
- const leftT = left.type;
2752
- const armPairs = rest.map((a) => ({ arm: a, src: lowerer.armTag(leftT.unionId, a), dst: lowerer.armTag(type.unionId, a) }));
2753
- if (rest.length > 0 && armPairs.every((pair) => pair.src >= 0 && pair.dst >= 0)) {
2754
- const stmts = [];
2755
- let stable = left;
2756
- if (!isSafeToRepeat(left)) {
2757
- const local = lowerer.declareHiddenLocal("%nullish", leftT);
2758
- stmts.push({ kind: "varDecl", localId: local.id, init: left, loc });
2759
- stable = varRef(local.id, leftT, loc);
2760
- }
2761
- const unitTags = def.arms
2762
- .map((arm, tag) => ({ arm, tag }))
2763
- .filter(({ arm }) => isUnitType(arm))
2764
- .map(({ tag }) => tag);
2765
- let isNullish = {
2766
- kind: "unionIsTag",
2767
- unionId: leftT.unionId,
2768
- tag: unitTags[0],
2769
- negated: false,
2770
- value: stable,
2771
- type: BOOL,
2772
- loc,
2773
- };
2774
- for (const tag of unitTags.slice(1)) {
2775
- isNullish = {
2776
- kind: "logical",
2777
- op: "||",
2778
- left: isNullish,
2779
- right: { kind: "unionIsTag", unionId: leftT.unionId, tag, negated: false, value: stable, type: BOOL, loc },
2780
- type: BOOL,
2781
- loc,
2782
- };
2783
- }
2784
- let present = null;
2785
- for (let i = armPairs.length - 1; i >= 0; i--) {
2786
- const pair = armPairs[i];
2787
- const wrapped = {
2788
- kind: "unionWrap",
2789
- unionId: type.unionId,
2790
- tag: pair.dst,
2791
- value: { kind: "unionNarrow", unionId: leftT.unionId, tag: pair.src, value: stable, type: pair.arm, loc },
2792
- type,
2793
- loc,
2794
- };
2795
- present = present === null
2796
- ? wrapped
2797
- : {
2798
- kind: "ternary",
2799
- cond: { kind: "unionIsTag", unionId: leftT.unionId, tag: pair.src, negated: false, value: stable, type: BOOL, loc },
2800
- then: wrapped,
2801
- else_: present,
2802
- type,
2803
- loc,
2804
- };
2805
- }
2806
- const result = {
2807
- kind: "ternary",
2808
- cond: isNullish,
2809
- then: lowerer.lowerExprExpecting(expr.right, type),
2810
- else_: present,
2811
- type,
2812
- loc,
2813
- };
2814
- return stmts.length === 0 ? result : { kind: "seqExpr", stmts, result, type, loc };
2815
- }
2816
- if (armPairs.every((p) => p.src >= 0 && p.dst >= 0)) {
2817
- const right = lowerer.lowerExprExpecting(expr.right, type);
2818
- if (isSafeToDiscard(right)) {
2819
- const helper = nullishRetagHelper(lowerer, leftT, type, loc);
2820
- return { kind: "call", callee: helper, args: [left, right], type, loc };
2821
- }
3020
+ const retag = lowerer.unionRetagHelper(left.type.unionId, type.unionId, loc) ??
3021
+ lowerer.narrowedRetagHelper(expr.left, left.type.unionId, type.unionId, loc);
3022
+ if (retag !== null) {
3023
+ // Narrowing may exclude a record/class arm while storage retains
3024
+ // it. The checked retag traps that impossible arm, but only AFTER
3025
+ // the original nullish test: null/undefined must reach the lazy
3026
+ // default even when neither is representable in the result.
3027
+ return nullishBranches(lowerer, left, left.type, def.arms, type, loc, () => lowerer.lowerExprExpecting(expr.right, type), (stable) => ({ kind: "call", callee: retag, args: [stable], type, loc }));
2822
3028
  }
2823
3029
  }
2824
3030
  lowerer.unsupported("SC1090", expr, rest.length !== 1
2825
3031
  ? `'??' on '${lowerer.fmt(left.type)}' (the non-nullish result is a sub-union; check a discriminant field first)`
2826
3032
  : `'??' where the default changes the result type (left is '${lowerer.fmt(left.type)}' but the whole expression is '${lowerer.fmt(type)}' — give both sides one type)`);
2827
3033
  }
2828
- /** Interned `%nullish.retag.<n>(l, r)` — the retagged `??` (see
2829
- * lowerNullishCoalesce): unit-armed left answers the pre-evaluated
2830
- * default; every other arm narrows out of the left union and wraps into
2831
- * the result union's matching arm. */
2832
- function nullishRetagHelper(lowerer, leftT, resT, loc) {
2833
- const key = `nullish:${leftT.unionId}:${typeKey(resT)}`;
2834
- const existing = lowerer.widthHelpers.get(key);
2835
- if (existing)
2836
- return existing;
2837
- const name = `%nullish.retag.${lowerer.widthHelpers.size}`;
2838
- lowerer.widthHelpers.set(key, name);
2839
- const def = lowerer.unions.get(leftT.unionId);
2840
- const l = varRef("l.0", leftT, loc);
2841
- const r = varRef("r.0", resT, loc);
2842
- const body = [];
2843
- def.arms.forEach((arm, tag) => {
2844
- if (isUnitType(arm))
2845
- return;
2846
- const dst = lowerer.armTag(resT.unionId, arm);
2847
- body.push({
2848
- kind: "if",
2849
- cond: { kind: "unionIsTag", unionId: leftT.unionId, tag, negated: false, value: l, type: BOOL, loc },
2850
- then: [
2851
- {
2852
- kind: "return",
2853
- value: {
2854
- kind: "unionWrap",
2855
- unionId: resT.unionId,
2856
- tag: dst,
2857
- value: { kind: "unionNarrow", unionId: leftT.unionId, tag, value: l, type: arm, loc },
2858
- type: resT,
2859
- loc,
2860
- },
2861
- loc,
2862
- },
2863
- ],
2864
- else_: null,
2865
- loc,
2866
- });
2867
- });
2868
- body.push({ kind: "return", value: r, loc });
2869
- lowerer.liftedFns.push({
2870
- name,
2871
- params: [
2872
- { localId: "l.0", name: "l", type: leftT },
2873
- { localId: "r.0", name: "r", type: resT },
2874
- ],
2875
- returnType: resT,
2876
- locals: [
2877
- { id: "l.0", name: "l", type: leftT, mutable: true },
2878
- { id: "r.0", name: "r", type: resT, mutable: true },
2879
- ],
2880
- body,
2881
- loc,
2882
- });
2883
- return name;
3034
+ /** Test the original storage before projecting its present value. Both
3035
+ * branches are lazy, and an effectful receiver is evaluated exactly once.
3036
+ * Keeping the original tags matters when the checker has removed an arm
3037
+ * or the default changes the union's layout. */
3038
+ function nullishBranches(lowerer, left, leftType, arms, type, loc, absent, present) {
3039
+ const stmts = [];
3040
+ let stable = left;
3041
+ if (!isSafeToRepeat(left)) {
3042
+ const local = lowerer.declareHiddenLocal("%nullish", leftType);
3043
+ stmts.push({ kind: "varDecl", localId: local.id, init: left, loc });
3044
+ stable = varRef(local.id, leftType, loc);
3045
+ }
3046
+ const unitTags = arms.flatMap((arm, tag) => isUnitType(arm) ? [tag] : []);
3047
+ let test = null;
3048
+ for (const tag of unitTags) {
3049
+ const part = { kind: "unionIsTag", unionId: leftType.unionId, tag, negated: false, value: stable, type: BOOL, loc };
3050
+ test = test === null ? part : { kind: "logical", op: "||", left: test, right: part, type: BOOL, loc };
3051
+ }
3052
+ if (test === null)
3053
+ throw new InternalCompilerError("nullish branch requires an absent arm");
3054
+ const result = { kind: "ternary", cond: test, then: absent(), else_: present(stable), type, loc };
3055
+ return stmts.length === 0 ? result : { kind: "seqExpr", stmts, result, type, loc };
2884
3056
  }
2885
3057
  /** A CONDITION-position expression: the result is consumed as a bool
2886
3058
  * only, so `&&`/`||` descend recursively over ToBoolean'd operands —
@@ -2939,14 +3111,23 @@ function lowerPromiseThenPresence(lowerer, access, result) {
2939
3111
  ? (() => {
2940
3112
  const arms = lowerer.unions.get(mapped.unionId)?.arms ?? [];
2941
3113
  const promises = arms.filter((arm) => arm.kind === "promise");
2942
- return promises.length === 1 && arms.every((arm) => arm.kind === "promise" || isUnitType(arm))
2943
- ? promises[0]
3114
+ const promise = promises[0];
3115
+ return promises.length === 1 && promise?.kind === "promise" && arms.every((arm) => arm.kind === "promise" || isUnitType(arm))
3116
+ ? promise
2944
3117
  : null;
2945
3118
  })()
2946
3119
  : null;
2947
3120
  if (promiseType === null)
2948
3121
  return null;
2949
3122
  let receiver = lowerer.lowerExpr(access.expression);
3123
+ if (receiver.type.kind === "union" && access.questionDotToken) {
3124
+ const arms = lowerer.unions.get(receiver.type.unionId)?.arms ?? [];
3125
+ if (arms.every((arm) => arm.kind === "promise" || isUnitType(arm))) {
3126
+ return nullishBranches(lowerer, receiver, receiver.type, arms, result.type, locOf(access), () => result.type.kind === "bool"
3127
+ ? { kind: "boolLit", value: false, type: BOOL, loc: result.loc }
3128
+ : { kind: "strLit", value: "undefined", type: STRING, loc: result.loc }, () => result);
3129
+ }
3130
+ }
2950
3131
  if (receiver.type.kind === "union") {
2951
3132
  const optional = lowerer.runtimeOptionalPropertyReceiver(access.expression, receiver, promiseType, access.name.text);
2952
3133
  if (optional !== null)
@@ -2958,6 +3139,29 @@ function lowerPromiseThenPresence(lowerer, access, result) {
2958
3139
  receiver = { kind: "call", callee: helper, args: [receiver], type: promiseType, loc: receiver.loc };
2959
3140
  }
2960
3141
  }
3142
+ if (receiver.type.kind === "dyn") {
3143
+ // A specialized JS parameter can retain checked storage while JSDoc
3144
+ // describes a promise. Probe its actual value, preserving nullish
3145
+ // guards and own-property reads on ordinary thenable objects.
3146
+ const loc = locOf(access);
3147
+ const local = lowerer.declareHiddenLocal("%thenReceiver", DYN);
3148
+ const stable = varRef(local.id, DYN, loc);
3149
+ const member = {
3150
+ kind: "dynKeyGet", value: stable,
3151
+ key: { kind: "strLit", value: "then", type: STRING, loc },
3152
+ ...(access.questionDotToken ? { optional: true } : {}), type: DYN, loc,
3153
+ };
3154
+ const other = result.type.kind === "bool"
3155
+ ? { kind: "dynTest", test: "truthy", value: member, type: BOOL, loc }
3156
+ : { kind: "libCall", fn: "dyn.typeof", args: [member], type: STRING, loc };
3157
+ return {
3158
+ kind: "seqExpr", stmts: [{ kind: "varDecl", localId: local.id, init: receiver, loc }],
3159
+ result: {
3160
+ kind: "ternary", cond: { kind: "dynTest", test: "promise", value: stable, type: BOOL, loc },
3161
+ then: result, else_: other, type: result.type, loc,
3162
+ }, type: result.type, loc,
3163
+ };
3164
+ }
2961
3165
  if (receiver.type.kind !== "promise")
2962
3166
  return null;
2963
3167
  if (isSafeToDiscard(receiver))
@@ -2980,15 +3184,107 @@ function runtimeOptionalLocalOf(lowerer, node) {
2980
3184
  return local && lowerer.runtimeOptionalLocals.has(lowerer.runtimeOptionalRootOf(local)) ? local : null;
2981
3185
  }
2982
3186
  export function runtimeOptionalTrueIds(lowerer, node) {
2983
- let expr = node;
2984
- while (ts.isParenthesizedExpression(expr))
2985
- expr = expr.expression;
2986
- if (ts.isBinaryExpression(expr) &&
2987
- expr.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) {
2988
- return [...runtimeOptionalTrueIds(lowerer, expr.left), ...runtimeOptionalTrueIds(lowerer, expr.right)];
2989
- }
2990
- const local = runtimeOptionalLocalOf(lowerer, expr);
2991
- return local === null ? [] : [local];
3187
+ return runtimeOptionalGuardIds(lowerer, node, true);
3188
+ }
3189
+ /** Presence proved by one outcome of a boolean guard. Negation flips
3190
+ * the outcome; all operands of a true AND or false OR have that outcome.
3191
+ * Assignments invalidate a syntactic proof even when checker types hide
3192
+ * the undefined produced by an out-of-bounds read. */
3193
+ export function runtimeOptionalGuardIds(lowerer, node, truthy) {
3194
+ const candidates = [];
3195
+ const collect = (expr, outcome) => {
3196
+ if (ts.isParenthesizedExpression(expr))
3197
+ return collect(expr.expression, outcome);
3198
+ if (ts.isPrefixUnaryExpression(expr) && expr.operator === ts.SyntaxKind.ExclamationToken) {
3199
+ return collect(expr.operand, outcome === null ? null : !outcome);
3200
+ }
3201
+ if (ts.isBinaryExpression(expr) &&
3202
+ (outcome === true && expr.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ||
3203
+ outcome === false && expr.operatorToken.kind === ts.SyntaxKind.BarBarToken)) {
3204
+ collect(expr.left, outcome);
3205
+ collect(expr.right, outcome);
3206
+ }
3207
+ else if (ts.isPropertyAccessExpression(expr) && !expr.questionDotToken) {
3208
+ // A completed ordinary member read checked its receiver even if
3209
+ // the property's value was falsy. Optional chains make no such
3210
+ // promise, and calls/captures retain their separate checked ABI.
3211
+ const local = runtimeOptionalLocalOf(lowerer, expr.expression);
3212
+ if (local !== null)
3213
+ candidates.push(local);
3214
+ else if (ts.isPropertyAccessExpression(expr.expression))
3215
+ collect(expr.expression, outcome);
3216
+ }
3217
+ else if (ts.isBinaryExpression(expr) &&
3218
+ expr.operatorToken.kind !== ts.SyntaxKind.AmpersandAmpersandToken &&
3219
+ expr.operatorToken.kind !== ts.SyntaxKind.BarBarToken &&
3220
+ expr.operatorToken.kind !== ts.SyntaxKind.QuestionQuestionToken) {
3221
+ // Both operands of a comparison/arithmetic operator are evaluated.
3222
+ // Only member reads prove presence; bare identifiers need truth.
3223
+ const member = (operand) => {
3224
+ while (ts.isParenthesizedExpression(operand))
3225
+ operand = operand.expression;
3226
+ if (ts.isPropertyAccessExpression(operand))
3227
+ collect(operand, outcome);
3228
+ };
3229
+ member(expr.left);
3230
+ member(expr.right);
3231
+ const op = expr.operatorToken.kind;
3232
+ const equal = op === ts.SyntaxKind.EqualsEqualsEqualsToken || op === ts.SyntaxKind.EqualsEqualsToken;
3233
+ const unequal = op === ts.SyntaxKind.ExclamationEqualsEqualsToken || op === ts.SyntaxKind.ExclamationEqualsToken;
3234
+ if ((equal && outcome === false) || (unequal && outcome === true)) {
3235
+ const present = (value, unit) => {
3236
+ while (ts.isParenthesizedExpression(unit))
3237
+ unit = unit.expression;
3238
+ const excludesUndefined = ts.isIdentifier(unit) && unit.text === "undefined" &&
3239
+ (lowerer.typeOf(unit).flags & ts.TypeFlags.Undefined) !== 0 ||
3240
+ unit.kind === ts.SyntaxKind.NullKeyword &&
3241
+ (op === ts.SyntaxKind.EqualsEqualsToken || op === ts.SyntaxKind.ExclamationEqualsToken);
3242
+ if (excludesUndefined) {
3243
+ const local = runtimeOptionalLocalOf(lowerer, value);
3244
+ if (local !== null)
3245
+ candidates.push(local);
3246
+ }
3247
+ };
3248
+ present(expr.left, expr.right);
3249
+ present(expr.right, expr.left);
3250
+ }
3251
+ }
3252
+ else if (outcome) {
3253
+ const local = runtimeOptionalLocalOf(lowerer, expr);
3254
+ if (local !== null)
3255
+ candidates.push(local);
3256
+ }
3257
+ };
3258
+ collect(node, truthy);
3259
+ if (candidates.length === 0)
3260
+ return candidates;
3261
+ const assigned = new Set();
3262
+ const visit = (part) => {
3263
+ if (ts.isFunctionLike(part))
3264
+ return;
3265
+ let target = null;
3266
+ if (ts.isBinaryExpression(part) && part.operatorToken.kind >= ts.SyntaxKind.FirstAssignment &&
3267
+ part.operatorToken.kind <= ts.SyntaxKind.LastAssignment)
3268
+ target = part.left;
3269
+ if ((ts.isPrefixUnaryExpression(part) || ts.isPostfixUnaryExpression(part)) &&
3270
+ (part.operator === ts.SyntaxKind.PlusPlusToken || part.operator === ts.SyntaxKind.MinusMinusToken))
3271
+ target = part.operand;
3272
+ if (target !== null) {
3273
+ // Destructuring assignments can contain several identifiers.
3274
+ const mark = (name) => {
3275
+ if (ts.isIdentifier(name)) {
3276
+ const local = lowerer.resolveLocal(name);
3277
+ if (local)
3278
+ assigned.add(lowerer.runtimeOptionalRootOf(local));
3279
+ }
3280
+ ts.forEachChild(name, mark);
3281
+ };
3282
+ mark(target);
3283
+ }
3284
+ ts.forEachChild(part, visit);
3285
+ };
3286
+ visit(node);
3287
+ return candidates.filter((local) => !assigned.has(lowerer.runtimeOptionalRootOf(local)));
2992
3288
  }
2993
3289
  export function withRuntimeOptionalNarrowed(lowerer, locals, fn) {
2994
3290
  if (locals.length === 0)
@@ -3072,6 +3368,15 @@ export function eqComparableUnion(lowerer, unionId) {
3072
3368
  export function lowerIntrinsicProperty(lowerer, expr) {
3073
3369
  if (lowerer.chainBlocked(expr))
3074
3370
  return null;
3371
+ if (lowerer.stdlibGlobalMember(expr, "Array") === "prototype")
3372
+ return { kind: "libCall", fn: "dyn.arrayPrototype", args: [], type: DYN, loc: locOf(expr) };
3373
+ if (lowerer.stdlibGlobalMember(expr, "Array") === "isArray")
3374
+ return lowerArrayIsArrayValue(lowerer, locOf(expr));
3375
+ if (stdlibGlobalNameOf(lowerer, expr.expression) === "Object") {
3376
+ const fn = own(OBJECT_CALLABLE_VALUES, expr.name.text);
3377
+ if (fn)
3378
+ return lowerer.lowerNativeCallableValue(fn, `Object.${expr.name.text}`, locOf(expr));
3379
+ }
3075
3380
  // A never-tainted JS receiver type lowered checked-dynamic
3076
3381
  // (neverTaintedJsType — `cmd.length` on `const cmd = ['pwd', []]`):
3077
3382
  // stand down so the dyn keyed read below the chain answers, instead
@@ -3107,6 +3412,8 @@ export function lowerIntrinsicProperty(lowerer, expr) {
3107
3412
  const name = expr.name.text;
3108
3413
  const strictReceiver = (expected) => {
3109
3414
  const value = lowerer.lowerExpr(expr.expression);
3415
+ if (value.type.kind === "dyn")
3416
+ return lowerer.coerceInto(expr.expression, value, expected);
3110
3417
  return lowerer.runtimeOptionalPropertyReceiver(expr.expression, value, expected, name) ?? value;
3111
3418
  };
3112
3419
  if (kind === "child") {
@@ -3327,15 +3634,14 @@ export function lowerIntrinsicProperty(lowerer, expr) {
3327
3634
  // SEMANTICS.md notes the divergence from Node's Buffer pooling),
3328
3635
  // the view's real offset for a DataView. A runtime read, so the
3329
3636
  // receiver's evaluation is never discarded.
3330
- const receiver = lowerer.lowerExpr(expr.expression);
3637
+ const expected = lowerer.mapTypeOf(lowerer.typeOf(expr.expression));
3638
+ const receiver = expected?.kind === "bytes" ? strictReceiver(expected) : lowerer.lowerExpr(expr.expression);
3331
3639
  return { kind: "bytesIntrinsic", method: "byteOffset", receiver, args: [], type: F64, loc };
3332
3640
  }
3333
3641
  if (name === "buffer") {
3334
- // No ArrayBuffer VALUE exists — `.buffer` compiles only in the
3335
- // one composed position that consumes it, the DataView
3336
- // constructor's first argument (lowerDataViewNew peels it).
3337
- lowerer.unsupported("SC1090", expr, "'.buffer' outside new DataView(x.buffer, ...) / Buffer.from(x.buffer, ...) " +
3338
- "(no free-standing ArrayBuffer value exists; subarray() answers an aliasing view directly)");
3642
+ const expected = lowerer.mapTypeOf(lowerer.typeOf(expr.expression));
3643
+ const receiver = expected?.kind === "bytes" ? strictReceiver(expected) : lowerer.lowerExpr(expr.expression);
3644
+ return { kind: "bytesIntrinsic", method: "buffer", receiver, args: [], type: DYN, loc };
3339
3645
  }
3340
3646
  if (name === "slice" || name === "subarray" || name === "set" || name === "toString") {
3341
3647
  lowerer.unsupported("SC1090", expr, `typed-array methods as values (call '${name}' directly)`);
@@ -3344,7 +3650,8 @@ export function lowerIntrinsicProperty(lowerer, expr) {
3344
3650
  }
3345
3651
  if (kind === "map") {
3346
3652
  if (name === "size") {
3347
- const receiver = lowerer.lowerExpr(expr.expression);
3653
+ const expected = lowerer.mapTypeOf(lowerer.typeOf(expr.expression));
3654
+ const receiver = expected?.kind === "map" ? strictReceiver(expected) : lowerer.lowerExpr(expr.expression);
3348
3655
  return { kind: "mapIntrinsic", method: "size", receiver, args: [], type: F64, loc: locOf(expr) };
3349
3656
  }
3350
3657
  if (MAP_METHODS.has(name) || name === "forEach") {
@@ -3354,7 +3661,8 @@ export function lowerIntrinsicProperty(lowerer, expr) {
3354
3661
  }
3355
3662
  if (kind === "set") {
3356
3663
  if (name === "size") {
3357
- const receiver = lowerer.lowerExpr(expr.expression);
3664
+ const expected = lowerer.mapTypeOf(lowerer.typeOf(expr.expression));
3665
+ const receiver = expected?.kind === "set" ? strictReceiver(expected) : lowerer.lowerExpr(expr.expression);
3358
3666
  return { kind: "setIntrinsic", method: "size", receiver, args: [], type: F64, loc: locOf(expr) };
3359
3667
  }
3360
3668
  if (SET_METHODS.has(name)) {
@@ -3429,7 +3737,10 @@ export function lowerIntrinsicProperty(lowerer, expr) {
3429
3737
  export function lowerArrayLiteral(lowerer, expr, expected) {
3430
3738
  const loc = locOf(expr);
3431
3739
  const ctxType = lowerer.checker.getContextualType(expr);
3432
- const tsType = ctxType ?? lowerer.typeOf(expr);
3740
+ // `as const satisfies readonly T[]` checks against an array but keeps
3741
+ // the literal's inferred tuple type. Build that shape; an actual array
3742
+ // destination can use the ordinary tuple-to-array coercion afterward.
3743
+ const tsType = underConstAssertion(expr) ? lowerer.typeOf(expr) : ctxType ?? lowerer.typeOf(expr);
3433
3744
  let mapped = expected ?? lowerer.mapTypeOf(tsType);
3434
3745
  // A JS literal whose OWN inferred type is never-tainted
3435
3746
  // (neverTaintedJsType — the evolving `const gb = []`, the mixed
@@ -3542,13 +3853,13 @@ export function lowerArrayLiteral(lowerer, expr, expected) {
3542
3853
  // literal constructs the tuple's record shape — one positional field
3543
3854
  // per element, source order (which IS index order, so evaluation order
3544
3855
  // is JS-exact). tsc has already checked the arity; the recount below
3545
- // backstops `as` smuggling. Spreads have no fixed positions — fenced.
3856
+ // backstops `as` smuggling. Fixed tuple spreads have known positions.
3546
3857
  if (mapped?.kind === "record") {
3547
3858
  const shape = lowerer.shapes.get(mapped.shapeId);
3548
3859
  if (shape?.tuple) {
3549
3860
  const spread = expr.elements.find(ts.isSpreadElement);
3550
3861
  if (spread) {
3551
- lowerer.unsupported("SC1090", spread, "spread elements in tuple literals (positions must be spelled out)");
3862
+ return lowerTupleSpreadLiteral(lowerer, expr, mapped, shape);
3552
3863
  }
3553
3864
  if (expr.elements.length !== shape.fields.length) {
3554
3865
  // tsc padded an UNDER-LENGTH literal against an optional-element
@@ -3594,6 +3905,13 @@ export function lowerArrayLiteral(lowerer, expr, expected) {
3594
3905
  // to DYN wholesale now (mapType's dyn-element array rule), so the
3595
3906
  // literal IS the dyn array.
3596
3907
  if (isJsSourceFile(expr.getSourceFile()) || mapped?.kind === "dyn") {
3908
+ if (expr.elements.some(ts.isSpreadElement)) {
3909
+ // Native collection drains return vectors of retained dyn
3910
+ // elements. Reuse the ordinary spread builder, then expose the
3911
+ // resulting array through the established unknown[] representation.
3912
+ const vector = lowerArrayLiteral(lowerer, expr, arrayOf(DYN));
3913
+ return lowerer.coerceInto(expr, vector, DYN);
3914
+ }
3597
3915
  const elems = expr.elements.map((el) => {
3598
3916
  if (ts.isSpreadElement(el)) {
3599
3917
  lowerer.unsupported("SC1090", el, "spread elements in a dynamic (unknown[]) array literal");
@@ -3644,19 +3962,7 @@ export function lowerArrayLiteral(lowerer, expr, expected) {
3644
3962
  (ts.isConditionalExpression(srcNode)
3645
3963
  ? lowerTernary(lowerer, srcNode, type)
3646
3964
  : lowerer.lowerExpr(el.expression));
3647
- // `[...someSet]`: a same-element Set drains into a fresh array in
3648
- // insertion order (setIntrinsic toArray); the spread machinery
3649
- // then copies like any array source.
3650
- if (src.type.kind === "set" && typeEquals(src.type.elem, type.elem)) {
3651
- src = {
3652
- kind: "setIntrinsic",
3653
- method: "toArray",
3654
- receiver: src,
3655
- args: [],
3656
- type: arrayOf(src.type.elem),
3657
- loc: locOf(el),
3658
- };
3659
- }
3965
+ src = lowerCollectionSpread(lowerer, src, el.expression) ?? src;
3660
3966
  // `[...new SymbolIterator]`: a CLASS ITERABLE drains through its
3661
3967
  // own protocol into a fresh element array (classIteratorDrainCall
3662
3968
  // — an infinite iterator loops forever, exactly Node), and the
@@ -3688,6 +3994,16 @@ export function lowerArrayLiteral(lowerer, expr, expected) {
3688
3994
  loc: locOf(el),
3689
3995
  };
3690
3996
  }
3997
+ // A native checked iterable can supply scalar elements even when
3998
+ // the checker inferred a typed array from its producer. Drain once
3999
+ // and validate the elements before the ordinary spread copy.
4000
+ if (src.type.kind === "dyn" && (type.elem.kind === "dyn" || type.elem.kind === "f64" || type.elem.kind === "string" || type.elem.kind === "bool")) {
4001
+ src = lowerer.coerceInto(el.expression, {
4002
+ kind: "libCall", fn: "dyn.iterPack", args: [src,
4003
+ { kind: "strLit", value: el.expression.getText(), type: STRING, loc: locOf(el) }],
4004
+ type: DYN, loc: locOf(el),
4005
+ }, type);
4006
+ }
3691
4007
  // A same-family array whose ELEMENT lifts (string[] into a
3692
4008
  // (string | symbol)[] literal — per-element wrap/width copy):
3693
4009
  // the interned width helper reshapes before the spread copies.
@@ -3817,6 +4133,62 @@ export function lowerArrayLiteral(lowerer, expr, expected) {
3817
4133
  loc,
3818
4134
  };
3819
4135
  }
4136
+ /** Build a fixed tuple from positional elements and fixed tuple spreads.
4137
+ * Capture each value in source order: deferring the reads until recordLit
4138
+ * would observe a later element's mutations of the spread source. */
4139
+ function lowerTupleSpreadLiteral(lowerer, expr, type, shape) {
4140
+ const loc = locOf(expr);
4141
+ const byName = new Map(shape.fields.map((field) => [field.name, field.type]));
4142
+ const stmts = [];
4143
+ const fields = [];
4144
+ const append = (node, value) => {
4145
+ const name = String(fields.length);
4146
+ const expected = byName.get(name);
4147
+ if (!expected)
4148
+ lowerer.badType(expr, lowerer.typeOf(expr));
4149
+ const coerced = lowerer.coerceInto(node, value, expected);
4150
+ const temp = lowerer.declareHiddenLocal("%tupleElement", expected);
4151
+ const at = locOf(node);
4152
+ stmts.push({ kind: "varDecl", localId: temp.id, init: coerced, loc: at });
4153
+ fields.push({ name, value: varRef(temp.id, expected, at) });
4154
+ };
4155
+ for (const element of expr.elements) {
4156
+ if (!ts.isSpreadElement(element)) {
4157
+ const expected = byName.get(String(fields.length));
4158
+ if (!expected)
4159
+ lowerer.badType(element, lowerer.typeOf(element));
4160
+ append(element, lowerer.lowerExprExpecting(element, expected));
4161
+ continue;
4162
+ }
4163
+ const source = lowerer.lowerExpr(element.expression);
4164
+ const sourceShape = source.type.kind === "record" ? lowerer.shapes.get(source.type.shapeId) : undefined;
4165
+ if (!sourceShape?.tuple) {
4166
+ // Empty tuples use a zero-length array representation. Preserve a
4167
+ // producing call's effects even though there are no positions to copy.
4168
+ const sourceTs = lowerer.typeOf(element.expression);
4169
+ if (source.type.kind === "array" && lowerer.checker.isTupleType(sourceTs) &&
4170
+ lowerer.checker.getTypeArguments(sourceTs).length === 0) {
4171
+ stmts.push({ kind: "exprStmt", expr: source, loc: locOf(element) });
4172
+ continue;
4173
+ }
4174
+ lowerer.unsupported("SC1090", element, "spreading a variable-length value into a fixed tuple literal");
4175
+ }
4176
+ const temp = lowerer.declareHiddenLocal("%tupleSpread", source.type);
4177
+ const at = locOf(element);
4178
+ stmts.push({ kind: "varDecl", localId: temp.id, init: source, loc: at });
4179
+ const receiver = varRef(temp.id, source.type, at);
4180
+ const positions = [...sourceShape.fields].sort((a, b) => Number(a.name) - Number(b.name));
4181
+ for (const field of positions) {
4182
+ append(element.expression, {
4183
+ kind: "recordGet", obj: receiver, shapeId: sourceShape.id,
4184
+ field: field.name, type: field.type, loc: at,
4185
+ });
4186
+ }
4187
+ }
4188
+ if (fields.length !== shape.fields.length)
4189
+ lowerer.badType(expr, lowerer.typeOf(expr));
4190
+ return { kind: "seqExpr", stmts, result: { kind: "recordLit", fields, type, loc }, type, loc };
4191
+ }
3820
4192
  /** Convert an undefined-armed numeric value at an arithmetic use. Ordinary
3821
4193
  * reads preserve their tagged value; JavaScript's ToNumber(undefined) is NaN,
3822
4194
  * so compounds and binary arithmetic use this checked one-time extraction. */
@@ -3891,16 +4263,17 @@ export function lowerElementAccess(lowerer, expr) {
3891
4263
  lowerer.fenceStaticResponseMember(expr, "read");
3892
4264
  lowerer.fenceStaticHeadersMember(expr, "read");
3893
4265
  lowerer.fenceStaticReadableStreamMember(expr, "read");
3894
- // `globalThis[<expr>]` — the dynamic global probe (the harness's
3895
- // conditional-globals sweep): a compiled binary's globals are
3896
- // compile-time bindings, not runtime properties, and none of the
3897
- // probed web-platform objects exist here — the honest answer is
3898
- // undefined for every dynamic name (SEMANTICS.md documents the
3899
- // divergence: statically-known globals are not reachable this way;
3900
- // the PROPERTY spelling answers the identity token instead — see
3901
- // lowerPropertyAccess's globalThis rule).
4266
+ // Computed global reads share the stored global object's custom
4267
+ // properties. Symbol keys retain their identity-indexed store.
3902
4268
  if (!expr.questionDotToken && stdlibGlobalNameOf(lowerer, expr.expression) === "globalThis") {
3903
- return { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc: locOf(expr) };
4269
+ const symbol = globalSymbolKey(lowerer, expr.expression, expr.argumentExpression);
4270
+ if (symbol)
4271
+ return { kind: "libCall", fn: "dyn.globalSymbolGet", args: [symbol], type: DYN, loc: locOf(expr) };
4272
+ const key = lowerer.lowerExpr(expr.argumentExpression);
4273
+ const name = key.type.kind === "string" ? key : {
4274
+ kind: "libCall", fn: "dyn.toStringCoerce", args: [lowerer.coerceInto(expr.argumentExpression, key, DYN)], type: STRING, loc: locOf(expr),
4275
+ };
4276
+ return { kind: "dynKeyGet", value: lowerGlobalValue(lowerer, expr.expression), key: name, type: DYN, loc: locOf(expr) };
3904
4277
  }
3905
4278
  // `req.headers["x-name"]` — the computed twin of `req.headers.host`
3906
4279
  // (the server spoke owns both; the envGet precedent).
@@ -3913,7 +4286,7 @@ export function lowerElementAccess(lowerer, expr) {
3913
4286
  // lower to the ONE process.envGet intrinsic. The read narrows like any
3914
4287
  // union-typed expression when the checker narrowed this occurrence.
3915
4288
  if (lowerer.isProcessEnv(expr.expression)) {
3916
- const key = lowerer.lowerExpr(expr.argumentExpression);
4289
+ const key = lowerEnvironmentKey(lowerer, expr.argumentExpression);
3917
4290
  if (key.type.kind !== "string") {
3918
4291
  lowerer.unsupported("SC1090", expr.argumentExpression, "indexing process.env with non-string keys");
3919
4292
  }
@@ -3936,17 +4309,27 @@ export function lowerElementAccess(lowerer, expr) {
3936
4309
  return lowerer.maybeNarrow(ex, expr);
3937
4310
  }
3938
4311
  // Symbol-keyed property READS (`this[kLimit]`): when the key is a
3939
- // statically-resolvable unique-symbol const declared as a field of the
3940
- // receiver's class (uniqueSymbolKeyOf — the countdown.js idiom), the
4312
+ // statically-resolvable symbol declared as a field of the receiver's
4313
+ // class (classSymbolKeyOf — the countdown.js/OpenTUI idioms), the
3941
4314
  // read IS an ordinary field read of the hidden slot. Every other
3942
4315
  // symbol key stays fenced: record shapes, runtime-identity keys
3943
- // (symbol parameters, Symbol.for consts), and keys no class declares
4316
+ // (symbol parameters, reassigned bindings), and keys no class declares
3944
4317
  // — the layouts are compile-time field lists.
3945
4318
  if (lowerer.mapTypeOf(lowerer.typeOf(expr.argumentExpression))?.kind === "symbol") {
3946
4319
  const target = symbolFieldTarget(lowerer, expr);
3947
4320
  if (target)
3948
4321
  return lowerer.maybeNarrow(lowerer.fieldGetExpr(target, locOf(expr), expr), expr);
3949
- lowerer.unsupported("SC1090", expr, "symbol-keyed property access outside class fields keyed by a module-level `const k = Symbol('desc')` (static shapes have no symbol-keyed storage)");
4322
+ let receiver = lowerer.lowerExpr(expr.expression);
4323
+ if (receiver.type.kind === "func") {
4324
+ const fnName = jsFuncNameOf(expr.expression);
4325
+ receiver = { kind: "dynFrom", value: receiver, type: DYN, loc: locOf(expr),
4326
+ ...(fnName !== null ? { fnName } : {}) };
4327
+ }
4328
+ if (receiver.type.kind === "dyn") {
4329
+ const key = lowerer.lowerExprExpecting(expr.argumentExpression, DYN);
4330
+ return lowerer.maybeNarrow({ kind: "dynKeyGet", value: receiver, key, type: DYN, loc: locOf(expr) }, expr);
4331
+ }
4332
+ lowerer.unsupported("SC1090", expr, "symbol-keyed property access outside class fields keyed by a stable module-level literal Symbol()/Symbol.for() (other static shapes have no symbol-keyed storage)");
3950
4333
  }
3951
4334
  // A never-tainted JS receiver type (neverTaintedJsType — `cmd[1]` on
3952
4335
  // `const cmd = ['pwd', []]`, whose binding lowered checked-dynamic)
@@ -3955,6 +4338,16 @@ export function lowerElementAccess(lowerer, expr) {
3955
4338
  let receiverIr = neverTaintedJsType(lowerer, expr.expression, lowerer.typeOf(expr.expression))
3956
4339
  ? null
3957
4340
  : lowerer.mapTypeOf(lowerer.typeOf(expr.expression));
4341
+ if (isJsSourceFile(expr.getSourceFile())) {
4342
+ const receiver = tryLowerExpression(lowerer, expr.expression);
4343
+ if (receiver?.type.kind === "func" && lowerer.dynConvertible(receiver.type)) {
4344
+ const loc = locOf(expr);
4345
+ const fnName = jsFuncNameOf(expr.expression);
4346
+ const boxed = { kind: "dynFrom", value: receiver, type: DYN, loc, ...(fnName !== null ? { fnName } : {}) };
4347
+ const key = lowerRecordPropertyKey(lowerer, lowerer.lowerExpr(expr.argumentExpression), expr.argumentExpression);
4348
+ return lowerer.maybeNarrow({ kind: "dynKeyGet", key, value: boxed, type: DYN, loc }, expr);
4349
+ }
4350
+ }
3958
4351
  // A readonly tuple union under Array.isArray is checker-typed as an
3959
4352
  // intersection with any[], whose structural mapping is not the tuple
3960
4353
  // shape. maybeNarrow on the receiver uses the runtime tag proof to
@@ -3963,6 +4356,19 @@ export function lowerElementAccess(lowerer, expr) {
3963
4356
  const checkerArray = lowerer.checkerArrayValue(expr.expression);
3964
4357
  if (checkerArray)
3965
4358
  receiverIr = checkerArray.type;
4359
+ if (receiverIr?.kind === "object") {
4360
+ const key = recordKeyLiteralText(expr.argumentExpression) ?? recordKeyTypeLiteralText(lowerer, expr.argumentExpression);
4361
+ if (key !== null) {
4362
+ const target = classFieldTarget(lowerer, expr.expression, receiverIr, key);
4363
+ if (target)
4364
+ return lowerer.maybeNarrow(lowerer.fieldGetExpr(target, locOf(expr), expr), expr);
4365
+ }
4366
+ if (isDynTypedRefType(receiverIr)) {
4367
+ const receiver = lowerer.coerceToExpected(lowerer.lowerExpr(expr.expression), DYN);
4368
+ const key = lowerer.lowerExprExpecting(expr.argumentExpression, DYN);
4369
+ return lowerer.maybeNarrow({ kind: "dynKeyGet", value: receiver, key, type: DYN, loc: locOf(expr) }, expr);
4370
+ }
4371
+ }
3966
4372
  if (receiverIr?.kind === "jsval") {
3967
4373
  // Dispatch follows the RUNTIME world (383(d)): a checker-'any'
3968
4374
  // receiver whose value LOWERED checked-dynamic (`bag.list[0]` where
@@ -3988,6 +4394,8 @@ export function lowerElementAccess(lowerer, expr) {
3988
4394
  // read undefined — divergence 4's policy).
3989
4395
  if (receiverIr?.kind === "bytes") {
3990
4396
  let recv = lowerer.lowerExpr(expr.expression);
4397
+ if (recv.type.kind === "dyn")
4398
+ recv = lowerer.coerceInto(expr.expression, recv, receiverIr);
3991
4399
  if (recv.type.kind === "union" && lowerer.armTag(recv.type.unionId, UNDEFINED_T) >= 0) {
3992
4400
  const present = lowerer.stripUndefinedArm(recv.type);
3993
4401
  const helper = present.kind === "bytes"
@@ -4004,6 +4412,9 @@ export function lowerElementAccess(lowerer, expr) {
4004
4412
  if (recv.type.kind === "jsval")
4005
4413
  return islandElementRead(lowerer, expr, recv);
4006
4414
  const index = lowerer.lowerExpr(expr.argumentExpression);
4415
+ if (index.type.kind === "dyn" && recv.type.kind === "bytes") {
4416
+ return { kind: "dynKeyGet", value: lowerer.coerceInto(expr.expression, recv, DYN), key: index, type: DYN, loc: locOf(expr) };
4417
+ }
4007
4418
  if (index.type.kind !== "f64") {
4008
4419
  lowerer.unsupported("SC1090", expr.argumentExpression, "indexing with non-number keys");
4009
4420
  }
@@ -4025,6 +4436,17 @@ export function lowerElementAccess(lowerer, expr) {
4025
4436
  // like the array path.
4026
4437
  if (obj.type.kind === "jsval")
4027
4438
  return islandElementRead(lowerer, expr, obj);
4439
+ if (obj.type.kind === "dyn") {
4440
+ // Object.entries over a checked value stores each pair in the
4441
+ // dynamic tree even when the checker names a tuple type.
4442
+ const key = lowerRecordPropertyKey(lowerer, lowerer.lowerExpr(expr.argumentExpression), expr.argumentExpression);
4443
+ const optional = hasOptionalChainGuard(expr.expression);
4444
+ return lowerer.maybeNarrow({
4445
+ kind: "dynKeyGet", value: obj, key,
4446
+ ...(optional ? { optional: true } : {}),
4447
+ type: DYN, loc: locOf(expr),
4448
+ }, expr);
4449
+ }
4028
4450
  if (obj.type.kind === "union" && lowerer.armTag(obj.type.unionId, UNDEFINED_T) >= 0) {
4029
4451
  // The checker sees the outer `arrays[i]` as a tuple when
4030
4452
  // noUncheckedIndexedAccess is disabled, but the lowered read is
@@ -4080,21 +4502,28 @@ export function lowerElementAccess(lowerer, expr) {
4080
4502
  // (`pkg.workspaces.packages["0"]` — the lowering world types the
4081
4503
  // unknown-rooted chain `any`); a non-dyn lowering falls through to
4082
4504
  // the fences below (re-lowering is pure IR construction).
4083
- if (receiverIr?.kind === "dyn" || receiverIr === null) {
4505
+ if (receiverIr?.kind === "dyn" || receiverIr?.kind === "union" || receiverIr === null) {
4084
4506
  const obj = lowerer.lowerExpr(expr.expression);
4085
- if (obj.type.kind === "dyn") {
4507
+ // A generic mapped type can stay unresolved at this body use even
4508
+ // though its instantiated parameter has a concrete record ABI.
4509
+ // Dispatch from that stored shape, as the array fallback below does.
4510
+ if (receiverIr === null && obj.type.kind === "record") {
4511
+ const shape = lowerer.shapes.get(obj.type.shapeId);
4512
+ if (shape && !shape.tuple) {
4513
+ return lowerer.lowerRecordKeyRead(expr, obj.type.shapeId, shape);
4514
+ }
4515
+ }
4516
+ if (obj.type.kind === "dyn" || isDynTypedRefType(obj.type)) {
4086
4517
  const rawKey = lowerer.lowerExpr(expr.argumentExpression);
4087
4518
  // Number, bool, and DYN keys stringify (ToPropertyKey) — the
4088
4519
  // dyn-keyed read `catchWarning[warning.name]` where the property
4089
4520
  // chain itself lowered dyn.
4090
- const key = rawKey.type.kind === "string"
4091
- ? rawKey
4092
- : rawKey.type.kind === "f64" || rawKey.type.kind === "bool" || rawKey.type.kind === "dyn"
4093
- ? { kind: "toString", operand: rawKey, type: STRING, loc: rawKey.loc }
4094
- : null;
4095
- if (key) {
4521
+ const key = rawKey.type.kind === "dyn" || rawKey.type.kind === "symbol"
4522
+ ? lowerer.coerceToExpected(rawKey, DYN)
4523
+ : lowerRecordPropertyKey(lowerer, rawKey, expr.argumentExpression);
4524
+ if (key.type.kind === "string" || key.type.kind === "dyn") {
4096
4525
  const opt = hasOptionalChainGuard(expr.expression);
4097
- return lowerer.maybeNarrow({ kind: "dynKeyGet", key, ...(opt ? { optional: true } : {}), value: obj, type: DYN, loc: locOf(expr) }, expr);
4526
+ return lowerer.maybeNarrow({ kind: "dynKeyGet", key, ...(opt ? { optional: true } : {}), value: lowerer.coerceToExpected(obj, DYN), type: DYN, loc: locOf(expr) }, expr);
4098
4527
  }
4099
4528
  }
4100
4529
  // A checker-`any` receiver that LOWERS to a static ARRAY
@@ -4104,6 +4533,9 @@ export function lowerElementAccess(lowerer, expr) {
4104
4533
  if (obj.type.kind === "array") {
4105
4534
  const index = lowerer.lowerExpr(expr.argumentExpression);
4106
4535
  if (index.type.kind === "f64") {
4536
+ const safe = lowerSafeIndexRead(lowerer, obj, index, locOf(expr));
4537
+ if (safe)
4538
+ return safe;
4107
4539
  return lowerer.maybeNarrow({ kind: "arrayGet", arr: obj, index, type: obj.type.elem, loc: locOf(expr) }, expr);
4108
4540
  }
4109
4541
  }
@@ -4126,7 +4558,7 @@ export function lowerElementAccess(lowerer, expr) {
4126
4558
  }
4127
4559
  }
4128
4560
  }
4129
- if (key.type.kind === "string") {
4561
+ if (key.type.kind === "string" || key.type.kind === "f64") {
4130
4562
  const lit = ts.isStringLiteral(expr.argumentExpression)
4131
4563
  ? expr.argumentExpression.text
4132
4564
  : null;
@@ -4134,33 +4566,20 @@ export function lowerElementAccess(lowerer, expr) {
4134
4566
  if (keyed)
4135
4567
  return lowerer.maybeNarrow(keyed, expr);
4136
4568
  }
4137
- // `u?.split(":")[0]` — a NUMBER index over an undefined-armed
4138
- // array union: the array arm answers its element (invalid indices
4139
- // trap — divergence 4's policy), unit arms answer undefined (the
4140
- // chain's short-circuit value). Every non-unit arm must be the
4141
- // same array type.
4142
- if (key.type.kind === "f64") {
4143
- const def = lowerer.unions.get(value.type.unionId);
4144
- const arrArms = def?.arms.filter((a) => a.kind === "array") ?? [];
4145
- if (def &&
4146
- arrArms.length > 0 &&
4147
- def.arms.every((a) => a.kind === "array" || isUnitType(a)) &&
4148
- arrArms.every((a) => typeEquals(a, arrArms[0]))) {
4149
- const elem = arrArms[0].elem;
4150
- const hasUnit = def.arms.some(isUnitType);
4151
- const t = !hasUnit
4152
- ? elem
4153
- : elem.kind === "union"
4154
- ? lowerer.withUndefinedArmOf(elem)
4155
- : lowerer.withUndefinedArm(elem);
4156
- if (t) {
4157
- return lowerer.maybeNarrow({ kind: "unionKeyGet", unionId: value.type.unionId, key, value, type: t, loc: locOf(expr) }, expr);
4158
- }
4159
- }
4160
- }
4161
4569
  }
4162
4570
  }
4163
4571
  if (receiverIr?.kind !== "array") {
4572
+ {
4573
+ const receiver = tryLowerExpression(lowerer, expr.expression);
4574
+ if (receiver?.type.kind === "func" &&
4575
+ canBoxFuncIntoDyn(receiver.type, (id) => lowerer.shapes.get(id), (id) => lowerer.unions.get(id))) {
4576
+ const key = lowerRecordPropertyKey(lowerer, lowerer.lowerExpr(expr.argumentExpression), expr.argumentExpression);
4577
+ if (key.type.kind === "string") {
4578
+ const boxed = { kind: "dynFrom", value: receiver, type: DYN, loc: locOf(expr.expression) };
4579
+ return lowerer.maybeNarrow({ kind: "dynKeyGet", key, value: boxed, type: DYN, loc: locOf(expr) }, expr);
4580
+ }
4581
+ }
4582
+ }
4164
4583
  if (receiverIr?.kind === "string") {
4165
4584
  // `s[i]` with a number index reads a UTF-16 code unit — charAt's
4166
4585
  // exact job (the UTF-16-exact runtime). Without
@@ -4168,16 +4587,23 @@ export function lowerElementAccess(lowerer, expr) {
4168
4587
  // and charAt's "" is the only string-typed answer for an
4169
4588
  // out-of-range or fractional index where JS reads `undefined` —
4170
4589
  // SEMANTICS.md documents the divergence; in-range integer reads
4171
- // (the loop pattern) are JS-exact. Under noUncheckedIndexedAccess
4172
- // the read types `string | undefined`, which charAt cannot honor —
4173
- // fenced.
4590
+ // (the loop pattern) are JS-exact. Optional results instead retain
4591
+ // undefined for missing string properties.
4592
+ const recv = lowerer.lowerExpr(expr.expression);
4174
4593
  const index = lowerer.lowerExpr(expr.argumentExpression);
4175
- if (index.type.kind === "f64" && lowerer.mapTypeOf(lowerer.typeOf(expr))?.kind === "string") {
4176
- const recv = lowerer.lowerExpr(expr.expression);
4177
- return { kind: "strIntrinsic", method: "charAt", receiver: recv, args: [index], type: STRING, loc: locOf(expr) };
4594
+ const resultType = lowerer.mapTypeOf(lowerer.typeOf(expr));
4595
+ if (index.type.kind === "f64" && recv.type.kind === "string") {
4596
+ if (resultType?.kind === "string") {
4597
+ return { kind: "strIntrinsic", method: "charAt", receiver: recv, args: [index], type: STRING, loc: locOf(expr) };
4598
+ }
4599
+ if (resultType?.kind === "union") {
4600
+ const arms = lowerer.unions.get(resultType.unionId)?.arms;
4601
+ if (arms?.length === 2 && lowerer.armTag(resultType.unionId, STRING) >= 0 && lowerer.armTag(resultType.unionId, UNDEFINED_T) >= 0) {
4602
+ return lowerOptionalStringIndex(lowerer, recv, index, resultType, locOf(expr));
4603
+ }
4604
+ }
4178
4605
  }
4179
- lowerer.unsupported("SC1090", expr, "string indexing with this index/result shape (a number index typed 'string' lowers to charAt; " +
4180
- "use .charAt(i) under noUncheckedIndexedAccess)");
4606
+ lowerer.unsupported("SC1090", expr, "string indexing with this index/result shape (expected a number index and a string or string | undefined result)");
4181
4607
  }
4182
4608
  lowerer.unsupported("SC1090", expr, "element access on non-array values");
4183
4609
  }
@@ -4255,14 +4681,17 @@ export function lowerElementAccess(lowerer, expr) {
4255
4681
  // can validate, and fences by name when it is not — an arrayGet over a
4256
4682
  // dyn receiver is never emitted (the validator ICE).
4257
4683
  if (arr.type.kind === "dyn") {
4258
- if (isJsonSafeType(receiverIr, (id) => lowerer.shapes.get(id), (id) => lowerer.unions.get(id))) {
4684
+ if (canDynCheckTo(receiverIr, (id) => lowerer.shapes.get(id), (id) => lowerer.unions.get(id))) {
4259
4685
  arr = { kind: "dynCheck", value: arr, type: receiverIr, loc: locOf(expr) };
4260
4686
  }
4261
4687
  else {
4262
4688
  lowerer.unsupported("SC1090", expr, `element access on checked-dynamic values with '${lowerer.fmt(receiverIr)}' elements`);
4263
4689
  }
4264
4690
  }
4265
- const index = lowerer.lowerExpr(expr.argumentExpression);
4691
+ // Array reads can themselves supply an index. A missing numeric value
4692
+ // is undefined, whose property lookup misses just like NaN; keep that
4693
+ // value instead of rejecting a checker-number index with optional IR.
4694
+ const index = lowerOptionalNumber(lowerer, lowerer.lowerExpr(expr.argumentExpression), locOf(expr.argumentExpression), expr.argumentExpression);
4266
4695
  if (index.type.kind !== "f64") {
4267
4696
  lowerer.unsupported("SC1090", expr.argumentExpression, "indexing with non-number keys");
4268
4697
  }
@@ -4384,6 +4813,26 @@ export function lowerRecordKeyRead(lowerer, expr, shapeId, shape, includeUndefin
4384
4813
  // to observe the missing value instead of throwing during that check.
4385
4814
  return includeUndefined ? read : lowerer.maybeNarrow(read, expr);
4386
4815
  }
4816
+ if (litKey !== null) {
4817
+ const nominal = representedClassFieldTarget(lowerer, expr.expression, litKey, obj);
4818
+ if (nominal) {
4819
+ const read = lowerer.fieldGetExpr(nominal, loc, expr);
4820
+ return includeUndefined ? read : lowerer.maybeNarrow(read, expr);
4821
+ }
4822
+ }
4823
+ if (hasClassPayload(lowerer, obj.type)) {
4824
+ lowerer.unsupported("SC1090", expr, "computed property reads through structural views of class instances (use a declared literal key)");
4825
+ }
4826
+ // Predicates can strengthen optional fields without changing the
4827
+ // receiver's stored layout. Use that layout for bracket reads just as
4828
+ // fieldTarget does for the corresponding dot reads.
4829
+ if (obj.type.kind === "record") {
4830
+ const actualShape = lowerer.shapes.get(obj.type.shapeId);
4831
+ if (!actualShape)
4832
+ throw new InternalCompilerError("record receiver is missing its stored shape");
4833
+ shapeId = obj.type.shapeId;
4834
+ shape = actualShape;
4835
+ }
4387
4836
  if (litKey !== null) {
4388
4837
  const field = shape.fields.find((f) => f.name === litKey);
4389
4838
  if (field) {
@@ -4429,7 +4878,7 @@ export function lowerRecordKeyRead(lowerer, expr, shapeId, shape, includeUndefin
4429
4878
  let declared = null;
4430
4879
  if (shape.indexValue) {
4431
4880
  declared = shape.indexValue;
4432
- if (includeUndefined || lowerer.program.getCompilerOptions().noUncheckedIndexedAccess) {
4881
+ if (declared.kind !== "dyn" && (includeUndefined || lowerer.program.getCompilerOptions().noUncheckedIndexedAccess)) {
4433
4882
  declared = lowerer.withUndefinedArmOf(declared);
4434
4883
  if (!declared)
4435
4884
  lowerer.badType(expr, lowerer.typeOf(expr));
@@ -4439,6 +4888,8 @@ export function lowerRecordKeyRead(lowerer, expr, shapeId, shape, includeUndefin
4439
4888
  shape.fields.every((f) => typeEquals(f.type, shape.fields[0].type))) {
4440
4889
  declared = shape.fields[0].type;
4441
4890
  }
4891
+ if (!declared && isJsSourceFile(expr.getSourceFile()) && recordKeyResultOk(lowerer, shape, DYN))
4892
+ declared = DYN;
4442
4893
  if (!declared) {
4443
4894
  lowerer.unsupported("SC1090", expr, `dynamic keyed reads of '${lowerer.fmt({ kind: "record", shapeId })}' (the declared fields have no one common type)`);
4444
4895
  }
@@ -4483,6 +4934,10 @@ export function lowerAbsenceProbe(lowerer, node) {
4483
4934
  }
4484
4935
  if (ts.isPropertyAccessExpression(expr)) {
4485
4936
  const target = lowerer.fieldTarget(expr);
4937
+ if (target?.container === "class" && target.fieldType.kind === "union" &&
4938
+ lowerer.unions.get(target.fieldType.unionId)?.arms.some(isUnitType)) {
4939
+ return lowerer.fieldGetExpr(target, locOf(expr), expr);
4940
+ }
4486
4941
  if (target?.container !== "recordOvf")
4487
4942
  return null;
4488
4943
  // A JS file-scope object-literal global is record-shaped to the
@@ -4584,6 +5039,7 @@ function recordKeyTypeLiteralText(lowerer, node) {
4584
5039
  function recordKeyResultOk(lowerer, shape, type) {
4585
5040
  const surfaces = (t) => typeEquals(t, type) ||
4586
5041
  (type.kind === "union" && lowerer.armTag(type.unionId, t) >= 0) ||
5042
+ exactUnionWidening(lowerer, t, type) ||
4587
5043
  (type.kind === "dyn" && lowerer.dynConvertible(t));
4588
5044
  if (!shape.fields.every((f) => surfaces(f.type)))
4589
5045
  return false;
@@ -4595,6 +5051,13 @@ function recordKeyResultOk(lowerer, shape, type) {
4595
5051
  }
4596
5052
  return true;
4597
5053
  }
5054
+ function exactUnionWidening(lowerer, from, to) {
5055
+ if (from.kind !== "union" || to.kind !== "union")
5056
+ return false;
5057
+ const source = lowerer.unions.get(from.unionId);
5058
+ const target = lowerer.unions.get(to.unionId);
5059
+ return !!source && !!target && unionWideningTags(source.arms, target.arms) !== null;
5060
+ }
4598
5061
  /** The literal index of a tuple access, or null when the expression isn't
4599
5062
  * a non-negative integer literal (parentheses tolerated — tsc's own
4600
5063
  * literal-index typing accepts them). */
@@ -4679,16 +5142,71 @@ export function lowerElementCompound(lowerer, expr, op) {
4679
5142
  loc,
4680
5143
  };
4681
5144
  }
4682
- /** `a[i] = v` in statement position → arraySet (element writes, like local
4683
- * assignment, produce no value in our subset). */
5145
+ /** Checked member assignment evaluates the reference before its RHS and
5146
+ * yields the original assigned value, including in a chained assignment. */
5147
+ function lowerDynMemberAssignment(lowerer, expr, recv) {
5148
+ const loc = locOf(expr);
5149
+ const target = expr.left;
5150
+ const receiver = lowerer.declareHiddenLocal("%setReceiver", DYN);
5151
+ const keyValue = ts.isElementAccessExpression(target)
5152
+ ? lowerer.lowerExprExpecting(target.argumentExpression, DYN)
5153
+ : lowerer.coerceToExpected({ kind: "strLit", value: target.name.text, type: STRING, loc }, DYN);
5154
+ const key = lowerer.declareHiddenLocal("%setKey", DYN);
5155
+ const raw = lowerer.lowerExpr(expr.right);
5156
+ const value = raw.type.kind === "void"
5157
+ ? {
5158
+ kind: "seqExpr", stmts: [{ kind: "exprStmt", expr: raw, loc }],
5159
+ result: dynUndefinedExpr(loc), type: DYN, loc,
5160
+ }
5161
+ : isUnitType(raw.type) ? lowerer.coerceInto(expr.right, raw, DYN) : raw;
5162
+ const assigned = lowerer.declareHiddenLocal("%setValue", value.type);
5163
+ const result = varRef(assigned.id, value.type, loc);
5164
+ const stored = lowerer.coerceInto(expr.right, result, DYN);
5165
+ // Evaluate the reference before the RHS, but defer key coercion until
5166
+ // PutValue. Yield the RHS without rereading a setter-backed property.
5167
+ return {
5168
+ kind: "seqExpr", stmts: [
5169
+ { kind: "varDecl", localId: receiver.id, init: recv, loc },
5170
+ { kind: "varDecl", localId: key.id, init: keyValue, loc },
5171
+ { kind: "varDecl", localId: assigned.id, init: value, loc },
5172
+ { kind: "exprStmt", expr: { kind: "libCall", fn: "dyn.keySetComputed", args: [varRef(receiver.id, DYN, loc), varRef(key.id, DYN, loc), stored], type: VOID, loc }, loc },
5173
+ ], result, type: result.type, loc,
5174
+ };
5175
+ }
5176
+ /** JavaScript callable members live on the closure, so aliases and chained
5177
+ * writes share the same storage as Object.defineProperties. */
5178
+ export function lowerNativeFunctionAssignment(lowerer, expr) {
5179
+ const target = expr.left;
5180
+ if (!isJsSourceFile(expr.getSourceFile()) ||
5181
+ (!ts.isPropertyAccessExpression(target) && !ts.isElementAccessExpression(target)) ||
5182
+ target.questionDotToken)
5183
+ return null;
5184
+ const receiver = tryLowerExpression(lowerer, target.expression);
5185
+ if (receiver?.type.kind !== "func" ||
5186
+ !canBoxFuncIntoDyn(receiver.type, (id) => lowerer.shapes.get(id), (id) => lowerer.unions.get(id)))
5187
+ return null;
5188
+ fenceNodeModuleMutation(lowerer, target, "assignment");
5189
+ const fnName = jsFuncNameOf(target.expression);
5190
+ const boxed = {
5191
+ kind: "dynFrom", value: receiver, type: DYN, loc: locOf(target.expression),
5192
+ ...(fnName !== null ? { fnName } : {}),
5193
+ };
5194
+ return lowerDynMemberAssignment(lowerer, expr, boxed);
5195
+ }
4684
5196
  export function lowerElementWrite(lowerer, expr) {
4685
5197
  const target = expr.left;
4686
5198
  fenceNodeModuleMutation(lowerer, target, "assignment");
5199
+ const globalKey = globalSymbolKey(lowerer, target.expression, target.argumentExpression);
5200
+ if (globalKey) {
5201
+ const value = lowerer.lowerExprExpecting(expr.right, DYN);
5202
+ const loc = locOf(expr);
5203
+ return { kind: "exprStmt", expr: { kind: "libCall", fn: "dyn.globalSymbolSet", args: [globalKey, value], type: VOID, loc }, loc };
5204
+ }
4687
5205
  // `process.env[key] = v` — the computed twin of the dotted env write:
4688
5206
  // setenv(3), string keys and string values only.
4689
5207
  if (lowerer.isProcessEnv(target.expression) && !target.questionDotToken) {
4690
5208
  const loc = locOf(expr);
4691
- const key = lowerer.lowerExpr(target.argumentExpression);
5209
+ const key = lowerEnvironmentKey(lowerer, target.argumentExpression);
4692
5210
  if (key.type.kind !== "string") {
4693
5211
  lowerer.unsupported("SC1090", target.argumentExpression, "indexing process.env with non-string keys");
4694
5212
  }
@@ -4712,9 +5230,21 @@ export function lowerElementWrite(lowerer, expr) {
4712
5230
  const value = lowerer.lowerExprExpecting(expr.right, fieldT.fieldType);
4713
5231
  return lowerer.fieldSetStmt(fieldT, value, locOf(expr), target);
4714
5232
  }
4715
- lowerer.unsupported("SC1090", target, "symbol-keyed property writes outside class fields keyed by a module-level `const k = Symbol('desc')` (static shapes have no symbol-keyed storage)");
5233
+ const receiver = lowerer.lowerExpr(target.expression);
5234
+ if (receiver.type.kind === "dyn")
5235
+ return { kind: "exprStmt", expr: lowerDynMemberAssignment(lowerer, expr, receiver), loc: locOf(expr) };
5236
+ lowerer.unsupported("SC1090", target, "symbol-keyed property writes outside class fields keyed by a stable module-level literal Symbol()/Symbol.for() (other static shapes have no symbol-keyed storage)");
5237
+ }
5238
+ const checkedReceiver = tryLowerExpression(lowerer, target.expression);
5239
+ if (checkedReceiver?.type.kind === "dyn") {
5240
+ return { kind: "exprStmt", expr: lowerDynMemberAssignment(lowerer, expr, checkedReceiver), loc: locOf(expr) };
5241
+ }
5242
+ let receiverIr = lowerer.mapTypeOf(lowerer.typeOf(target.expression));
5243
+ if (receiverIr === null || receiverIr.kind === "dyn") {
5244
+ const receiver = tryLowerExpression(lowerer, target.expression);
5245
+ if (receiver?.type.kind === "array")
5246
+ receiverIr = receiver.type;
4716
5247
  }
4717
- const receiverIr = lowerer.mapTypeOf(lowerer.typeOf(target.expression));
4718
5248
  if (receiverIr?.kind === "jsval") {
4719
5249
  const obj = lowerer.lowerExpr(target.expression);
4720
5250
  // Dispatch follows the RUNTIME world (383(d)): a checker-'any'
@@ -4756,9 +5286,7 @@ export function lowerElementWrite(lowerer, expr) {
4756
5286
  const obj = lowerer.lowerExpr(target.expression);
4757
5287
  if (obj.type.kind === "dyn") {
4758
5288
  const loc = locOf(expr);
4759
- let key = lowerer.lowerExpr(target.argumentExpression);
4760
- if (key.type.kind === "f64")
4761
- key = lowerer.ensureString(key, target.argumentExpression);
5289
+ const key = lowerRecordPropertyKey(lowerer, lowerer.lowerExpr(target.argumentExpression), target.argumentExpression);
4762
5290
  if (key.type.kind !== "string") {
4763
5291
  lowerer.unsupported("SC1090", target.argumentExpression, "indexing with non-string or non-number keys");
4764
5292
  }
@@ -4769,6 +5297,20 @@ export function lowerElementWrite(lowerer, expr) {
4769
5297
  return { kind: "exprStmt", expr: { kind: "libCall", fn: "dyn.keySet", args: [obj, key, value], type: VOID, loc }, loc };
4770
5298
  }
4771
5299
  }
5300
+ if (receiverIr?.kind === "object" || receiverIr?.kind === "record") {
5301
+ const key = recordKeyLiteralText(target.argumentExpression) ?? recordKeyTypeLiteralText(lowerer, target.argumentExpression);
5302
+ if (key !== null) {
5303
+ const receiver = lowerer.lowerExpr(target.expression);
5304
+ const field = representedClassFieldTarget(lowerer, target.expression, key, receiver);
5305
+ if (field) {
5306
+ const value = lowerer.lowerExprExpecting(expr.right, field.fieldType);
5307
+ return lowerer.fieldSetStmt(field, value, locOf(expr), target);
5308
+ }
5309
+ if (hasClassPayload(lowerer, receiver.type)) {
5310
+ lowerer.unsupported("SC1090", target, "writing undeclared properties through structural views of class instances");
5311
+ }
5312
+ }
5313
+ }
4772
5314
  // Typed-array element write `b[i] = v` — bytesSet: the value is an f64
4773
5315
  // the runtime coerces JS-exactly; invalid indices trap (no appends —
4774
5316
  // typed arrays are fixed-length; JS would ignore the write).
@@ -4783,6 +5325,9 @@ export function lowerElementWrite(lowerer, expr) {
4783
5325
  return { kind: "exprStmt", expr: { kind: "jsOp", op: "setIdx", args: [recv, key, value], type: VOID, loc }, loc };
4784
5326
  }
4785
5327
  const index = lowerer.lowerExpr(target.argumentExpression);
5328
+ if (index.type.kind === "dyn" && recv.type.kind === "bytes") {
5329
+ return { kind: "exprStmt", expr: lowerDynMemberAssignment(lowerer, expr, lowerer.coerceInto(target.expression, recv, DYN)), loc: locOf(expr) };
5330
+ }
4786
5331
  if (index.type.kind !== "f64") {
4787
5332
  lowerer.unsupported("SC1090", target.argumentExpression, "indexing with non-number keys");
4788
5333
  }
@@ -4798,6 +5343,9 @@ export function lowerElementWrite(lowerer, expr) {
4798
5343
  // a recordSet/recordKeySet over a jsval.
4799
5344
  if (shape) {
4800
5345
  const obj = lowerer.lowerExpr(target.expression);
5346
+ if (hasClassPayload(lowerer, obj.type)) {
5347
+ lowerer.unsupported("SC1090", target, "computed property writes through structural views of class instances (use a declared literal key)");
5348
+ }
4801
5349
  if (obj.type.kind === "jsval") {
4802
5350
  const key = lowerer.jsvalIn(lowerer.lowerExpr(target.argumentExpression), target.argumentExpression);
4803
5351
  const value = lowerer.jsvalIn(lowerer.lowerExpr(expr.right), expr.right);
@@ -4949,9 +5497,9 @@ export function lowerElementWrite(lowerer, expr) {
4949
5497
  // type (static `any` — mapTypeOf answers null without --dynamic)
4950
5498
  // probes the receiver's own lowered world: a dyn value takes the same
4951
5499
  // write, anything else falls through to the fences.
4952
- if (receiverIr?.kind === "dyn" || receiverIr === null) {
5500
+ if (receiverIr?.kind === "dyn" || receiverIr?.kind === "object" || receiverIr === null) {
4953
5501
  const obj = receiverIr !== null ? lowerer.lowerExpr(target.expression) : tryLowerExpression(lowerer, target.expression);
4954
- if (obj !== null && obj.type.kind === "dyn") {
5502
+ if (obj !== null && (obj.type.kind === "dyn" || isDynTypedRefType(obj.type))) {
4955
5503
  const loc = locOf(expr);
4956
5504
  const litKey = recordKeyLiteralText(target.argumentExpression);
4957
5505
  let key = litKey !== null
@@ -4965,7 +5513,7 @@ export function lowerElementWrite(lowerer, expr) {
4965
5513
  if (value.type.kind !== "dyn") {
4966
5514
  lowerer.unsupported("SC1101", expr.right, `storing '${lowerer.fmt(value.type)}' values in a checked-dynamic object (the value cannot convert into the checked-dynamic tree)`);
4967
5515
  }
4968
- return { kind: "exprStmt", expr: { kind: "libCall", fn: "dyn.keySet", args: [obj, key, value], type: VOID, loc }, loc };
5516
+ return { kind: "exprStmt", expr: { kind: "libCall", fn: "dyn.keySet", args: [lowerer.coerceToExpected(obj, DYN), key, value], type: VOID, loc }, loc };
4969
5517
  }
4970
5518
  }
4971
5519
  if (receiverIr?.kind !== "array") {
@@ -4995,19 +5543,42 @@ export function lowerElementWrite(lowerer, expr) {
4995
5543
  if (arr.type.kind !== "array") {
4996
5544
  lowerer.unsupported("SC1090", target.expression, "assignment through a possibly missing nested array receiver");
4997
5545
  }
4998
- const index = lowerer.lowerExpr(target.argumentExpression);
5546
+ const index = lowerOptionalNumber(lowerer, lowerer.lowerExpr(target.argumentExpression), locOf(target.argumentExpression), target.argumentExpression);
4999
5547
  if (index.type.kind !== "f64") {
5000
5548
  lowerer.unsupported("SC1090", target.argumentExpression, "indexing with non-number keys");
5001
5549
  }
5002
5550
  // Optional array reads carry the source array's payload type plus an
5003
5551
  // undefined arm. arrayValueStore turns that arm into the runtime's
5004
5552
  // UNDEFINED state while keeping number[]/string[] payload storage scalar.
5005
- const value = lowerer.lowerExpr(expr.right);
5006
- return arrayValueStore(lowerer, arr, index, value, receiverIr.elem, locOf(expr));
5553
+ let literal = expr.right;
5554
+ while (ts.isParenthesizedExpression(literal) || ts.isSatisfiesExpression(literal))
5555
+ literal = literal.expression;
5556
+ const value = ts.isObjectLiteralExpression(literal) || ts.isArrayLiteralExpression(literal)
5557
+ ? lowerer.lowerExprExpecting(literal, arr.type.elem) : lowerer.lowerExpr(expr.right);
5558
+ // Validate the payload before emitting a native store. A dormant JS
5559
+ // method can contradict its JSDoc element type; retain its call-time
5560
+ // refusal instead of leaving invalid IR in a newly reachable method.
5561
+ const expected = value.type.kind === "undefinedT" ||
5562
+ value.type.kind === "union" && lowerer.armTag(value.type.unionId, UNDEFINED_T) >= 0
5563
+ ? arrayValueType(lowerer, arr.type.elem) : arr.type.elem;
5564
+ return arrayValueStore(lowerer, arr, index, lowerer.coerceInto(expr.right, value, expected), arr.type.elem, locOf(expr));
5565
+ }
5566
+ /** Environment property names use ToPrimitive with the string hint, so
5567
+ * checked objects must execute their own conversion hooks. */
5568
+ export function lowerEnvironmentKey(lowerer, node) {
5569
+ const key = lowerer.lowerExpr(node);
5570
+ if (key.type.kind === "dyn")
5571
+ return { kind: "libCall", fn: "dyn.toStringCoerce", args: [key], type: STRING, loc: key.loc };
5572
+ return lowerRecordPropertyKey(lowerer, key, node);
5007
5573
  }
5008
5574
  export function ensureString(lowerer, e, node) {
5009
5575
  if (e.type.kind === "string")
5010
5576
  return e;
5577
+ if (e.type.kind === "url")
5578
+ return { kind: "libCall", fn: "url.href", args: [e], type: STRING, loc: e.loc };
5579
+ if (e.type.kind === "regex") {
5580
+ return { kind: "regexIntrinsic", method: "toString", receiver: e, args: [], type: STRING, loc: e.loc };
5581
+ }
5011
5582
  if (e.type.kind === "bigint") {
5012
5583
  return {
5013
5584
  kind: "libCall",
@@ -5023,18 +5594,21 @@ export function ensureString(lowerer, e, node) {
5023
5594
  // strings verbatim, arrays via join, objects as "[object Object]").
5024
5595
  return { kind: "toString", operand: e, type: STRING, loc: e.loc };
5025
5596
  }
5597
+ if (e.type.kind === "bytes") {
5598
+ // Preserve the runtime brand: typed arrays join numeric elements,
5599
+ // while a Buffer stored in a Uint8Array slot decodes its bytes.
5600
+ const boxed = { kind: "dynFrom", value: e, type: DYN, loc: e.loc };
5601
+ return { kind: "toString", operand: boxed, type: STRING, loc: e.loc };
5602
+ }
5026
5603
  if (e.type.kind === "jsval") {
5027
5604
  // String(v) in the engine — JS-exact (and Node-exact in templates).
5028
5605
  return { kind: "jsOp", op: "toStr", args: [e], type: STRING, loc: e.loc };
5029
5606
  }
5030
5607
  if (e.type.kind === "object") {
5031
- // String(err) / `${err}` over the Error hierarchy: Error.prototype
5032
- // .toString — the ONE runtime implementation (overriding it is
5033
- // fenced), "name: message" with the empty-side elisions, exactly
5034
- // Node's String(err), which carries no stack either.
5608
+ // String(err) / `${err}` uses the same dispatch as err.toString().
5035
5609
  for (let c = lowerer.classes.get(e.type.className) ?? null; c; c = c.base) {
5036
5610
  if (c.builtinError) {
5037
- return { kind: "libCall", fn: "error.toString", args: [lowerer.upcastTo(e, "%Error")], type: STRING, loc: e.loc };
5611
+ return errorToStringCall(lowerer, e);
5038
5612
  }
5039
5613
  }
5040
5614
  }
@@ -5225,6 +5799,38 @@ export function lowerAsExpression(lowerer, expr) {
5225
5799
  return lowerer.jsvalIn(inner, expr.expression);
5226
5800
  }
5227
5801
  const target = lowerer.mapTypeOf(targetTs0);
5802
+ // Unknown-only record views use the checked-dynamic representation.
5803
+ // A native record asserted to such a view still denotes the original
5804
+ // object. Box a live reference instead of erasing the assertion and
5805
+ // handing native storage to a checked-dynamic property operation.
5806
+ if (target?.kind === "dyn" && (targetTs0.flags & ts.TypeFlags.Object) !== 0) {
5807
+ const sourceShape = inner.type.kind === "record" ? lowerer.shapes.get(inner.type.shapeId) : undefined;
5808
+ if (hasClassPayload(lowerer, inner.type) || sourceShape && shapeHasAccessorSlots(sourceShape)) {
5809
+ lowerer.unsupported("SC1090", expr, "opaque structural views of classes or accessor records (name the property's concrete type to retain native accessor dispatch)");
5810
+ }
5811
+ const boxed = lowerer.coerceInto(expr, inner, DYN);
5812
+ return boxed.kind === "dynFrom" ? { ...boxed, liveRef: true } : boxed;
5813
+ }
5814
+ // Collection assertions are interface views only: their backing
5815
+ // value must already have the same native key/element/value layout.
5816
+ // Erasing a mock or a different instantiation here would let member
5817
+ // lowering interpret a record (or another ABI) as a native handle.
5818
+ if (target?.kind === "map" || target?.kind === "set") {
5819
+ return lowerer.coerceInto(expr, inner, target);
5820
+ }
5821
+ // A class-derived interface still names the class's native layout.
5822
+ // An assertion must not let a structural mock acquire that layout;
5823
+ // unions retain their checked extraction below, and real instances
5824
+ // keep the ordinary class narrowing path.
5825
+ if (target?.kind === "object" && inner.type.kind !== "object" && inner.type.kind !== "union") {
5826
+ return lowerer.coerceInto(expr, inner, target);
5827
+ }
5828
+ if (target?.kind === "object" && inner.type.kind === "object" &&
5829
+ !typeEquals(target, inner.type) &&
5830
+ !lowerer.isSubclassOf(inner.type.className, target.className) &&
5831
+ !lowerer.isSubclassOf(target.className, inner.type.className)) {
5832
+ return lowerer.coerceInto(expr, inner, target);
5833
+ }
5228
5834
  // Static assertions normally erase, but record layouts are
5229
5835
  // monomorphic: a consumer selected from the asserted shape must see
5230
5836
  // that shape physically. Reuse the ordinary slot coercion so plain
@@ -5277,7 +5883,21 @@ export function lowerAsExpression(lowerer, expr) {
5277
5883
  lowerer.badType(expr.type, targetTs);
5278
5884
  if (target.kind === "dyn")
5279
5885
  return inner; // `as unknown`: erasure
5280
- if (target.kind === "void" || !lowerer.jsonSafe(target)) {
5886
+ // An all-`unknown`-fields record target (`err as { code?: unknown }`
5887
+ // — the errno-probing idiom): there is nothing to validate (every
5888
+ // field is unknown, exactly what the dyn value already answers) and
5889
+ // nothing to build — the cast is pure typing, so it ERASES and the
5890
+ // reads ride the dyn keyed read.
5891
+ if (target.kind === "record") {
5892
+ const shape = lowerer.shapes.get(target.shapeId);
5893
+ if (shape &&
5894
+ !shape.tuple &&
5895
+ shape.fields.every((f) => f.type.kind === "dyn") &&
5896
+ (!shape.indexValue || shape.indexValue.kind === "dyn")) {
5897
+ return inner;
5898
+ }
5899
+ }
5900
+ if (target.kind === "void" || !canDynCheckTo(target, (id) => lowerer.shapes.get(id), (id) => lowerer.unions.get(id))) {
5281
5901
  // Bare undefined-armed targets pass when every OTHER arm is
5282
5902
  // JSON-safe: the checked-dynamic tree holds a first-class undefined value now
5283
5903
  // (index-signature overflow reads produce it for missing keys), and
@@ -5313,20 +5933,6 @@ export function lowerAsExpression(lowerer, expr) {
5313
5933
  if (DYN_HANDLE_KINDS.has(target.kind)) {
5314
5934
  return { kind: "dynCheck", value: inner, type: target, loc: locOf(expr) };
5315
5935
  }
5316
- // An all-`unknown`-fields record target (`err as { code?: unknown }`
5317
- // — the errno-probing idiom): there is nothing to validate (every
5318
- // field is unknown, exactly what the dyn value already answers) and
5319
- // nothing to build — the cast is pure typing, so it ERASES and the
5320
- // reads ride the dyn keyed read.
5321
- if (target.kind === "record") {
5322
- const shape = lowerer.shapes.get(target.shapeId);
5323
- if (shape &&
5324
- !shape.tuple &&
5325
- shape.fields.every((f) => f.type.kind === "dyn") &&
5326
- (!shape.indexValue || shape.indexValue.kind === "dyn")) {
5327
- return inner;
5328
- }
5329
- }
5330
5936
  lowerer.unsupported("SC1090", expr, `a checked cast of 'unknown' to '${target.kind === "void" ? "void" : lowerer.fmt(target)}' ` +
5331
5937
  `(a dynamic value can only be validated against JSON-representable types: ` +
5332
5938
  `number, string, boolean, records, arrays, and unions of those)`);
@@ -5344,7 +5950,10 @@ export function lowerPrefixUnary(lowerer, expr) {
5344
5950
  if (raw.type.kind === "bigint") {
5345
5951
  return { kind: "libCall", fn: "bigint.neg", args: [raw], type: BIGINT_T, loc };
5346
5952
  }
5347
- const operand = lowerOptionalNumber(lowerer, raw, loc, expr.operand);
5953
+ // Match the checked numeric boundary used by JS binary arithmetic.
5954
+ const operand = raw.type.kind === "dyn" && isJsSourceFile(expr.getSourceFile())
5955
+ ? { kind: "dynCheck", value: raw, type: F64, loc }
5956
+ : lowerOptionalNumber(lowerer, raw, loc, expr.operand);
5348
5957
  if (operand.type.kind !== "f64")
5349
5958
  lowerer.unsupported("SC1043", expr);
5350
5959
  if (operand.kind === "numLit")
@@ -5353,6 +5962,9 @@ export function lowerPrefixUnary(lowerer, expr) {
5353
5962
  }
5354
5963
  case ts.SyntaxKind.PlusToken: {
5355
5964
  const raw = lowerer.lowerExpr(expr.operand);
5965
+ if (raw.type.kind === "dyn") {
5966
+ return { kind: "libCall", fn: "dyn.toNumberCoerce", args: [raw], type: F64, loc };
5967
+ }
5356
5968
  // Unary + is ToNumber; on an already-number operand it's identity,
5357
5969
  // and a STRING operand runs the runtime's ECMA-exact StringToNumber
5358
5970
  // (num.fromString — Number(aString)'s lowering, scr_string.c).
@@ -5378,14 +5990,16 @@ export function lowerPrefixUnary(lowerer, expr) {
5378
5990
  if (raw.type.kind === "bigint") {
5379
5991
  return { kind: "libCall", fn: "bigint.not", args: [raw], type: BIGINT_T, loc };
5380
5992
  }
5993
+ if (raw.type.kind === "dyn")
5994
+ return {
5995
+ kind: "libCall", fn: "dyn.bitwise", args: [raw, dynUndefinedExpr(loc), { kind: "strLit", value: "~", type: STRING, loc }], type: DYN, loc,
5996
+ };
5381
5997
  // A checked-dynamic value retains enough JavaScript structure to
5382
5998
  // run exact ToNumber, including the object valueOf/toString
5383
5999
  // protocol. This is the static `any`/JS-lane answer for idioms
5384
6000
  // such as `while (~index)`, where inference may have widened an
5385
6001
  // otherwise numeric slot to the checked-dynamic tree.
5386
- const operand = raw.type.kind === "dyn"
5387
- ? { kind: "libCall", fn: "dyn.toNumberCoerce", args: [raw], type: F64, loc }
5388
- : lowerOptionalNumber(lowerer, raw, loc, expr.operand);
6002
+ const operand = lowerOptionalNumber(lowerer, raw, loc, expr.operand);
5389
6003
  if (operand.type.kind !== "f64")
5390
6004
  lowerer.unsupported("SC1043", expr);
5391
6005
  return { kind: "unary", op: "~", operand, type: F64, loc };
@@ -5395,7 +6009,7 @@ export function lowerPrefixUnary(lowerer, expr) {
5395
6009
  // `++x` / `--x` in expression position: yields the NEW value.
5396
6010
  return lowerIncDec(lowerer, expr, true);
5397
6011
  }
5398
- lowerer.unsupported("SC1090", expr, `syntax '${ts.SyntaxKind[expr.kind]}'`);
6012
+ lowerer.unsupported("SC1090", expr, `syntax '${ts.syntaxKindName(expr.kind)}'`);
5399
6013
  }
5400
6014
  /** `x++`/`x--`/`++x`/`--x` in EXPRESSION position — the incDec node:
5401
6015
  * read, write ±1, yield old (postfix) or new (prefix) value. Receivers
@@ -5407,6 +6021,14 @@ function lowerIncDec(lowerer, expr, prefix) {
5407
6021
  const loc = locOf(expr);
5408
6022
  const op = expr.operator === ts.SyntaxKind.PlusPlusToken ? "+" : "-";
5409
6023
  if (!ts.isIdentifier(expr.operand)) {
6024
+ if (ts.isPropertyAccessExpression(expr.operand)) {
6025
+ const target = staticFieldWriteTarget(lowerer, expr.operand);
6026
+ if (target) {
6027
+ if (target.type.kind !== "f64")
6028
+ lowerer.unsupported("SC1043", expr);
6029
+ return { kind: "incDec", op, prefix, localId: target.id, type: F64, loc };
6030
+ }
6031
+ }
5410
6032
  // CLASS-FIELD receivers (`if (--this.limit === 0)` — countdown.js's
5411
6033
  // dec(); `--this[kLimit]` — its symbol-keyed spelling): a single-
5412
6034
  // evaluation read-modify-write over the instance, yielding old/new
@@ -5447,6 +6069,16 @@ function lowerIncDec(lowerer, expr, prefix) {
5447
6069
  if (!target) {
5448
6070
  lowerer.rejectUnresolved(expr.operand, `increment/decrement of '${expr.operand.text}' (not a writable local or module global)`);
5449
6071
  }
6072
+ if (target.type.kind === "dyn") {
6073
+ const old = lowerer.declareHiddenLocal("%numericOld", DYN);
6074
+ const next = lowerer.declareHiddenLocal("%numericNext", DYN);
6075
+ const read = lowerer.lowerExpr(expr.operand);
6076
+ return { kind: "seqExpr", stmts: [
6077
+ { kind: "varDecl", localId: old.id, init: { kind: "libCall", fn: "dyn.toNumeric", args: [read], type: DYN, loc }, loc },
6078
+ { kind: "varDecl", localId: next.id, init: { kind: "libCall", fn: "dyn.increment", args: [varRef(old.id, DYN, loc), { kind: "boolLit", value: op === "+", type: BOOL, loc }], type: DYN, loc }, loc },
6079
+ { kind: "assign", localId: target.id, value: varRef(next.id, DYN, loc), loc },
6080
+ ], result: varRef(prefix ? next.id : old.id, DYN, loc), type: DYN, loc };
6081
+ }
5450
6082
  if (target.type.kind !== "f64")
5451
6083
  lowerer.unsupported("SC1043", expr);
5452
6084
  return { kind: "incDec", op, prefix, localId: target.id, type: F64, loc };
@@ -5498,7 +6130,7 @@ function lowerAnyBinaryInIsland(lowerer, expr, left, right, loc) {
5498
6130
  };
5499
6131
  const jop = JS_BIN[op];
5500
6132
  if (jop === undefined) {
5501
- lowerer.unsupported("SC1090", expr, `operator '${ts.tokenToString(op) ?? ts.SyntaxKind[op]}' on 'any' values`);
6133
+ lowerer.unsupported("SC1090", expr, `operator '${ts.tokenToString(op) ?? ts.syntaxKindName(op)}' on 'any' values`);
5502
6134
  }
5503
6135
  const type = jsOpResultKind(jop) === "bool" ? BOOL : JSVAL;
5504
6136
  return {
@@ -5507,6 +6139,39 @@ function lowerAnyBinaryInIsland(lowerer, expr, left, right, loc) {
5507
6139
  type, loc,
5508
6140
  };
5509
6141
  }
6142
+ export function lowerCompoundValueToTarget(lowerer, expr, compound, target) {
6143
+ const loc = locOf(expr);
6144
+ const read = { kind: "varRef", localId: target.id, type: target.type, loc: locOf(expr.left) };
6145
+ const rhs = lowerer.lowerExpr(expr.right);
6146
+ const numericRhs = lowerOptionalNumber(lowerer, rhs, loc);
6147
+ if (target.type.kind === "jsval" || rhs.type.kind === "jsval") {
6148
+ const JS_COMPOUND = { "+": "add", "-": "sub", "*": "mul", "/": "div", "%": "mod", "**": "pow" };
6149
+ const jop = JS_COMPOUND[compound];
6150
+ if (jop === undefined)
6151
+ lowerer.unsupported("SC1043", expr);
6152
+ const wrapped = {
6153
+ kind: "jsOp", op: jop,
6154
+ args: [lowerer.jsvalIn(read, expr.left), lowerer.jsvalIn(rhs, expr.right)],
6155
+ type: JSVAL, loc,
6156
+ };
6157
+ return lowerer.coerceInto(expr, wrapped, target.type);
6158
+ }
6159
+ if (compound === "+" && target.type.kind === "string") {
6160
+ return { kind: "strConcat", left: read, right: lowerer.ensureString(rhs, expr.right), type: STRING, loc };
6161
+ }
6162
+ if (target.type.kind === "f64" && numericRhs.type.kind === "f64") {
6163
+ return { kind: "bin", op: compound, left: read, right: numericRhs, type: F64, loc };
6164
+ }
6165
+ if ((target.type.kind === "dyn" || rhs.type.kind === "dyn") &&
6166
+ (target.type.kind === "dyn" || target.type.kind === "f64") &&
6167
+ (rhs.type.kind === "dyn" || rhs.type.kind === "f64") &&
6168
+ isJsSourceFile(expr.getSourceFile())) {
6169
+ const checkNum = (value) => value.type.kind === "dyn" ? { kind: "dynCheck", value, type: F64, loc: value.loc } : value;
6170
+ const computed = { kind: "bin", op: compound, left: checkNum(read), right: checkNum(rhs), type: F64, loc };
6171
+ return target.type.kind === "dyn" ? { kind: "dynFrom", value: computed, type: DYN, loc } : computed;
6172
+ }
6173
+ lowerer.unsupported("SC1043", expr);
6174
+ }
5510
6175
  export function lowerBinary(lowerer, expr) {
5511
6176
  const loc = locOf(expr);
5512
6177
  const op = expr.operatorToken.kind;
@@ -5514,6 +6179,15 @@ export function lowerBinary(lowerer, expr) {
5514
6179
  if (cacheHas)
5515
6180
  return cacheHas;
5516
6181
  if (op === ts.SyntaxKind.EqualsToken || (op >= ts.SyntaxKind.FirstCompoundAssignment && op <= ts.SyntaxKind.LastCompoundAssignment)) {
6182
+ if (op === ts.SyntaxKind.EqualsToken) {
6183
+ const callableWrite = lowerNativeFunctionAssignment(lowerer, expr);
6184
+ if (callableWrite)
6185
+ return callableWrite;
6186
+ }
6187
+ if (op === ts.SyntaxKind.QuestionQuestionEqualsToken ||
6188
+ op === ts.SyntaxKind.AmpersandAmpersandEqualsToken || op === ts.SyntaxKind.BarBarEqualsToken) {
6189
+ return lowerShortCircuitAssignment(lowerer, expr);
6190
+ }
5517
6191
  if (ts.isPropertyAccessExpression(expr.left) || ts.isElementAccessExpression(expr.left)) {
5518
6192
  fenceNodeModuleMutation(lowerer, expr.left, "assignment");
5519
6193
  }
@@ -5539,8 +6213,29 @@ export function lowerBinary(lowerer, expr) {
5539
6213
  }
5540
6214
  const indexedCompound = COMPOUND_ASSIGN_OPS[op];
5541
6215
  if (indexedCompound !== undefined && ts.isElementAccessExpression(expr.left)) {
6216
+ if (symbolFieldInfo(lowerer, expr.left))
6217
+ return lowerFieldCompoundValue(lowerer, expr.left, indexedCompound, expr.right, loc);
5542
6218
  return lowerElementCompound(lowerer, expr, indexedCompound);
5543
6219
  }
6220
+ if (indexedCompound !== undefined && ts.isIdentifier(expr.left)) {
6221
+ const target = lowerer.resolveWritable(expr.left);
6222
+ if (!target) {
6223
+ lowerer.rejectUnresolved(expr.left, "assignment to an unresolved variable");
6224
+ }
6225
+ const value = lowerCompoundValueToTarget(lowerer, expr, indexedCompound, target);
6226
+ return { kind: "assignExpr", localId: target.id, value, type: target.type, loc };
6227
+ }
6228
+ if (indexedCompound !== undefined &&
6229
+ (ts.isPropertyAccessExpression(expr.left) || ts.isElementAccessExpression(expr.left)) &&
6230
+ !(ts.isPropertyAccessExpression(expr.left) && expr.left.questionDotToken)) {
6231
+ const target = expandoWritableTarget(lowerer, expr.left) ??
6232
+ (ts.isPropertyAccessExpression(expr.left) ? nsWritableTarget(lowerer, expr.left) : null);
6233
+ if (target) {
6234
+ const value = lowerCompoundValueToTarget(lowerer, expr, indexedCompound, target);
6235
+ return { kind: "assignExpr", localId: target.id, value, type: target.type, loc };
6236
+ }
6237
+ return lowerFieldCompoundValue(lowerer, expr.left, indexedCompound, expr.right, loc);
6238
+ }
5544
6239
  // `events.defaultMaxListeners = v` — the module-property write
5545
6240
  // Node validates (validateNumber(n, 'defaultMaxListeners', 0)):
5546
6241
  // the value crosses into the checked-dynamic tree and the runtime ladder throws
@@ -5593,6 +6288,15 @@ export function lowerBinary(lowerer, expr) {
5593
6288
  const value = lowerer.lowerExprExpecting(expr.right, target.type);
5594
6289
  return { kind: "assignExpr", localId: target.id, value, type: target.type, loc };
5595
6290
  }
6291
+ // Bundled numeric enums use e[e["name"] = value] = "name".
6292
+ // Capture the receiver and key before the RHS. PutValue coerces the
6293
+ // key afterward, then yields the RHS without rereading the property.
6294
+ if (ts.isElementAccessExpression(expr.left) && !expr.left.questionDotToken) {
6295
+ const recv = tryLowerExpression(lowerer, expr.left.expression);
6296
+ if (recv?.type.kind === "dyn") {
6297
+ return lowerDynMemberAssignment(lowerer, expr, recv);
6298
+ }
6299
+ }
5596
6300
  // `h.k = v` on an ISLAND receiver in VALUE position: the engine
5597
6301
  // property write (setProp throws the engine's TypeErrors on
5598
6302
  // nullish receivers, bridged catchably), the RHS value threaded
@@ -5659,6 +6363,22 @@ export function lowerBinary(lowerer, expr) {
5659
6363
  loc,
5660
6364
  };
5661
6365
  }
6366
+ const field = lowerer.fieldTarget(expr.left);
6367
+ if (field) {
6368
+ const recvTmp = lowerer.declareHiddenLocal("%setReceiver", field.obj.type);
6369
+ const receiver = field.obj;
6370
+ field.obj = varRef(recvTmp.id, receiver.type, loc);
6371
+ const value = lowerer.lowerExprExpecting(expr.right, field.fieldType);
6372
+ const valTmp = lowerer.declareHiddenLocal("%setValue", value.type);
6373
+ const result = varRef(valTmp.id, value.type, loc);
6374
+ return {
6375
+ kind: "seqExpr", stmts: [
6376
+ { kind: "varDecl", localId: recvTmp.id, init: receiver, loc },
6377
+ { kind: "varDecl", localId: valTmp.id, init: value, loc },
6378
+ lowerer.fieldSetStmt(field, result, loc, expr.left),
6379
+ ], result, type: result.type, loc,
6380
+ };
6381
+ }
5662
6382
  }
5663
6383
  }
5664
6384
  // Destructuring assignment in VALUE position (`(() => [i] = [i+1])()`,
@@ -5685,221 +6405,7 @@ export function lowerBinary(lowerer, expr) {
5685
6405
  lowerer.unsupported("SC1040", expr, undefined, "object-to-primitive loose equality can call user-defined valueOf/toString methods — compare explicit primitive conversions instead");
5686
6406
  }
5687
6407
  if (op === ts.SyntaxKind.AmpersandAmpersandToken || op === ts.SyntaxKind.BarBarToken) {
5688
- if (op === ts.SyntaxKind.BarBarToken) {
5689
- const fallback = lowerAbsenceAwareOrChain(lowerer, expr);
5690
- if (fallback)
5691
- return fallback;
5692
- }
5693
- // JS value semantics: `a && b` is `toBool(a) ? b : a` — the result is
5694
- // an operand value, so both operands must share one IR kind (which is
5695
- // also what tsc's type says, modulo literal-union collapsing that
5696
- // mapType already handles). Mixed kinds (`n && s`) stay rejected.
5697
- const left = lowerAbsenceProbe(lowerer, expr.left) ?? lowerer.lowerExpr(expr.left);
5698
- const right = lowerer.lowerExpr(expr.right);
5699
- // A LITERAL-unit left operand (a compile-time undefined/null — the
5700
- // capability-probe members: `process.features.inspector ||
5701
- // !flag.startsWith('--inspect')` reads undefined in a compiled
5702
- // binary): statically falsy, so the operator folds JS-exactly —
5703
- // `unit || X` IS X, `unit && X` IS the unit (X's side effects never
5704
- // run in JS either; the lowered right is simply dropped). Literal
5705
- // units only — computed unit-typed values keep the fences below.
5706
- if (left.kind === "unitLit") {
5707
- return op === ts.SyntaxKind.BarBarToken ? right : left;
5708
- }
5709
- // JS objects and symbols are always truthy. In `ref || fallback`
5710
- // the fallback is therefore unreachable and the result is the
5711
- // already-evaluated left value — the browser-fallback idiom used by
5712
- // packages for `process.argv || []` and `process.env || {}`. The
5713
- // operand itself remains in the IR, preserving reads/calls that
5714
- // produce the reference; only the unreachable right lowering drops.
5715
- if (op === ts.SyntaxKind.BarBarToken && REF_TRUTHY_KINDS.has(left.type.kind)) {
5716
- return left;
5717
- }
5718
- if (left.type.kind === "dyn" || right.type.kind === "dyn") {
5719
- // A checked-dynamic operand (`fn.name || '<anonymous>'` —
5720
- // test/common's _mustCallInner): both sides live in the checked-dynamic tree and
5721
- // the deciding test is ToBoolean over the dyn kind
5722
- // (scr_dyn_truthy) — JS value semantics exactly, result dyn. The
5723
- // non-dyn side converts through the usual boundary; a value with
5724
- // no dyn representation keeps the fence.
5725
- const l = lowerer.coerceToExpected(left, DYN);
5726
- const r = lowerer.coerceToExpected(right, DYN);
5727
- if (l.type.kind !== "dyn" || r.type.kind !== "dyn") {
5728
- lowerer.unsupported("SC1100", expr, "logical operators on 'unknown' values");
5729
- }
5730
- return {
5731
- kind: "logical",
5732
- op: op === ts.SyntaxKind.AmpersandAmpersandToken ? "&&" : "||",
5733
- left: l,
5734
- right: r,
5735
- type: DYN, loc,
5736
- };
5737
- }
5738
- if (left.type.kind === "jsval" || right.type.kind === "jsval") {
5739
- return {
5740
- kind: "logical",
5741
- op: op === ts.SyntaxKind.AmpersandAmpersandToken ? "&&" : "||",
5742
- left: lowerer.jsvalIn(left, expr.left),
5743
- right: lowerer.jsvalIn(right, expr.right),
5744
- type: JSVAL, loc,
5745
- };
5746
- }
5747
- if (left.type.kind === "union" || right.type.kind === "union") {
5748
- // JS value semantics over a union: the deciding test is the ARM
5749
- // value's ToBoolean (a per-union helper), and the result is the
5750
- // deciding operand. Supported when the checker's type of the whole
5751
- // expression maps to ONE union both operands coerce into (same
5752
- // union passes through; a plain arm wraps — `u || undefined`).
5753
- // Everything else (`u && flag`, whose value would need a wider
5754
- // re-tagged union) stays fenced — in CONDITION position those
5755
- // shapes lower through lowerCondition's bool descent instead.
5756
- let target = lowerer.mapTypeOf(lowerer.typeOf(expr));
5757
- // A broad-JSDoc npm-static body can leave the CHECKER result `any`
5758
- // even after declaration-backed specialization has recovered both
5759
- // operands (`helpOption && args.find(...)` in commander). When the
5760
- // left consists only of always-truthy references and falsy units,
5761
- // the static result is exactly the right operand plus those units.
5762
- if (target === null &&
5763
- lowerer.implicitParamTypes !== null &&
5764
- npmStaticPackageOfPath(expr.getSourceFile().fileName) !== null &&
5765
- (lowerer.typeOf(expr).flags & ts.TypeFlags.Any) !== 0 &&
5766
- left.type.kind === "union") {
5767
- const leftArms = lowerer.unions.get(left.type.unionId)?.arms;
5768
- const rightArms = right.type.kind === "union"
5769
- ? lowerer.unions.get(right.type.unionId)?.arms
5770
- : [right.type];
5771
- const rightIsStatic = rightArms?.every((arm) => arm.kind !== "dyn" && arm.kind !== "caught" && arm.kind !== "jsval" && arm.kind !== "void") ?? false;
5772
- if (leftArms && rightArms && rightIsStatic &&
5773
- leftArms.some(isUnitType) &&
5774
- leftArms.every((arm) => isUnitType(arm) || REF_TRUTHY_KINDS.has(arm.kind))) {
5775
- const byKey = new Map();
5776
- for (const arm of [...rightArms, ...leftArms.filter(isUnitType)]) {
5777
- byKey.set(typeKey(arm), arm);
5778
- }
5779
- const arms = [...byKey.values()].sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1));
5780
- if (arms.length > 1) {
5781
- target = { kind: "union", unionId: lowerer.unions.intern(arms) };
5782
- }
5783
- }
5784
- }
5785
- if (target?.kind === "union") {
5786
- // `&&` whose left does NOT fit the result union: the checker
5787
- // built that result by DROPPING left arms which are always
5788
- // truthy (`Option | undefined && string | undefined` answers
5789
- // `string | undefined`, not `Option | string | undefined`).
5790
- // Evaluate the left once, test it in its own union, and retag it
5791
- // only on the falsy path. Any stranded reference arms are
5792
- // unreachable there because every JS object is truthy.
5793
- if (op === ts.SyntaxKind.AmpersandAmpersandToken && left.type.kind === "union" && !typeEquals(left.type, target)) {
5794
- const def = lowerer.unions.get(left.type.unionId);
5795
- const trappable = new Set();
5796
- def?.arms.forEach((arm, tag) => {
5797
- if (lowerer.armTag(target.unionId, arm) < 0 && REF_TRUTHY_KINDS.has(arm.kind)) {
5798
- trappable.add(tag);
5799
- }
5800
- });
5801
- const retag = trappable.size === 0
5802
- ? null
5803
- : lowerer.unionRetagHelper(left.type.unionId, target.unionId, loc, trappable);
5804
- if (retag) {
5805
- lowerer.requireTruthyUnion(left.type.unionId, expr);
5806
- const stmts = [];
5807
- let stable = left;
5808
- if (!isSafeToRepeat(left)) {
5809
- const local = lowerer.declareHiddenLocal("%and", left.type);
5810
- stmts.push({ kind: "varDecl", localId: local.id, init: left, loc });
5811
- stable = varRef(local.id, left.type, loc);
5812
- }
5813
- const result = {
5814
- kind: "ternary",
5815
- cond: lowerer.ensureBool(stable, expr.left),
5816
- then: lowerer.coerceInto(expr.right, right, target),
5817
- else_: { kind: "call", callee: retag, args: [stable], type: target, loc },
5818
- type: target,
5819
- loc,
5820
- };
5821
- return stmts.length === 0 ? result : { kind: "seqExpr", stmts, result, type: target, loc };
5822
- }
5823
- }
5824
- // `||` whose left does NOT fit the result union: the checker built
5825
- // that result by DROPPING the left's falsy arms (`process.env.X ||
5826
- // null` is `string | null`, `|| 3000` is `string | number` — the
5827
- // `undefined` is gone from both), so coercing the left eagerly, as
5828
- // the shared shape below does, retags an arm the test is about to
5829
- // rule out and throws where Node yields the default. Single-eval
5830
- // instead: test the left in its OWN union and retag only on the
5831
- // truthy side, where the dropped arms are unreachable.
5832
- if (op === ts.SyntaxKind.BarBarToken && left.type.kind === "union" && !typeEquals(left.type, target)) {
5833
- const retag = lowerer.unionRetagHelper(left.type.unionId, target.unionId, loc);
5834
- if (retag) {
5835
- lowerer.requireTruthyUnion(left.type.unionId, expr);
5836
- return {
5837
- kind: "orDefault",
5838
- left,
5839
- right: lowerer.coerceInto(expr.right, right, target),
5840
- retag,
5841
- type: target,
5842
- loc,
5843
- };
5844
- }
5845
- }
5846
- lowerer.requireTruthyUnion(target.unionId, expr);
5847
- return {
5848
- kind: "logical",
5849
- op: op === ts.SyntaxKind.AmpersandAmpersandToken ? "&&" : "||",
5850
- left: lowerer.coerceInto(expr.left, left, target),
5851
- right: lowerer.coerceInto(expr.right, right, target),
5852
- type: target, loc,
5853
- };
5854
- }
5855
- // `u || d` NARROWED by the default: the checker types the result
5856
- // as u's single non-unit arm (`marker() || "default"`) — evaluate
5857
- // u once, truthy extracts the arm, falsy takes d lazily (the
5858
- // orDefault node, nullish's truthiness sibling). An UNMAPPABLE
5859
- // checker result takes the same rule (`options.runner ||
5860
- // defaultRunner` — tsc's union of two structurally-compatible
5861
- // function types has no representation, while the left's one
5862
- // non-unit arm is the only representable answer): the default
5863
- // must coerce into the arm or fence on its own.
5864
- if (op === ts.SyntaxKind.BarBarToken &&
5865
- left.type.kind === "union") {
5866
- const def = lowerer.unions.get(left.type.unionId);
5867
- const rest = def ? def.arms.filter((a) => !isUnitType(a)) : [];
5868
- const funcArmDefault = rest[0]?.kind === "func" && (target === null || target.kind === "func");
5869
- if (rest.length === 1 && ((target !== null && typeEquals(target, rest[0])) || funcArmDefault)) {
5870
- lowerer.requireTruthyUnion(left.type.unionId, expr);
5871
- const dflt = lowerer.lowerExprExpecting(expr.right, rest[0]);
5872
- return { kind: "orDefault", left, right: dflt, type: rest[0], loc };
5873
- }
5874
- }
5875
- lowerer.unsupported("SC1090", expr, `logical operators on union-typed values outside conditions (${NARROW_FIRST})`);
5876
- }
5877
- const kind = left.type.kind;
5878
- if (kind !== right.type.kind ||
5879
- (kind !== "f64" && kind !== "string" && kind !== "bool")) {
5880
- // Mixed PLAIN operands (`value || null`, `flag || undefined`,
5881
- // `s || 0`): JS value semantics still compile when the checker
5882
- // types the RESULT as one union both operands coerce into — the
5883
- // same lift the union-operand path above takes, arriving here
5884
- // with two plain arm values instead.
5885
- const target = lowerer.mapTypeOf(lowerer.typeOf(expr));
5886
- if (target?.kind === "union") {
5887
- lowerer.requireTruthyUnion(target.unionId, expr);
5888
- return {
5889
- kind: "logical",
5890
- op: op === ts.SyntaxKind.AmpersandAmpersandToken ? "&&" : "||",
5891
- left: lowerer.coerceInto(expr.left, left, target),
5892
- right: lowerer.coerceInto(expr.right, right, target),
5893
- type: target, loc,
5894
- };
5895
- }
5896
- lowerer.unsupported("SC1042", expr);
5897
- }
5898
- return {
5899
- kind: "logical",
5900
- op: op === ts.SyntaxKind.AmpersandAmpersandToken ? "&&" : "||",
5901
- left, right, type: left.type, loc,
5902
- };
6408
+ return lowerLogicalChain(lowerer, expr);
5903
6409
  }
5904
6410
  if (op === ts.SyntaxKind.QuestionQuestionToken) {
5905
6411
  return lowerer.lowerNullishCoalesce(expr, loc);
@@ -5910,7 +6416,7 @@ export function lowerBinary(lowerer, expr) {
5910
6416
  // runs for EFFECT exactly as its statement lowering (JS discards its
5911
6417
  // value), the right operand is the expression's value — a seqExpr.
5912
6418
  // The validator restricts seqExpr statements to straight-line writes
5913
- // (the C emission point is mid-expression), so a left operand whose
6419
+ // (the lowering point is mid-expression), so a left operand whose
5914
6420
  // statement lowering needs real control flow keeps a pointed fence
5915
6421
  // instead of tripping the validator downstream.
5916
6422
  const effect = lowerer.lowerExprStatement(expr.left);
@@ -5951,7 +6457,7 @@ export function lowerBinary(lowerer, expr) {
5951
6457
  op === ts.SyntaxKind.ExclamationEqualsEqualsToken) {
5952
6458
  const negated = op === ts.SyntaxKind.ExclamationEqualsEqualsToken;
5953
6459
  const unit = left.kind === "unitLit" ? left : right.kind === "unitLit" ? right : null;
5954
- const other = unit === left ? right : left;
6460
+ const other = left.kind === "unitLit" ? right : left;
5955
6461
  if (unit && other.type.kind === "dyn") {
5956
6462
  return {
5957
6463
  kind: "dynTest",
@@ -5968,12 +6474,30 @@ export function lowerBinary(lowerer, expr) {
5968
6474
  // answers false). dyn vs dyn keeps the fence below.
5969
6475
  const dynSide = left.type.kind === "dyn" ? left : right;
5970
6476
  const scalarSide = dynSide === left ? right : left;
6477
+ const reference = scalarSide.type;
6478
+ if ((isDynTypedRefType(reference) || reference.kind === "record" || reference.kind === "array" ||
6479
+ reference.kind === "bytes" || reference.kind === "func" || reference.kind === "regex" || reference.kind === "url" ||
6480
+ reference.kind === "bigint" || reference.kind === "symbol" || reference.kind === "set" || reference.kind === "union" || DYN_HANDLE_KINDS.has(reference.kind)) &&
6481
+ lowerer.dynConvertible(reference)) {
6482
+ const boxed = {
6483
+ kind: "dynFrom", value: scalarSide, type: DYN, loc: scalarSide.loc,
6484
+ ...(reference.kind === "record" || reference.kind === "array" || reference.kind === "bytes" ? { liveRef: true } : {}),
6485
+ };
6486
+ return {
6487
+ kind: "dynScalarEq",
6488
+ left: scalarSide === left ? boxed : left,
6489
+ right: scalarSide === right ? boxed : right,
6490
+ ...(negated ? { negated: true } : {}),
6491
+ type: BOOL,
6492
+ loc,
6493
+ };
6494
+ }
5971
6495
  if (dynSide.type.kind === "dyn" &&
5972
6496
  (scalarSide.type.kind === "f64" || scalarSide.type.kind === "string" || scalarSide.type.kind === "bool" ||
5973
6497
  // dyn vs dyn (`context.actual !== context.exact` —
5974
6498
  // test/common's exit accounting): the runtime's whole-dyn
5975
6499
  // strict equality — scalars by value, units by kind,
5976
- // reference kinds by node identity (scr_dyn_strict_eq).
6500
+ // reference kinds by identity (scr_dyn_strict_eq).
5977
6501
  scalarSide.type.kind === "dyn")) {
5978
6502
  return {
5979
6503
  kind: "dynScalarEq",
@@ -5985,15 +6509,22 @@ export function lowerBinary(lowerer, expr) {
5985
6509
  };
5986
6510
  }
5987
6511
  }
5988
- // JS `any`-origin operands (the checked-dynamic declaration story):
5989
- // arithmetic and ordering CHECK the dyn side to the static side's
5990
- // scalar kind (dynCheck — a catchable TypeError on mismatch) and
5991
- // compute natively. Node would ToNumber-coerce instead — the honest
5992
- // divergence is loud (a throw), never a silent wrong answer
5993
- // (SEMANTICS.md). tsc rejects these forms on real `unknown`, so only
5994
- // any-typed JS reaches this; when BOTH sides are dyn a number
5995
- // context is the only honest guess for arithmetic — both check.
5996
- if (isJsSourceFile(expr.getSourceFile())) {
6512
+ const bitwise = {
6513
+ [ts.SyntaxKind.AmpersandToken]: "&", [ts.SyntaxKind.BarToken]: "|", [ts.SyntaxKind.CaretToken]: "^",
6514
+ [ts.SyntaxKind.LessThanLessThanToken]: "<<", [ts.SyntaxKind.GreaterThanGreaterThanToken]: ">>",
6515
+ [ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken]: ">>>",
6516
+ };
6517
+ if (bitwise[op]) {
6518
+ const l = lowerer.coerceToExpected(left, DYN);
6519
+ const r = lowerer.coerceToExpected(right, DYN);
6520
+ if (l.type.kind === "dyn" && r.type.kind === "dyn") {
6521
+ const value = { kind: "libCall", fn: "dyn.bitwise", args: [l, r, { kind: "strLit", value: bitwise[op], type: STRING, loc }], type: DYN, loc };
6522
+ return left.type.kind === "f64" || right.type.kind === "f64" ? { kind: "dynCheck", value, type: F64, loc } : value;
6523
+ }
6524
+ }
6525
+ // Numeric operators dispatch on ToNumeric's actual Number/BigInt
6526
+ // result, including values flowing through native callable properties.
6527
+ if (isJsSourceFile(expr.getSourceFile()) || op === ts.SyntaxKind.PlusToken) {
5997
6528
  const checkNum = (e) => e.type.kind === "dyn" ? { kind: "dynCheck", value: e, type: F64, loc: e.loc } : e;
5998
6529
  const other = left.type.kind === "dyn" ? right : left;
5999
6530
  const NUM_BIN = {
@@ -6011,26 +6542,29 @@ export function lowerBinary(lowerer, expr) {
6011
6542
  };
6012
6543
  const arith = NUM_BIN[op];
6013
6544
  const cmp = NUM_CMP[op];
6014
- if ((arith || cmp) && (other.type.kind === "f64" || other.type.kind === "dyn")) {
6015
- const l = checkNum(left);
6016
- const r = checkNum(right);
6017
- if (arith)
6018
- return { kind: "bin", op: arith, left: l, right: r, type: F64, loc };
6019
- return { kind: "bin", op: cmp, left: l, right: r, type: BOOL, loc };
6020
- }
6021
- // `+`: number when the OTHER side is a number, string concat when
6022
- // it is a string — the two static homes; dyn+dyn stays a number.
6023
- if (op === ts.SyntaxKind.PlusToken) {
6024
- if (other.type.kind === "f64" || other.type.kind === "dyn") {
6025
- return { kind: "bin", op: "+", left: checkNum(left), right: checkNum(right), type: F64, loc };
6545
+ if (arith) {
6546
+ const l = lowerer.coerceToExpected(left, DYN);
6547
+ const r = lowerer.coerceToExpected(right, DYN);
6548
+ if (l.type.kind === "dyn" && r.type.kind === "dyn") {
6549
+ const value = { kind: "libCall", fn: "dyn.arithmetic",
6550
+ args: [l, r, { kind: "strLit", value: arith, type: STRING, loc }], type: DYN, loc };
6551
+ const numeric = left.type.kind === "bigint" || right.type.kind === "bigint" ? BIGINT_T
6552
+ : left.type.kind === "f64" || right.type.kind === "f64" ? F64 : null;
6553
+ return numeric ? { kind: "dynCheck", value, type: numeric, loc } : value;
6026
6554
  }
6027
- if (other.type.kind === "string") {
6028
- // String-context `+`: JS's answer is String(unknown) — the
6029
- // JS-exact dyn walker (numbers format, arrays join, objects
6030
- // print [object Object], handles the same) — never a checked
6031
- // cast: `'status ' + res.statusCode` concatenates like Node.
6032
- const strOf = (e) => e.type.kind === "dyn" ? { kind: "toString", operand: e, type: STRING, loc: e.loc } : e;
6033
- return { kind: "strConcat", left: strOf(left), right: strOf(right), type: STRING, loc };
6555
+ }
6556
+ if (cmp && (other.type.kind === "f64" || other.type.kind === "dyn")) {
6557
+ return { kind: "bin", op: cmp, left: checkNum(left), right: checkNum(right), type: BOOL, loc };
6558
+ }
6559
+ // Untyped addition chooses concatenation or numeric addition only
6560
+ // after both operands have undergone ToPrimitive at runtime.
6561
+ if (op === ts.SyntaxKind.PlusToken) {
6562
+ const l = lowerer.coerceToExpected(left, DYN);
6563
+ const r = lowerer.coerceToExpected(right, DYN);
6564
+ if (l.type.kind === "dyn" && r.type.kind === "dyn") {
6565
+ const value = { kind: "libCall", fn: "dyn.add", args: [l, r], type: DYN, loc };
6566
+ // A statically known string operand guarantees a string result.
6567
+ return other.type.kind === "string" ? { kind: "dynCheck", value, type: STRING, loc } : value;
6034
6568
  }
6035
6569
  }
6036
6570
  }
@@ -6049,7 +6583,7 @@ export function lowerBinary(lowerer, expr) {
6049
6583
  // retain the SC2011 promise that --dynamic lifts this site.
6050
6584
  if (lowerer.dynamic)
6051
6585
  return lowerAnyBinaryInIsland(lowerer, expr, left, right, loc);
6052
- lowerer.anyOpFence(`the '${ts.tokenToString(op) ?? ts.SyntaxKind[op]}' operator`, expr);
6586
+ lowerer.anyOpFence(`the '${ts.tokenToString(op) ?? ts.syntaxKindName(op)}' operator`, expr);
6053
6587
  }
6054
6588
  // tsc allows ===/!== on unknown (arithmetic/comparisons it rejects
6055
6589
  // itself); a dynamic equality would need a dyn walk — validate first.
@@ -6252,6 +6786,14 @@ export function lowerBinary(lowerer, expr) {
6252
6786
  const optionalStringPlus = lowerOptionalStringPlus();
6253
6787
  if (optionalStringPlus)
6254
6788
  return optionalStringPlus;
6789
+ if (isJsSourceFile(expr.getSourceFile())) {
6790
+ const primitive = (type) => type.kind === "f64" || type.kind === "string" || type.kind === "bool" || isUnitType(type) ||
6791
+ (type.kind === "union" && (lowerer.unions.get(type.unionId)?.arms.every(primitive) ?? false));
6792
+ if ((left.type.kind === "union" || right.type.kind === "union") && primitive(left.type) && primitive(right.type)) {
6793
+ lowerer.runtimeOptionalArithmeticTypes.set(expr, DYN);
6794
+ return { kind: "libCall", fn: "dyn.add", args: [lowerer.coerceToExpected(left, DYN), lowerer.coerceToExpected(right, DYN)], type: DYN, loc };
6795
+ }
6796
+ }
6255
6797
  lowerer.unsupported("SC1043", expr);
6256
6798
  break;
6257
6799
  case ts.SyntaxKind.MinusToken:
@@ -6294,8 +6836,11 @@ export function lowerBinary(lowerer, expr) {
6294
6836
  return indexed;
6295
6837
  if (plainBothNum)
6296
6838
  return { kind: "bin", op: negated ? "!==" : "===", left, right, type: BOOL, loc };
6297
- if (bothStr)
6839
+ if (bothStr) {
6840
+ if (left.kind === "strLit" && right.kind === "strLit")
6841
+ return { kind: "boolLit", value: (left.value === right.value) !== negated, type: BOOL, loc };
6298
6842
  return { kind: "strEq", negated, left, right, type: BOOL, loc };
6843
+ }
6299
6844
  // bool === bool: a plain value compare (the config-drift checks'
6300
6845
  // `desired.lanMode !== actual.lanMode` shape).
6301
6846
  if (left.type.kind === "bool" && right.type.kind === "bool") {
@@ -6419,6 +6964,8 @@ export function lowerBinary(lowerer, expr) {
6419
6964
  if ((idLeft.type.kind === "array" ||
6420
6965
  idLeft.type.kind === "map" ||
6421
6966
  idLeft.type.kind === "set" ||
6967
+ idLeft.type.kind === "regex" ||
6968
+ idLeft.type.kind === "url" ||
6422
6969
  idLeft.type.kind === "object" ||
6423
6970
  idLeft.type.kind === "record" ||
6424
6971
  // Symbols ARE identity: `Symbol('a') === Symbol('a')` is false,
@@ -6459,21 +7006,283 @@ export function lowerBinary(lowerer, expr) {
6459
7006
  default:
6460
7007
  break;
6461
7008
  }
6462
- lowerer.unsupported("SC1090", expr, `operator '${ts.tokenToString(op) ?? ts.SyntaxKind[op]}'`);
7009
+ lowerer.unsupported("SC1090", expr, `operator '${ts.tokenToString(op) ?? ts.syntaxKindName(op)}'`);
7010
+ }
7011
+ function lowerLogicalChain(lowerer, expr) {
7012
+ // Logical chains have a left-nested AST. Keep the native stack bounded
7013
+ // while retaining each pair's checker type and lazy operand evaluation.
7014
+ const parents = [];
7015
+ let current = expr;
7016
+ let result;
7017
+ while (true) {
7018
+ if (current.operatorToken.kind === ts.SyntaxKind.BarBarToken) {
7019
+ const fallback = lowerAbsenceAwareOrChain(lowerer, current);
7020
+ if (fallback !== null) {
7021
+ result = fallback;
7022
+ break;
7023
+ }
7024
+ }
7025
+ const left = current.left;
7026
+ if (ts.isBinaryExpression(left) &&
7027
+ (left.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ||
7028
+ left.operatorToken.kind === ts.SyntaxKind.BarBarToken) &&
7029
+ !lowerer.chainRecvByNode.has(left)) {
7030
+ parents.push(current);
7031
+ current = left;
7032
+ continue;
7033
+ }
7034
+ result = lowerLogicalPair(lowerer, current, lowerAbsenceProbe(lowerer, left) ?? lowerer.lowerExpr(left));
7035
+ break;
7036
+ }
7037
+ for (let i = parents.length - 1; i >= 0; i--) {
7038
+ result = lowerLogicalPair(lowerer, parents[i], result);
7039
+ }
7040
+ return result;
7041
+ }
7042
+ function lowerLogicalPair(lowerer, expr, left) {
7043
+ const op = expr.operatorToken.kind;
7044
+ const loc = locOf(expr);
7045
+ const right = lowerer.lowerExpr(expr.right);
7046
+ // A LITERAL-unit left operand (a compile-time undefined/null — the
7047
+ // capability-probe members: `process.features.inspector ||
7048
+ // !flag.startsWith('--inspect')` reads undefined in a compiled
7049
+ // binary): statically falsy, so the operator folds JS-exactly —
7050
+ // `unit || X` IS X, `unit && X` IS the unit (X's side effects never
7051
+ // run in JS either; the lowered right is simply dropped). Literal
7052
+ // units only — computed unit-typed values keep the fences below.
7053
+ if (left.kind === "unitLit") {
7054
+ return op === ts.SyntaxKind.BarBarToken ? right : left;
7055
+ }
7056
+ // JS objects and symbols are always truthy. In `ref || fallback`
7057
+ // the fallback is therefore unreachable and the result is the
7058
+ // already-evaluated left value — the browser-fallback idiom used by
7059
+ // packages for `process.argv || []` and `process.env || {}`. The
7060
+ // operand itself remains in the IR, preserving reads/calls that
7061
+ // produce the reference; only the unreachable right lowering drops.
7062
+ if (op === ts.SyntaxKind.BarBarToken && REF_TRUTHY_KINDS.has(left.type.kind)) {
7063
+ return left;
7064
+ }
7065
+ if (left.type.kind === "dyn" || right.type.kind === "dyn") {
7066
+ // A checked-dynamic operand (`fn.name || '<anonymous>'` —
7067
+ // test/common's _mustCallInner): both sides live in the checked-dynamic tree and
7068
+ // the deciding test is ToBoolean over the dyn kind
7069
+ // (scr_dyn_truthy) — JS value semantics exactly, result dyn. The
7070
+ // non-dyn side converts through the usual boundary; a value with
7071
+ // no dyn representation keeps the fence.
7072
+ const l = lowerer.coerceToExpected(left, DYN);
7073
+ const r = lowerer.coerceToExpected(right, DYN);
7074
+ if (l.type.kind !== "dyn" || r.type.kind !== "dyn") {
7075
+ lowerer.unsupported("SC1100", expr, "logical operators on 'unknown' values");
7076
+ }
7077
+ return {
7078
+ kind: "logical",
7079
+ op: op === ts.SyntaxKind.AmpersandAmpersandToken ? "&&" : "||",
7080
+ left: l,
7081
+ right: r,
7082
+ type: DYN, loc,
7083
+ };
7084
+ }
7085
+ if (left.type.kind === "jsval" || right.type.kind === "jsval") {
7086
+ return {
7087
+ kind: "logical",
7088
+ op: op === ts.SyntaxKind.AmpersandAmpersandToken ? "&&" : "||",
7089
+ left: lowerer.jsvalIn(left, expr.left),
7090
+ right: lowerer.jsvalIn(right, expr.right),
7091
+ type: JSVAL, loc,
7092
+ };
7093
+ }
7094
+ if (left.type.kind === "union" || right.type.kind === "union") {
7095
+ // Test the deciding operand in its own representation. The result
7096
+ // contains only the values that can survive short-circuit evaluation.
7097
+ let target = lowerer.mapTypeOf(lowerer.typeOf(expr));
7098
+ // Index reads can be absent without noUncheckedIndexedAccess. Their
7099
+ // explicit nullish fallback still runs, even if the checker discarded it.
7100
+ if (target !== null) {
7101
+ const returned = [right.type, ...(op === ts.SyntaxKind.AmpersandAmpersandToken ? [left.type] : [])];
7102
+ for (const type of returned) {
7103
+ const arms = type.kind === "union" ? lowerer.unions.get(type.unionId)?.arms ?? [] : [type];
7104
+ for (const arm of arms) {
7105
+ if (arm.kind === "undefinedT" && !isUnitType(target))
7106
+ target = lowerer.runtimeOptionalType(target);
7107
+ if (arm.kind === "nullT")
7108
+ target = withUnitArm(target, arm.kind, lowerer.unions) ?? target;
7109
+ }
7110
+ }
7111
+ }
7112
+ // A broad-JSDoc npm-static body can leave the CHECKER result `any`
7113
+ // even after declaration-backed specialization has recovered both
7114
+ // operands (`helpOption && args.find(...)` in commander). When the
7115
+ // left consists only of always-truthy references and falsy units,
7116
+ // the static result is exactly the right operand plus those units.
7117
+ if (op === ts.SyntaxKind.AmpersandAmpersandToken &&
7118
+ target === null &&
7119
+ lowerer.implicitParamTypes !== null &&
7120
+ npmStaticPackageOfPath(expr.getSourceFile().fileName) !== null &&
7121
+ (lowerer.typeOf(expr).flags & ts.TypeFlags.Any) !== 0 &&
7122
+ left.type.kind === "union") {
7123
+ const leftArms = lowerer.unions.get(left.type.unionId)?.arms;
7124
+ const rightArms = right.type.kind === "union"
7125
+ ? lowerer.unions.get(right.type.unionId)?.arms
7126
+ : [right.type];
7127
+ const rightIsStatic = rightArms?.every((arm) => arm.kind !== "dyn" && arm.kind !== "caught" && arm.kind !== "jsval" && arm.kind !== "void") ?? false;
7128
+ if (leftArms && rightArms && rightIsStatic &&
7129
+ leftArms.some(isUnitType) &&
7130
+ leftArms.every((arm) => isUnitType(arm) || REF_TRUTHY_KINDS.has(arm.kind))) {
7131
+ const byKey = new Map();
7132
+ for (const arm of [...rightArms, ...leftArms.filter(isUnitType)]) {
7133
+ byKey.set(typeKey(arm), arm);
7134
+ }
7135
+ const arms = [...byKey.values()].sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1));
7136
+ if (arms.length > 1) {
7137
+ const source = right.type.kind === "union" ? lowerer.unions.get(right.type.unionId) : undefined;
7138
+ target = { kind: "union", unionId: source
7139
+ ? lowerer.unions.transform(source, arms)
7140
+ : lowerer.unions.intern(arms) };
7141
+ }
7142
+ }
7143
+ }
7144
+ if (target?.kind === "union") {
7145
+ // `&&` whose left does NOT fit the result union: the checker
7146
+ // built that result by DROPPING left arms which are always
7147
+ // truthy (`Option | undefined && string | undefined` answers
7148
+ // `string | undefined`, not `Option | string | undefined`).
7149
+ // Evaluate the left once, test it in its own union, and retag it
7150
+ // only on the falsy path. Any stranded reference arms are
7151
+ // unreachable there because every JS object is truthy.
7152
+ if (op === ts.SyntaxKind.AmpersandAmpersandToken && left.type.kind === "union" && !typeEquals(left.type, target)) {
7153
+ const def = lowerer.unions.get(left.type.unionId);
7154
+ const trappable = new Set();
7155
+ def?.arms.forEach((arm, tag) => {
7156
+ if (lowerer.armTag(target.unionId, arm) < 0 && REF_TRUTHY_KINDS.has(arm.kind)) {
7157
+ trappable.add(tag);
7158
+ }
7159
+ });
7160
+ const retag = trappable.size === 0
7161
+ ? null
7162
+ : lowerer.unionRetagHelper(left.type.unionId, target.unionId, loc, trappable);
7163
+ if (retag) {
7164
+ lowerer.requireTruthyUnion(left.type.unionId, expr);
7165
+ const stmts = [];
7166
+ let stable = left;
7167
+ if (!isSafeToRepeat(left)) {
7168
+ const local = lowerer.declareHiddenLocal("%and", left.type);
7169
+ stmts.push({ kind: "varDecl", localId: local.id, init: left, loc });
7170
+ stable = varRef(local.id, left.type, loc);
7171
+ }
7172
+ const result = {
7173
+ kind: "ternary",
7174
+ cond: lowerer.ensureBool(stable, expr.left),
7175
+ then: lowerer.coerceInto(expr.right, right, target),
7176
+ else_: { kind: "call", callee: retag, args: [stable], type: target, loc },
7177
+ type: target,
7178
+ loc,
7179
+ };
7180
+ return stmts.length === 0 ? result : { kind: "seqExpr", stmts, result, type: target, loc };
7181
+ }
7182
+ }
7183
+ // `||` whose left does NOT fit the result union: the checker built
7184
+ // that result by DROPPING the left's falsy arms (`process.env.X ||
7185
+ // null` is `string | null`, `|| 3000` is `string | number` — the
7186
+ // `undefined` is gone from both), so coercing the left eagerly, as
7187
+ // the shared shape below does, retags an arm the test is about to
7188
+ // rule out and throws where Node yields the default. Single-eval
7189
+ // instead: test the left in its OWN union and retag only on the
7190
+ // truthy side, where the dropped arms are unreachable.
7191
+ if (op === ts.SyntaxKind.BarBarToken && left.type.kind === "union" && !typeEquals(left.type, target)) {
7192
+ const retag = lowerer.unionRetagHelper(left.type.unionId, target.unionId, loc);
7193
+ if (retag) {
7194
+ lowerer.requireTruthyUnion(left.type.unionId, expr);
7195
+ return {
7196
+ kind: "orDefault",
7197
+ left,
7198
+ right: lowerer.coerceInto(expr.right, right, target),
7199
+ retag,
7200
+ type: target,
7201
+ loc,
7202
+ };
7203
+ }
7204
+ }
7205
+ lowerer.requireTruthyUnion(target.unionId, expr);
7206
+ return {
7207
+ kind: "logical",
7208
+ op: op === ts.SyntaxKind.AmpersandAmpersandToken ? "&&" : "||",
7209
+ left: lowerer.coerceInto(expr.left, left, target),
7210
+ right: lowerer.coerceInto(expr.right, right, target),
7211
+ type: target, loc,
7212
+ };
7213
+ }
7214
+ // `u || d` NARROWED by the default: the checker types the result
7215
+ // as u's single non-unit arm (`marker() || "default"`) — evaluate
7216
+ // u once, truthy extracts the arm, falsy takes d lazily (the
7217
+ // orDefault node, nullish's truthiness sibling). An UNMAPPABLE
7218
+ // checker result takes the same rule (`options.runner ||
7219
+ // defaultRunner` — tsc's union of two structurally-compatible
7220
+ // function types has no representation, while the left's one
7221
+ // non-unit arm is the only representable answer): the default
7222
+ // must coerce into the arm or fence on its own.
7223
+ if (op === ts.SyntaxKind.BarBarToken &&
7224
+ left.type.kind === "union") {
7225
+ const def = lowerer.unions.get(left.type.unionId);
7226
+ const rest = def ? def.arms.filter((a) => !isUnitType(a)) : [];
7227
+ const funcArmDefault = rest[0]?.kind === "func" && (target === null || target.kind === "func");
7228
+ if (rest.length === 1 && ((target !== null && typeEquals(target, rest[0])) || funcArmDefault)) {
7229
+ lowerer.requireTruthyUnion(left.type.unionId, expr);
7230
+ const dflt = lowerer.lowerExprExpecting(expr.right, rest[0]);
7231
+ return { kind: "orDefault", left, right: dflt, type: rest[0], loc };
7232
+ }
7233
+ }
7234
+ // JavaScript defaults can join differently inferred object layouts.
7235
+ // When both operands have native checked representations, preserve
7236
+ // the deciding value and short-circuit evaluation in that domain.
7237
+ if (isJsSourceFile(expr.getSourceFile()) &&
7238
+ lowerer.dynConvertible(left.type) && lowerer.dynConvertible(right.type)) {
7239
+ return {
7240
+ kind: "logical", op: op === ts.SyntaxKind.AmpersandAmpersandToken ? "&&" : "||",
7241
+ left: lowerer.coerceInto(expr.left, left, DYN),
7242
+ right: lowerer.coerceInto(expr.right, right, DYN), type: DYN, loc,
7243
+ };
7244
+ }
7245
+ lowerer.unsupported("SC1090", expr, `logical operators on union-typed values outside conditions (${NARROW_FIRST})`);
7246
+ }
7247
+ const kind = left.type.kind;
7248
+ if (kind !== right.type.kind ||
7249
+ (kind !== "f64" && kind !== "string" && kind !== "bool")) {
7250
+ // Mixed PLAIN operands (`value || null`, `flag || undefined`,
7251
+ // `s || 0`): JS value semantics still compile when the checker
7252
+ // types the RESULT as one union both operands coerce into — the
7253
+ // same lift the union-operand path above takes, arriving here
7254
+ // with two plain arm values instead.
7255
+ const target = lowerer.mapTypeOf(lowerer.typeOf(expr));
7256
+ if (target?.kind === "union") {
7257
+ lowerer.requireTruthyUnion(target.unionId, expr);
7258
+ return {
7259
+ kind: "logical",
7260
+ op: op === ts.SyntaxKind.AmpersandAmpersandToken ? "&&" : "||",
7261
+ left: lowerer.coerceInto(expr.left, left, target),
7262
+ right: lowerer.coerceInto(expr.right, right, target),
7263
+ type: target, loc,
7264
+ };
7265
+ }
7266
+ lowerer.unsupported("SC1042", expr);
7267
+ }
7268
+ return {
7269
+ kind: "logical",
7270
+ op: op === ts.SyntaxKind.AmpersandAmpersandToken ? "&&" : "||",
7271
+ left, right, type: left.type, loc,
7272
+ };
6463
7273
  }
6464
7274
  function lowerAbsenceAwareOrChain(lowerer, expr) {
6465
7275
  const nodes = [];
6466
- const collect = (node) => {
6467
- if (ts.isBinaryExpression(node) &&
6468
- node.operatorToken.kind === ts.SyntaxKind.BarBarToken) {
6469
- collect(node.left);
6470
- collect(node.right);
7276
+ const pending = [expr];
7277
+ while (pending.length !== 0) {
7278
+ const node = pending.pop();
7279
+ if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.BarBarToken) {
7280
+ pending.push(node.right, node.left);
6471
7281
  }
6472
7282
  else {
6473
7283
  nodes.push(node);
6474
7284
  }
6475
- };
6476
- collect(expr);
7285
+ }
6477
7286
  if (nodes.length < 3)
6478
7287
  return null;
6479
7288
  const lowered = nodes.map((node) => lowerAbsenceProbe(lowerer, node) ?? lowerer.lowerExpr(node));
@@ -6544,10 +7353,7 @@ function errorRootedObjectOf(lowerer, t) {
6544
7353
  * shape, so the sibling typeof lowerings keep trying. */
6545
7354
  function lowerErrorCodeTypeofTest(lowerer, expr, loc) {
6546
7355
  const negated = expr.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken;
6547
- for (const [a, b] of [
6548
- [expr.left, expr.right],
6549
- [expr.right, expr.left],
6550
- ]) {
7356
+ for (const [a, b] of operandPairs(expr)) {
6551
7357
  if (!ts.isTypeOfExpression(a))
6552
7358
  continue;
6553
7359
  let prop = a.expression;
@@ -6607,10 +7413,7 @@ function lowerErrorCodeTypeofTest(lowerer, expr, loc) {
6607
7413
  }
6608
7414
  export function lowerCaughtTypeofTest(lowerer, expr, loc) {
6609
7415
  const negated = expr.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken;
6610
- for (const [a, b] of [
6611
- [expr.left, expr.right],
6612
- [expr.right, expr.left],
6613
- ]) {
7416
+ for (const [a, b] of operandPairs(expr)) {
6614
7417
  if (!ts.isTypeOfExpression(a))
6615
7418
  continue;
6616
7419
  const local = lowerer.caughtLocalOf(a.expression);
@@ -6619,7 +7422,7 @@ export function lowerCaughtTypeofTest(lowerer, expr, loc) {
6619
7422
  if (!ts.isStringLiteral(b)) {
6620
7423
  lowerer.unsupported("SC1090", expr, "'typeof' catch-binding tests against non-literal strings");
6621
7424
  }
6622
- if (b.text === "string" || b.text === "number" || b.text === "boolean") {
7425
+ if (b.text === "string" || b.text === "number" || b.text === "boolean" || b.text === "object") {
6623
7426
  return {
6624
7427
  kind: "caughtTest",
6625
7428
  value: { kind: "varRef", localId: local.id, type: CAUGHT, loc },
@@ -6629,8 +7432,7 @@ export function lowerCaughtTypeofTest(lowerer, expr, loc) {
6629
7432
  loc,
6630
7433
  };
6631
7434
  }
6632
- lowerer.unsupported("SC1090", b, `'typeof' catch-binding tests against "${b.text}" (only "string"/"number"/"boolean" ` +
6633
- `narrow a catch binding; use 'instanceof' for objects)`);
7435
+ lowerer.unsupported("SC1090", b, `'typeof' catch-binding tests against "${b.text}" (only "string"/"number"/"boolean"/"object" are supported)`);
6634
7436
  }
6635
7437
  return null;
6636
7438
  }
@@ -6734,12 +7536,13 @@ export function aliasTypeofNarrows(lowerer, cond, polarity) {
6734
7536
  walk(cond, polarity);
6735
7537
  return out;
6736
7538
  }
7539
+ /** Visit both operand orders while keeping the sequence an array of pairs. */
7540
+ function operandPairs(expr) {
7541
+ return [[expr.left, expr.right], [expr.right, expr.left]];
7542
+ }
6737
7543
  /** One ===/!== leaf of aliasTypeofNarrows — the qualification battery. */
6738
7544
  function aliasTypeofArmOf(lowerer, e, strip) {
6739
- for (const [a0, b] of [
6740
- [e.left, e.right],
6741
- [e.right, e.left],
6742
- ]) {
7545
+ for (const [a0, b] of operandPairs(e)) {
6743
7546
  if (!ts.isStringLiteral(b))
6744
7547
  continue;
6745
7548
  const a = strip(a0);
@@ -6768,7 +7571,7 @@ function aliasTypeofArmOf(lowerer, e, strip) {
6768
7571
  if (!bindingNeverReassigned(lowerer, valSym, valDecl))
6769
7572
  continue;
6770
7573
  const fn = valDecl.parent;
6771
- if (!(e.pos >= fn.pos && e.end <= fn.end))
7574
+ if (fn === undefined || !(e.pos >= fn.pos && e.end <= fn.end))
6772
7575
  continue;
6773
7576
  const valT = lowerer.typeOf(opnd); // override-aware: implicit bindings compose
6774
7577
  const mapped = lowerer.mapTypeOf(valT);
@@ -6810,10 +7613,7 @@ function aliasTypeofArmOf(lowerer, e, strip) {
6810
7613
  * bare-typeof value form then composes with strEq for pure operands). */
6811
7614
  function lowerUnionTypeofTest(lowerer, expr, loc) {
6812
7615
  const negated = expr.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken;
6813
- for (const [a, b] of [
6814
- [expr.left, expr.right],
6815
- [expr.right, expr.left],
6816
- ]) {
7616
+ for (const [a, b] of operandPairs(expr)) {
6817
7617
  if (!ts.isTypeOfExpression(a))
6818
7618
  continue;
6819
7619
  if (lowerer.mapTypeOf(lowerer.typeOf(a.expression))?.kind !== "union")
@@ -6861,12 +7661,13 @@ function lowerUnionTypeofTest(lowerer, expr, loc) {
6861
7661
  * side is a typeof over a dyn-typed operand (not this pattern). */
6862
7662
  function lowerDynTypeofTest(lowerer, expr, loc) {
6863
7663
  const negated = expr.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken;
6864
- for (const [a, b] of [
6865
- [expr.left, expr.right],
6866
- [expr.right, expr.left],
6867
- ]) {
7664
+ for (const [a, b] of operandPairs(expr)) {
6868
7665
  if (!ts.isTypeOfExpression(a))
6869
7666
  continue;
7667
+ // Non-literal comparisons use the ordinary typeof string value,
7668
+ // preserving both operands' evaluation order without a narrowing test.
7669
+ if (!ts.isStringLiteral(b))
7670
+ continue;
6870
7671
  const declared = lowerer.mapTypeOf(lowerer.typeOf(a.expression));
6871
7672
  let value;
6872
7673
  if (declared?.kind === "dyn") {
@@ -6924,10 +7725,7 @@ function lowerDynTypeofTest(lowerer, expr, loc) {
6924
7725
  value = probed;
6925
7726
  }
6926
7727
  }
6927
- if (!ts.isStringLiteral(b)) {
6928
- lowerer.unsupported("SC1090", expr, "'typeof' tests on 'unknown' values against non-literal strings");
6929
- }
6930
- if (b.text === "string" || b.text === "number" || b.text === "boolean" || b.text === "undefined") {
7728
+ if (b.text === "string" || b.text === "number" || b.text === "boolean" || b.text === "undefined" || b.text === "bigint" || b.text === "symbol") {
6931
7729
  return {
6932
7730
  kind: "dynTest",
6933
7731
  test: b.text,
@@ -6964,14 +7762,8 @@ function lowerDynTypeofTest(lowerer, expr, loc) {
6964
7762
  loc,
6965
7763
  };
6966
7764
  }
6967
- // Kinds a dyn box can NEVER hold — no conversion into 'unknown'
6968
- // exists for bigints or symbols (dynFrom's domain is JSON-safe data
6969
- // + bytes + Error + functions), so the test's answer is a
6970
- // compile-time constant: false for ===, true for !==. The capability
6971
- // probes this settles (`typeof ms === 'bigint'` in dual-mode number
6972
- // helpers) then FOLD their impossible arm (lowerTernary), which is
6973
- // what lets the reachable arm compile statically.
6974
- if (b.text === "bigint" || b.text === "symbol") {
7765
+ // Native checked values do not yet carry symbols.
7766
+ if (b.text === "symbol") {
6975
7767
  return { kind: "boolLit", value: negated, type: BOOL, loc };
6976
7768
  }
6977
7769
  lowerer.unsupported("SC1090", b, `'typeof' tests against "${b.text}" on 'unknown' values (only "string"/"number"/` +
@@ -6993,7 +7785,7 @@ function lowerReplacedExportsRead(lowerer, expr) {
6993
7785
  if (!ts.isIdentifier(recv) || recv.text !== "exports")
6994
7786
  return null;
6995
7787
  const sf = expr.getSourceFile();
6996
- if (!isJsSourceFile(sf) || isNodeEsmFile(sf))
7788
+ if (!isJsSourceFile(sf) || isNodeEsmFile(sf, lowerer.program))
6997
7789
  return null;
6998
7790
  if (lowerer.peekLocal(recv) || lowerer.globalOf(recv))
6999
7791
  return null; // a user binding shadows
@@ -7170,6 +7962,34 @@ export function lowerInstanceOf(lowerer, expr, loc) {
7170
7962
  const right = lowerer.lowerExpr(expr.right);
7171
7963
  return { kind: "jsOp", op: "instanceOf", args: [left, right], type: BOOL, loc };
7172
7964
  }
7965
+ if (!lowerer.dynamic && (lowerer.isStdlibGlobal(expr.right, "Set") || lowerer.isStdlibGlobal(expr.right, "Map"))) {
7966
+ const collection = lowerer.isStdlibGlobal(expr.right, "Map") ? "map" : "set";
7967
+ const value = lowerer.lowerExpr(expr.left);
7968
+ if (value.type.kind === "dyn")
7969
+ return { kind: "libCall", fn: collection === "map" ? "dyn.nativeMapIs" : "dyn.nativeSetIs", args: [value], type: BOOL, loc };
7970
+ if (value.type.kind === collection)
7971
+ return { kind: "seqExpr", stmts: [{ kind: "exprStmt", expr: value, loc }],
7972
+ result: { kind: "boolLit", value: true, type: BOOL, loc }, type: BOOL, loc };
7973
+ }
7974
+ if (lowerer.isStdlibGlobal(expr.right, "WeakMap") || lowerer.isStdlibGlobal(expr.right, "WeakSet")) {
7975
+ if (lowerer.dynamic) {
7976
+ const value = lowerer.jsvalIn(lowerer.lowerExpr(expr.left), expr.left);
7977
+ const ctor = { kind: "jsOp", op: "globalGet", name: lowerer.isStdlibGlobal(expr.right, "WeakMap") ? "WeakMap" : "WeakSet", args: [], type: JSVAL, loc };
7978
+ return { kind: "jsOp", op: "instanceOf", args: [value, ctor], type: BOOL, loc };
7979
+ }
7980
+ const value = lowerer.coerceInto(expr.left, lowerer.lowerExpr(expr.left), DYN);
7981
+ return { kind: "libCall", fn: lowerer.isStdlibGlobal(expr.right, "WeakMap") ? "weakMap.is" : "weakSet.is", args: [value], type: BOOL, loc };
7982
+ }
7983
+ const rhsBuiltin = ts.isIdentifier(expr.right) ? lowerer.builtinImportOf(expr.right) :
7984
+ ts.isPropertyAccessExpression(expr.right) ? lowerer.builtinMemberOf(expr.right) : null;
7985
+ if (lowerer.isStdlibGlobal(expr.right, "URL") || rhsBuiltin?.module === "url" && rhsBuiltin.member === "URL") {
7986
+ const value = lowerer.coerceInto(expr.left, lowerer.lowerExpr(expr.left), DYN);
7987
+ return { kind: "libCall", fn: "dyn.nativeUrlIs", args: [value], type: BOOL, loc };
7988
+ }
7989
+ if (lowerer.isStdlibGlobal(expr.right, "ArrayBuffer")) {
7990
+ const value = lowerer.coerceInto(expr.left, lowerer.lowerExpr(expr.left), DYN);
7991
+ return { kind: "libCall", fn: "arrayBuffer.is", args: [value], type: BOOL, loc };
7992
+ }
7173
7993
  const rhsSymbol = ts.isIdentifier(expr.right) ? lowerer.resolveValueSymbol(expr.right) : null;
7174
7994
  // `x instanceof events.EventEmitter` — the namespace-member spelling
7175
7995
  // resolves to the same ambient class as the named import.
@@ -7203,29 +8023,41 @@ export function lowerInstanceOf(lowerer, expr, loc) {
7203
8023
  // decoration result — fall to the class-VALUE path below, whose
7204
8024
  // instanceOfValue reads the interval off the bound class object.
7205
8025
  const directRhs = rhsSymbol ? lowerer.classBySymbol.get(rhsSymbol) : undefined;
7206
- const target = (directRhs?.classDecorators?.valueGlobalId !== undefined ? undefined : directRhs) ??
8026
+ const target = (directRhs?.localClass || directRhs?.classDecorators?.valueGlobalId !== undefined ? undefined : directRhs) ??
7207
8027
  rhsNsClass ??
7208
8028
  lowerer.builtinErrorInfoOf(rhsSymbol) ??
8029
+ lowerer.builtinErrorInfoOf(rhsMemberSymbol) ??
7209
8030
  lowerer.builtinEmitterInfoOf(rhsSymbol) ??
7210
8031
  lowerer.builtinEmitterInfoOf(rhsMemberSymbol) ??
7211
8032
  lowerer.builtinStreamInfoOf(rhsSymbol) ??
7212
8033
  lowerer.builtinStreamInfoOf(rhsMemberSymbol) ??
7213
8034
  undefined;
7214
8035
  if (!target) {
7215
- // `u instanceof Uint8Array` on an `unknown` value: the checked-dynamic tree carries a
7216
- // bytes kind — one runtime tag test, and tsc's narrowing types the
7217
- // true branch (reads bridge through maybeNarrow's validated
7218
- // extraction, like the typeof tests). Node's Buffer IS a Uint8Array
7219
- // subclass and rides the same bytes kind, so both worlds answer true
7220
- // for Buffer payloads — Node-exact (the bytes kind's other
7221
- // divergences are SEMANTICS.md 45). Catch bindings stay out (their
7222
- // payload is a typed snapshot, not a dyn).
7223
- if (ts.isIdentifier(expr.right) &&
7224
- lowerer.isStdlibGlobal(expr.right, "Uint8Array") &&
7225
- !lowerer.caughtLocalOf(expr.left)) {
7226
- const left = lowerer.lowerExpr(expr.left);
7227
- if (left.type.kind === "dyn") {
7228
- return { kind: "dynTest", test: "bytes", value: left, type: BOOL, loc };
8036
+ const date = lowerer.isStdlibGlobal(expr.right, "Date");
8037
+ const url = lowerer.isStdlibGlobal(expr.right, "URL");
8038
+ if ((date || url) && !lowerer.caughtLocalOf(expr.left)) {
8039
+ const value = lowerer.lowerExpr(expr.left);
8040
+ if (value.type.kind === "dyn")
8041
+ return { kind: "libCall", fn: date ? "dyn.nativeDateIs" : "dyn.nativeUrlIs", args: [value], type: BOOL, loc };
8042
+ if (value.type.kind === "union")
8043
+ lowerer.unsupported("SC1090", expr, "Date or URL instanceof over a union");
8044
+ return { kind: "seqExpr", stmts: [{ kind: "exprStmt", expr: value, loc }],
8045
+ result: { kind: "boolLit", value: value.type.kind === (date ? "date" : "url"), type: BOOL, loc }, type: BOOL, loc };
8046
+ }
8047
+ // Unknown storage retains each numeric typed array's exact native brand.
8048
+ if (ts.isIdentifier(expr.right) && !lowerer.caughtLocalOf(expr.left)) {
8049
+ const elem = Object.keys(BYTES_ELEMENT_NAME).find((kind) => lowerer.isStdlibGlobal(expr.right, BYTES_ELEMENT_NAME[kind]));
8050
+ if (elem !== undefined) {
8051
+ const left = lowerer.lowerExpr(expr.left);
8052
+ if (left.type.kind === "dyn") {
8053
+ return { kind: "dynTest", test: "bytes", bytesElem: elem, value: left, type: BOOL, loc };
8054
+ }
8055
+ if (left.type.kind === "bytes") {
8056
+ return {
8057
+ kind: "seqExpr", stmts: [{ kind: "exprStmt", expr: left, loc }],
8058
+ result: { kind: "boolLit", value: left.type.elem === elem, type: BOOL, loc }, type: BOOL, loc,
8059
+ };
8060
+ }
7229
8061
  }
7230
8062
  }
7231
8063
  // `x instanceof RegExp` over a union with a regex arm (the
@@ -7238,6 +8070,8 @@ export function lowerInstanceOf(lowerer, expr, loc) {
7238
8070
  lowerer.isStdlibGlobal(expr.right, "RegExp") &&
7239
8071
  !lowerer.caughtLocalOf(expr.left)) {
7240
8072
  const left = lowerer.lowerExpr(expr.left);
8073
+ if (left.type.kind === "dyn")
8074
+ return { kind: "libCall", fn: "dyn.nativeRegexIs", args: [left], type: BOOL, loc };
7241
8075
  if (left.type.kind === "union") {
7242
8076
  const def = lowerer.unions.get(left.type.unionId);
7243
8077
  const tag = def ? def.arms.findIndex((a) => a.kind === "regex") : -1;
@@ -7255,7 +8089,7 @@ export function lowerInstanceOf(lowerer, expr, loc) {
7255
8089
  // STANDALONE target class has exactly one possible runtime value
7256
8090
  // (itself — no descendants can flow into the slot), so the answer
7257
8091
  // folds statically exactly like the named-target folds below.
7258
- const rhsClassval = lowerer.mapTypeOf(lowerer.typeOf(expr.right));
8092
+ const rhsClassval = storedClassValueType(lowerer, expr.right);
7259
8093
  if (rhsClassval?.kind === "classval" && !lowerer.caughtLocalOf(expr.left)) {
7260
8094
  const targetInfo = lowerer.classes.get(rhsClassval.className);
7261
8095
  if (!targetInfo) {
@@ -7272,7 +8106,8 @@ export function lowerInstanceOf(lowerer, expr, loc) {
7272
8106
  const lhsInfo = lowerer.classes.get(left.type.className);
7273
8107
  if (!lhsInfo)
7274
8108
  throw new InternalCompilerError(`lowerer bug: unknown class ${left.type.className}`);
7275
- if (lowerer.inHierarchy(targetInfo) && lowerer.inHierarchy(lhsInfo)) {
8109
+ if ((lowerer.inHierarchy(targetInfo) && lowerer.inHierarchy(lhsInfo)) ||
8110
+ (targetInfo.localClass !== undefined && lhsInfo.localClass !== undefined)) {
7276
8111
  const classValue = lowerer.lowerExpr(expr.right);
7277
8112
  if (classValue.type.kind !== "classval")
7278
8113
  lowerer.badType(expr.right, lowerer.typeOf(expr.right));
@@ -7319,6 +8154,9 @@ export function lowerInstanceOf(lowerer, expr, loc) {
7319
8154
  // (name strings are user-writable), so `u instanceof TypeError` keeps
7320
8155
  // the fence. Reads past the narrow bridge through maybeNarrow's
7321
8156
  // validated %Error extraction. SEMANTICS.md 67.
8157
+ if (left.type.kind === "dyn" && !target.def.runtime && !target.builtinError && !target.builtinEmitter && !target.builtinStream && !target.localClass) {
8158
+ return classInstanceOf(lowerer, left, target, loc);
8159
+ }
7322
8160
  if (left.type.kind === "dyn" && target.def.name === "%Error") {
7323
8161
  return { kind: "dynTest", test: "error", value: left, type: BOOL, loc };
7324
8162
  }
@@ -7341,6 +8179,44 @@ export function lowerInstanceOf(lowerer, expr, loc) {
7341
8179
  if (left.type.kind === "dyn") {
7342
8180
  lowerer.unsupported("SC1090", expr, `'instanceof ${target.def.name.replace(/^%/, "")}' on 'unknown' values (only the Error classes answer — test 'instanceof Error' and read '.name')`);
7343
8181
  }
8182
+ if (left.type.kind === "union") {
8183
+ const unionId = left.type.unionId;
8184
+ const arms = lowerer.unions.get(unionId)?.arms;
8185
+ // Ordinary records and scalar values do not carry a class prototype.
8186
+ // Dynamic/opaque runtime objects keep their existing instanceof fences.
8187
+ if (arms?.every((arm) => arm.kind === "object" || arm.kind === "record" ||
8188
+ arm.kind === "f64" || arm.kind === "string" || arm.kind === "bool" ||
8189
+ arm.kind === "bigint" || arm.kind === "symbol" || isUnitType(arm))) {
8190
+ const key = `instanceof.union:${unionId}:${target.def.name}`;
8191
+ let helper = lowerer.widthHelpers.get(key);
8192
+ if (!helper) {
8193
+ helper = `%instanceof.union.${lowerer.widthHelpers.size}`;
8194
+ lowerer.widthHelpers.set(key, helper);
8195
+ const value = { kind: "varRef", localId: "value.0", type: left.type, loc };
8196
+ const body = [];
8197
+ arms.forEach((arm, tag) => {
8198
+ if (arm.kind !== "object")
8199
+ return;
8200
+ const lhsInfo = lowerer.classes.get(arm.className);
8201
+ if (!lhsInfo)
8202
+ throw new InternalCompilerError(`lowerer bug: unknown class ${arm.className}`);
8203
+ const result = lowerer.inHierarchy(lhsInfo) && lowerer.inHierarchy(target)
8204
+ ? { kind: "instanceOf", value: { kind: "unionNarrow", unionId, tag, value, type: arm, loc }, className: target.def.name, type: BOOL, loc }
8205
+ : { kind: "boolLit", value: arm.className === target.def.name || lowerer.isSubclassOf(arm.className, target.def.name), type: BOOL, loc };
8206
+ body.push({
8207
+ kind: "if", cond: { kind: "unionIsTag", unionId, tag, negated: false, value, type: BOOL, loc },
8208
+ then: [{ kind: "return", value: result, loc }], else_: null, loc,
8209
+ });
8210
+ });
8211
+ body.push({ kind: "return", value: { kind: "boolLit", value: false, type: BOOL, loc }, loc });
8212
+ lowerer.liftedFns.push({
8213
+ name: helper, params: [{ localId: "value.0", name: "value", type: left.type }], returnType: BOOL,
8214
+ locals: [{ id: "value.0", name: "value", type: left.type, mutable: false }], body, loc,
8215
+ });
8216
+ }
8217
+ return { kind: "call", callee: helper, args: [left], type: BOOL, loc };
8218
+ }
8219
+ }
7344
8220
  if (left.type.kind !== "object") {
7345
8221
  lowerer.unsupported("SC1090", expr, "'instanceof' on values other than class instances (narrow union-typed values first)");
7346
8222
  }
@@ -7513,6 +8389,12 @@ function lowerPrivateIn(lowerer, expr, priv, loc) {
7513
8389
  * and dyn/unknown stay fenced. Keys are literal strings — a computed key
7514
8390
  * over a shape would need the runtime key table. */
7515
8391
  function lowerInExpression(lowerer, expr, loc) {
8392
+ if (lowerer.isStdlibGlobal(expr.right, "process") && lowerer.foldedStringKeyOf(expr.left) === "hrtime") {
8393
+ return { kind: "boolLit", value: true, type: BOOL, loc };
8394
+ }
8395
+ const globalKey = globalSymbolKey(lowerer, expr.right, expr.left);
8396
+ if (globalKey)
8397
+ return { kind: "libCall", fn: "dyn.globalSymbolHas", args: [globalKey], type: BOOL, loc };
7516
8398
  // `#name in obj` — the ergonomic brand check (ES2022) — resolves
7517
8399
  // before any string-key machinery: the left operand is a private
7518
8400
  // NAME, not a value.
@@ -7564,14 +8446,13 @@ function lowerInExpression(lowerer, expr, loc) {
7564
8446
  // `in` shares the coercion).
7565
8447
  {
7566
8448
  const probed = tryLowerExpression(lowerer, expr.right);
7567
- if (probed?.type.kind === "dyn") {
7568
- let k = lowerer.lowerExpr(expr.left); // JS order: the key evaluates first
7569
- if (k.type.kind === "f64" || k.type.kind === "string" || k.type.kind === "dyn") {
7570
- k = lowerer.ensureString(k, expr.left);
7571
- const recvD = lowerer.lowerExpr(expr.right);
7572
- if (recvD.type.kind === "dyn") {
7573
- return { kind: "libCall", fn: "dyn.hasKey", args: [recvD, k], type: BOOL, loc };
7574
- }
8449
+ if (probed && (probed.type.kind === "dyn" || isDynTypedRefType(probed.type))) {
8450
+ const k = lowerer.lowerExpr(expr.left); // JS order: the key evaluates first
8451
+ if (k.type.kind === "symbol" || k.type.kind === "dyn" || k.type.kind === "string" || k.type.kind === "f64") {
8452
+ const boxed = lowerer.coerceToExpected(k, DYN);
8453
+ const keyLocal = lowerer.declareHiddenLocal("%inKey", DYN);
8454
+ return { kind: "seqExpr", stmts: [{ kind: "varDecl", localId: keyLocal.id, init: boxed, loc }],
8455
+ result: { kind: "libCall", fn: "dyn.hasKeyComputed", args: [lowerer.coerceToExpected(lowerer.lowerExpr(expr.right), DYN), varRef(keyLocal.id, DYN, loc)], type: BOOL, loc }, type: BOOL, loc };
7575
8456
  }
7576
8457
  }
7577
8458
  }
@@ -7597,7 +8478,16 @@ function lowerInExpression(lowerer, expr, loc) {
7597
8478
  };
7598
8479
  return { kind: "unionIsTag", unionId: envType.unionId, tag: undefTag, negated: true, value: read, type: BOOL, loc };
7599
8480
  }
7600
- const recv = lowerer.lowerExpr(expr.right);
8481
+ let recv = lowerer.lowerExpr(expr.right);
8482
+ if (isJsSourceFile(expr.getSourceFile()) && recv.type.kind === "func" && lowerer.dynConvertible(recv.type)) {
8483
+ recv = lowerer.coerceToExpected(recv, DYN);
8484
+ }
8485
+ const siteType = lowerer.mapTypeOf(lowerer.typeOf(expr.right));
8486
+ if (recv.type.kind === "union" && siteType?.kind === "union" && !typeEquals(recv.type, siteType)) {
8487
+ const helper = lowerer.narrowedRetagHelper(expr.right, recv.type.unionId, siteType.unionId, loc);
8488
+ if (helper)
8489
+ recv = { kind: "call", callee: helper, args: [recv], type: siteType, loc };
8490
+ }
7601
8491
  // Error-rooted receivers (builtin or user subclass — the isErrnoException
7602
8492
  // predicate's `err instanceof Error && "code" in err` shape): `code`
7603
8493
  // answers from the runtime error's code slot (stamped by fs/system/
@@ -7622,6 +8512,9 @@ function lowerInExpression(lowerer, expr, loc) {
7622
8512
  while (info && info.base)
7623
8513
  info = info.base;
7624
8514
  if (info?.def.name === "%Error") {
8515
+ if (key === "cause") {
8516
+ return { kind: "libCall", fn: "error.hasCause", args: [recv], type: BOOL, loc };
8517
+ }
7625
8518
  if (key === "code") {
7626
8519
  const codeType = lowerer.envValueType();
7627
8520
  if (codeType.kind !== "union")
@@ -8266,12 +9159,24 @@ export function lowerGroupsProjection(lowerer, recv, groups, loc) {
8266
9159
  }
8267
9160
  return { kind: "call", callee: helper, args: [recv], type: recordT, loc };
8268
9161
  }
8269
- /** Field read `obj.f` on class-instance and record receivers, through the
8270
- * shared FieldTarget union (fieldGet / recordGet). Bound method references
8271
- * on classes are rejected specifically; func-typed record fields are
8272
- * ordinary closure values, so bare references to them work (unlike class
8273
- * methods, which have no bound-value form). */
9162
+ /** Field reads use the shared FieldTarget union. Native method reads select
9163
+ * an interned unbound callable; invocation supplies its receiver separately. */
8274
9164
  export function lowerFieldRead(lowerer, expr) {
9165
+ // A checked receiver already owns its property table. An ambient
9166
+ // generic method signature must not divert its value into native
9167
+ // object-literal specialization (Array.prototype.map is one example).
9168
+ if (ts.isIdentifier(expr.expression)) {
9169
+ const stored = lowerer.peekLocal(expr.expression) ?? lowerer.globalOf(expr.expression);
9170
+ if (stored?.type.kind === "dyn") {
9171
+ const value = lowerer.lowerExpr(expr.expression);
9172
+ if (value.type.kind === "dyn")
9173
+ return {
9174
+ kind: "dynKeyGet", value,
9175
+ key: { kind: "strLit", value: expr.name.text, type: STRING, loc: locOf(expr.name) },
9176
+ ...(expr.questionDotToken || hasOptionalChainGuard(expr.expression) ? { optional: true } : {}), type: DYN, loc: locOf(expr),
9177
+ };
9178
+ }
9179
+ }
8275
9180
  const target = lowerer.fieldTarget(expr);
8276
9181
  if (target)
8277
9182
  return lowerer.fieldGetExpr(target, locOf(expr), expr);
@@ -8288,7 +9193,9 @@ export function lowerFieldRead(lowerer, expr) {
8288
9193
  if (receiverIr?.kind === "object" &&
8289
9194
  (lowerer.findMethodOn(lowerer.classes.get(receiverIr.className) ?? null, expr.name.text) ||
8290
9195
  findGenericMethodOn(lowerer, lowerer.classes.get(receiverIr.className) ?? null, expr.name.text))) {
8291
- lowerer.unsupported("SC1090", expr, `bound method references (call '${expr.name.text}' directly)`);
9196
+ const info = lowerer.classes.get(receiverIr.className);
9197
+ if (info)
9198
+ return lowerClassMethodValue(lowerer, expr, info);
8292
9199
  }
8293
9200
  // An object-literal GENERIC method as a VALUE (`o.m` — the member is
8294
9201
  // excluded from the record shape): the pinned-value rule verbatim when
@@ -8348,16 +9255,22 @@ export function lowerFieldRead(lowerer, expr) {
8348
9255
  * and `switch (r.kind)` work without dedicated test nodes. Anything else
8349
9256
  * on a union receiver is rejected specifically (narrow first). */
8350
9257
  export function lowerUnionProperty(lowerer, expr) {
8351
- if (expr.questionDotToken)
9258
+ if (lowerer.chainBlocked(expr))
8352
9259
  return null;
8353
9260
  const receiverIr = lowerer.mapTypeOf(lowerer.typeOf(expr.expression));
8354
9261
  if (receiverIr?.kind !== "union")
8355
9262
  return null;
8356
- // Lower the receiver FIRST and read its actual IR union: a partially
8357
- // narrowed receiver (checker type = a SUB-union of the binding's union)
8358
- // stays the full union at runtime, and the tag switch must cover the
8359
- // full union's arms.
8360
- const value = lowerer.lowerExpr(expr.expression);
9263
+ // Lower the receiver first: its runtime value can still carry the full
9264
+ // binding union even when the checker has removed several variants.
9265
+ let value = lowerer.lowerExpr(expr.expression);
9266
+ // A guard can remove several variants without selecting one record.
9267
+ // Materialize that sub-union before looking for common fields; arms
9268
+ // ruled out by the guard must not veto a field shared by the survivors.
9269
+ if (value.type.kind === "union" && !typeEquals(value.type, receiverIr)) {
9270
+ const helper = lowerer.narrowedRetagHelper(expr.expression, value.type.unionId, receiverIr.unionId, locOf(expr));
9271
+ if (helper)
9272
+ value = { kind: "call", callee: helper, args: [value], type: receiverIr, loc: locOf(expr) };
9273
+ }
8361
9274
  // A checker-union receiver whose VALUE lowered to a plain RECORD (the
8362
9275
  // merged-signature fiction — `runner(cmd, args)` where runner joined
8363
9276
  // a structural runner type with spawnSync's, and the local adopted
@@ -8453,20 +9366,29 @@ export function lowerUnionProperty(lowerer, expr) {
8453
9366
  const keyed = lowerUnionKeyedRead(lowerer, expr, value.type.unionId, value, key, field);
8454
9367
  if (keyed)
8455
9368
  return keyed;
9369
+ // JavaScript overloads commonly inspect a class brand on a scalar-or-
9370
+ // instance argument (e.g. Matrix4.makeTranslation). Preserve primitive
9371
+ // missing-property semantics and dispatch class data through its native
9372
+ // capsule instead of demanding a field shared by every union arm.
9373
+ if (isJsSourceFile(expr.getSourceFile()) && def.arms.some(isDynTypedRefType) && lowerer.dynConvertible(value.type)) {
9374
+ return { kind: "dynKeyGet", value: lowerer.coerceToExpected(value, DYN), key,
9375
+ ...(hasOptionalChainGuard(expr.expression) ? { optional: true } : {}), type: DYN, loc: locOf(expr) };
9376
+ }
8456
9377
  lowerer.unsupported("SC1090", expr, `reading '${field}' on a union-typed value (every arm must be an object/record ` +
8457
9378
  `with a same-typed field '${field}'; ` +
8458
9379
  `${NARROW_FIRST})`);
8459
9380
  }
8460
9381
  /** The unionDisc generalization: a keyed read on a union receiver whose
8461
9382
  * arms answer DIFFERENT (but joinable) types. Each arm contributes its
8462
- * declared answer — a declared field's type (literal keys), an
9383
+ * declared answer — an array element's type (number keys), a declared field's type (literal keys), an
8463
9384
  * index-signature arm's value type (plus its declared fields' types for
8464
9385
  * runtime keys, which reach them through the keyed-read helper), and
8465
9386
  * UNDEFINED for unit arms (reachable only through optional-chain tails,
8466
9387
  * where JS short-circuits to undefined; a unit arm the checker narrowed
8467
9388
  * away is simply unreachable). The result type is the JOIN of those
8468
- * answers; every arm's answer must be the join itself or one of its
8469
- * arms (sub-union RE-TAGGING between distinct unions stays fenced).
9389
+ * answers; every arm's answer must be the join itself, one of its
9390
+ * arms, or a sub-union whose payload representations are unchanged.
9391
+ * Array reads retain the ordinary invalid-index trap policy.
8470
9392
  * Returns null when any arm cannot answer — the caller owns the fence
8471
9393
  * message. The caller (the property/element dispatch) maybeNarrows. */
8472
9394
  function lowerUnionKeyedRead(lowerer, expr, unionId, value, key, literalField) {
@@ -8502,6 +9424,11 @@ function lowerUnionKeyedRead(lowerer, expr, unionId, value, key, literalField) {
8502
9424
  push(UNDEFINED_T);
8503
9425
  continue;
8504
9426
  }
9427
+ if (key.type.kind === "f64") {
9428
+ if (arm.kind !== "array" || !pushAnswer(arm.elem))
9429
+ return null;
9430
+ continue;
9431
+ }
8505
9432
  if (arm.kind !== "record")
8506
9433
  return null;
8507
9434
  const shape = lowerer.shapes.get(arm.shapeId);
@@ -8531,13 +9458,11 @@ function lowerUnionKeyedRead(lowerer, expr, unionId, value, key, literalField) {
8531
9458
  if (joinArms.length === 0)
8532
9459
  return null;
8533
9460
  // The join must be a BUILDABLE union: arm kinds the union invariants
8534
- // admit (no map/dyn/jsval/generator arms; func arms only beside
8535
- // func/unit siblings — unionFuncSetArmsOk, the validator's rule). A
8536
- // join mixing, say, a func answer with data answers has no union to
8537
- // surface as; the caller owns the fence message.
9461
+ // admit (no dyn/jsval/generator arms; containers only beside unit
9462
+ // siblings). The caller owns the diagnostic for a refused join.
8538
9463
  if (joinArms.length > 1 &&
8539
- (!unionFuncSetArmsOk(joinArms) ||
8540
- joinArms.some((a) => a.kind === "map" || a.kind === "dyn" || a.kind === "jsval" || a.kind === "generator" || a.kind === "caught"))) {
9464
+ (!unionContainerArmsOk(joinArms) ||
9465
+ joinArms.some((a) => a.kind === "dyn" || a.kind === "jsval" || a.kind === "generator" || a.kind === "caught"))) {
8541
9466
  return null;
8542
9467
  }
8543
9468
  joinArms.sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1));
@@ -8545,13 +9470,19 @@ function lowerUnionKeyedRead(lowerer, expr, unionId, value, key, literalField) {
8545
9470
  // Pass 2: every arm's answer must SURFACE as the join, and index arms
8546
9471
  // must pass the single-record keyed-read constraints (the helper is
8547
9472
  // shared with recordKeyGet, its missing-key policy included).
8548
- const surfaces = (t) => typeEquals(t, type) || (type.kind === "union" && lowerer.armTag(type.unionId, t) >= 0);
9473
+ const surfaces = (t) => typeEquals(t, type) || (type.kind === "union" && lowerer.armTag(type.unionId, t) >= 0) ||
9474
+ exactUnionWidening(lowerer, t, type);
8549
9475
  for (const arm of def.arms) {
8550
9476
  if (isUnitType(arm)) {
8551
9477
  if (!(type.kind === "union" && lowerer.armTag(type.unionId, UNDEFINED_T) >= 0))
8552
9478
  return null;
8553
9479
  continue;
8554
9480
  }
9481
+ if (key.type.kind === "f64") {
9482
+ if (arm.kind !== "array" || !surfaces(arm.elem))
9483
+ return null;
9484
+ continue;
9485
+ }
8555
9486
  if (arm.kind !== "record")
8556
9487
  return null;
8557
9488
  const shape = lowerer.shapes.get(arm.shapeId);
@@ -8569,6 +9500,75 @@ function lowerUnionKeyedRead(lowerer, expr, unionId, value, key, literalField) {
8569
9500
  }
8570
9501
  return { kind: "unionKeyGet", unionId, key, value, type, loc: locOf(expr) };
8571
9502
  }
9503
+ /** A structural TypeScript view does not change a nominal object's layout
9504
+ * or accessor dispatch. Resolve the property from the represented class;
9505
+ * the optional checker view must not turn its getter into a record read. */
9506
+ function classFieldTarget(lowerer, receiverNode, receiverType, fieldName, saved) {
9507
+ const info = lowerer.classes.get(receiverType.className);
9508
+ if (!info) {
9509
+ // A receiver typed as a class whose collection deferred: the
9510
+ // deferred diagnostics are what explains the miss.
9511
+ lowerer.flushDeferredClass(receiverType.className);
9512
+ return null;
9513
+ }
9514
+ const fieldType = info.fields.get(fieldName);
9515
+ const lowerObjectReceiver = () => {
9516
+ let obj = saved ?? lowerer.lowerExpr(receiverNode);
9517
+ if (obj.type.kind === "union") {
9518
+ const helper = lowerer.narrowedArmHelper(obj.type.unionId, receiverType, locOf(receiverNode));
9519
+ obj = helper
9520
+ ? { kind: "call", callee: helper, args: [obj], type: receiverType, loc: locOf(receiverNode) }
9521
+ : lowerer.maybeNarrow(obj, receiverNode);
9522
+ }
9523
+ return obj.type.kind === "dyn" ? lowerer.coerceInto(receiverNode, obj, receiverType) : obj;
9524
+ };
9525
+ if (fieldName === "cause" && receiverType.className !== "%DOMException") {
9526
+ let root = info;
9527
+ while (root.base)
9528
+ root = root.base;
9529
+ if (root.def.name === "%Error") {
9530
+ return { container: "errorCause", obj: lowerObjectReceiver(), field: "cause", fieldType: DYN };
9531
+ }
9532
+ }
9533
+ if (fieldType) {
9534
+ const obj = lowerObjectReceiver();
9535
+ return { container: "class", obj, className: receiverType.className, field: fieldName, fieldType };
9536
+ }
9537
+ // Accessor property: either half declared anywhere on the chain
9538
+ // makes the name an accessor target (fields and accessors share a
9539
+ // namespace — tsc rejects mixing them, so the halves agree on kind).
9540
+ const getF = lowerer.findMethodOn(info, `get:${fieldName}`);
9541
+ const setF = lowerer.findMethodOn(info, `set:${fieldName}`);
9542
+ if (getF || setF) {
9543
+ const obj = lowerObjectReceiver();
9544
+ return {
9545
+ container: "accessor",
9546
+ obj,
9547
+ className: receiverType.className,
9548
+ field: fieldName,
9549
+ fieldType: getF ? getF.sig.ret : setF.sig.params[0].type,
9550
+ };
9551
+ }
9552
+ return null;
9553
+ }
9554
+ /** A structural assertion can leave the value in its original class
9555
+ * representation, including an unchecked array read's undefined arm. Only
9556
+ * a single nominal payload has an unambiguous property layout. Validate
9557
+ * that payload before dispatching a field or accessor; an absent receiver
9558
+ * must never reach the backend as an object pointer. */
9559
+ function hasClassPayload(lowerer, type) {
9560
+ return type.kind === "object" || type.kind === "union" &&
9561
+ (lowerer.unions.get(type.unionId)?.arms.some((arm) => arm.kind === "object") ?? false);
9562
+ }
9563
+ function representedClassFieldTarget(lowerer, receiverNode, fieldName, value) {
9564
+ const arms = value.type.kind === "union"
9565
+ ? lowerer.unions.get(value.type.unionId)?.arms.filter((arm) => !isUnitType(arm))
9566
+ : [value.type];
9567
+ const objectType = arms?.length === 1 ? arms[0] : null;
9568
+ return objectType?.kind === "object"
9569
+ ? classFieldTarget(lowerer, receiverNode, objectType, fieldName, value)
9570
+ : null;
9571
+ }
8572
9572
  /** Recognizes `obj.field` as an assignable field target: receiver is a
8573
9573
  * known class instance OR a record, and the member is a field or (class
8574
9574
  * receivers) a declared accessor property. Returns the pieces of a
@@ -8576,55 +9576,26 @@ function lowerUnionKeyedRead(lowerer, expr, unionId, value, key, literalField) {
8576
9576
  export function fieldTarget(lowerer, access) {
8577
9577
  if (lowerer.chainBlocked(access))
8578
9578
  return null;
8579
- const receiverIr = lowerer.mapTypeOf(lowerer.typeOf(access.expression));
9579
+ if (access.name.text === "stackTraceLimit" && lowerer.isStdlibGlobal(access.expression, "Error")) {
9580
+ return { container: "errorStackLimit", obj: { kind: "numLit", value: 0, type: F64, loc: locOf(access) }, field: "stackTraceLimit", fieldType: F64 };
9581
+ }
9582
+ const stored = ts.isIdentifier(access.expression)
9583
+ ? lowerer.peekLocal(access.expression)?.type ?? lowerer.globalOf(access.expression)?.type : undefined;
9584
+ const receiverIr = stored?.kind === "record" ? stored : lowerer.mapTypeOf(lowerer.typeOf(access.expression));
8580
9585
  if (receiverIr?.kind === "object") {
8581
- const info = lowerer.classes.get(receiverIr.className);
8582
- if (!info) {
8583
- // A receiver typed as a class whose collection deferred: the
8584
- // deferred diagnostics are what explains the miss.
8585
- lowerer.flushDeferredClass(receiverIr.className);
8586
- return null;
8587
- }
8588
- const fieldType = info.fields.get(access.name.text);
8589
- const lowerObjectReceiver = () => {
8590
- let obj = lowerer.lowerExpr(access.expression);
8591
- if (obj.type.kind === "union" && lowerer.armTag(obj.type.unionId, UNDEFINED_T) >= 0) {
8592
- const present = lowerer.stripUndefinedArm(obj.type);
8593
- const helper = present.kind === "object"
8594
- ? lowerer.narrowedArmHelper(obj.type.unionId, present, locOf(access.expression))
8595
- : null;
8596
- obj = helper
8597
- ? { kind: "call", callee: helper, args: [obj], type: present, loc: locOf(access.expression) }
8598
- : lowerer.maybeNarrow(obj, access.expression);
8599
- }
8600
- return obj;
8601
- };
8602
- if (fieldType) {
8603
- const obj = lowerObjectReceiver();
8604
- return { container: "class", obj, className: receiverIr.className, field: access.name.text, fieldType };
8605
- }
8606
- // Accessor property: either half declared anywhere on the chain
8607
- // makes the name an accessor target (fields and accessors share a
8608
- // namespace — tsc rejects mixing them, so the halves agree on kind).
8609
- const getF = lowerer.findMethodOn(info, `get:${access.name.text}`);
8610
- const setF = lowerer.findMethodOn(info, `set:${access.name.text}`);
8611
- if (getF || setF) {
8612
- const obj = lowerObjectReceiver();
8613
- return {
8614
- container: "accessor",
8615
- obj,
8616
- className: receiverIr.className,
8617
- field: access.name.text,
8618
- fieldType: getF ? getF.sig.ret : setF.sig.params[0].type,
8619
- };
8620
- }
8621
- return null;
9586
+ return classFieldTarget(lowerer, access.expression, receiverIr, access.name.text);
9587
+ }
9588
+ if (receiverIr?.kind === "union" && hasClassPayload(lowerer, receiverIr)) {
9589
+ return representedClassFieldTarget(lowerer, access.expression, access.name.text, lowerer.lowerExpr(access.expression));
8622
9590
  }
8623
9591
  if (receiverIr?.kind === "record") {
8624
9592
  const shape = lowerer.shapes.get(receiverIr.shapeId);
8625
9593
  const fieldType = shape?.fields.find((f) => f.name === access.name.text)?.type;
8626
9594
  if (fieldType) {
8627
9595
  let obj = lowerer.lowerExpr(access.expression);
9596
+ const nominal = representedClassFieldTarget(lowerer, access.expression, access.name.text, obj);
9597
+ if (nominal)
9598
+ return nominal;
8628
9599
  // A checker-record receiver whose VALUE stayed dyn (the erased
8629
9600
  // all-unknown-fields cast — `(err as { code?: unknown }).code`):
8630
9601
  // decline, and the dyn keyed-read fallback answers.
@@ -8758,7 +9729,7 @@ export function uniqueSymbolKeyOf(lowerer, key) {
8758
9729
  // Module level only: a unique-symbol const inside a FUNCTION is a
8759
9730
  // fresh runtime identity per call — one static slot would conflate
8760
9731
  // what JS keeps distinct.
8761
- if (!ts.isVariableStatement(decl.parent.parent) || !ts.isSourceFile(decl.parent.parent.parent))
9732
+ if (!ts.isVariableStatement(decl.parent?.parent) || !ts.isSourceFile(decl.parent?.parent?.parent))
8762
9733
  return null;
8763
9734
  const init = decl.initializer;
8764
9735
  if (!init || !ts.isCallExpression(init) || init.questionDotToken)
@@ -8791,10 +9762,10 @@ export function symbolFieldInfo(lowerer, expr) {
8791
9762
  lowerer.flushDeferredClass(receiverIr.className);
8792
9763
  return null;
8793
9764
  }
8794
- const key = uniqueSymbolKeyOf(lowerer, expr.argumentExpression);
9765
+ const key = classSymbolKeyOf(lowerer, expr.argumentExpression);
8795
9766
  if (!key)
8796
9767
  return null;
8797
- const field = info.symbolFields?.get(key.sym);
9768
+ const field = info.symbolFields?.get(key.identity);
8798
9769
  if (field === undefined)
8799
9770
  return null;
8800
9771
  const fieldType = info.fields.get(field);
@@ -8816,6 +9787,12 @@ function symbolFieldTarget(lowerer, expr) {
8816
9787
  * tsc-clean (the property types as the setter's param), but Node yields
8817
9788
  * undefined, which these property types cannot represent. */
8818
9789
  export function fieldGetExpr(lowerer, target, loc, blame) {
9790
+ if (target.container === "errorStackLimit") {
9791
+ return { kind: "libCall", fn: "error.stackLimitGet", args: [], type: F64, loc };
9792
+ }
9793
+ if (target.container === "errorCause") {
9794
+ return { kind: "libCall", fn: "error.cause", args: [target.obj], type: DYN, loc };
9795
+ }
8819
9796
  // A record-shaped CHECKER target whose receiver VALUE lives in the checked-dynamic tree
8820
9797
  // (a JS file-scope object-literal global): the checked-dynamic keyed
8821
9798
  // read — dynKeyGet (a missing key answers the dyn undefined, exactly
@@ -8912,6 +9889,12 @@ export function fieldGetExpr(lowerer, target, loc, blame) {
8912
9889
  * call). A write to a getter-only property never gets here in a clean
8913
9890
  * program (tsc's TS2540 is the fence); the rejection is the backstop. */
8914
9891
  export function fieldSetStmt(lowerer, target, value, loc, blame) {
9892
+ if (target.container === "errorStackLimit") {
9893
+ return { kind: "exprStmt", expr: { kind: "libCall", fn: "error.stackLimitSet", args: [value], type: VOID, loc }, loc };
9894
+ }
9895
+ if (target.container === "errorCause") {
9896
+ return { kind: "exprStmt", expr: { kind: "libCall", fn: "error.setCause", args: [target.obj, value], type: VOID, loc }, loc };
9897
+ }
8915
9898
  // A record-shaped CHECKER target whose receiver VALUE lives in the checked-dynamic tree:
8916
9899
  // the checked-dynamic keyed write — dyn.keySet (later writes win,
8917
9900
  // insertion order; Node's TypeErrors on non-object receivers), the
@@ -9080,61 +10063,73 @@ export function lowerPromiseAllTupleCall(lowerer, call, access) {
9080
10063
  }
9081
10064
  /** `obj.f op= e` (and `obj.f++` with rhs null ≡ 1) — the element spelling
9082
10065
  * `obj[k] op= e` included when k is a declared symbol-keyed field.
9083
- * Restricted to side-effect-free receivers (identifier or `this`)
9084
- * because the desugar evaluates the receiver twice. */
10066
+ * Save the receiver and old value before the RHS, then write through
10067
+ * that same receiver even if the RHS replaces its original binding. */
9085
10068
  export function lowerFieldCompound(lowerer, access, op, rhsNode, loc) {
10069
+ return { kind: "exprStmt", expr: lowerFieldCompoundValue(lowerer, access, op, rhsNode, loc), loc };
10070
+ }
10071
+ function lowerFieldCompoundValue(lowerer, access, op, rhsNode, loc) {
9086
10072
  if (access.expression.kind === ts.SyntaxKind.SuperKeyword) {
9087
10073
  lowerer.unsupported("SC1090", access, "compound assignment through 'super' (read and write separately)");
9088
10074
  }
9089
- if (!ts.isIdentifier(access.expression) && access.expression.kind !== ts.SyntaxKind.ThisKeyword) {
9090
- lowerer.unsupported("SC1090", access, "compound assignment to fields of computed receivers");
9091
- }
10075
+ const body = [];
10076
+ const save = (value, name) => {
10077
+ const local = lowerer.declareHiddenLocal(name, value.type);
10078
+ body.push({ kind: "varDecl", localId: local.id, init: value, loc: value.loc });
10079
+ return varRef(local.id, value.type, value.loc);
10080
+ };
9092
10081
  // A CHECKED-DYNAMIC receiver (`context.actual++` — test/common's call
9093
10082
  // accounting; dot spelling only — symbol-keyed element targets are
9094
10083
  // static fields): read the member (dynKeyGet), VALIDATE it as a
9095
10084
  // number (dynCheck — a non-number member throws the catchable
9096
10085
  // TypeError where JS would ToNumber-coerce; loud, never a silent
9097
10086
  // NaN — SEMANTICS.md), combine, write back (dyn.keySet). The
9098
- // receiver is an identifier (checked above), so evaluating it for
9099
- // read and write matches JS's once-evaluation observably.
10087
+ // receiver and member read are saved before evaluating the RHS.
9100
10088
  if (ts.isPropertyAccessExpression(access)) {
9101
10089
  const probed = tryLowerExpression(lowerer, access.expression);
9102
10090
  if (probed?.type.kind === "dyn") {
10091
+ const receiver = save(probed, "%compoundReceiver");
9103
10092
  const key = { kind: "strLit", value: access.name.text, type: STRING, loc: locOf(access.name) };
9104
- const read = { kind: "dynKeyGet", key, value: probed, type: DYN, loc };
10093
+ const read = save({ kind: "dynKeyGet", key, value: receiver, type: DYN, loc }, "%compoundOld");
9105
10094
  const cur = { kind: "dynCheck", value: read, type: F64, loc };
9106
- const rhs = rhsNode
10095
+ const rhs = save(rhsNode
9107
10096
  ? lowerer.lowerExpr(rhsNode)
9108
- : { kind: "numLit", value: 1, type: F64, loc };
9109
- if (rhs.type.kind !== "f64")
10097
+ : { kind: "numLit", value: 1, type: F64, loc }, "%compoundRhs");
10098
+ const numericRhs = rhs.type.kind === "dyn"
10099
+ ? { kind: "dynCheck", value: rhs, type: F64, loc: rhs.loc }
10100
+ : lowerOptionalNumber(lowerer, rhs, loc);
10101
+ if (numericRhs.type.kind !== "f64")
9110
10102
  lowerer.unsupported("SC1043", access);
9111
- const value = { kind: "bin", op, left: cur, right: rhs, type: F64, loc };
9112
- const recv2 = lowerer.lowerExpr(access.expression);
10103
+ const value = save({ kind: "bin", op, left: cur, right: numericRhs, type: F64, loc }, "%compoundResult");
9113
10104
  const boxed = { kind: "dynFrom", value, type: DYN, loc };
9114
- return {
10105
+ body.push({
9115
10106
  kind: "exprStmt",
9116
- expr: { kind: "libCall", fn: "dyn.keySet", args: [recv2, { ...key }, boxed], type: VOID, loc },
10107
+ expr: { kind: "libCall", fn: "dyn.keySet", args: [receiver, { ...key }, boxed], type: VOID, loc },
9117
10108
  loc,
9118
- };
10109
+ });
10110
+ return { kind: "seqExpr", stmts: body, result: value, type: value.type, loc };
9119
10111
  }
9120
10112
  }
9121
10113
  const targetOf = () => ts.isPropertyAccessExpression(access) ? lowerer.fieldTarget(access) : symbolFieldTarget(lowerer, access);
9122
10114
  const target = targetOf();
9123
10115
  if (!target)
9124
10116
  lowerer.unsupported("SC1090", access, "compound assignment to unsupported field targets");
9125
- // Through an accessor target this desugars to get, op, set — with the
9126
- // receiver an identifier/this, the observable order matches JS exactly:
9127
- // getter, rhs side effects, setter (verified against Node).
9128
- const read = lowerer.fieldGetExpr(target, locOf(access), access);
9129
- const rhs = rhsNode
10117
+ target.obj = save(target.obj, "%compoundReceiver");
10118
+ // Accessors observe getter, RHS side effects, then setter, all through
10119
+ // the saved receiver.
10120
+ const read = save(lowerer.fieldGetExpr(target, locOf(access), access), "%compoundOld");
10121
+ const rhs = save(rhsNode
9130
10122
  ? lowerer.lowerExpr(rhsNode)
9131
- : { kind: "numLit", value: 1, type: F64, loc };
10123
+ : { kind: "numLit", value: 1, type: F64, loc }, "%compoundRhs");
10124
+ const numericRhs = rhs.type.kind === "dyn" && isJsSourceFile(access.getSourceFile())
10125
+ ? { kind: "dynCheck", value: rhs, type: F64, loc: rhs.loc }
10126
+ : lowerOptionalNumber(lowerer, rhs, loc);
9132
10127
  let value;
9133
10128
  if (op === "+" && target.fieldType.kind === "string") {
9134
10129
  value = { kind: "strConcat", left: read, right: lowerer.ensureString(rhs, rhsNode ?? access), type: STRING, loc };
9135
10130
  }
9136
- else if (target.fieldType.kind === "f64" && rhs.type.kind === "f64") {
9137
- value = { kind: "bin", op, left: read, right: rhs, type: F64, loc };
10131
+ else if (target.fieldType.kind === "f64" && numericRhs.type.kind === "f64") {
10132
+ value = { kind: "bin", op, left: read, right: numericRhs, type: F64, loc };
9138
10133
  }
9139
10134
  else if (target.fieldType.kind === "dyn" &&
9140
10135
  isJsSourceFile(access.getSourceFile()) &&
@@ -9170,9 +10165,9 @@ export function lowerFieldCompound(lowerer, access, op, rhsNode, loc) {
9170
10165
  else {
9171
10166
  lowerer.unsupported("SC1043", access);
9172
10167
  }
9173
- // Second, independent evaluation of the (side-effect-free) receiver.
9174
- const reevaluatedTarget = targetOf();
9175
- return lowerer.fieldSetStmt(reevaluatedTarget, value, loc, access);
10168
+ const result = save(value, "%compoundResult");
10169
+ body.push(lowerer.fieldSetStmt(target, result, loc, access));
10170
+ return { kind: "seqExpr", stmts: body, result, type: result.type, loc };
9176
10171
  }
9177
10172
  /** Stream-rooted receivers' property surface (readableEnded, destroyed,
9178
10173
  * ...): the receiver's mapped class dispatches into lower-stream.ts.