@scriptc/compiler 0.1.7 → 0.2.1

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 (1043) hide show
  1. package/ambient/scriptc-node-fallback.d.ts +98 -39
  2. package/ambient/scriptc-overrides.d.ts +10 -6
  3. package/ambient/scriptc.d.ts +15 -0
  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 +108 -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/executable-features.d.ts +4 -0
  16. package/dist/backend/executable-features.js +42 -0
  17. package/dist/backend/executable-features.js.map +1 -0
  18. package/dist/backend/external-c.d.ts +5 -27
  19. package/dist/backend/external-c.js +5 -40
  20. package/dist/backend/external-c.js.map +1 -1
  21. package/dist/backend/ffi-callbacks.d.ts +2 -3
  22. package/dist/backend/ffi-callbacks.js +2 -3
  23. package/dist/backend/ffi-callbacks.js.map +1 -1
  24. package/dist/backend/library-identity-markers.d.ts +4 -9
  25. package/dist/backend/library-identity-markers.js +11 -49
  26. package/dist/backend/library-identity-markers.js.map +1 -1
  27. package/dist/backend/library-pack.d.ts +5 -0
  28. package/dist/backend/library-pack.js +123 -0
  29. package/dist/backend/library-pack.js.map +1 -0
  30. package/dist/backend/link-plan-core.d.ts +20 -0
  31. package/dist/backend/link-plan-core.js +18 -0
  32. package/dist/backend/link-plan-core.js.map +1 -0
  33. package/dist/backend/link-plan.d.ts +1 -1
  34. package/dist/backend/link-plan.js +16 -17
  35. package/dist/backend/link-plan.js.map +1 -1
  36. package/dist/backend/link-trace.d.ts +2 -0
  37. package/dist/backend/link-trace.js +24 -0
  38. package/dist/backend/link-trace.js.map +1 -0
  39. package/dist/backend/linker.js +1 -1
  40. package/dist/backend/linker.js.map +1 -1
  41. package/dist/backend/literals.d.ts +9 -0
  42. package/dist/backend/literals.js +35 -0
  43. package/dist/backend/literals.js.map +1 -0
  44. package/dist/backend/literals.test.d.ts +1 -0
  45. package/dist/backend/literals.test.js +53 -0
  46. package/dist/backend/literals.test.js.map +1 -0
  47. package/dist/backend/llvm/blocks.d.ts +2 -0
  48. package/dist/backend/llvm/blocks.js +9 -4
  49. package/dist/backend/llvm/blocks.js.map +1 -1
  50. package/dist/backend/llvm/blocks.test.d.ts +1 -0
  51. package/dist/backend/llvm/blocks.test.js +91 -0
  52. package/dist/backend/llvm/blocks.test.js.map +1 -0
  53. package/dist/backend/llvm/classes.d.ts +14 -8
  54. package/dist/backend/llvm/classes.js +88 -30
  55. package/dist/backend/llvm/classes.js.map +1 -1
  56. package/dist/backend/llvm/common.d.ts +5 -1
  57. package/dist/backend/llvm/common.js +45 -4
  58. package/dist/backend/llvm/common.js.map +1 -1
  59. package/dist/backend/llvm/debug-info.js +2 -7
  60. package/dist/backend/llvm/debug-info.js.map +1 -1
  61. package/dist/backend/llvm/debug-info.test.d.ts +1 -0
  62. package/dist/backend/llvm/debug-info.test.js +110 -0
  63. package/dist/backend/llvm/debug-info.test.js.map +1 -0
  64. package/dist/backend/llvm/dyn.d.ts +21 -2
  65. package/dist/backend/llvm/dyn.js +647 -251
  66. package/dist/backend/llvm/dyn.js.map +1 -1
  67. package/dist/backend/llvm/emitter.d.ts +596 -1
  68. package/dist/backend/llvm/emitter.js +769 -425
  69. package/dist/backend/llvm/emitter.js.map +1 -1
  70. package/dist/backend/llvm/emitter.test.d.ts +1 -0
  71. package/dist/backend/llvm/emitter.test.js +179 -0
  72. package/dist/backend/llvm/emitter.test.js.map +1 -0
  73. package/dist/backend/llvm/expr-async.js +23 -14
  74. package/dist/backend/llvm/expr-async.js.map +1 -1
  75. package/dist/backend/llvm/expr-bytes.d.ts +2 -2
  76. package/dist/backend/llvm/expr-bytes.js +60 -31
  77. package/dist/backend/llvm/expr-bytes.js.map +1 -1
  78. package/dist/backend/llvm/expr-callbacks.d.ts +1 -1
  79. package/dist/backend/llvm/expr-callbacks.js +25 -22
  80. package/dist/backend/llvm/expr-callbacks.js.map +1 -1
  81. package/dist/backend/llvm/expr-calls.js +136 -35
  82. package/dist/backend/llvm/expr-calls.js.map +1 -1
  83. package/dist/backend/llvm/expr-containers.d.ts +1 -1
  84. package/dist/backend/llvm/expr-containers.js +43 -14
  85. package/dist/backend/llvm/expr-containers.js.map +1 -1
  86. package/dist/backend/llvm/expr-context.d.ts +5 -260
  87. package/dist/backend/llvm/expr-control.js +10 -6
  88. package/dist/backend/llvm/expr-control.js.map +1 -1
  89. package/dist/backend/llvm/expr-dispatch.d.ts +1 -1
  90. package/dist/backend/llvm/expr-dispatch.js +2 -0
  91. package/dist/backend/llvm/expr-dispatch.js.map +1 -1
  92. package/dist/backend/llvm/expr-dynamic.js +78 -81
  93. package/dist/backend/llvm/expr-dynamic.js.map +1 -1
  94. package/dist/backend/llvm/expr-island.d.ts +1 -1
  95. package/dist/backend/llvm/expr-island.js +11 -7
  96. package/dist/backend/llvm/expr-island.js.map +1 -1
  97. package/dist/backend/llvm/expr-primitives.d.ts +1 -1
  98. package/dist/backend/llvm/expr-primitives.js +62 -31
  99. package/dist/backend/llvm/expr-primitives.js.map +1 -1
  100. package/dist/backend/llvm/expr-primitives.test.js +1 -1
  101. package/dist/backend/llvm/expr-records.d.ts +5 -1
  102. package/dist/backend/llvm/expr-records.js +54 -7
  103. package/dist/backend/llvm/expr-records.js.map +1 -1
  104. package/dist/backend/llvm/expr-stream-bridges.d.ts +4 -4
  105. package/dist/backend/llvm/expr-stream-bridges.js +220 -131
  106. package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
  107. package/dist/backend/llvm/expr-stream-bridges.test.d.ts +1 -0
  108. package/dist/backend/llvm/expr-stream-bridges.test.js +55 -0
  109. package/dist/backend/llvm/expr-stream-bridges.test.js.map +1 -0
  110. package/dist/backend/llvm/expr-stream-callbacks.d.ts +1 -1
  111. package/dist/backend/llvm/expr-stream-callbacks.js +12 -12
  112. package/dist/backend/llvm/expr-stream-callbacks.js.map +1 -1
  113. package/dist/backend/llvm/layouts.d.ts +19 -0
  114. package/dist/backend/llvm/layouts.js +12 -0
  115. package/dist/backend/llvm/layouts.js.map +1 -0
  116. package/dist/backend/llvm/lib-abi.d.ts +19 -0
  117. package/dist/backend/llvm/lib-abi.js +82 -0
  118. package/dist/backend/llvm/lib-abi.js.map +1 -0
  119. package/dist/backend/llvm/lib-connect.d.ts +2 -0
  120. package/dist/backend/llvm/lib-connect.js +51 -0
  121. package/dist/backend/llvm/lib-connect.js.map +1 -0
  122. package/dist/backend/llvm/lib-datagram.d.ts +2 -0
  123. package/dist/backend/llvm/lib-datagram.js +135 -0
  124. package/dist/backend/llvm/lib-datagram.js.map +1 -0
  125. package/dist/backend/llvm/lib-dispatch.js +50 -6
  126. package/dist/backend/llvm/lib-dispatch.js.map +1 -1
  127. package/dist/backend/llvm/lib-filesystem.js +30 -18
  128. package/dist/backend/llvm/lib-filesystem.js.map +1 -1
  129. package/dist/backend/llvm/lib-http2.d.ts +2 -0
  130. package/dist/backend/llvm/lib-http2.js +215 -0
  131. package/dist/backend/llvm/lib-http2.js.map +1 -0
  132. package/dist/backend/llvm/lib-lookup.d.ts +2 -0
  133. package/dist/backend/llvm/lib-lookup.js +39 -0
  134. package/dist/backend/llvm/lib-lookup.js.map +1 -0
  135. package/dist/backend/llvm/lib-network.js +25 -2
  136. package/dist/backend/llvm/lib-network.js.map +1 -1
  137. package/dist/backend/llvm/lib-process.js +44 -19
  138. package/dist/backend/llvm/lib-process.js.map +1 -1
  139. package/dist/backend/llvm/lib-shared.js +162 -3
  140. package/dist/backend/llvm/lib-shared.js.map +1 -1
  141. package/dist/backend/llvm/lib-stream.js +3 -2
  142. package/dist/backend/llvm/lib-stream.js.map +1 -1
  143. package/dist/backend/llvm/lib-test.d.ts +2 -0
  144. package/dist/backend/llvm/lib-test.js +34 -0
  145. package/dist/backend/llvm/lib-test.js.map +1 -0
  146. package/dist/backend/llvm/lib-tls.d.ts +2 -0
  147. package/dist/backend/llvm/lib-tls.js +82 -0
  148. package/dist/backend/llvm/lib-tls.js.map +1 -0
  149. package/dist/backend/llvm/shapes.d.ts +10 -9
  150. package/dist/backend/llvm/shapes.js +42 -131
  151. package/dist/backend/llvm/shapes.js.map +1 -1
  152. package/dist/backend/llvm/unsupported.d.ts +1 -0
  153. package/dist/backend/llvm/unsupported.js +1 -2
  154. package/dist/backend/llvm/unsupported.js.map +1 -1
  155. package/dist/backend/llvm/walkers.js +1 -7
  156. package/dist/backend/llvm/walkers.js.map +1 -1
  157. package/dist/backend/mangle.d.ts +2 -2
  158. package/dist/backend/mangle.js +16 -5
  159. package/dist/backend/mangle.js.map +1 -1
  160. package/dist/backend/{c/may-throw.d.ts → may-throw.d.ts} +1 -1
  161. package/dist/backend/{c/may-throw.js → may-throw.js} +51 -46
  162. package/dist/backend/may-throw.js.map +1 -0
  163. package/dist/backend/may-throw.test.d.ts +1 -0
  164. package/dist/backend/may-throw.test.js +58 -0
  165. package/dist/backend/may-throw.test.js.map +1 -0
  166. package/dist/backend/native-codegen-core.d.ts +22 -0
  167. package/dist/backend/native-codegen-core.js +44 -0
  168. package/dist/backend/native-codegen-core.js.map +1 -0
  169. package/dist/backend/native-codegen.d.ts +3 -19
  170. package/dist/backend/native-codegen.js +5 -44
  171. package/dist/backend/native-codegen.js.map +1 -1
  172. package/dist/backend/native-codegen.test.js +2 -2
  173. package/dist/backend/native-codegen.test.js.map +1 -1
  174. package/dist/backend/native-link-info-core.d.ts +94 -0
  175. package/dist/backend/native-link-info-core.js +62 -0
  176. package/dist/backend/native-link-info-core.js.map +1 -0
  177. package/dist/backend/native-link-info.d.ts +3 -88
  178. package/dist/backend/native-link-info.js +12 -243
  179. package/dist/backend/native-link-info.js.map +1 -1
  180. package/dist/backend/native-link-info.test.js +41 -146
  181. package/dist/backend/native-link-info.test.js.map +1 -1
  182. package/dist/backend/native-toolchain.d.ts +12 -30
  183. package/dist/backend/native-toolchain.js +83 -233
  184. package/dist/backend/native-toolchain.js.map +1 -1
  185. package/dist/backend/native-toolchain.test.js +77 -5
  186. package/dist/backend/native-toolchain.test.js.map +1 -1
  187. package/dist/backend/native-tools.d.ts +24 -0
  188. package/dist/backend/native-tools.js +33 -0
  189. package/dist/backend/native-tools.js.map +1 -0
  190. package/dist/backend/object-localize.js +19 -8
  191. package/dist/backend/object-localize.js.map +1 -1
  192. package/dist/backend/object-localize.test.js +22 -0
  193. package/dist/backend/object-localize.test.js.map +1 -1
  194. package/dist/backend/runtime-abi.d.ts +2 -2
  195. package/dist/backend/runtime-abi.js +2 -2
  196. package/dist/backend/runtime-pack-core.d.ts +101 -0
  197. package/dist/backend/runtime-pack-core.js +172 -0
  198. package/dist/backend/runtime-pack-core.js.map +1 -0
  199. package/dist/backend/runtime-pack-native.d.ts +21 -0
  200. package/dist/backend/runtime-pack-native.js +73 -0
  201. package/dist/backend/runtime-pack-native.js.map +1 -0
  202. package/dist/backend/runtime-pack-targets.test.js +1 -1
  203. package/dist/backend/runtime-pack.d.ts +3 -83
  204. package/dist/backend/runtime-pack.js +8 -151
  205. package/dist/backend/runtime-pack.js.map +1 -1
  206. package/dist/backend/runtime-pack.test.js +24 -1
  207. package/dist/backend/runtime-pack.test.js.map +1 -1
  208. package/dist/backend/target-diagnostics.d.ts +22 -0
  209. package/dist/backend/target-diagnostics.js +108 -0
  210. package/dist/backend/target-diagnostics.js.map +1 -0
  211. package/dist/backend/target-platform.d.ts +15 -0
  212. package/dist/backend/target-platform.js +72 -0
  213. package/dist/backend/target-platform.js.map +1 -0
  214. package/dist/backend/targets.d.ts +13 -4
  215. package/dist/backend/targets.js +103 -34
  216. package/dist/backend/targets.js.map +1 -1
  217. package/dist/backend/targets.test.js +72 -4
  218. package/dist/backend/targets.test.js.map +1 -1
  219. package/dist/backend/toolchain-environment.d.ts +6 -0
  220. package/dist/backend/toolchain-environment.js +99 -0
  221. package/dist/backend/toolchain-environment.js.map +1 -0
  222. package/dist/backend/vendor-archives.d.ts +1 -9
  223. package/dist/backend/vendor-archives.js +2 -9
  224. package/dist/backend/vendor-archives.js.map +1 -1
  225. package/dist/backend/vendor-inputs.d.ts +9 -0
  226. package/dist/backend/vendor-inputs.js +10 -0
  227. package/dist/backend/vendor-inputs.js.map +1 -0
  228. package/dist/backend/warm-cache.d.ts +3 -0
  229. package/dist/backend/warm-cache.js +41 -0
  230. package/dist/backend/warm-cache.js.map +1 -0
  231. package/dist/cli/command.d.ts +3 -0
  232. package/dist/cli/command.js +328 -0
  233. package/dist/cli/command.js.map +1 -0
  234. package/dist/cli/host.d.ts +24 -0
  235. package/dist/cli/host.js +2 -0
  236. package/dist/cli/host.js.map +1 -0
  237. package/dist/cli/output-options.d.ts +26 -0
  238. package/dist/cli/output-options.js +60 -0
  239. package/dist/cli/output-options.js.map +1 -0
  240. package/dist/cli/paths.d.ts +12 -0
  241. package/dist/cli/paths.js +42 -0
  242. package/dist/cli/paths.js.map +1 -0
  243. package/dist/cli/usage.d.ts +74 -0
  244. package/dist/cli/usage.js +86 -0
  245. package/dist/cli/usage.js.map +1 -0
  246. package/dist/cli/wasi-paths.d.ts +8 -0
  247. package/dist/cli/wasi-paths.js +41 -0
  248. package/dist/cli/wasi-paths.js.map +1 -0
  249. package/dist/cli/wasi-runner.d.ts +2 -0
  250. package/dist/cli/wasi-runner.js +28 -0
  251. package/dist/cli/wasi-runner.js.map +1 -0
  252. package/dist/compat/fetch-profile.js +37 -7
  253. package/dist/compat/fetch-profile.js.map +1 -1
  254. package/dist/compile-types.d.ts +162 -0
  255. package/dist/compile-types.js +2 -0
  256. package/dist/compile-types.js.map +1 -0
  257. package/dist/coverage/report.js +11 -5
  258. package/dist/coverage/report.js.map +1 -1
  259. package/dist/coverage/surface-manifest.js +5 -2
  260. package/dist/coverage/surface-manifest.js.map +1 -1
  261. package/dist/diagnostics/diagnostic.js +5 -10
  262. package/dist/diagnostics/diagnostic.js.map +1 -1
  263. package/dist/executable/executable-cache.d.ts +3 -4
  264. package/dist/executable/executable-cache.js +5 -6
  265. package/dist/executable/executable-cache.js.map +1 -1
  266. package/dist/executable/executable-cache.test.js +16 -17
  267. package/dist/executable/executable-cache.test.js.map +1 -1
  268. package/dist/executable/prepare.d.ts +14 -0
  269. package/dist/executable/prepare.js +66 -0
  270. package/dist/executable/prepare.js.map +1 -0
  271. package/dist/ffi/ffi-manifest.d.ts +11 -4
  272. package/dist/ffi/ffi-manifest.js +170 -58
  273. package/dist/ffi/ffi-manifest.js.map +1 -1
  274. package/dist/ffi/ffi-manifest.test.d.ts +1 -0
  275. package/dist/ffi/ffi-manifest.test.js +111 -0
  276. package/dist/ffi/ffi-manifest.test.js.map +1 -0
  277. package/dist/format-integer.d.ts +3 -0
  278. package/dist/format-integer.js +14 -0
  279. package/dist/format-integer.js.map +1 -0
  280. package/dist/frontend/analysis.d.ts +5 -0
  281. package/dist/frontend/analysis.js +88 -0
  282. package/dist/frontend/analysis.js.map +1 -0
  283. package/dist/frontend/arguments-usage.d.ts +1 -3
  284. package/dist/frontend/arguments-usage.js.map +1 -1
  285. package/dist/frontend/builtin-modules.d.ts +1 -1
  286. package/dist/frontend/builtin-modules.js +1 -1
  287. package/dist/frontend/builtin-modules.js.map +1 -1
  288. package/dist/frontend/cjs-lexer.d.ts +4 -25
  289. package/dist/frontend/cjs-lexer.js +6 -806
  290. package/dist/frontend/cjs-lexer.js.map +1 -1
  291. package/dist/frontend/cjs-resolve.d.ts +5 -0
  292. package/dist/frontend/cjs-resolve.js +429 -0
  293. package/dist/frontend/cjs-resolve.js.map +1 -0
  294. package/dist/frontend/cjs-syntax.d.ts +26 -0
  295. package/dist/frontend/cjs-syntax.js +806 -0
  296. package/dist/frontend/cjs-syntax.js.map +1 -0
  297. package/dist/frontend/comptime-node.d.ts +4 -0
  298. package/dist/frontend/comptime-node.js +16 -0
  299. package/dist/frontend/comptime-node.js.map +1 -0
  300. package/dist/frontend/comptime-node.test.d.ts +1 -0
  301. package/dist/frontend/comptime-node.test.js +37 -0
  302. package/dist/frontend/comptime-node.test.js.map +1 -0
  303. package/dist/frontend/dts-paths.d.ts +3 -4
  304. package/dist/frontend/dts-paths.js +15 -8
  305. package/dist/frontend/dts-paths.js.map +1 -1
  306. package/dist/frontend/fork-target.js +2 -2
  307. package/dist/frontend/fork-target.js.map +1 -1
  308. package/dist/frontend/helper-tokens.d.ts +1 -0
  309. package/dist/frontend/helper-tokens.js +147 -0
  310. package/dist/frontend/helper-tokens.js.map +1 -0
  311. package/dist/frontend/helper-tokens.test.d.ts +1 -0
  312. package/dist/frontend/helper-tokens.test.js +35 -0
  313. package/dist/frontend/helper-tokens.test.js.map +1 -0
  314. package/dist/frontend/inferred-js-diagnostics.d.ts +5 -0
  315. package/dist/frontend/inferred-js-diagnostics.js +69 -0
  316. package/dist/frontend/inferred-js-diagnostics.js.map +1 -0
  317. package/dist/frontend/inferred-js-diagnostics.test.d.ts +1 -0
  318. package/dist/frontend/inferred-js-diagnostics.test.js +58 -0
  319. package/dist/frontend/inferred-js-diagnostics.test.js.map +1 -0
  320. package/dist/frontend/input-tracker.d.ts +5 -2
  321. package/dist/frontend/input-tracker.js +24 -3
  322. package/dist/frontend/input-tracker.js.map +1 -1
  323. package/dist/frontend/input-tracker.test.js +23 -0
  324. package/dist/frontend/input-tracker.test.js.map +1 -1
  325. package/dist/frontend/lib-contract.js +32 -24
  326. package/dist/frontend/lib-contract.js.map +1 -1
  327. package/dist/frontend/literal-values.d.ts +4 -0
  328. package/dist/frontend/literal-values.js +21 -0
  329. package/dist/frontend/literal-values.js.map +1 -0
  330. package/dist/frontend/literal-values.test.d.ts +1 -0
  331. package/dist/frontend/literal-values.test.js +91 -0
  332. package/dist/frontend/literal-values.test.js.map +1 -0
  333. package/dist/frontend/lowering/abstract-equality.d.ts +1 -1
  334. package/dist/frontend/lowering/abstract-equality.js +3 -0
  335. package/dist/frontend/lowering/abstract-equality.js.map +1 -1
  336. package/dist/frontend/lowering/array-indexed-mutation.js +11 -6
  337. package/dist/frontend/lowering/array-indexed-mutation.js.map +1 -1
  338. package/dist/frontend/lowering/array-values.d.ts +4 -1
  339. package/dist/frontend/lowering/array-values.js +43 -1
  340. package/dist/frontend/lowering/array-values.js.map +1 -1
  341. package/dist/frontend/lowering/callback-arg.js +1 -0
  342. package/dist/frontend/lowering/callback-arg.js.map +1 -1
  343. package/dist/frontend/lowering/class-callbacks.d.ts +12 -0
  344. package/dist/frontend/lowering/class-callbacks.js +74 -0
  345. package/dist/frontend/lowering/class-callbacks.js.map +1 -0
  346. package/dist/frontend/lowering/class-construction.d.ts +12 -0
  347. package/dist/frontend/lowering/class-construction.js +156 -0
  348. package/dist/frontend/lowering/class-construction.js.map +1 -0
  349. package/dist/frontend/lowering/class-descriptors.d.ts +10 -0
  350. package/dist/frontend/lowering/class-descriptors.js +114 -0
  351. package/dist/frontend/lowering/class-descriptors.js.map +1 -0
  352. package/dist/frontend/lowering/class-dynamic-dispatch.d.ts +51 -0
  353. package/dist/frontend/lowering/class-dynamic-dispatch.js +1158 -0
  354. package/dist/frontend/lowering/class-dynamic-dispatch.js.map +1 -0
  355. package/dist/frontend/lowering/class-dynamic-dispatch.test.d.ts +1 -0
  356. package/dist/frontend/lowering/class-dynamic-dispatch.test.js +93 -0
  357. package/dist/frontend/lowering/class-dynamic-dispatch.test.js.map +1 -0
  358. package/dist/frontend/lowering/class-instance-constructor.d.ts +7 -0
  359. package/dist/frontend/lowering/class-instance-constructor.js +67 -0
  360. package/dist/frontend/lowering/class-instance-constructor.js.map +1 -0
  361. package/dist/frontend/lowering/class-method-values.d.ts +13 -0
  362. package/dist/frontend/lowering/class-method-values.js +235 -0
  363. package/dist/frontend/lowering/class-method-values.js.map +1 -0
  364. package/dist/frontend/lowering/class-prototypes.d.ts +19 -0
  365. package/dist/frontend/lowering/class-prototypes.js +242 -0
  366. package/dist/frontend/lowering/class-prototypes.js.map +1 -0
  367. package/dist/frontend/lowering/class-runtime-statics.d.ts +8 -0
  368. package/dist/frontend/lowering/class-runtime-statics.js +66 -0
  369. package/dist/frontend/lowering/class-runtime-statics.js.map +1 -0
  370. package/dist/frontend/lowering/class-static-data.d.ts +6 -0
  371. package/dist/frontend/lowering/class-static-data.js +56 -0
  372. package/dist/frontend/lowering/class-static-data.js.map +1 -0
  373. package/dist/frontend/lowering/containers/buffer-numeric-methods.d.ts +4 -0
  374. package/dist/frontend/lowering/containers/buffer-numeric-methods.js +26 -0
  375. package/dist/frontend/lowering/containers/buffer-numeric-methods.js.map +1 -0
  376. package/dist/frontend/lowering/containers/bytes.d.ts +11 -7
  377. package/dist/frontend/lowering/containers/bytes.js +208 -163
  378. package/dist/frontend/lowering/containers/bytes.js.map +1 -1
  379. package/dist/frontend/lowering/containers/string-and-regexp.d.ts +2 -2
  380. package/dist/frontend/lowering/containers/string-and-regexp.js +58 -63
  381. package/dist/frontend/lowering/containers/string-and-regexp.js.map +1 -1
  382. package/dist/frontend/lowering/containers/string-replacement.d.ts +7 -0
  383. package/dist/frontend/lowering/containers/string-replacement.js +217 -0
  384. package/dist/frontend/lowering/containers/string-replacement.js.map +1 -0
  385. package/dist/frontend/lowering/error-methods.d.ts +14 -0
  386. package/dist/frontend/lowering/error-methods.js +177 -0
  387. package/dist/frontend/lowering/error-methods.js.map +1 -0
  388. package/dist/frontend/lowering/expressions/global-symbols.d.ts +6 -0
  389. package/dist/frontend/lowering/expressions/global-symbols.js +11 -0
  390. package/dist/frontend/lowering/expressions/global-symbols.js.map +1 -0
  391. package/dist/frontend/lowering/expressions/native-proxy.d.ts +7 -0
  392. package/dist/frontend/lowering/expressions/native-proxy.js +51 -0
  393. package/dist/frontend/lowering/expressions/native-proxy.js.map +1 -0
  394. package/dist/frontend/lowering/expressions/nullish-assignment.d.ts +6 -0
  395. package/dist/frontend/lowering/expressions/nullish-assignment.js +132 -0
  396. package/dist/frontend/lowering/expressions/nullish-assignment.js.map +1 -0
  397. package/dist/frontend/lowering/expressions/object-literals.d.ts +8 -8
  398. package/dist/frontend/lowering/expressions/object-literals.js +509 -183
  399. package/dist/frontend/lowering/expressions/object-literals.js.map +1 -1
  400. package/dist/frontend/lowering/expressions/optional-chains.d.ts +3 -4
  401. package/dist/frontend/lowering/expressions/optional-chains.js +88 -24
  402. package/dist/frontend/lowering/expressions/optional-chains.js.map +1 -1
  403. package/dist/frontend/lowering/expressions/try-lower-expression.js +1 -0
  404. package/dist/frontend/lowering/expressions/try-lower-expression.js.map +1 -1
  405. package/dist/frontend/lowering/expressions/union-field-write.d.ts +8 -0
  406. package/dist/frontend/lowering/expressions/union-field-write.js +71 -0
  407. package/dist/frontend/lowering/expressions/union-field-write.js.map +1 -0
  408. package/dist/frontend/lowering/function-context.d.ts +101 -0
  409. package/dist/frontend/lowering/function-context.js +109 -0
  410. package/dist/frontend/lowering/function-context.js.map +1 -0
  411. package/dist/frontend/lowering/generator-delegation.d.ts +7 -0
  412. package/dist/frontend/lowering/generator-delegation.js +85 -0
  413. package/dist/frontend/lowering/generator-delegation.js.map +1 -0
  414. package/dist/frontend/lowering/generator-dynamic-dispatch.d.ts +11 -0
  415. package/dist/frontend/lowering/generator-dynamic-dispatch.js +143 -0
  416. package/dist/frontend/lowering/generator-dynamic-dispatch.js.map +1 -0
  417. package/dist/frontend/lowering/lib-boundary.d.ts +4 -1
  418. package/dist/frontend/lowering/lib-boundary.js +112 -20
  419. package/dist/frontend/lowering/lib-boundary.js.map +1 -1
  420. package/dist/frontend/lowering/lib-boundary.test.d.ts +1 -0
  421. package/dist/frontend/lowering/lib-boundary.test.js +45 -0
  422. package/dist/frontend/lowering/lib-boundary.test.js.map +1 -0
  423. package/dist/frontend/lowering/lower-array-sort.d.ts +1 -1
  424. package/dist/frontend/lowering/lower-assert.d.ts +1 -1
  425. package/dist/frontend/lowering/lower-assert.js.map +1 -1
  426. package/dist/frontend/lowering/lower-async-iteration.d.ts +1 -1
  427. package/dist/frontend/lowering/lower-async-iteration.js.map +1 -1
  428. package/dist/frontend/lowering/lower-builtin-values.d.ts +18 -0
  429. package/dist/frontend/lowering/lower-builtin-values.js +249 -0
  430. package/dist/frontend/lowering/lower-builtin-values.js.map +1 -0
  431. package/dist/frontend/lowering/lower-builtins.d.ts +33 -46
  432. package/dist/frontend/lowering/lower-builtins.js +676 -365
  433. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  434. package/dist/frontend/lowering/lower-calls.d.ts +28 -22
  435. package/dist/frontend/lowering/lower-calls.js +1660 -412
  436. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  437. package/dist/frontend/lowering/lower-classes.d.ts +95 -57
  438. package/dist/frontend/lowering/lower-classes.js +1491 -404
  439. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  440. package/dist/frontend/lowering/lower-comptime.d.ts +1 -1
  441. package/dist/frontend/lowering/lower-comptime.js +23 -32
  442. package/dist/frontend/lowering/lower-comptime.js.map +1 -1
  443. package/dist/frontend/lowering/lower-containers.d.ts +36 -32
  444. package/dist/frontend/lowering/lower-containers.js +925 -181
  445. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  446. package/dist/frontend/lowering/lower-dgram.d.ts +1 -1
  447. package/dist/frontend/lowering/lower-dgram.js +3 -2
  448. package/dist/frontend/lowering/lower-dgram.js.map +1 -1
  449. package/dist/frontend/lowering/lower-enums.d.ts +1 -1
  450. package/dist/frontend/lowering/lower-enums.js.map +1 -1
  451. package/dist/frontend/lowering/lower-event-emitter.d.ts +30 -1
  452. package/dist/frontend/lowering/lower-event-emitter.js +58 -26
  453. package/dist/frontend/lowering/lower-event-emitter.js.map +1 -1
  454. package/dist/frontend/lowering/lower-expando.d.ts +7 -1
  455. package/dist/frontend/lowering/lower-expando.js +14 -6
  456. package/dist/frontend/lowering/lower-expando.js.map +1 -1
  457. package/dist/frontend/lowering/lower-exprs.d.ts +37 -13
  458. package/dist/frontend/lowering/lower-exprs.js +2163 -863
  459. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  460. package/dist/frontend/lowering/lower-generators.d.ts +2 -5
  461. package/dist/frontend/lowering/lower-generators.js +88 -46
  462. package/dist/frontend/lowering/lower-generators.js.map +1 -1
  463. package/dist/frontend/lowering/lower-global-value.d.ts +7 -0
  464. package/dist/frontend/lowering/lower-global-value.js +17 -0
  465. package/dist/frontend/lowering/lower-global-value.js.map +1 -0
  466. package/dist/frontend/lowering/lower-inspect.d.ts +1 -1
  467. package/dist/frontend/lowering/lower-inspect.js +17 -8
  468. package/dist/frontend/lowering/lower-inspect.js.map +1 -1
  469. package/dist/frontend/lowering/lower-island.d.ts +7 -8
  470. package/dist/frontend/lowering/lower-island.js +66 -33
  471. package/dist/frontend/lowering/lower-island.js.map +1 -1
  472. package/dist/frontend/lowering/lower-mixins.d.ts +2 -2
  473. package/dist/frontend/lowering/lower-mixins.js +5 -3
  474. package/dist/frontend/lowering/lower-mixins.js.map +1 -1
  475. package/dist/frontend/lowering/lower-modules.d.ts +1 -1
  476. package/dist/frontend/lowering/lower-modules.js +445 -39
  477. package/dist/frontend/lowering/lower-modules.js.map +1 -1
  478. package/dist/frontend/lowering/lower-modules.test.d.ts +1 -0
  479. package/dist/frontend/lowering/lower-modules.test.js +39 -0
  480. package/dist/frontend/lowering/lower-modules.test.js.map +1 -0
  481. package/dist/frontend/lowering/lower-namespaces.d.ts +1 -1
  482. package/dist/frontend/lowering/lower-namespaces.js.map +1 -1
  483. package/dist/frontend/lowering/lower-node-module.d.ts +1 -1
  484. package/dist/frontend/lowering/lower-node-module.js +13 -10
  485. package/dist/frontend/lowering/lower-node-module.js.map +1 -1
  486. package/dist/frontend/lowering/lower-server.d.ts +1 -1
  487. package/dist/frontend/lowering/lower-server.js +6 -10
  488. package/dist/frontend/lowering/lower-server.js.map +1 -1
  489. package/dist/frontend/lowering/lower-stmts.d.ts +17 -17
  490. package/dist/frontend/lowering/lower-stmts.js +855 -514
  491. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  492. package/dist/frontend/lowering/lower-stream.d.ts +1 -1
  493. package/dist/frontend/lowering/lower-stream.js +20 -11
  494. package/dist/frontend/lowering/lower-stream.js.map +1 -1
  495. package/dist/frontend/lowering/lower-test.d.ts +1 -1
  496. package/dist/frontend/lowering/lower-test.js.map +1 -1
  497. package/dist/frontend/lowering/lower-url.d.ts +6 -0
  498. package/dist/frontend/lowering/lower-url.js +77 -0
  499. package/dist/frontend/lowering/lower-url.js.map +1 -0
  500. package/dist/frontend/lowering/lowerer.d.ts +140 -190
  501. package/dist/frontend/lowering/lowerer.js +1193 -712
  502. package/dist/frontend/lowering/lowerer.js.map +1 -1
  503. package/dist/frontend/lowering/lowerer.test.d.ts +1 -0
  504. package/dist/frontend/lowering/lowerer.test.js +172 -0
  505. package/dist/frontend/lowering/lowerer.test.js.map +1 -0
  506. package/dist/frontend/lowering/native-ffi.d.ts +5 -0
  507. package/dist/frontend/lowering/native-ffi.js +40 -0
  508. package/dist/frontend/lowering/native-ffi.js.map +1 -0
  509. package/dist/frontend/lowering/object-enumeration-receiver.d.ts +7 -0
  510. package/dist/frontend/lowering/object-enumeration-receiver.js +33 -0
  511. package/dist/frontend/lowering/object-enumeration-receiver.js.map +1 -0
  512. package/dist/frontend/lowering/object-factory-new.d.ts +8 -0
  513. package/dist/frontend/lowering/object-factory-new.js +68 -0
  514. package/dist/frontend/lowering/object-factory-new.js.map +1 -0
  515. package/dist/frontend/lowering/optional-arguments.js +6 -1
  516. package/dist/frontend/lowering/optional-arguments.js.map +1 -1
  517. package/dist/frontend/lowering/pairs-snapshot.js +2 -1
  518. package/dist/frontend/lowering/pairs-snapshot.js.map +1 -1
  519. package/dist/frontend/lowering/runtime-optional-locals.d.ts +14 -0
  520. package/dist/frontend/lowering/runtime-optional-locals.js +41 -0
  521. package/dist/frontend/lowering/runtime-optional-locals.js.map +1 -0
  522. package/dist/frontend/lowering/runtime-optional-locals.test.d.ts +1 -0
  523. package/dist/frontend/lowering/runtime-optional-locals.test.js +85 -0
  524. package/dist/frontend/lowering/runtime-optional-locals.test.js.map +1 -0
  525. package/dist/frontend/lowering/sanitize-class-types.d.ts +8 -0
  526. package/dist/frontend/lowering/sanitize-class-types.js +79 -0
  527. package/dist/frontend/lowering/sanitize-class-types.js.map +1 -0
  528. package/dist/frontend/lowering/sanitize-class-types.test.d.ts +1 -0
  529. package/dist/frontend/lowering/sanitize-class-types.test.js +60 -0
  530. package/dist/frontend/lowering/sanitize-class-types.test.js.map +1 -0
  531. package/dist/frontend/lowering/static-this.d.ts +5 -1
  532. package/dist/frontend/lowering/static-this.js +28 -2
  533. package/dist/frontend/lowering/static-this.js.map +1 -1
  534. package/dist/frontend/lowering/string-index.d.ts +7 -0
  535. package/dist/frontend/lowering/string-index.js +39 -0
  536. package/dist/frontend/lowering/string-index.js.map +1 -0
  537. package/dist/frontend/lowering/surfaces.d.ts +10 -3
  538. package/dist/frontend/lowering/surfaces.js +86 -37
  539. package/dist/frontend/lowering/surfaces.js.map +1 -1
  540. package/dist/frontend/lowering/symbol-fields.d.ts +22 -0
  541. package/dist/frontend/lowering/symbol-fields.js +183 -0
  542. package/dist/frontend/lowering/symbol-fields.js.map +1 -0
  543. package/dist/frontend/lowering/symbol-methods.d.ts +7 -0
  544. package/dist/frontend/lowering/symbol-methods.js +30 -0
  545. package/dist/frontend/lowering/symbol-methods.js.map +1 -0
  546. package/dist/frontend/lowering/text-decoder-encoding.d.ts +12 -0
  547. package/dist/frontend/lowering/text-decoder-encoding.js +82 -0
  548. package/dist/frontend/lowering/text-decoder-encoding.js.map +1 -0
  549. package/dist/frontend/lowering/text-decoder-encoding.test.d.ts +1 -0
  550. package/dist/frontend/lowering/text-decoder-encoding.test.js +38 -0
  551. package/dist/frontend/lowering/text-decoder-encoding.test.js.map +1 -0
  552. package/dist/frontend/lowering/union-narrow.d.ts +9 -0
  553. package/dist/frontend/lowering/union-narrow.js +59 -0
  554. package/dist/frontend/lowering/union-narrow.js.map +1 -0
  555. package/dist/frontend/lowering/union-narrow.test.d.ts +1 -0
  556. package/dist/frontend/lowering/union-narrow.test.js +72 -0
  557. package/dist/frontend/lowering/union-narrow.test.js.map +1 -0
  558. package/dist/frontend/lowering/union-retag.d.ts +43 -0
  559. package/dist/frontend/lowering/union-retag.js +259 -0
  560. package/dist/frontend/lowering/union-retag.js.map +1 -0
  561. package/dist/frontend/lowering/union-retag.test.d.ts +1 -0
  562. package/dist/frontend/lowering/union-retag.test.js +405 -0
  563. package/dist/frontend/lowering/union-retag.test.js.map +1 -0
  564. package/dist/frontend/lowering/unregistered-class-types.d.ts +14 -0
  565. package/dist/frontend/lowering/unregistered-class-types.js +94 -0
  566. package/dist/frontend/lowering/unregistered-class-types.js.map +1 -0
  567. package/dist/frontend/lowering/unregistered-class-types.test.d.ts +1 -0
  568. package/dist/frontend/lowering/unregistered-class-types.test.js +65 -0
  569. package/dist/frontend/lowering/unregistered-class-types.test.js.map +1 -0
  570. package/dist/frontend/lowering/width-lift.d.ts +40 -0
  571. package/dist/frontend/lowering/width-lift.js +2 -0
  572. package/dist/frontend/lowering/width-lift.js.map +1 -0
  573. package/dist/frontend/module-syntax.d.ts +58 -0
  574. package/dist/frontend/module-syntax.js +123 -0
  575. package/dist/frontend/module-syntax.js.map +1 -0
  576. package/dist/frontend/module-syntax.test.d.ts +1 -0
  577. package/dist/frontend/module-syntax.test.js +78 -0
  578. package/dist/frontend/module-syntax.test.js.map +1 -0
  579. package/dist/frontend/npm-fetch-analysis.d.ts +17 -0
  580. package/dist/frontend/npm-fetch-analysis.js +100 -0
  581. package/dist/frontend/npm-fetch-analysis.js.map +1 -0
  582. package/dist/frontend/npm-fetch-analysis.test.d.ts +1 -0
  583. package/dist/frontend/npm-fetch-analysis.test.js +107 -0
  584. package/dist/frontend/npm-fetch-analysis.test.js.map +1 -0
  585. package/dist/frontend/npm-fetch-node.d.ts +2 -0
  586. package/dist/frontend/npm-fetch-node.js +12 -0
  587. package/dist/frontend/npm-fetch-node.js.map +1 -0
  588. package/dist/frontend/npm-fetch-syntax.d.ts +6 -0
  589. package/dist/frontend/npm-fetch-syntax.js +207 -0
  590. package/dist/frontend/npm-fetch-syntax.js.map +1 -0
  591. package/dist/frontend/npm-node.d.ts +10 -0
  592. package/dist/frontend/npm-node.js +14 -0
  593. package/dist/frontend/npm-node.js.map +1 -0
  594. package/dist/frontend/npm-static-bundled-cjs.d.ts +8 -0
  595. package/dist/frontend/npm-static-bundled-cjs.js +236 -0
  596. package/dist/frontend/npm-static-bundled-cjs.js.map +1 -0
  597. package/dist/frontend/npm-static-bundled-cjs.test.d.ts +1 -0
  598. package/dist/frontend/npm-static-bundled-cjs.test.js +69 -0
  599. package/dist/frontend/npm-static-bundled-cjs.test.js.map +1 -0
  600. package/dist/frontend/npm-static-declaration-syntax.d.ts +57 -0
  601. package/dist/frontend/npm-static-declaration-syntax.js +944 -0
  602. package/dist/frontend/npm-static-declaration-syntax.js.map +1 -0
  603. package/dist/frontend/npm-static-declarations.d.ts +11 -39
  604. package/dist/frontend/npm-static-declarations.js +22 -561
  605. package/dist/frontend/npm-static-declarations.js.map +1 -1
  606. package/dist/frontend/npm-static-declarations.test.js +245 -6
  607. package/dist/frontend/npm-static-declarations.test.js.map +1 -1
  608. package/dist/frontend/npm-static-prune.d.ts +1 -1
  609. package/dist/frontend/npm-static-prune.js +121 -38
  610. package/dist/frontend/npm-static-prune.js.map +1 -1
  611. package/dist/frontend/npm-static-prune.test.js +9 -1
  612. package/dist/frontend/npm-static-prune.test.js.map +1 -1
  613. package/dist/frontend/npm-static-rewrite-host.d.ts +4 -0
  614. package/dist/frontend/npm-static-rewrite-host.js +179 -0
  615. package/dist/frontend/npm-static-rewrite-host.js.map +1 -0
  616. package/dist/frontend/npm-static-rewrite-syntax.d.ts +16 -0
  617. package/dist/frontend/npm-static-rewrite-syntax.js +905 -0
  618. package/dist/frontend/npm-static-rewrite-syntax.js.map +1 -0
  619. package/dist/frontend/npm-static-rewrite.d.ts +0 -5
  620. package/dist/frontend/npm-static-rewrite.js +2 -1079
  621. package/dist/frontend/npm-static-rewrite.js.map +1 -1
  622. package/dist/frontend/npm-static-subtyping.d.ts +5 -0
  623. package/dist/frontend/npm-static-subtyping.js +55 -0
  624. package/dist/frontend/npm-static-subtyping.js.map +1 -0
  625. package/dist/frontend/npm-static-subtyping.test.d.ts +1 -0
  626. package/dist/frontend/npm-static-subtyping.test.js +58 -0
  627. package/dist/frontend/npm-static-subtyping.test.js.map +1 -0
  628. package/dist/frontend/npm-static.d.ts +7 -15
  629. package/dist/frontend/npm-static.js +19 -28
  630. package/dist/frontend/npm-static.js.map +1 -1
  631. package/dist/frontend/npm.d.ts +8 -69
  632. package/dist/frontend/npm.js +21 -361
  633. package/dist/frontend/npm.js.map +1 -1
  634. package/dist/frontend/npm.test.js +2 -2
  635. package/dist/frontend/npm.test.js.map +1 -1
  636. package/dist/frontend/pipeline-native.d.ts +6 -0
  637. package/dist/frontend/pipeline-native.js +32 -0
  638. package/dist/frontend/pipeline-native.js.map +1 -0
  639. package/dist/frontend/pipeline.d.ts +54 -0
  640. package/dist/frontend/pipeline.js +382 -0
  641. package/dist/frontend/pipeline.js.map +1 -0
  642. package/dist/frontend/pipeline.test.d.ts +1 -0
  643. package/dist/frontend/pipeline.test.js +77 -0
  644. package/dist/frontend/pipeline.test.js.map +1 -0
  645. package/dist/frontend/program-analysis.d.ts +10 -0
  646. package/dist/frontend/program-analysis.js +14 -0
  647. package/dist/frontend/program-analysis.js.map +1 -0
  648. package/dist/frontend/program-node.d.ts +9 -0
  649. package/dist/frontend/program-node.js +48 -0
  650. package/dist/frontend/program-node.js.map +1 -0
  651. package/dist/frontend/program.d.ts +39 -20
  652. package/dist/frontend/program.js +393 -189
  653. package/dist/frontend/program.js.map +1 -1
  654. package/dist/frontend/program.test.js +105 -2
  655. package/dist/frontend/program.test.js.map +1 -1
  656. package/dist/frontend/provenance-core.d.ts +9 -0
  657. package/dist/frontend/provenance-core.js +435 -0
  658. package/dist/frontend/provenance-core.js.map +1 -0
  659. package/dist/frontend/provenance.d.ts +0 -4
  660. package/dist/frontend/provenance.js +4 -459
  661. package/dist/frontend/provenance.js.map +1 -1
  662. package/dist/frontend/resolve.d.ts +3 -1
  663. package/dist/frontend/resolve.js +6 -3
  664. package/dist/frontend/resolve.js.map +1 -1
  665. package/dist/frontend/runtime-resolve.d.ts +4 -3
  666. package/dist/frontend/runtime-resolve.js +22 -18
  667. package/dist/frontend/runtime-resolve.js.map +1 -1
  668. package/dist/frontend/runtime-resolve.test.js +53 -0
  669. package/dist/frontend/runtime-resolve.test.js.map +1 -1
  670. package/dist/frontend/services-node.d.ts +5 -0
  671. package/dist/frontend/services-node.js +19 -0
  672. package/dist/frontend/services-node.js.map +1 -0
  673. package/dist/frontend/services-node.test.d.ts +1 -0
  674. package/dist/frontend/services-node.test.js +70 -0
  675. package/dist/frontend/services-node.test.js.map +1 -0
  676. package/dist/frontend/services.d.ts +44 -0
  677. package/dist/frontend/services.js +90 -0
  678. package/dist/frontend/services.js.map +1 -0
  679. package/dist/frontend/services.test.d.ts +1 -0
  680. package/dist/frontend/services.test.js +174 -0
  681. package/dist/frontend/services.test.js.map +1 -0
  682. package/dist/frontend/ts7/adapter.d.ts +3 -3
  683. package/dist/frontend/ts7/adapter.js +14 -31
  684. package/dist/frontend/ts7/adapter.js.map +1 -1
  685. package/dist/frontend/ts7/api.d.ts +16 -0
  686. package/dist/frontend/ts7/api.js +24 -0
  687. package/dist/frontend/ts7/api.js.map +1 -0
  688. package/dist/frontend/ts7/ast-bytes.d.ts +31 -0
  689. package/dist/frontend/ts7/ast-bytes.js +137 -0
  690. package/dist/frontend/ts7/ast-bytes.js.map +1 -0
  691. package/dist/frontend/ts7/ast-bytes.test.d.ts +1 -0
  692. package/dist/frontend/ts7/ast-bytes.test.js +73 -0
  693. package/dist/frontend/ts7/ast-bytes.test.js.map +1 -0
  694. package/dist/frontend/ts7/ast-guards.generated.d.ts +353 -0
  695. package/dist/frontend/ts7/ast-guards.generated.js +1951 -0
  696. package/dist/frontend/ts7/ast-guards.generated.js.map +1 -0
  697. package/dist/frontend/ts7/ast-guards.test.d.ts +1 -0
  698. package/dist/frontend/ts7/ast-guards.test.js +43 -0
  699. package/dist/frontend/ts7/ast-guards.test.js.map +1 -0
  700. package/dist/frontend/ts7/ast-node.d.ts +194 -0
  701. package/dist/frontend/ts7/ast-node.js +448 -0
  702. package/dist/frontend/ts7/ast-node.js.map +1 -0
  703. package/dist/frontend/ts7/ast-node.test.d.ts +1 -0
  704. package/dist/frontend/ts7/ast-node.test.js +266 -0
  705. package/dist/frontend/ts7/ast-node.test.js.map +1 -0
  706. package/dist/frontend/ts7/ast-schema.generated.d.ts +503 -0
  707. package/dist/frontend/ts7/ast-schema.generated.js +1446 -0
  708. package/dist/frontend/ts7/ast-schema.generated.js.map +1 -0
  709. package/dist/frontend/ts7/ast-text.d.ts +6 -0
  710. package/dist/frontend/ts7/ast-text.js +124 -0
  711. package/dist/frontend/ts7/ast-text.js.map +1 -0
  712. package/dist/frontend/ts7/ast-text.test.d.ts +1 -0
  713. package/dist/frontend/ts7/ast-text.test.js +38 -0
  714. package/dist/frontend/ts7/ast-text.test.js.map +1 -0
  715. package/dist/frontend/ts7/ast-tokens.generated.d.ts +2 -0
  716. package/dist/frontend/ts7/ast-tokens.generated.js +155 -0
  717. package/dist/frontend/ts7/ast-tokens.generated.js.map +1 -0
  718. package/dist/frontend/ts7/ast-types.d.ts +42 -0
  719. package/dist/frontend/ts7/ast-types.generated.d.ts +1615 -0
  720. package/dist/frontend/ts7/ast-types.generated.js +5 -0
  721. package/dist/frontend/ts7/ast-types.generated.js.map +1 -0
  722. package/dist/frontend/ts7/ast-types.js +3 -0
  723. package/dist/frontend/ts7/ast-types.js.map +1 -0
  724. package/dist/frontend/ts7/ast-wire.d.ts +58 -0
  725. package/dist/frontend/ts7/ast-wire.js +267 -0
  726. package/dist/frontend/ts7/ast-wire.js.map +1 -0
  727. package/dist/frontend/ts7/ast-wire.test.d.ts +1 -0
  728. package/dist/frontend/ts7/ast-wire.test.js +151 -0
  729. package/dist/frontend/ts7/ast-wire.test.js.map +1 -0
  730. package/dist/frontend/ts7/ast.d.ts +14 -14
  731. package/dist/frontend/ts7/ast.js +25 -18
  732. package/dist/frontend/ts7/ast.js.map +1 -1
  733. package/dist/frontend/ts7/census-check.js +1 -0
  734. package/dist/frontend/ts7/census-check.js.map +1 -1
  735. package/dist/frontend/ts7/checker-cache.d.ts +48 -0
  736. package/dist/frontend/ts7/checker-cache.js +92 -0
  737. package/dist/frontend/ts7/checker-cache.js.map +1 -0
  738. package/dist/frontend/ts7/checker.d.ts +19 -46
  739. package/dist/frontend/ts7/checker.js +231 -161
  740. package/dist/frontend/ts7/checker.js.map +1 -1
  741. package/dist/frontend/ts7/checker.test.d.ts +1 -0
  742. package/dist/frontend/ts7/checker.test.js +335 -0
  743. package/dist/frontend/ts7/checker.test.js.map +1 -0
  744. package/dist/frontend/ts7/enums.d.ts +4 -27
  745. package/dist/frontend/ts7/enums.generated.d.ts +878 -0
  746. package/dist/frontend/ts7/enums.generated.js +1313 -0
  747. package/dist/frontend/ts7/enums.generated.js.map +1 -0
  748. package/dist/frontend/ts7/enums.js +4 -45
  749. package/dist/frontend/ts7/enums.js.map +1 -1
  750. package/dist/frontend/ts7/enums.test.d.ts +1 -0
  751. package/dist/frontend/ts7/enums.test.js +48 -0
  752. package/dist/frontend/ts7/enums.test.js.map +1 -0
  753. package/dist/frontend/ts7/native-api.d.ts +10 -0
  754. package/dist/frontend/ts7/native-api.js +10 -0
  755. package/dist/frontend/ts7/native-api.js.map +1 -0
  756. package/dist/frontend/ts7/native-host.d.ts +2 -0
  757. package/dist/frontend/ts7/native-host.js +11 -0
  758. package/dist/frontend/ts7/native-host.js.map +1 -0
  759. package/dist/frontend/ts7/native-process.d.ts +14 -0
  760. package/dist/frontend/ts7/native-process.js +62 -0
  761. package/dist/frontend/ts7/native-process.js.map +1 -0
  762. package/dist/frontend/ts7/program-adapter.d.ts +10 -98
  763. package/dist/frontend/ts7/program-adapter.js +16 -310
  764. package/dist/frontend/ts7/program-adapter.js.map +1 -1
  765. package/dist/frontend/ts7/program-host.d.ts +115 -0
  766. package/dist/frontend/ts7/program-host.js +347 -0
  767. package/dist/frontend/ts7/program-host.js.map +1 -0
  768. package/dist/frontend/ts7/program-host.test.d.ts +1 -0
  769. package/dist/frontend/ts7/program-host.test.js +207 -0
  770. package/dist/frontend/ts7/program-host.test.js.map +1 -0
  771. package/dist/frontend/ts7/rpc-api.d.ts +12 -0
  772. package/dist/frontend/ts7/rpc-api.js +23 -0
  773. package/dist/frontend/ts7/rpc-api.js.map +1 -0
  774. package/dist/frontend/ts7/rpc-client.d.ts +27 -0
  775. package/dist/frontend/ts7/rpc-client.js +102 -0
  776. package/dist/frontend/ts7/rpc-client.js.map +1 -0
  777. package/dist/frontend/ts7/rpc-client.test.d.ts +1 -0
  778. package/dist/frontend/ts7/rpc-client.test.js +133 -0
  779. package/dist/frontend/ts7/rpc-client.test.js.map +1 -0
  780. package/dist/frontend/ts7/rpc-filesystem.d.ts +16 -0
  781. package/dist/frontend/ts7/rpc-filesystem.js +42 -0
  782. package/dist/frontend/ts7/rpc-filesystem.js.map +1 -0
  783. package/dist/frontend/ts7/rpc-filesystem.test.d.ts +1 -0
  784. package/dist/frontend/ts7/rpc-filesystem.test.js +89 -0
  785. package/dist/frontend/ts7/rpc-filesystem.test.js.map +1 -0
  786. package/dist/frontend/ts7/rpc-process.d.ts +2 -0
  787. package/dist/frontend/ts7/rpc-process.js +148 -0
  788. package/dist/frontend/ts7/rpc-process.js.map +1 -0
  789. package/dist/frontend/ts7/rpc-process.test.d.ts +1 -0
  790. package/dist/frontend/ts7/rpc-process.test.js +29 -0
  791. package/dist/frontend/ts7/rpc-process.test.js.map +1 -0
  792. package/dist/frontend/ts7/rpc-wire.d.ts +46 -0
  793. package/dist/frontend/ts7/rpc-wire.js +195 -0
  794. package/dist/frontend/ts7/rpc-wire.js.map +1 -0
  795. package/dist/frontend/ts7/rpc-wire.test.d.ts +1 -0
  796. package/dist/frontend/ts7/rpc-wire.test.js +168 -0
  797. package/dist/frontend/ts7/rpc-wire.test.js.map +1 -0
  798. package/dist/frontend/ts7/semantic-checker.d.ts +137 -0
  799. package/dist/frontend/ts7/semantic-checker.js +221 -0
  800. package/dist/frontend/ts7/semantic-checker.js.map +1 -0
  801. package/dist/frontend/ts7/semantic-checker.test.d.ts +1 -0
  802. package/dist/frontend/ts7/semantic-checker.test.js +119 -0
  803. package/dist/frontend/ts7/semantic-checker.test.js.map +1 -0
  804. package/dist/frontend/ts7/semantic-json.d.ts +5 -0
  805. package/dist/frontend/ts7/semantic-json.js +47 -0
  806. package/dist/frontend/ts7/semantic-json.js.map +1 -0
  807. package/dist/frontend/ts7/semantic-json.test.d.ts +1 -0
  808. package/dist/frontend/ts7/semantic-json.test.js +42 -0
  809. package/dist/frontend/ts7/semantic-json.test.js.map +1 -0
  810. package/dist/frontend/ts7/semantic-model.d.ts +222 -0
  811. package/dist/frontend/ts7/semantic-model.js +423 -0
  812. package/dist/frontend/ts7/semantic-model.js.map +1 -0
  813. package/dist/frontend/ts7/semantic-model.test.d.ts +1 -0
  814. package/dist/frontend/ts7/semantic-model.test.js +322 -0
  815. package/dist/frontend/ts7/semantic-model.test.js.map +1 -0
  816. package/dist/frontend/ts7/semantic-schema.generated.d.ts +283 -0
  817. package/dist/frontend/ts7/semantic-schema.generated.js +222 -0
  818. package/dist/frontend/ts7/semantic-schema.generated.js.map +1 -0
  819. package/dist/frontend/ts7/semantic-types.d.ts +5 -0
  820. package/dist/frontend/ts7/semantic-types.js +2 -0
  821. package/dist/frontend/ts7/semantic-types.js.map +1 -0
  822. package/dist/frontend/ts7/session-cache.d.ts +17 -0
  823. package/dist/frontend/ts7/session-cache.js +97 -0
  824. package/dist/frontend/ts7/session-cache.js.map +1 -0
  825. package/dist/frontend/ts7/session-cache.test.d.ts +1 -0
  826. package/dist/frontend/ts7/session-cache.test.js +87 -0
  827. package/dist/frontend/ts7/session-cache.test.js.map +1 -0
  828. package/dist/frontend/ts7/session-path.d.ts +18 -0
  829. package/dist/frontend/ts7/session-path.js +203 -0
  830. package/dist/frontend/ts7/session-path.js.map +1 -0
  831. package/dist/frontend/ts7/session-path.test.d.ts +1 -0
  832. package/dist/frontend/ts7/session-path.test.js +34 -0
  833. package/dist/frontend/ts7/session-path.test.js.map +1 -0
  834. package/dist/frontend/ts7/session-schema.generated.d.ts +167 -0
  835. package/dist/frontend/ts7/session-schema.generated.js +5 -0
  836. package/dist/frontend/ts7/session-schema.generated.js.map +1 -0
  837. package/dist/frontend/ts7/session-timing.d.ts +50 -0
  838. package/dist/frontend/ts7/session-timing.js +61 -0
  839. package/dist/frontend/ts7/session-timing.js.map +1 -0
  840. package/dist/frontend/ts7/session-timing.test.d.ts +1 -0
  841. package/dist/frontend/ts7/session-timing.test.js +52 -0
  842. package/dist/frontend/ts7/session-timing.test.js.map +1 -0
  843. package/dist/frontend/ts7/session.d.ts +105 -0
  844. package/dist/frontend/ts7/session.js +233 -0
  845. package/dist/frontend/ts7/session.js.map +1 -0
  846. package/dist/frontend/ts7/session.test.d.ts +1 -0
  847. package/dist/frontend/ts7/session.test.js +215 -0
  848. package/dist/frontend/ts7/session.test.js.map +1 -0
  849. package/dist/frontend/ts7/source-parser-node.d.ts +6 -0
  850. package/dist/frontend/ts7/source-parser-node.js +15 -0
  851. package/dist/frontend/ts7/source-parser-node.js.map +1 -0
  852. package/dist/frontend/ts7/source-parser.d.ts +25 -0
  853. package/dist/frontend/ts7/source-parser.js +96 -0
  854. package/dist/frontend/ts7/source-parser.js.map +1 -0
  855. package/dist/frontend/ts7/source-parser.test.d.ts +1 -0
  856. package/dist/frontend/ts7/source-parser.test.js +151 -0
  857. package/dist/frontend/ts7/source-parser.test.js.map +1 -0
  858. package/dist/frontend/ts7/syntax.d.ts +3 -0
  859. package/dist/frontend/ts7/syntax.js +4 -0
  860. package/dist/frontend/ts7/syntax.js.map +1 -0
  861. package/dist/frontend/tsc-codes.js +3 -0
  862. package/dist/frontend/tsc-codes.js.map +1 -1
  863. package/dist/frontend/type-mapper.d.ts +34 -18
  864. package/dist/frontend/type-mapper.js +552 -131
  865. package/dist/frontend/type-mapper.js.map +1 -1
  866. package/dist/frontend/type-mapper.test.js +146 -2
  867. package/dist/frontend/type-mapper.test.js.map +1 -1
  868. package/dist/frontend/union-discriminants.d.ts +16 -0
  869. package/dist/frontend/union-discriminants.js +98 -0
  870. package/dist/frontend/union-discriminants.js.map +1 -0
  871. package/dist/frontend/union-discriminants.test.d.ts +1 -0
  872. package/dist/frontend/union-discriminants.test.js +182 -0
  873. package/dist/frontend/union-discriminants.test.js.map +1 -0
  874. package/dist/index.d.ts +7 -198
  875. package/dist/index.js +369 -1572
  876. package/dist/index.js.map +1 -1
  877. package/dist/ir/analysis.d.ts +16 -4
  878. package/dist/ir/analysis.js +23 -4
  879. package/dist/ir/analysis.js.map +1 -1
  880. package/dist/ir/analysis.test.js +15 -2
  881. package/dist/ir/analysis.test.js.map +1 -1
  882. package/dist/ir/constant-tables.js +67 -49
  883. package/dist/ir/constant-tables.js.map +1 -1
  884. package/dist/ir/constant-tables.test.js +1 -1
  885. package/dist/ir/control-flow.d.ts +6 -0
  886. package/dist/ir/control-flow.js +142 -0
  887. package/dist/ir/control-flow.js.map +1 -0
  888. package/dist/ir/control-flow.test.d.ts +1 -0
  889. package/dist/ir/control-flow.test.js +113 -0
  890. package/dist/ir/control-flow.test.js.map +1 -0
  891. package/dist/ir/integer-loops.js +7 -18
  892. package/dist/ir/integer-loops.js.map +1 -1
  893. package/dist/ir/integer-ranges.js +53 -27
  894. package/dist/ir/integer-ranges.js.map +1 -1
  895. package/dist/ir/ir.d.ts +334 -176
  896. package/dist/ir/ir.js +710 -903
  897. package/dist/ir/ir.js.map +1 -1
  898. package/dist/ir/ir.test.js +162 -1
  899. package/dist/ir/ir.test.js.map +1 -1
  900. package/dist/ir/scalar-records.js +165 -134
  901. package/dist/ir/scalar-records.js.map +1 -1
  902. package/dist/ir/scalar-records.test.js +1 -1
  903. package/dist/ir/serialize.d.ts +4 -2
  904. package/dist/ir/serialize.js +33 -14
  905. package/dist/ir/serialize.js.map +1 -1
  906. package/dist/ir/serialize.test.d.ts +1 -0
  907. package/dist/ir/serialize.test.js +57 -0
  908. package/dist/ir/serialize.test.js.map +1 -0
  909. package/dist/ir/traverse.d.ts +41 -0
  910. package/dist/ir/traverse.js +660 -0
  911. package/dist/ir/traverse.js.map +1 -0
  912. package/dist/ir/traverse.test.d.ts +1 -0
  913. package/dist/ir/traverse.test.js +242 -0
  914. package/dist/ir/traverse.test.js.map +1 -0
  915. package/dist/ir/validate.js +2018 -1498
  916. package/dist/ir/validate.js.map +1 -1
  917. package/dist/ir/validate.test.js +536 -2
  918. package/dist/ir/validate.test.js.map +1 -1
  919. package/dist/library/cache-primitives.d.ts +2 -3
  920. package/dist/library/cache-primitives.js +4 -6
  921. package/dist/library/cache-primitives.js.map +1 -1
  922. package/dist/library/fence-eval.js +2 -2
  923. package/dist/library/fence-eval.js.map +1 -1
  924. package/dist/library/int-infer.js +7 -6
  925. package/dist/library/int-infer.js.map +1 -1
  926. package/dist/library/int-infer.test.js +4 -4
  927. package/dist/library/library-cache.d.ts +3 -2
  928. package/dist/library/library-cache.js +5 -17
  929. package/dist/library/library-cache.js.map +1 -1
  930. package/dist/library/library-cache.test.js +54 -41
  931. package/dist/library/library-cache.test.js.map +1 -1
  932. package/dist/library/library-profile.d.ts +3 -1
  933. package/dist/library/library-profile.js +15 -6
  934. package/dist/library/library-profile.js.map +1 -1
  935. package/dist/library/prepare.d.ts +15 -0
  936. package/dist/library/prepare.js +584 -0
  937. package/dist/library/prepare.js.map +1 -0
  938. package/dist/library/semantic-source.d.ts +1 -1
  939. package/dist/library/semantic-source.js +8 -6
  940. package/dist/library/semantic-source.js.map +1 -1
  941. package/dist/library/sidecar-validate.js +4 -4
  942. package/dist/library/sidecar-validate.js.map +1 -1
  943. package/dist/library/sidecar.d.ts +2 -0
  944. package/dist/library/sidecar.js +10 -3
  945. package/dist/library/sidecar.js.map +1 -1
  946. package/dist/native/cache.d.ts +11 -0
  947. package/dist/native/cache.js +113 -0
  948. package/dist/native/cache.js.map +1 -0
  949. package/dist/native/cache.test.d.ts +1 -0
  950. package/dist/native/cache.test.js +66 -0
  951. package/dist/native/cache.test.js.map +1 -0
  952. package/dist/native/cli.d.ts +1 -0
  953. package/dist/native/cli.js +112 -0
  954. package/dist/native/cli.js.map +1 -0
  955. package/dist/native/compiler.d.ts +15 -0
  956. package/dist/native/compiler.js +336 -0
  957. package/dist/native/compiler.js.map +1 -0
  958. package/dist/native/compiler.test.d.ts +1 -0
  959. package/dist/native/compiler.test.js +174 -0
  960. package/dist/native/compiler.test.js.map +1 -0
  961. package/dist/native/comptime.d.ts +5 -0
  962. package/dist/native/comptime.js +73 -0
  963. package/dist/native/comptime.js.map +1 -0
  964. package/dist/native/comptime.test.d.ts +1 -0
  965. package/dist/native/comptime.test.js +65 -0
  966. package/dist/native/comptime.test.js.map +1 -0
  967. package/dist/native/executable-cache.d.ts +26 -0
  968. package/dist/native/executable-cache.js +228 -0
  969. package/dist/native/executable-cache.js.map +1 -0
  970. package/dist/native/executable-cache.test.d.ts +1 -0
  971. package/dist/native/executable-cache.test.js +151 -0
  972. package/dist/native/executable-cache.test.js.map +1 -0
  973. package/dist/native/file-identity.d.ts +4 -0
  974. package/dist/native/file-identity.js +14 -0
  975. package/dist/native/file-identity.js.map +1 -0
  976. package/dist/native/file-identity.test.d.ts +1 -0
  977. package/dist/native/file-identity.test.js +43 -0
  978. package/dist/native/file-identity.test.js.map +1 -0
  979. package/dist/native/library.d.ts +22 -0
  980. package/dist/native/library.js +83 -0
  981. package/dist/native/library.js.map +1 -0
  982. package/dist/native/prepare.d.ts +21 -0
  983. package/dist/native/prepare.js +174 -0
  984. package/dist/native/prepare.js.map +1 -0
  985. package/dist/native/prepare.test.d.ts +1 -0
  986. package/dist/native/prepare.test.js +70 -0
  987. package/dist/native/prepare.test.js.map +1 -0
  988. package/dist/native/sanitizer.d.ts +11 -0
  989. package/dist/native/sanitizer.js +137 -0
  990. package/dist/native/sanitizer.js.map +1 -0
  991. package/dist/native/sanitizer.test.d.ts +1 -0
  992. package/dist/native/sanitizer.test.js +58 -0
  993. package/dist/native/sanitizer.test.js.map +1 -0
  994. package/dist/native/task.d.ts +4 -0
  995. package/dist/native/task.js +16 -0
  996. package/dist/native/task.js.map +1 -0
  997. package/dist/native/task.test.d.ts +1 -0
  998. package/dist/native/task.test.js +17 -0
  999. package/dist/native/task.test.js.map +1 -0
  1000. package/dist/native/toolchain.d.ts +45 -0
  1001. package/dist/native/toolchain.js +94 -0
  1002. package/dist/native/toolchain.js.map +1 -0
  1003. package/dist/native/toolchain.test.d.ts +1 -0
  1004. package/dist/native/toolchain.test.js +85 -0
  1005. package/dist/native/toolchain.test.js.map +1 -0
  1006. package/dist/startup-cache.d.ts +1 -1
  1007. package/dist/startup-cache.js +2 -5
  1008. package/dist/startup-cache.js.map +1 -1
  1009. package/dist/timing.d.ts +4 -0
  1010. package/dist/timing.js +19 -0
  1011. package/dist/timing.js.map +1 -0
  1012. package/dist/wasi-paths.js +10 -7
  1013. package/dist/wasi-paths.js.map +1 -1
  1014. package/package.json +28 -19
  1015. package/surface-manifest.json +107 -98
  1016. package/dist/backend/c/async.d.ts +0 -171
  1017. package/dist/backend/c/async.js +0 -1317
  1018. package/dist/backend/c/async.js.map +0 -1
  1019. package/dist/backend/c/c-emitter.d.ts +0 -536
  1020. package/dist/backend/c/c-emitter.js +0 -1932
  1021. package/dist/backend/c/c-emitter.js.map +0 -1
  1022. package/dist/backend/c/exprs.d.ts +0 -15
  1023. package/dist/backend/c/exprs.js +0 -8847
  1024. package/dist/backend/c/exprs.js.map +0 -1
  1025. package/dist/backend/c/island.d.ts +0 -45
  1026. package/dist/backend/c/island.js +0 -281
  1027. package/dist/backend/c/island.js.map +0 -1
  1028. package/dist/backend/c/may-throw.js.map +0 -1
  1029. package/dist/backend/c/shapes.d.ts +0 -142
  1030. package/dist/backend/c/shapes.js +0 -603
  1031. package/dist/backend/c/shapes.js.map +0 -1
  1032. package/dist/backend/c/stmts.d.ts +0 -76
  1033. package/dist/backend/c/stmts.js +0 -1108
  1034. package/dist/backend/c/stmts.js.map +0 -1
  1035. package/dist/backend/c/types.d.ts +0 -71
  1036. package/dist/backend/c/types.js +0 -459
  1037. package/dist/backend/c/types.js.map +0 -1
  1038. package/dist/backend/c/types.test.js +0 -32
  1039. package/dist/backend/c/types.test.js.map +0 -1
  1040. package/dist/backend/c/walkers.d.ts +0 -134
  1041. package/dist/backend/c/walkers.js +0 -1772
  1042. package/dist/backend/c/walkers.js.map +0 -1
  1043. /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,20 @@ 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 { captureContextArguments } from "./function-context.js";
15
+ import { OBJECT_CALLABLE_VALUES } from "./surfaces.js";
11
16
  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";
17
+ 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
18
  import { cjsClassExprWholeExportOf, cjsExportAssignmentOf, cjsExportDiscardReason, isCjsExportTableLiteral, isCjsJsFile, isJsSourceFile, isModuleExportsAccess, isNodeEsmFile, locOf } from "../program.js";
14
19
  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";
16
- 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";
20
+ import { UNSUPPORTED, blockedBindingUseDiag, requiresDynamicPackageDiag, unsupportedDiag } from "../../diagnostics/diagnostic.js";
21
+ import { PoisonError, dynFallbackType, dynUndefinedExpr, jsFuncNameOf, neverTaintedJsType, nodeThrowExpr, own } from "./lowerer.js";
22
+ import { lowerCollectionSpread, lowerNpmStaticSafeIndexRead, lowerSafeIndexRead, strCharsCall, tryLowerNumericIndexRead } from "./lower-containers.js";
23
+ import { arrayValueRead, arrayValueStore, arrayValueType } from "./array-values.js";
24
+ import { lowerOptionalStringIndex } from "./string-index.js";
19
25
  import { tryLowerIndexedComparison } from "./indexed-comparison.js";
20
26
  import { npmStaticPackageOfPath } from "../npm-static.js";
21
27
  import { unsupportedModuleFeatureOf } from "../builtin-modules.js";
@@ -23,20 +29,37 @@ import { fenceEnumObjectValue, lowerEnumAccess } from "./lower-enums.js";
23
29
  import { ambientNsRootOf, ambientUndefReadType, ambientUndefVarRootOf, ambientUndefinedFnSymbolOf, contextualUndefReadType, fenceEarlyAliasUse, fenceEarlyNsMemberRef, lowerNsIdentifierValue, nsMemberIdentOf, nsUndefRead, nsWritableTarget } from "./lower-namespaces.js";
24
30
  import { expandoMemberRead, expandoWritableTarget } from "./lower-expando.js";
25
31
  import { lowerSocketInstanceOf, lowerTlsRootCertificates } from "./lower-server.js";
26
- import { findGenericMethodOn, lowerStaticFieldRead } from "./lower-classes.js";
32
+ import { findGenericMethodOn, lowerStaticFieldRead, staticFieldWriteTarget, storedClassValueType } from "./lower-classes.js";
33
+ import { hasRuntimeStatics } from "./class-runtime-statics.js";
27
34
  import { bindingNeverReassigned, funcTypeFromParamShapes, implicitMonoFile, lowerTaggedTemplate, nullishGenericBindingUnitOf, objLitGenericFnInfoOf, objLitGenericFnNodeOf, requireObjLitGenericReceiver } from "./lower-calls.js";
28
35
  import { mixinFnOfCallee } from "./lower-mixins.js";
29
- import { isConstAssertionTypeNode, isGenericCallableMemberType, isParseArgsDynTypeName, underConstAssertion, unitOnlyUnion } from "../type-mapper.js";
36
+ import { isConstAssertionTypeNode, isGenericCallableMemberType, isParseArgsDynTypeName, underConstAssertion, unitOnlyUnion, withUnitArm } from "../type-mapper.js";
30
37
  import { lowerYield } from "./lower-generators.js";
38
+ import { errorPropertyRead, errorPropertyWrite, errorToStringCall } from "./error-methods.js";
31
39
  import { lowerStreamProperty, lowerStreamStateProperty, streamSidesOf } from "./lower-stream.js";
32
- import { countedFor, numLit, varRef } from "../../ir/build.js";
40
+ import { countedFor, numLit, strLit, varRef } from "../../ir/build.js";
41
+ import { unionWideningTags } from "../../ir/analysis.js";
33
42
  import { isSafeToDiscard, isSafeToMoveConditionEarlier, isSafeToRepeat } from "./expressions/evaluation-safety.js";
43
+ import { globalSymbolKey } from "./expressions/global-symbols.js";
44
+ import { lowerShortCircuitAssignment } from "./expressions/nullish-assignment.js";
34
45
  import { hasOptionalChainGuard, isOptionalChainTail, isRequireMainFilename } from "./expressions/optional-chains.js";
35
46
  import { conditionalSpreadOf, foldedStringKeyOf } from "./expressions/object-literals.js";
36
47
  import { tryLowerExpression } from "./expressions/try-lower-expression.js";
37
48
  import { fenceNodeModuleMutation, isNodeModuleValue, lowerNodeModuleIdentifier, lowerNodeModuleProperty, lowerRequireCacheElement, lowerRequireCacheHas, lowerRequireMainProperty } from "./lower-node-module.js";
38
49
  import { lowerAbstractEquality } from "./abstract-equality.js";
39
50
  import { coerceStringSearchValue, defaultAfterUndefined, lowerStaticallyUndefinedArgument } from "./optional-arguments.js";
51
+ import { recordTextCodecClass } from "../../ir/ir.js";
52
+ import { classSymbolKeyOf } from "./symbol-fields.js";
53
+ import { lowerClassMethodValue } from "./class-method-values.js";
54
+ import { classInstanceOf } from "./class-dynamic-dispatch.js";
55
+ import { lowerGlobalValue } from "./lower-global-value.js";
56
+ import { lowerClassPrototypeData } from "./class-prototypes.js";
57
+ import { isClassCallback } from "./class-callbacks.js";
58
+ import { builtinPrototypeMethod, lowerArrayIsArrayValue, lowerCheckedPredicateValue, lowerNumberParserValue, lowerObjectAssignValue, lowerStringCodesValue } from "./lower-builtin-values.js";
59
+ import { lowerArrayFromValue } from "./lower-containers.js";
60
+ import { lowerPerfHooksTypeof } from "./lower-builtins.js";
61
+ import { jsBindingHasOpenWrites } from "./lower-stmts.js";
62
+ import { checkedClassInstanceOf } from "./class-construction.js";
40
63
  /** A template piece's RAW text (String.raw's contract: escapes stay
41
64
  * characters). 7's client AST ships no rawText at runtime (the typing
42
65
  * declares it; the serialized node data omits it), so the raw span comes
@@ -313,6 +336,12 @@ function lowerExprInner(lowerer, expr) {
313
336
  return !sf.isDeclarationFile && !sf.fileName.includes("/node_modules/");
314
337
  });
315
338
  if (projectDeclared) {
339
+ // A spread already stored in the island must copy there too.
340
+ // Later overrides can make the literal's own inferred shape
341
+ // appear static without making that source a native record.
342
+ if (ts.isObjectLiteralExpression(expr) && expr.properties.some((p) => ts.isSpreadAssignment(p) && lowerer.mapTypeOf(lowerer.typeOf(p.expression))?.kind === "jsval")) {
343
+ return true;
344
+ }
316
345
  const own = lowerer.mapTypeOf(lowerer.typeOf(expr));
317
346
  if (own?.kind === "record" || own?.kind === "array")
318
347
  return false;
@@ -350,7 +379,7 @@ function lowerExprInner(lowerer, expr) {
350
379
  // null keeps the closure shape (function-shaped members, and the
351
380
  // conditional-spread arms where getters cannot combine).
352
381
  const islandMemberFence = (diagsBefore, err, valueNode, getterName = null) => {
353
- if (!(err instanceof PoisonError) || !isJsSourceFile(expr.getSourceFile()))
382
+ if (!isJsSourceFile(expr.getSourceFile()))
354
383
  throw err;
355
384
  const fence = lowerer.deferToRuntimeFence(diagsBefore, valueNode, {
356
385
  kind: "closure",
@@ -398,6 +427,8 @@ function lowerExprInner(lowerer, expr) {
398
427
  // instance): func-typed values keep the call-time closure;
399
428
  // data-shaped values fence at the read (the getter), unless a
400
429
  // conditional spread owns the literal (getters cannot combine).
430
+ if (!(err instanceof PoisonError))
431
+ throw err;
401
432
  const asGetter = value.type.kind !== "func" && spread === null;
402
433
  const fence = islandMemberFence(diagsBefore, err, valueNode, asGetter ? name.text : null);
403
434
  if (fence === null)
@@ -486,6 +517,8 @@ function lowerExprInner(lowerer, expr) {
486
517
  : null;
487
518
  }
488
519
  catch (err) {
520
+ if (!(err instanceof PoisonError))
521
+ throw err;
489
522
  const nameText = name && (ts.isIdentifier(name) || ts.isStringLiteral(name)) ? name.text : null;
490
523
  const asGetter = nameText !== null && spread === null && !funcShapedMember(prop);
491
524
  value = islandMemberFence(valueDiagsBefore, err, prop, asGetter ? nameText : null);
@@ -551,6 +584,11 @@ function lowerExprInner(lowerer, expr) {
551
584
  return { kind: "jsOp", op: "arrLit", args, type: JSVAL, loc };
552
585
  }
553
586
  if (ts.isTypeOfExpression(expr)) {
587
+ if (lowerer.isStdlibGlobal(expr.expression, "process"))
588
+ return { kind: "strLit", value: "object", type: STRING, loc };
589
+ const performance = lowerPerfHooksTypeof(lowerer, expr.expression);
590
+ if (performance)
591
+ return performance;
554
592
  if (isNodeModuleValue(lowerer, expr.expression)) {
555
593
  return { kind: "strLit", value: "object", type: STRING, loc };
556
594
  }
@@ -580,8 +618,14 @@ function lowerExprInner(lowerer, expr) {
580
618
  // every function and constructor global is "function" (the harness's
581
619
  // `typeof queueMicrotask === 'function'` probes). Shadowing locals
582
620
  // have non-stdlib symbols and keep the ordinary path.
583
- if (ts.isIdentifier(expr.expression)) {
584
- const sym = lowerer.checker.getSymbolAtLocation(expr.expression);
621
+ let tested = expr.expression;
622
+ while (ts.isParenthesizedExpression(tested))
623
+ tested = tested.expression;
624
+ if (ts.isIdentifier(tested)) {
625
+ const sym = lowerer.checker.getSymbolAtLocation(tested);
626
+ if (!sym && !lowerer.dynamic) {
627
+ return { kind: "strLit", value: "undefined", type: STRING, loc };
628
+ }
585
629
  if (lowerer.isStdlibSymbol(sym)) {
586
630
  const t = lowerer.typeOf(expr.expression);
587
631
  if (lowerer.checker.getCallSignatures(t).length > 0 ||
@@ -590,11 +634,9 @@ function lowerExprInner(lowerer, expr) {
590
634
  }
591
635
  }
592
636
  }
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).
637
+ // A known static result type fixes typeof's answer, but its producer
638
+ // still evaluates: calls, getters, and checked reads can have effects
639
+ // or throw. Only trivial operands may disappear.
598
640
  let operand = lowerer.lowerExpr(expr.expression);
599
641
  if (operand.type.kind === "jsval") {
600
642
  return { kind: "jsOp", op: "typeof", args: [operand], type: STRING, loc };
@@ -612,13 +654,16 @@ function lowerExprInner(lowerer, expr) {
612
654
  array: "object", object: "object", record: "object",
613
655
  symbol: "symbol",
614
656
  map: "object", set: "object", promise: "object", bytes: "object",
615
- regexp: "object", generator: "object", classval: "function",
657
+ regex: "object", generator: "object", classval: "function",
616
658
  moduleNs: "object",
617
659
  undefinedT: "undefined", nullT: "object",
618
660
  };
619
661
  const folded = FOLD[operand.type.kind];
620
- if (folded && isSafeToDiscard(operand)) {
621
- return { kind: "strLit", value: folded, type: STRING, loc };
662
+ if (folded) {
663
+ const result = { kind: "strLit", value: folded, type: STRING, loc };
664
+ return isSafeToDiscard(operand) ? result : {
665
+ kind: "seqExpr", stmts: [{ kind: "exprStmt", expr: operand, loc }], result, type: STRING, loc,
666
+ };
622
667
  }
623
668
  // A union operand: every arm's typeof answer is static, so the value
624
669
  // form is a ternary chain over runtime TAG tests (arms grouped by
@@ -653,6 +698,9 @@ function lowerExprInner(lowerer, expr) {
653
698
  lowerer.unsupported("SC1090", expr, "typeof expressions on statically-typed values");
654
699
  }
655
700
  if (ts.isIdentifier(expr)) {
701
+ if (lowerer.isStdlibGlobal(expr, "console")) {
702
+ return { kind: "libCall", fn: "console.native", args: [], type: DYN, loc };
703
+ }
656
704
  const moduleValue = lowerNodeModuleIdentifier(lowerer, expr);
657
705
  if (moduleValue)
658
706
  return moduleValue;
@@ -662,8 +710,10 @@ function lowerExprInner(lowerer, expr) {
662
710
  // `arguments` in a variadic JS function (the rest-marked form): the
663
711
  // synthetic trailing dyn-array param — lambdaSignature marked the
664
712
  // type and lowerLambda declared the local.
665
- if (expr.text === "arguments" && lowerer.ctx.argumentsLocal) {
666
- return { kind: "varRef", localId: lowerer.ctx.argumentsLocal.id, type: DYN, loc };
713
+ if (expr.text === "arguments") {
714
+ const binding = captureContextArguments(lowerer.fnStack);
715
+ if (binding)
716
+ return { kind: "varRef", localId: binding.id, type: DYN, loc };
667
717
  }
668
718
  // A compiler-projected adapter as a VALUE: the adapter has no
669
719
  // runtime function object (its calls rewrite directly) — call it
@@ -676,7 +726,7 @@ function lowerExprInner(lowerer, expr) {
676
726
  }
677
727
  if (projection?.kind === "builtin-function") {
678
728
  if (!isJsSourceFile(expr.getSourceFile())) {
679
- const callable = lowerer.lowerBuiltinCallableValue(projection, loc);
729
+ const callable = lowerer.lowerBuiltinCallableValue({ module: projection.module, member: projection.member }, loc);
680
730
  if (callable)
681
731
  return callable;
682
732
  }
@@ -688,6 +738,21 @@ function lowerExprInner(lowerer, expr) {
688
738
  // bridges the tagged representation with a unionNarrow.
689
739
  const local = lowerer.resolveLocal(expr);
690
740
  if (local) {
741
+ // A switch can dispatch past a lexical declaration. Keep this
742
+ // previously refused codec form out until switch-scope TDZ boxes
743
+ // model that skipped initialization, including value aliases.
744
+ if (local.type.kind === "record" && recordTextCodecClass(lowerer.shapes.get(local.type.shapeId)) !== null) {
745
+ const symbol = lowerer.resolveValueSymbol(expr);
746
+ const decl = symbol ? lowerer.checker.valueDeclarationOf(symbol) : undefined;
747
+ if (decl && ts.isVariableDeclaration(decl) && ts.isVariableDeclarationList(decl.parent)
748
+ && (decl.parent.flags & ts.NodeFlags.BlockScoped) !== 0 && ts.isVariableStatement(decl.parent.parent)) {
749
+ const clause = decl.parent.parent.parent;
750
+ if ((ts.isCaseClause(clause) || ts.isDefaultClause(clause))
751
+ && !(expr.getStart() >= clause.getStart() && expr.end <= clause.end)) {
752
+ lowerer.unsupported("SC1090", expr, "codec bindings read from another switch clause before initialization is proven");
753
+ }
754
+ }
755
+ }
691
756
  if (local.type.kind === "caught")
692
757
  return lowerer.caughtRead(expr, local, loc);
693
758
  const symbol = lowerer.resolveValueSymbol(expr);
@@ -696,7 +761,7 @@ function lowerExprInner(lowerer, expr) {
696
761
  const arithmetic = lowerer.runtimeOptionalArithmeticLocals.has(runtimeOptionalRoot);
697
762
  const captured = !!symbol &&
698
763
  lowerer.runtimeOptionalStorageLocals.has(runtimeOptionalRoot) &&
699
- (lowerer.ctx.captureBySymbol.get(symbol) === local || runtimeOptionalRoot !== local);
764
+ (local.boxed === true || lowerer.ctx.captureBySymbol.get(symbol) === local || runtimeOptionalRoot !== local);
700
765
  if (arithmetic && !runtimeOptionalUseOf(expr)) {
701
766
  return { kind: "varRef", localId: local.id, type: local.type, loc };
702
767
  }
@@ -737,7 +802,24 @@ function lowerExprInner(lowerer, expr) {
737
802
  }
738
803
  return { kind: "varRef", localId: local.id, type: local.type, loc };
739
804
  }
740
- return lowerer.maybeNarrow({ kind: "varRef", localId: local.id, type: local.type, loc }, expr);
805
+ const narrowed = lowerer.maybeNarrow({ kind: "varRef", localId: local.id, type: local.type, loc }, expr);
806
+ if (narrowed.type.kind === "union" && lowerer.runtimeOptionalStorageLocals.has(runtimeOptionalRoot) &&
807
+ lowerer.mapTypeOf(lowerer.typeOf(expr))?.kind === "record") {
808
+ // A presence guard can be followed by a predicate strengthening
809
+ // the record's fields. Its refined shape is not a stored union
810
+ // arm: extract the original layout before reading a member.
811
+ const use = runtimeOptionalUseOf(expr);
812
+ if (use && !use.complex && !use.optional) {
813
+ if (use.kind === "property")
814
+ return runtimeOptionalReceiverRead(lowerer, expr, local, use.access.name.text, loc);
815
+ if (use.kind === "element") {
816
+ const key = runtimeOptionalElementKey(use.access.argumentExpression);
817
+ if (key !== null)
818
+ return runtimeOptionalReceiverRead(lowerer, expr, local, key, loc);
819
+ }
820
+ }
821
+ }
822
+ return narrowed;
741
823
  }
742
824
  // `import x = N.y` aliases resolve transparently through globalOf/
743
825
  // fnSigOf below; their source-order guards live here (a no-op for
@@ -774,6 +856,7 @@ function lowerExprInner(lowerer, expr) {
774
856
  lowerer.unions.get(g.type.unionId).arms.some((a) => a.kind === "string");
775
857
  if (!arithmeticUnion && g.type.kind === "union" && narrowed && narrowed.kind !== "union" &&
776
858
  !isUnitType(narrowed) &&
859
+ !(narrowed.kind === "record" && recordTextCodecClass(lowerer.shapes.get(narrowed.shapeId)) !== null) &&
777
860
  narrowed.kind !== "f64" && narrowed.kind !== "string" && narrowed.kind !== "bool" &&
778
861
  lowerer.armTag(g.type.unionId, narrowed) >= 0) {
779
862
  const helper = lowerer.narrowedArmHelper(g.type.unionId, narrowed, loc);
@@ -817,7 +900,7 @@ function lowerExprInner(lowerer, expr) {
817
900
  if ((expr.text === "__dirname" || expr.text === "__filename") &&
818
901
  lowerer.isStdlibGlobal(expr, expr.text)) {
819
902
  const sf = expr.getSourceFile();
820
- if (isNodeEsmFile(sf)) {
903
+ if (isNodeEsmFile(sf, lowerer.program)) {
821
904
  lowerer.unsupported("SC1090", expr, `'${expr.text}' in an ES module (Node defines the CJS module globals only in CommonJS modules)`);
822
905
  }
823
906
  // The WASI runner mounts the host cwd at guest `/` and the host temp
@@ -899,17 +982,17 @@ function lowerExprInner(lowerer, expr) {
899
982
  if (roots)
900
983
  return roots;
901
984
  }
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
985
  {
909
986
  const callable = lowerer.lowerBuiltinCallableValue(bi, loc);
910
987
  if (callable)
911
988
  return callable;
912
989
  }
990
+ // JavaScript sources: an otherwise unsupported builtin VALUE is
991
+ // the same identity-token story as stdlib globals above (the
992
+ // harness adds worker_threads.Worker to its identity Set).
993
+ if (isJsSourceFile(expr.getSourceFile())) {
994
+ return { kind: "strLit", value: `[builtin ${bi.module}.${bi.member}]`, type: STRING, loc };
995
+ }
913
996
  if (builtinModuleFnOf(lowerer, bi.module, bi.member)) {
914
997
  lowerer.unsupported("SC1090", expr, `library functions as values (call '${expr.text}' directly)`);
915
998
  }
@@ -977,14 +1060,36 @@ function lowerExprInner(lowerer, expr) {
977
1060
  // type-mapper.ts pins the one concrete signature `(value: string) =>
978
1061
  // primitive`; direct calls `String(x)` never reach here — the call
979
1062
  // lowering intercepts them with the wider static coercions).
980
- const jsPrimitiveCtorSelector = isJsSourceFile(expr.getSourceFile()) &&
981
- expr.parent !== undefined &&
982
- ts.isArrowFunction(expr.parent) &&
983
- expr.parent.body === expr;
984
- if ((expr.text === "String" || expr.text === "Number" || expr.text === "Boolean") &&
985
- (!isJsSourceFile(expr.getSourceFile()) || jsPrimitiveCtorSelector) &&
986
- lowerer.isStdlibSymbol(lowerer.checker.getSymbolAtLocation(expr))) {
987
- return primitiveCtorClosure(lowerer, expr.text, loc, jsPrimitiveCtorSelector);
1063
+ const jsPrimitiveCtorSelector = isJsSourceFile(expr.getSourceFile());
1064
+ const primitiveName = stdlibGlobalNameOf(lowerer, expr);
1065
+ if (primitiveName === "String" || primitiveName === "Number" || primitiveName === "Boolean" || primitiveName === "BigInt") {
1066
+ return primitiveCtorClosure(lowerer, primitiveName, loc, jsPrimitiveCtorSelector);
1067
+ }
1068
+ if (lowerer.isStdlibGlobal(expr, "ArrayBuffer"))
1069
+ return { kind: "libCall", fn: "arrayBuffer.constructor", args: [], type: DYN, loc };
1070
+ if (Object.values(BYTES_ELEMENT_NAME).includes(expr.text) && lowerer.isStdlibSymbol(lowerer.checker.getSymbolAtLocation(expr))) {
1071
+ return { kind: "libCall", fn: "bytes.constructor", args: [{ kind: "strLit", value: expr.text, type: STRING, loc }], type: DYN, loc };
1072
+ }
1073
+ if ((expr.text === "encodeURIComponent" || expr.text === "encodeURI" || expr.text === "decodeURIComponent" || expr.text === "decodeURI") && lowerer.isStdlibGlobal(expr, expr.text)) {
1074
+ const name = `%builtin.${expr.text}`;
1075
+ const type = funcOf([DYN], STRING);
1076
+ if (!lowerer.liftedFns.some((fn) => fn.name === name)) {
1077
+ const value = { kind: "libCall", fn: "dyn.toStringCoerce", args: [varRef("value", DYN, loc)], type: STRING, loc };
1078
+ const fn = expr.text === "encodeURIComponent" ? "str.encodeUriComponent" : expr.text === "encodeURI" ? "str.encodeUri" : expr.text === "decodeURI" ? "str.decodeUri" : "str.decodeUriComponent";
1079
+ lowerer.liftedFns.push({ name, params: [{ localId: "value", name: "value", type: DYN }],
1080
+ locals: [{ id: "value", name: "value", type: DYN, mutable: false }], returnType: STRING,
1081
+ body: [{ kind: "return", value: { kind: "libCall", fn, args: [value], type: STRING, loc }, loc }], loc });
1082
+ }
1083
+ return { kind: "closure", fnName: name, captures: [], type, loc };
1084
+ }
1085
+ if (!lowerer.dynamic && lowerer.isStdlibGlobal(expr, "crypto"))
1086
+ return { kind: "libCall", fn: "crypto.native", args: [], type: DYN, loc };
1087
+ const nativeAlias = stdlibGlobalNameOf(lowerer, expr);
1088
+ if (nativeAlias === "Buffer" && expr.text !== "Buffer") {
1089
+ return { kind: "strLit", value: "[builtin Buffer]", type: STRING, loc };
1090
+ }
1091
+ if (!lowerer.dynamic && lowerer.isStdlibGlobal(expr, "fetch")) {
1092
+ return { kind: "libCall", fn: "fetch.function", args: [], type: DYN, loc };
988
1093
  }
989
1094
  // The lib fence's IDENTIFIER chokepoint: the real standard library
990
1095
  // resolves names the old minimal ambient world never declared
@@ -999,6 +1104,9 @@ function lowerExprInner(lowerer, expr) {
999
1104
  // global, one token; what a token cannot do (be called, answer
1000
1105
  // typeof "function") meets per-site fences/divergences, and
1001
1106
  // SEMANTICS.md documents the stance.
1107
+ if (!lowerer.dynamic && stdlibGlobalNameOf(lowerer, expr) === "globalThis") {
1108
+ return lowerGlobalValue(lowerer, expr);
1109
+ }
1002
1110
  {
1003
1111
  const sym = lowerer.checker.getSymbolAtLocation(expr);
1004
1112
  if (lowerer.isStdlibSymbol(sym) || expr.text === "globalThis") {
@@ -1016,11 +1124,11 @@ function lowerExprInner(lowerer, expr) {
1016
1124
  // The families with a WHY: each hint states what makes the
1017
1125
  // surface genuinely non-static (or what to use instead).
1018
1126
  const globalHints = {
1019
- Proxy: "property-access metaprogramming has no static lowering (every property read must resolve at compile time)",
1127
+ Proxy: "Proxy constructor values have no native lowering; use a direct new Proxy with checked-native plain objects",
1020
1128
  Reflect: "reflective property access has no static lowering — read and call members directly",
1021
- 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",
1129
+ 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",
1022
1130
  SharedArrayBuffer: "no shared-memory threads exist in a compiled program — Uint8Array is the byte storage",
1023
- 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)",
1131
+ ArrayBuffer: "ArrayBuffer constructor values have no native lowering; use new ArrayBuffer(length) for fixed-length shared storage",
1024
1132
  WeakRef: "deref()-after-collect exposes GC timing — genuinely dynamic; hold a strong reference instead",
1025
1133
  FinalizationRegistry: "finalization callbacks expose GC timing — genuinely dynamic; release resources explicitly instead",
1026
1134
  eval: "runtime code evaluation cannot be compiled ahead of time",
@@ -1182,8 +1290,13 @@ function lowerExprInner(lowerer, expr) {
1182
1290
  // `this` in plain nested functions is already a tsc error under
1183
1291
  // noImplicitThis, so the generic scope walk here is preflight-safe.
1184
1292
  const local = lowerer.resolveThis();
1185
- if (local)
1186
- return { kind: "varRef", localId: local.id, type: local.type, loc };
1293
+ if (local) {
1294
+ const value = { kind: "varRef", localId: local.id, type: local.type, loc };
1295
+ const initialized = lowerer.ctx.superInitialized;
1296
+ if (initialized)
1297
+ return { kind: "seqExpr", stmts: [{ kind: "if", cond: { kind: "unary", op: "!", operand: { kind: "varRef", localId: initialized.id, type: BOOL, loc }, type: BOOL, loc }, then: [{ kind: "exprStmt", expr: nodeThrowExpr(5, "", "Must call super constructor in derived class before accessing 'this' or returning from derived constructor", DYN, loc), loc }], else_: null, loc }], result: value, type: value.type, loc };
1298
+ return value;
1299
+ }
1187
1300
  // `this` in a plain JS FUNCTION (not a method): the AMBIENT
1188
1301
  // RECEIVER (libCall dyn.this — scr_dyn_this_get). Firing sites bind
1189
1302
  // the emitting handle around each listener call (Node calls
@@ -1370,6 +1483,20 @@ function lowerExprInner(lowerer, expr) {
1370
1483
  if (ts.isConditionalExpression(expr))
1371
1484
  return lowerTernary(lowerer, expr);
1372
1485
  if (ts.isPropertyAccessExpression(expr)) {
1486
+ // Expando statics may still be absent even when JS inference sees
1487
+ // the eventual assignment's scalar type. Preserve the checked value.
1488
+ const staticField = lowerStaticFieldRead(lowerer, expr);
1489
+ if (staticField)
1490
+ return staticField;
1491
+ const prototypeData = lowerClassPrototypeData(lowerer, expr);
1492
+ if (prototypeData)
1493
+ return prototypeData;
1494
+ if (ts.isPropertyAccessExpression(expr.expression) && expr.expression.name.text === "prototype") {
1495
+ const prototype = lowerClassPrototypeData(lowerer, expr.expression);
1496
+ if (prototype)
1497
+ return { kind: "dynKeyGet", value: prototype,
1498
+ key: { kind: "strLit", value: expr.name.text, type: STRING, loc }, type: DYN, loc };
1499
+ }
1373
1500
  // `super.x`: the base chain's GETTER, called directly (super
1374
1501
  // dispatch is static in JS — never through the dynamic class).
1375
1502
  // super.method() calls are routed at the call site; a bare super
@@ -1411,6 +1538,33 @@ function lowerExprInner(lowerer, expr) {
1411
1538
  const moduleProperty = lowerNodeModuleProperty(lowerer, expr);
1412
1539
  if (moduleProperty)
1413
1540
  return moduleProperty;
1541
+ if (stdlibGlobalNameOf(lowerer, expr.expression) === "Symbol" &&
1542
+ ["iterator", "asyncIterator", "toStringTag", "toPrimitive", "hasInstance", "isConcatSpreadable", "match", "matchAll", "replace", "search", "split", "species", "unscopables", "dispose", "asyncDispose"].includes(expr.name.text)) {
1543
+ return { kind: "libCall", fn: "sym.wellKnown", args: [{ kind: "strLit", value: expr.name.text, type: STRING, loc }], type: { kind: "symbol" }, loc };
1544
+ }
1545
+ if (!lowerer.dynamic && lowerer.stdlibGlobalMember(expr, "Object") === "freeze") {
1546
+ const name = "%builtin.Object.freeze";
1547
+ if (!lowerer.liftedFns.some((fn) => fn.name === name))
1548
+ lowerer.liftedFns.push({ name,
1549
+ params: [{ localId: "value", name: "value", type: DYN }], locals: [{ id: "value", name: "value", type: DYN, mutable: false }], returnType: DYN,
1550
+ body: [{ kind: "return", value: { kind: "libCall", fn: "dyn.freeze", args: [varRef("value", DYN, loc)], type: DYN, loc }, loc }], loc });
1551
+ return { kind: "closure", fnName: name, captures: [], type: funcOf([DYN], DYN), loc };
1552
+ }
1553
+ if (lowerer.stdlibGlobalMember(expr, "globalThis") === "console") {
1554
+ return { kind: "libCall", fn: "console.native", args: [], type: DYN, loc };
1555
+ }
1556
+ if (lowerer.stdlibGlobalMember(expr, "console") !== null) {
1557
+ return { kind: "dynKeyGet", value: { kind: "libCall", fn: "console.native", args: [], type: DYN, loc },
1558
+ key: { kind: "strLit", value: expr.name.text, type: STRING, loc }, type: DYN, loc };
1559
+ }
1560
+ // Missing host globals (Bun/Deno capability probes) have no binding
1561
+ // in a native program. Declared globals retain their own lowering;
1562
+ // arbitrary global-object mutation remains fenced. Preserve a dyn
1563
+ // undefined value so stored probes compose with optional calls.
1564
+ if (!lowerer.dynamic && stdlibGlobalNameOf(lowerer, expr.expression) === "globalThis" &&
1565
+ !lowerer.checker.getPropertyOfType(lowerer.typeOf(expr.expression), expr.name.text)) {
1566
+ return { kind: "dynFrom", value: { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc }, type: DYN, loc };
1567
+ }
1414
1568
  if (expr.questionDotToken && expr.name.text === "electron") {
1415
1569
  const processProperty = lowerer.lowerProcessProperty(expr);
1416
1570
  if (processProperty)
@@ -1427,6 +1581,12 @@ function lowerExprInner(lowerer, expr) {
1427
1581
  if (!expr.questionDotToken && isOptionalChainTail(lowerer, expr)) {
1428
1582
  return lowerer.lowerOptionalChain(expr);
1429
1583
  }
1584
+ // Known Web handles retain their declared surface even when their
1585
+ // storage uses checked values. Apply this before generic reads.
1586
+ lowerer.fenceStaticAbortControllerMemberRead(expr);
1587
+ lowerer.fenceStaticResponseMember(expr, "read");
1588
+ lowerer.fenceStaticHeadersMember(expr, "read");
1589
+ lowerer.fenceStaticReadableStreamMember(expr, "read");
1430
1590
  // Island receiver: o.x is an engine property read (getProp may throw
1431
1591
  // — reading off null/undefined — bridged catchably like everything
1432
1592
  // at the boundary). `o?.x` arrives here too, through the chain
@@ -1475,15 +1635,6 @@ function lowerExprInner(lowerer, expr) {
1475
1635
  }
1476
1636
  return read;
1477
1637
  }
1478
- // `m.groups` on a match result whose regex is statically known:
1479
- // the compile-time record projection over the honest slice (or
1480
- // Node's undefined when the pattern has no named groups) — see
1481
- // lowerMatchGroupsRead.
1482
- {
1483
- const g = lowerMatchGroupsRead(lowerer, expr);
1484
- if (g !== null)
1485
- return g;
1486
- }
1487
1638
  // `m.index` on a for-of-over-matchAll binding reads the companion-
1488
1639
  // index array at this iteration's cursor — always a number: every
1489
1640
  // drained row matched, so the lib's `.index` is never undefined
@@ -1501,6 +1652,25 @@ function lowerExprInner(lowerer, expr) {
1501
1652
  };
1502
1653
  }
1503
1654
  }
1655
+ if ((expr.name.text === "index" || expr.name.text === "input") && !expr.questionDotToken) {
1656
+ const represented = tryLowerExpression(lowerer, expr.expression);
1657
+ if (represented && isMatchSliceType(lowerer, represented.type) &&
1658
+ (lowerer.isStdlibMember(expr) || isJsSourceFile(expr.getSourceFile()))) {
1659
+ const recv = represented;
1660
+ const read = { kind: "dynKeyGet", value: lowerer.coerceToExpected(recv, DYN),
1661
+ key: { kind: "strLit", value: expr.name.text, type: STRING, loc }, type: DYN, loc };
1662
+ return lowerer.maybeNarrow(read, expr);
1663
+ }
1664
+ }
1665
+ // `m.groups` on a match result whose regex is statically known:
1666
+ // the compile-time record projection over the honest slice (or
1667
+ // Node's undefined when the pattern has no named groups) — see
1668
+ // lowerMatchGroupsRead.
1669
+ {
1670
+ const g = lowerMatchGroupsRead(lowerer, expr);
1671
+ if (g !== null)
1672
+ return g;
1673
+ }
1504
1674
  // `arguments.length` in a TYPED function whose signature is
1505
1675
  // FIXED-ARITY (no optional/default/rest parameters): tsc enforces
1506
1676
  // exact arity at every call site and call/apply/bind indirection is
@@ -1566,11 +1736,11 @@ function lowerExprInner(lowerer, expr) {
1566
1736
  // resolve unconditionally — they run after the module evaluated,
1567
1737
  // the same approximation identifier exports already make. Every
1568
1738
  // other module.exports read keeps its existing story.
1569
- if (!expr.questionDotToken && isModuleExportsAccess(expr) && isCjsJsFile(expr.getSourceFile())) {
1739
+ if (!expr.questionDotToken && isModuleExportsAccess(expr) && isCjsJsFile(expr.getSourceFile(), lowerer.program)) {
1570
1740
  const whole = cjsClassExprWholeExportOf(expr.getSourceFile());
1571
1741
  if (whole) {
1572
1742
  let inBody = false;
1573
- for (let p = expr.parent; !ts.isSourceFile(p); p = p.parent) {
1743
+ for (let p = expr.parent; p !== undefined && !ts.isSourceFile(p); p = p.parent) {
1574
1744
  if (ts.isFunctionLike(p) || ts.isClassDeclaration(p) || ts.isClassExpression(p)) {
1575
1745
  inBody = true;
1576
1746
  break;
@@ -1598,7 +1768,7 @@ function lowerExprInner(lowerer, expr) {
1598
1768
  const writePos = ts.isBinaryExpression(expr.parent) &&
1599
1769
  expr.parent.left === expr &&
1600
1770
  expr.parent.operatorToken.kind === ts.SyntaxKind.EqualsToken;
1601
- if (!expr.questionDotToken && cjsMemberRecv && !writePos && isCjsJsFile(sf)) {
1771
+ if (!expr.questionDotToken && cjsMemberRecv && !writePos && isCjsJsFile(sf, lowerer.program)) {
1602
1772
  const sym = lowerer.checker.getSymbolAtLocation(expr.name) ??
1603
1773
  lowerer.cjsModuleExportSymbol(sf, expr.name.text);
1604
1774
  const resolved = sym !== undefined && sym.flags & ts.SymbolFlags.Alias
@@ -1720,6 +1890,14 @@ function lowerExprInner(lowerer, expr) {
1720
1890
  isJsSourceFile(expr.getSourceFile())) {
1721
1891
  const canonical = stdlibGlobalNameOf(lowerer, expr);
1722
1892
  if (canonical !== null) {
1893
+ if (!lowerer.dynamic && canonical === "globalThis")
1894
+ return lowerGlobalValue(lowerer, expr);
1895
+ if (!lowerer.dynamic && canonical === "ArrayBuffer")
1896
+ return { kind: "libCall", fn: "arrayBuffer.constructor", args: [], type: DYN, loc };
1897
+ if (!lowerer.dynamic && canonical === "fetch")
1898
+ return { kind: "libCall", fn: "fetch.function", args: [], type: DYN, loc };
1899
+ if (!lowerer.dynamic && Object.values(BYTES_ELEMENT_NAME).includes(canonical))
1900
+ return { kind: "libCall", fn: "bytes.constructor", args: [{ kind: "strLit", value: canonical, type: STRING, loc }], type: DYN, loc };
1723
1901
  return { kind: "strLit", value: `[builtin ${canonical}]`, type: STRING, loc };
1724
1902
  }
1725
1903
  }
@@ -1743,6 +1921,7 @@ function lowerExprInner(lowerer, expr) {
1743
1921
  // instead.
1744
1922
  if ((isJsSourceFile(expr.getSourceFile()) &&
1745
1923
  (lowerer.mapTypeOf(lowerer.typeOf(expr.expression)) === null ||
1924
+ (lowerer.typeOf(expr.expression).flags & ts.TypeFlags.Never) !== 0 ||
1746
1925
  // A never-tainted receiver type maps (never rides as f64) but
1747
1926
  // its VALUE lowered checked-dynamic — same dyn read.
1748
1927
  neverTaintedJsType(lowerer, expr.expression, lowerer.typeOf(expr.expression)))) ||
@@ -1756,6 +1935,11 @@ function lowerExprInner(lowerer, expr) {
1756
1935
  const opt = hasOptionalChainGuard(expr.expression);
1757
1936
  return lowerer.maybeNarrow({ kind: "dynKeyGet", key, ...(opt ? { optional: true } : {}), value: recv, type: DYN, loc }, expr);
1758
1937
  }
1938
+ if (expr.name.text === "length" && (recv.type.kind === "array" || recv.type.kind === "string")) {
1939
+ return recv.type.kind === "array"
1940
+ ? { kind: "arrIntrinsic", method: "length", receiver: recv, args: [], type: F64, loc }
1941
+ : { kind: "strIntrinsic", method: "length", receiver: recv, args: [], type: F64, loc };
1942
+ }
1759
1943
  }
1760
1944
  // `f.name` / `f.length` / own properties on a FUNCTION-typed JS
1761
1945
  // value (the mustCall wrapper's function-instance members): read
@@ -1764,11 +1948,24 @@ function lowerExprInner(lowerer, expr) {
1764
1948
  // best-effort static name and the declared arity; anything else is
1765
1949
  // the own-property answer, undefined. Function.prototype METHOD
1766
1950
  // names stay fenced (a stored-member undefined would mis-answer
1767
- // `f.call` as a value). JS files only; TS keeps the fence.
1768
- if (!["apply", "bind", "call", "toString", "constructor", "prototype", "caller", "arguments"].includes(expr.name.text) &&
1769
- isJsSourceFile(expr.getSourceFile())) {
1951
+ // `f.call` as a value). Static callable values keep the existing TS fence; checked callable
1952
+ // storage can expose its own name and length in both source languages.
1953
+ if (!["apply", "bind", "call", "toString", "caller", "arguments"].includes(expr.name.text) &&
1954
+ (isJsSourceFile(expr.getSourceFile()) || expr.name.text === "name" || expr.name.text === "length" || expr.name.text === "constructor")) {
1770
1955
  const probed = tryLowerExpression(lowerer, expr.expression);
1771
- if (probed?.type.kind === "func" &&
1956
+ // Inferred callable returns can use checked storage even when the
1957
+ // checker still describes a function. Read its actual dyn value;
1958
+ // the ambient Function member must not hide its own properties.
1959
+ const receiverType = lowerer.typeOf(expr.expression);
1960
+ const receiverSymbol = receiverType.getSymbol();
1961
+ const checkedFunction = lowerer.checker.getCallSignatures(receiverType).length > 0 ||
1962
+ (receiverSymbol?.name === "Function" && lowerer.isStdlibSymbol(receiverSymbol));
1963
+ if (probed?.type.kind === "dyn" && checkedFunction) {
1964
+ const key = { kind: "strLit", value: expr.name.text, type: STRING, loc: locOf(expr.name) };
1965
+ const opt = hasOptionalChainGuard(expr.expression);
1966
+ return lowerer.maybeNarrow({ kind: "dynKeyGet", key, value: probed, ...(opt ? { optional: true } : {}), type: DYN, loc }, expr);
1967
+ }
1968
+ if ((isJsSourceFile(expr.getSourceFile()) || expr.name.text === "constructor") && probed?.type.kind === "func" &&
1772
1969
  canBoxFuncIntoDyn(probed.type, (id) => lowerer.shapes.get(id), (id) => lowerer.unions.get(id))) {
1773
1970
  const fnName = jsFuncNameOf(expr.expression);
1774
1971
  const boxed = {
@@ -1789,10 +1986,14 @@ function lowerExprInner(lowerer, expr) {
1789
1986
  // is an ordinary recordGet — maybeNarrow then bridges the union slot
1790
1987
  // to the checker's narrowed arm, exactly like a union field read.
1791
1988
  if (expr.name.text === "done" || expr.name.text === "value") {
1792
- const recvSym = lowerer.typeOf(expr.expression).getSymbol();
1793
- if ((recvSym?.name === "IteratorYieldResult" || recvSym?.name === "IteratorReturnResult") &&
1794
- lowerer.checker.declarationsOf(recvSym).some((d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(d.getSourceFile()))) {
1989
+ const receiverType = lowerer.typeOf(expr.expression);
1990
+ const recvSym = receiverType.getAliasSymbol()?.name === "IteratorResult" ? receiverType.getAliasSymbol() : receiverType.getSymbol();
1991
+ if ((recvSym?.name === "IteratorResult" || recvSym?.name === "IteratorYieldResult" || recvSym?.name === "IteratorReturnResult") &&
1992
+ lowerer.checker.declarationsOf(recvSym).some((d) => (ts.isInterfaceDeclaration(d) || ts.isTypeAliasDeclaration(d)) && lowerer.isStdlibFile(d.getSourceFile()))) {
1795
1993
  const recv = lowerer.lowerExpr(expr.expression);
1994
+ if (recv.type.kind === "dyn")
1995
+ return lowerer.maybeNarrow({ kind: "dynKeyGet", value: recv,
1996
+ key: { kind: "strLit", value: expr.name.text, type: STRING, loc }, type: DYN, loc }, expr);
1796
1997
  if (recv.type.kind === "record") {
1797
1998
  const shape = lowerer.shapes.get(recv.type.shapeId);
1798
1999
  const field = shape?.fields.find((f) => f.name === expr.name.text);
@@ -1823,7 +2024,10 @@ function lowerExprInner(lowerer, expr) {
1823
2024
  // the generic dyn receiver path below. Provenance is limited to the
1824
2025
  // node:util parseArgs family; every other stdlib member keeps its
1825
2026
  // ordinary surface fence.
1826
- if (isParseArgsDynProperty(lowerer, expr)) {
2027
+ const segmentDataSymbol = lowerer.typeOf(expr.expression).getSymbol();
2028
+ const segmentDataProperty = segmentDataSymbol?.name === "SegmentData" && lowerer.isStdlibSymbol(segmentDataSymbol) &&
2029
+ ["segment", "index", "input", "isWordLike"].includes(expr.name.text);
2030
+ if (isParseArgsDynProperty(lowerer, expr) || segmentDataProperty) {
1827
2031
  const recv = lowerer.lowerExpr(expr.expression);
1828
2032
  if (recv.type.kind === "dyn") {
1829
2033
  const key = {
@@ -1857,10 +2061,6 @@ function lowerExprInner(lowerer, expr) {
1857
2061
  // The lib fence's PROPERTY chokepoint: a stdlib-declared member that
1858
2062
  // no lowering above claimed ([1,2].entries, Math.SQRT2, Promise.all,
1859
2063
  // re.exec as a value, ...) reports SC2020 here.
1860
- lowerer.fenceStaticAbortControllerMemberRead(expr);
1861
- lowerer.fenceStaticResponseMember(expr, "read");
1862
- lowerer.fenceStaticHeadersMember(expr, "read");
1863
- lowerer.fenceStaticReadableStreamMember(expr, "read");
1864
2064
  lowerer.stdlibMemberFence(expr);
1865
2065
  // The npm chokepoint: a member on a package-typed receiver in a
1866
2066
  // static build — attributed to the package, like every other site.
@@ -1890,10 +2090,11 @@ function lowerExprInner(lowerer, expr) {
1890
2090
  // on undefined/null receivers unless an earlier `?.` guards the
1891
2091
  // chain. Scalar-narrowed occurrences bridge via maybeNarrow's
1892
2092
  // validated dynCheck as usual.
1893
- if (recvLowered.type.kind === "dyn") {
2093
+ if (recvLowered.type.kind === "dyn" || isJsSourceFile(expr.getSourceFile()) &&
2094
+ ["f64", "bool", "bigint", "string", "symbol"].includes(recvLowered.type.kind)) {
1894
2095
  const key = { kind: "strLit", value: expr.name.text, type: STRING, loc: locOf(expr.name) };
1895
2096
  const opt = hasOptionalChainGuard(expr.expression);
1896
- return lowerer.maybeNarrow({ kind: "dynKeyGet", key, ...(opt ? { optional: true } : {}), value: recvLowered, type: DYN, loc }, expr);
2097
+ return lowerer.maybeNarrow({ kind: "dynKeyGet", key, ...(opt ? { optional: true } : {}), value: lowerer.coerceToExpected(recvLowered, DYN), type: DYN, loc }, expr);
1897
2098
  }
1898
2099
  // The lowered receiver is a RECORD the checker spelled wider —
1899
2100
  // `s.match(re).groups.key` in a JS file: the checker says
@@ -1919,6 +2120,17 @@ function lowerExprInner(lowerer, expr) {
1919
2120
  loc,
1920
2121
  }, expr);
1921
2122
  }
2123
+ // An inferred package-JS call specializes a parameter to the
2124
+ // caller's own record shape. Optional option names omitted by that
2125
+ // caller read undefined; the missing field is not a failed type
2126
+ // assertion. Known prototype members were handled/fenced above.
2127
+ if (recvShape && !recvShape.tuple && lowerer.implicitParamTypes !== null &&
2128
+ isJsSourceFile(expr.getSourceFile()) && npmStaticPackageOfPath(expr.getSourceFile().fileName) !== null) {
2129
+ return {
2130
+ kind: "seqExpr", stmts: [{ kind: "exprStmt", expr: recvLowered, loc }],
2131
+ result: dynUndefinedExpr(loc), type: DYN, loc,
2132
+ };
2133
+ }
1922
2134
  }
1923
2135
  // An ABSTRACT property through an abstract-typed receiver: the
1924
2136
  // declaration is erased at runtime — Node defines no field for it,
@@ -1928,6 +2140,29 @@ function lowerExprInner(lowerer, expr) {
1928
2140
  if (recvLowered.type.kind === "object" && abstractPropertyDeclOf(lowerer, expr)) {
1929
2141
  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)`);
1930
2142
  }
2143
+ // Array reads may add an undefined arm even when the checker still
2144
+ // reports a class. Its property bag must remain reachable, while
2145
+ // an absent element takes the normal checked-value TypeError.
2146
+ const nativeReceiver = lowerer.stripUndefinedArm(recvLowered.type);
2147
+ if (ts.isPrivateIdentifier(expr.name) && recvLowered.type.kind === "object") {
2148
+ const field = lowerer.classes.get(recvLowered.type.className)?.fields.get(expr.name.text);
2149
+ if (field)
2150
+ return { kind: "fieldGet", obj: recvLowered, className: recvLowered.type.className,
2151
+ field: expr.name.text, type: field, loc };
2152
+ }
2153
+ if (nativeReceiver.kind === "object" && nativeReceiver.className === "%Error") {
2154
+ return lowerer.maybeNarrow({ kind: "dynKeyGet", value: lowerer.coerceToExpected(recvLowered, DYN),
2155
+ key: { kind: "strLit", value: expr.name.text, type: STRING, loc }, type: DYN, loc }, expr);
2156
+ }
2157
+ if (isDynTypedRefType(nativeReceiver)) {
2158
+ const info = lowerer.classes.get(nativeReceiver.className);
2159
+ if (info && !info.def.runtime && !info.builtinError && !info.builtinEmitter && !info.builtinStream) {
2160
+ return lowerer.maybeNarrow({
2161
+ kind: "dynKeyGet", value: lowerer.coerceToExpected(recvLowered, DYN),
2162
+ key: { kind: "strLit", value: expr.name.text, type: STRING, loc }, type: DYN, loc,
2163
+ }, expr);
2164
+ }
2165
+ }
1931
2166
  lowerer.unsupported("SC1090", expr, `reading '${expr.name.text}' from a value of type '${lowerer.checker.typeToString(lowerer.typeOf(expr.expression))}'`);
1932
2167
  }
1933
2168
  // Meta-properties, named: `new.target` reflects HOW a function was
@@ -1944,7 +2179,7 @@ function lowerExprInner(lowerer, expr) {
1944
2179
  const entry = UNSUPPORTED_EXPR[expr.kind];
1945
2180
  if (entry)
1946
2181
  lowerer.unsupported(entry.code, expr, entry.feature);
1947
- lowerer.unsupported("SC1090", expr, `syntax '${ts.SyntaxKind[expr.kind]}'`);
2182
+ lowerer.unsupported("SC1090", expr, `syntax '${ts.syntaxKindName(expr.kind)}'`);
1948
2183
  }
1949
2184
  function moduleFileName(lowerer, sf) {
1950
2185
  return lowerer.targetPlatform === "wasi"
@@ -1952,7 +2187,7 @@ function moduleFileName(lowerer, sf) {
1952
2187
  : sf.fileName;
1953
2188
  }
1954
2189
  function importMetaProperty(lowerer, expr) {
1955
- if (expr.questionDotToken !== undefined || !ts.isMetaProperty(expr.expression))
2190
+ if (!ts.isMetaProperty(expr.expression))
1956
2191
  return null;
1957
2192
  const meta = expr.expression;
1958
2193
  if (meta.keywordToken !== ts.SyntaxKind.ImportKeyword || meta.name.text !== "meta")
@@ -1960,6 +2195,8 @@ function importMetaProperty(lowerer, expr) {
1960
2195
  const sf = expr.getSourceFile();
1961
2196
  const fileName = moduleFileName(lowerer, sf);
1962
2197
  switch (expr.name.text) {
2198
+ case "env":
2199
+ return dynUndefinedExpr(locOf(expr));
1963
2200
  case "url":
1964
2201
  return {
1965
2202
  kind: "strLit",
@@ -1983,11 +2220,10 @@ function importMetaProperty(lowerer, expr) {
1983
2220
  return null;
1984
2221
  }
1985
2222
  }
1986
- /** `c ? a : b` — see the inline comments. `expected` plays the contextual
1987
- * array type's role when the caller knows the slot's array type and tsc's
1988
- * API doesn't surface it (a ternary as a SPREAD source — lowerArrayLiteral
1989
- * threads the literal's own type in). */
1990
- function lowerTernary(lowerer, expr, expected) {
2223
+ /** `c ? a : b`. An explicit destination constructs each branch at that
2224
+ * layout before joining it. Without one, retain the expression's inferred
2225
+ * type and the contextual/sibling rules below. */
2226
+ export function lowerTernary(lowerer, expr, expected) {
1991
2227
  const loc = locOf(expr);
1992
2228
  // `Array.isArray(x) ? x : [x]` over a `T | readonly T[]` union: tsc
1993
2229
  // narrows the TRUE branch to `any[]` (maybeNarrow's isArray bridge
@@ -2041,7 +2277,16 @@ function lowerTernary(lowerer, expr, expected) {
2041
2277
  // literals) sit untaken in compiled JS. A boolLit carries no
2042
2278
  // effects, so dropping the condition read loses nothing.
2043
2279
  if (cond.kind === "boolLit") {
2044
- return lowerer.lowerExpr(cond.value ? expr.whenTrue : expr.whenFalse);
2280
+ return lowerer.lowerExprExpecting(cond.value ? expr.whenTrue : expr.whenFalse, expected);
2281
+ }
2282
+ if (expected) {
2283
+ // Construct fresh literals in their actual destination. Joining
2284
+ // inferred layouts first can erase the literal discriminator or
2285
+ // choose an empty array's uninhabited element representation.
2286
+ // Runtime-optional proofs apply only within the selected true arm.
2287
+ const then = withRuntimeOptionalNarrowed(lowerer, runtimeOptionalTrueIds(lowerer, expr.condition), () => lowerer.lowerExprExpecting(expr.whenTrue, expected));
2288
+ const else_ = lowerer.lowerExprExpecting(expr.whenFalse, expected);
2289
+ return { kind: "ternary", cond, then, else_, type: expected, loc };
2045
2290
  }
2046
2291
  const ctxTs = lowerer.checker.getContextualType(expr);
2047
2292
  const ctxMapped = ctxTs ? lowerer.mapTypeOf(ctxTs) : null;
@@ -2058,7 +2303,7 @@ function lowerTernary(lowerer, expr, expected) {
2058
2303
  // lowerArrayLiteral, and just as unambiguous — tsc already typed the
2059
2304
  // whole ternary by the filled arm alone. Both arms empty stays
2060
2305
  // fenced (no element type exists anywhere).
2061
- const ctxArray = expected ?? (ctxMapped?.kind === "array" ? ctxMapped : null);
2306
+ const ctxArray = ctxMapped?.kind === "array" ? ctxMapped : null;
2062
2307
  const armLiteral = (e) => {
2063
2308
  let x = e;
2064
2309
  while (ts.isParenthesizedExpression(x))
@@ -2082,7 +2327,21 @@ function lowerTernary(lowerer, expr, expected) {
2082
2327
  }
2083
2328
  return false;
2084
2329
  };
2330
+ // Inferred signature tables often join a populated argument array
2331
+ // with nested empty-array branches. Carry the whole record layout
2332
+ // through those branches before constructing their fresh fields.
2333
+ const ownJoin = lowerer.mapTypeOf(lowerer.typeOf(expr));
2334
+ const recordJoin = ctxMapped?.kind === "record" ? ctxMapped :
2335
+ ownJoin?.kind === "record" ? ownJoin : undefined;
2085
2336
  const lowerArm = (e, siblingType) => {
2337
+ let fresh = e;
2338
+ while (ts.isParenthesizedExpression(fresh))
2339
+ fresh = fresh.expression;
2340
+ if (recordJoin && ts.isConditionalExpression(fresh))
2341
+ return lowerTernary(lowerer, fresh, recordJoin);
2342
+ if (recordJoin && ts.isObjectLiteralExpression(fresh)) {
2343
+ return lowerer.lowerObjectLiteral(fresh, recordJoin);
2344
+ }
2086
2345
  const lit = armLiteral(e);
2087
2346
  if (lit && ctxArray)
2088
2347
  return lowerer.lowerArrayLiteral(lit, ctxArray);
@@ -2135,11 +2394,14 @@ function lowerTernary(lowerer, expr, expected) {
2135
2394
  const lowerTrueArm = () => withRuntimeOptionalNarrowed(lowerer, runtimeTrueIds, () => lowerArm(expr.whenTrue, ownArrayJoin ?? undefined));
2136
2395
  if (!ctxArray && emptyUntypedArrayArm(expr.whenTrue) && !emptyUntypedArrayArm(expr.whenFalse)) {
2137
2396
  elseRaw = lowerer.lowerExpr(expr.whenFalse);
2138
- thenRaw = withRuntimeOptionalNarrowed(lowerer, runtimeTrueIds, () => lowerArm(expr.whenTrue, elseRaw.type));
2397
+ thenRaw = withRuntimeOptionalNarrowed(lowerer, runtimeTrueIds, () => lowerArm(expr.whenTrue, elseRaw.type.kind === "dyn" && ownArrayJoin ? ownArrayJoin : elseRaw.type));
2139
2398
  }
2140
2399
  else {
2141
2400
  thenRaw = lowerTrueArm();
2142
- elseRaw = lowerArm(expr.whenFalse, thenRaw.type);
2401
+ // A checked-dynamic producer (Object.entries(...).filter(...))
2402
+ // can still have a precise checker array type. Empty literal arms
2403
+ // must adopt that element layout before the checked array boundary.
2404
+ elseRaw = lowerArm(expr.whenFalse, thenRaw.type.kind === "dyn" && ownArrayJoin ? ownArrayJoin : thenRaw.type);
2143
2405
  }
2144
2406
  // The ternary's IR type is normally the checker's own: it collapses
2145
2407
  // same-kind literal unions ("a" | "b" → string) and forms tagged
@@ -2211,14 +2473,28 @@ function lowerTernary(lowerer, expr, expected) {
2211
2473
  }
2212
2474
  }
2213
2475
  }
2214
- const type = thenRaw.type.kind === "array" && typeEquals(thenRaw.type, elseRaw.type)
2215
- ? thenRaw.type
2216
- : dynJoin
2217
- ? DYN
2218
- : (anyJoin ??
2219
- (useCtx && ctxMapped
2220
- ? ctxMapped
2221
- : lowerer.irTypeOf(expr)));
2476
+ // JavaScript function selection preserves the chosen closure's full
2477
+ // call ABI. A checker join can erase a rest/arguments pack even though
2478
+ // a later call supplies additional observable arguments.
2479
+ const callableJoin = isJsSourceFile(expr.getSourceFile()) &&
2480
+ thenRaw.type.kind === "func" && elseRaw.type.kind === "func" &&
2481
+ !typeEquals(thenRaw.type, elseRaw.type) &&
2482
+ lowerer.dynConvertible(thenRaw.type) && lowerer.dynConvertible(elseRaw.type);
2483
+ // JavaScript inference can discard an initially-undefined field from
2484
+ // a conditional's type even after that field acquired other values.
2485
+ // A checked arm must retain its actual representation at the join.
2486
+ const checkedJsJoin = isJsSourceFile(expr.getSourceFile()) &&
2487
+ (thenRaw.type.kind === "dyn" || elseRaw.type.kind === "dyn") &&
2488
+ lowerer.dynConvertible(thenRaw.type) && lowerer.dynConvertible(elseRaw.type);
2489
+ const type = callableJoin || checkedJsJoin ? DYN :
2490
+ thenRaw.type.kind === "array" && typeEquals(thenRaw.type, elseRaw.type)
2491
+ ? thenRaw.type
2492
+ : dynJoin
2493
+ ? DYN
2494
+ : (anyJoin ??
2495
+ (useCtx && ctxMapped
2496
+ ? ctxMapped
2497
+ : lowerer.irTypeOf(expr)));
2222
2498
  // Each arm flows into the ternary's type through the slot-coercion
2223
2499
  // path: union arms wrap, dyn slots reject the non-dyn arm with
2224
2500
  // SC1101, mismatched record shapes get SC2002; coerceInto is
@@ -2247,6 +2523,16 @@ function lowerTernary(lowerer, expr, expected) {
2247
2523
  * unrepresentable without a re-tag), or to nothing (`never` in an
2248
2524
  * exhaustive default) leaves the expression union-typed. */
2249
2525
  export function maybeNarrow(lowerer, expr, node) {
2526
+ if ((expr.type.kind === "union" || expr.type.kind === "dyn") &&
2527
+ isJsSourceFile(node.getSourceFile()) && ts.isPropertyAccessExpression(node)) {
2528
+ const symbol = lowerer.checker.getSymbolAtLocation(node.name);
2529
+ if (symbol && lowerer.checker.declarationsOf(symbol).some((decl) => ts.isPropertyDeclaration(decl) && !decl.initializer && !decl.type &&
2530
+ isJsSourceFile(decl.getSourceFile()))) {
2531
+ // Inference describes later writes, not the initial undefined.
2532
+ // A use that requires the inferred type must validate the value.
2533
+ return expr.type.kind === "dyn" ? expr : { kind: "dynFrom", value: expr, type: DYN, loc: expr.loc };
2534
+ }
2535
+ }
2250
2536
  // A dyn read tsc narrowed to a SCALAR (a typeof test proved the kind):
2251
2537
  // bridge with a VALIDATED extraction — dynCheck, the checked-cast
2252
2538
  // machinery — rather than a trusted one. After the guard the check
@@ -2254,15 +2540,45 @@ export function maybeNarrow(lowerer, expr, node) {
2254
2540
  // instead of misreading the payload (the dyn boundary's usual stance).
2255
2541
  // Object/array narrowings stay dyn-typed and keep their fences.
2256
2542
  if (expr.type.kind === "dyn") {
2543
+ if (isJsSourceFile(node.getSourceFile()) && ts.isIdentifier(node)) {
2544
+ const symbol = lowerer.resolveValueSymbol(node);
2545
+ if (symbol && lowerer.checker.declarationsOf(symbol).some((decl) => ts.isVariableDeclaration(decl) && jsBindingHasOpenWrites(lowerer, decl)))
2546
+ return expr;
2547
+ if (symbol && lowerer.checker.declarationsOf(symbol).some((decl) => ts.isParameter(decl) && decl.initializer && !decl.type &&
2548
+ !/@(?:param|type)\b/.test(decl.getSourceFile().text.slice(decl.parent?.pos ?? decl.pos, decl.getStart()))))
2549
+ return expr;
2550
+ if (symbol && lowerer.checker.declarationsOf(symbol).some((decl) => {
2551
+ if (!ts.isParameter(decl) || decl.type)
2552
+ return false;
2553
+ const fn = decl.parent;
2554
+ if (!ts.isFunctionExpression(fn) && !ts.isArrowFunction(fn))
2555
+ return false;
2556
+ const assignment = fn.parent;
2557
+ return ts.isBinaryExpression(assignment) && assignment.right === fn &&
2558
+ ts.isPropertyAccessExpression(assignment.left) && assignment.left.name.text === "write" &&
2559
+ lowerer.isStdlibMember(assignment.left);
2560
+ }))
2561
+ return expr;
2562
+ }
2563
+ // Node's TTY declarations promise booleans/numbers even when a pipe
2564
+ // has no such property. Preserve the checked read for JS capability
2565
+ // probes instead of treating a declaration as a runtime type guard.
2566
+ if (isJsSourceFile(node.getSourceFile()) && ts.isPropertyAccessExpression(node) &&
2567
+ ["isTTY", "columns", "rows"].includes(node.name.text) && lowerer.isStdlibMember(node))
2568
+ return expr;
2569
+ // A null-initialized JS field can later receive an object even when
2570
+ // checker flow calls its non-null branch never. The live checked
2571
+ // value remains authoritative; never is not a numeric guard.
2572
+ if (isJsSourceFile(node.getSourceFile()) && (lowerer.typeOf(node).flags & ts.TypeFlags.Never) !== 0)
2573
+ return expr;
2257
2574
  const narrowed = lowerer.mapTypeOf(lowerer.typeOf(node));
2258
2575
  if (narrowed &&
2259
- (narrowed.kind === "f64" || narrowed.kind === "bool" || narrowed.kind === "string")) {
2576
+ (narrowed.kind === "f64" || narrowed.kind === "bool" || narrowed.kind === "string" || narrowed.kind === "bigint")) {
2260
2577
  return { kind: "dynCheck", value: expr, type: narrowed, loc: expr.loc };
2261
2578
  }
2262
- // An `instanceof Uint8Array` narrow: the checked-dynamic tree's bytes kind, extracted
2263
- // with the same validated copy the checked cast uses (a Buffer that
2264
- // crossed in rides the kind too — it IS a Uint8Array in Node).
2265
- if (narrowed?.kind === "bytes" && narrowed.elem === "u8") {
2579
+ // An instanceof narrow retains the native view after validating its
2580
+ // exact element kind. Buffer also matches Uint8Array.
2581
+ if (narrowed?.kind === "bytes" || narrowed?.kind === "url") {
2266
2582
  return { kind: "dynCheck", value: expr, type: narrowed, loc: expr.loc };
2267
2583
  }
2268
2584
  // An `instanceof Error` narrow: the checked-dynamic tree's error encoding rebuilds a
@@ -2289,6 +2605,11 @@ export function maybeNarrow(lowerer, expr, node) {
2289
2605
  }
2290
2606
  if (expr.type.kind !== "union")
2291
2607
  return expr;
2608
+ if (ts.isIdentifier(node)) {
2609
+ const symbol = lowerer.resolveValueSymbol(node);
2610
+ if (symbol && lowerer.unstableSwitchBindings.has(symbol))
2611
+ return expr;
2612
+ }
2292
2613
  const narrowedTs = lowerer.typeOf(node);
2293
2614
  const narrowed = lowerer.mapTypeOf(narrowedTs);
2294
2615
  // `Array.isArray(u)` on a union with one readonly array/tuple arm: the
@@ -2315,14 +2636,32 @@ export function maybeNarrow(lowerer, expr, node) {
2315
2636
  }
2316
2637
  const tag = lowerer.armTag(expr.type.unionId, narrowed);
2317
2638
  if (tag < 0) {
2318
- // A proven-present optional base class can narrow further to a
2319
- // subclass. The payload is still stored under the base class tag;
2320
- // unwrap it before applying the ordinary class downcast.
2639
+ // The checker may refine fields inside a discriminated record too.
2640
+ // That creates a different mapped shape, but does not change the
2641
+ // stored payload layout. A required literal discriminator proves
2642
+ // which original arm to read; retain all of that arm's field types.
2643
+ const def = lowerer.unions.get(expr.type.unionId);
2644
+ if (narrowed.kind === "record" && def?.discriminant) {
2645
+ const property = lowerer.checker.getPropertyOfType(narrowedTs, def.discriminant.field);
2646
+ if (property && !(property.flags & (ts.SymbolFlags.Optional | ts.SymbolFlags.GetAccessor | ts.SymbolFlags.SetAccessor))) {
2647
+ const values = literalValues(lowerer.checker.getTypeOfSymbol(property));
2648
+ const arm = values && literalUnionArm(def, values, (id) => lowerer.shapes.get(id));
2649
+ if (arm) {
2650
+ return { kind: "unionNarrow", unionId: expr.type.unionId,
2651
+ tag: lowerer.armTag(expr.type.unionId, arm), value: expr, type: arm, loc: expr.loc };
2652
+ }
2653
+ }
2654
+ }
2655
+ // A base-class arm can narrow to a subclass after instanceof. The
2656
+ // payload retains its base-class tag even when the union also has
2657
+ // records or other classes. Require one unambiguous containing arm.
2321
2658
  const arms = lowerer.unions.get(expr.type.unionId)?.arms ?? [];
2322
- const valueTag = arms.findIndex((arm) => !isUnitType(arm));
2659
+ const valueTag = narrowed.kind === "object"
2660
+ ? arms.findIndex((arm) => arm.kind === "object" && lowerer.isSubclassOf(narrowed.className, arm.className))
2661
+ : -1;
2323
2662
  const valueType = arms[valueTag];
2324
2663
  if (narrowed.kind === "object" && valueType?.kind === "object" &&
2325
- arms.every((arm, i) => i === valueTag || isUnitType(arm)) &&
2664
+ arms.every((arm, i) => i === valueTag || arm.kind !== "object" || !lowerer.isSubclassOf(narrowed.className, arm.className)) &&
2326
2665
  lowerer.isSubclassOf(narrowed.className, valueType.className)) {
2327
2666
  return {
2328
2667
  kind: "downcast",
@@ -2701,7 +3040,26 @@ function lowerLooseNullCompare(lowerer, expr, loc) {
2701
3040
  * results (several non-unit arms) and defaults that change the result
2702
3041
  * type are fenced with narrow-first hints. */
2703
3042
  export function lowerNullishCoalesce(lowerer, expr, loc) {
2704
- const left = lowerAbsenceProbe(lowerer, expr.left) ?? lowerer.lowerExpr(expr.left);
3043
+ // `a ?? b ?? c` is a left-nested AST. Lower its spine bottom-up so
3044
+ // long dispatch chains do not retain a full expression/binary lowering
3045
+ // frame per operand in a native compiler. The pair lowering still
3046
+ // decides each result layout and preserves the lazy right operand.
3047
+ const parents = [];
3048
+ let first = expr;
3049
+ while (ts.isBinaryExpression(first.left) &&
3050
+ first.left.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken &&
3051
+ !lowerer.chainRecvByNode.has(first.left)) {
3052
+ parents.push(first);
3053
+ first = first.left;
3054
+ }
3055
+ let result = lowerNullishPair(lowerer, first, first === expr ? loc : locOf(first), lowerAbsenceProbe(lowerer, first.left) ?? lowerer.lowerExpr(first.left));
3056
+ for (let i = parents.length - 1; i >= 0; i--) {
3057
+ const parent = parents[i];
3058
+ result = lowerNullishPair(lowerer, parent, parent === expr ? loc : locOf(parent), result);
3059
+ }
3060
+ return result;
3061
+ }
3062
+ function lowerNullishPair(lowerer, expr, loc, left) {
2705
3063
  if (left.type.kind === "dyn") {
2706
3064
  // `a ?? b` on a CHECKED-DYNAMIC left: the deciding test is the
2707
3065
  // runtime kind (scr_dyn_is_nullish — UNDEF/NULL take the default;
@@ -2730,8 +3088,18 @@ export function lowerNullishCoalesce(lowerer, expr, loc) {
2730
3088
  lowerer.badType(expr.left, lowerer.typeOf(expr.left));
2731
3089
  if (!def.arms.some(isUnitType))
2732
3090
  return left;
2733
- const type = lowerer.irTypeOf(expr);
2734
3091
  const rest = def.arms.filter((a) => !isUnitType(a));
3092
+ // `optionalMap ?? new Map()` carries its element contract on the left;
3093
+ // the empty fallback may use it without constructing a Map<any, any>
3094
+ // arm that has no first-class union representation.
3095
+ const fresh = rest.length === 1 ? lowerer.emptyCollectionFor(expr.right, rest[0]) : null;
3096
+ if (fresh)
3097
+ return { kind: "nullish", left, right: fresh, type: fresh.type, loc };
3098
+ const type = lowerer.irTypeOf(expr);
3099
+ if (type.kind === "dyn" && lowerer.dynConvertible(left.type)) {
3100
+ return { kind: "nullish", left: lowerer.coerceToExpected(left, DYN),
3101
+ right: lowerer.lowerExprExpecting(expr.right, DYN), type: DYN, loc };
3102
+ }
2735
3103
  if (typeEquals(type, left.type) || (rest.length === 1 && typeEquals(type, rest[0]))) {
2736
3104
  const right = lowerer.lowerExprExpecting(expr.right, type);
2737
3105
  return { kind: "nullish", left, right, type, loc };
@@ -2739,180 +3107,49 @@ export function lowerNullishCoalesce(lowerer, expr, loc) {
2739
3107
  if (type.kind !== "union" && rest.length > 1 && rest.some((arm) => typeEquals(arm, type))) {
2740
3108
  const helper = lowerer.narrowedArmHelper(left.type.unionId, type, loc);
2741
3109
  if (helper !== null) {
2742
- const stmts = [];
2743
- let stable = left;
2744
- if (!isSafeToRepeat(left)) {
2745
- const local = lowerer.declareHiddenLocal("%nullish", left.type);
2746
- stmts.push({ kind: "varDecl", localId: local.id, init: left, loc });
2747
- stable = varRef(local.id, left.type, loc);
2748
- }
2749
- const unitTags = def.arms
2750
- .map((arm, tag) => ({ arm, tag }))
2751
- .filter(({ arm }) => isUnitType(arm))
2752
- .map(({ tag }) => tag);
2753
- let isNullish = { kind: "unionIsTag", unionId: left.type.unionId, tag: unitTags[0], negated: false, value: stable, type: BOOL, loc };
2754
- for (const tag of unitTags.slice(1)) {
2755
- isNullish = {
2756
- kind: "logical",
2757
- op: "||",
2758
- left: isNullish,
2759
- right: { kind: "unionIsTag", unionId: left.type.unionId, tag, negated: false, value: stable, type: BOOL, loc },
2760
- type: BOOL,
2761
- loc,
2762
- };
2763
- }
2764
- const result = {
2765
- kind: "ternary",
2766
- cond: isNullish,
2767
- then: lowerer.lowerExprExpecting(expr.right, type),
2768
- else_: { kind: "call", callee: helper, args: [stable], type, loc },
2769
- type,
2770
- loc,
2771
- };
2772
- return stmts.length === 0 ? result : { kind: "seqExpr", stmts, result, type, loc };
3110
+ return nullishBranches(lowerer, left, left.type, def.arms, type, loc, () => lowerer.lowerExprExpecting(expr.right, type), (stable) => ({ kind: "call", callee: helper, args: [stable], type, loc }));
2773
3111
  }
2774
3112
  }
2775
- // The RETAGGED shape — the default changes the result union (`s ??
2776
- // null` over `string | undefined` answers `null | string`): every
2777
- // non-unit arm of the left has a home in the result, so an interned
2778
- // helper tests the unit tags and re-wraps the payload per arm. The
2779
- // helper call evaluates the default EAGERLY where JS is lazy, so only
2780
- // effect-free defaults (the literal null/0/"" spellings) qualify —
2781
- // anything effectful keeps the fence.
3113
+ // A default may change the union or widen its records. Test the
3114
+ // original tag before coercing: converting the whole left first can
3115
+ // lose nullish arms, and evaluating the default eagerly loses laziness.
2782
3116
  if (type.kind === "union") {
2783
- const leftT = left.type;
2784
- const armPairs = rest.map((a) => ({ arm: a, src: lowerer.armTag(leftT.unionId, a), dst: lowerer.armTag(type.unionId, a) }));
2785
- if (rest.length > 0 && armPairs.every((pair) => pair.src >= 0 && pair.dst >= 0)) {
2786
- const stmts = [];
2787
- let stable = left;
2788
- if (!isSafeToRepeat(left)) {
2789
- const local = lowerer.declareHiddenLocal("%nullish", leftT);
2790
- stmts.push({ kind: "varDecl", localId: local.id, init: left, loc });
2791
- stable = varRef(local.id, leftT, loc);
2792
- }
2793
- const unitTags = def.arms
2794
- .map((arm, tag) => ({ arm, tag }))
2795
- .filter(({ arm }) => isUnitType(arm))
2796
- .map(({ tag }) => tag);
2797
- let isNullish = {
2798
- kind: "unionIsTag",
2799
- unionId: leftT.unionId,
2800
- tag: unitTags[0],
2801
- negated: false,
2802
- value: stable,
2803
- type: BOOL,
2804
- loc,
2805
- };
2806
- for (const tag of unitTags.slice(1)) {
2807
- isNullish = {
2808
- kind: "logical",
2809
- op: "||",
2810
- left: isNullish,
2811
- right: { kind: "unionIsTag", unionId: leftT.unionId, tag, negated: false, value: stable, type: BOOL, loc },
2812
- type: BOOL,
2813
- loc,
2814
- };
2815
- }
2816
- let present = null;
2817
- for (let i = armPairs.length - 1; i >= 0; i--) {
2818
- const pair = armPairs[i];
2819
- const wrapped = {
2820
- kind: "unionWrap",
2821
- unionId: type.unionId,
2822
- tag: pair.dst,
2823
- value: { kind: "unionNarrow", unionId: leftT.unionId, tag: pair.src, value: stable, type: pair.arm, loc },
2824
- type,
2825
- loc,
2826
- };
2827
- present = present === null
2828
- ? wrapped
2829
- : {
2830
- kind: "ternary",
2831
- cond: { kind: "unionIsTag", unionId: leftT.unionId, tag: pair.src, negated: false, value: stable, type: BOOL, loc },
2832
- then: wrapped,
2833
- else_: present,
2834
- type,
2835
- loc,
2836
- };
2837
- }
2838
- const result = {
2839
- kind: "ternary",
2840
- cond: isNullish,
2841
- then: lowerer.lowerExprExpecting(expr.right, type),
2842
- else_: present,
2843
- type,
2844
- loc,
2845
- };
2846
- return stmts.length === 0 ? result : { kind: "seqExpr", stmts, result, type, loc };
2847
- }
2848
- if (armPairs.every((p) => p.src >= 0 && p.dst >= 0)) {
2849
- const right = lowerer.lowerExprExpecting(expr.right, type);
2850
- if (isSafeToDiscard(right)) {
2851
- const helper = nullishRetagHelper(lowerer, leftT, type, loc);
2852
- return { kind: "call", callee: helper, args: [left, right], type, loc };
2853
- }
3117
+ const retag = lowerer.unionRetagHelper(left.type.unionId, type.unionId, loc) ??
3118
+ lowerer.narrowedRetagHelper(expr.left, left.type.unionId, type.unionId, loc);
3119
+ if (retag !== null) {
3120
+ // Narrowing may exclude a record/class arm while storage retains
3121
+ // it. The checked retag traps that impossible arm, but only AFTER
3122
+ // the original nullish test: null/undefined must reach the lazy
3123
+ // default even when neither is representable in the result.
3124
+ return nullishBranches(lowerer, left, left.type, def.arms, type, loc, () => lowerer.lowerExprExpecting(expr.right, type), (stable) => ({ kind: "call", callee: retag, args: [stable], type, loc }));
2854
3125
  }
2855
3126
  }
2856
3127
  lowerer.unsupported("SC1090", expr, rest.length !== 1
2857
3128
  ? `'??' on '${lowerer.fmt(left.type)}' (the non-nullish result is a sub-union; check a discriminant field first)`
2858
3129
  : `'??' 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)`);
2859
3130
  }
2860
- /** Interned `%nullish.retag.<n>(l, r)` — the retagged `??` (see
2861
- * lowerNullishCoalesce): unit-armed left answers the pre-evaluated
2862
- * default; every other arm narrows out of the left union and wraps into
2863
- * the result union's matching arm. */
2864
- function nullishRetagHelper(lowerer, leftT, resT, loc) {
2865
- const key = `nullish:${leftT.unionId}:${typeKey(resT)}`;
2866
- const existing = lowerer.widthHelpers.get(key);
2867
- if (existing)
2868
- return existing;
2869
- const name = `%nullish.retag.${lowerer.widthHelpers.size}`;
2870
- lowerer.widthHelpers.set(key, name);
2871
- const def = lowerer.unions.get(leftT.unionId);
2872
- const l = varRef("l.0", leftT, loc);
2873
- const r = varRef("r.0", resT, loc);
2874
- const body = [];
2875
- def.arms.forEach((arm, tag) => {
2876
- if (isUnitType(arm))
2877
- return;
2878
- const dst = lowerer.armTag(resT.unionId, arm);
2879
- body.push({
2880
- kind: "if",
2881
- cond: { kind: "unionIsTag", unionId: leftT.unionId, tag, negated: false, value: l, type: BOOL, loc },
2882
- then: [
2883
- {
2884
- kind: "return",
2885
- value: {
2886
- kind: "unionWrap",
2887
- unionId: resT.unionId,
2888
- tag: dst,
2889
- value: { kind: "unionNarrow", unionId: leftT.unionId, tag, value: l, type: arm, loc },
2890
- type: resT,
2891
- loc,
2892
- },
2893
- loc,
2894
- },
2895
- ],
2896
- else_: null,
2897
- loc,
2898
- });
2899
- });
2900
- body.push({ kind: "return", value: r, loc });
2901
- lowerer.liftedFns.push({
2902
- name,
2903
- params: [
2904
- { localId: "l.0", name: "l", type: leftT },
2905
- { localId: "r.0", name: "r", type: resT },
2906
- ],
2907
- returnType: resT,
2908
- locals: [
2909
- { id: "l.0", name: "l", type: leftT, mutable: true },
2910
- { id: "r.0", name: "r", type: resT, mutable: true },
2911
- ],
2912
- body,
2913
- loc,
2914
- });
2915
- return name;
3131
+ /** Test the original storage before projecting its present value. Both
3132
+ * branches are lazy, and an effectful receiver is evaluated exactly once.
3133
+ * Keeping the original tags matters when the checker has removed an arm
3134
+ * or the default changes the union's layout. */
3135
+ function nullishBranches(lowerer, left, leftType, arms, type, loc, absent, present) {
3136
+ const stmts = [];
3137
+ let stable = left;
3138
+ if (!isSafeToRepeat(left)) {
3139
+ const local = lowerer.declareHiddenLocal("%nullish", leftType);
3140
+ stmts.push({ kind: "varDecl", localId: local.id, init: left, loc });
3141
+ stable = varRef(local.id, leftType, loc);
3142
+ }
3143
+ const unitTags = arms.flatMap((arm, tag) => isUnitType(arm) ? [tag] : []);
3144
+ let test = null;
3145
+ for (const tag of unitTags) {
3146
+ const part = { kind: "unionIsTag", unionId: leftType.unionId, tag, negated: false, value: stable, type: BOOL, loc };
3147
+ test = test === null ? part : { kind: "logical", op: "||", left: test, right: part, type: BOOL, loc };
3148
+ }
3149
+ if (test === null)
3150
+ throw new InternalCompilerError("nullish branch requires an absent arm");
3151
+ const result = { kind: "ternary", cond: test, then: absent(), else_: present(stable), type, loc };
3152
+ return stmts.length === 0 ? result : { kind: "seqExpr", stmts, result, type, loc };
2916
3153
  }
2917
3154
  /** A CONDITION-position expression: the result is consumed as a bool
2918
3155
  * only, so `&&`/`||` descend recursively over ToBoolean'd operands —
@@ -2933,6 +3170,8 @@ export function lowerCondition(lowerer, expr) {
2933
3170
  if (op === ts.SyntaxKind.AmpersandAmpersandToken || op === ts.SyntaxKind.BarBarToken) {
2934
3171
  const isAnd = op === ts.SyntaxKind.AmpersandAmpersandToken;
2935
3172
  const left = lowerer.lowerCondition(e.left);
3173
+ if (left.kind === "boolLit" && left.value !== isAnd)
3174
+ return left;
2936
3175
  // The right operand evaluates only when the left already answered
2937
3176
  // (true for &&, false for ||) — aliased-typeof narrows the left
2938
3177
  // PROVES under that polarity hold while it lowers (`type ===
@@ -2949,6 +3188,8 @@ export function lowerCondition(lowerer, expr) {
2949
3188
  lowerer.runtimeOptionalLocals.add(lowerer.runtimeOptionalRootOf(runtimeOptionalLocal));
2950
3189
  }
2951
3190
  });
3191
+ if (left.kind === "boolLit")
3192
+ return right;
2952
3193
  return {
2953
3194
  kind: "logical",
2954
3195
  op: isAnd ? "&&" : "||",
@@ -2971,14 +3212,23 @@ function lowerPromiseThenPresence(lowerer, access, result) {
2971
3212
  ? (() => {
2972
3213
  const arms = lowerer.unions.get(mapped.unionId)?.arms ?? [];
2973
3214
  const promises = arms.filter((arm) => arm.kind === "promise");
2974
- return promises.length === 1 && arms.every((arm) => arm.kind === "promise" || isUnitType(arm))
2975
- ? promises[0]
3215
+ const promise = promises[0];
3216
+ return promises.length === 1 && promise?.kind === "promise" && arms.every((arm) => arm.kind === "promise" || isUnitType(arm))
3217
+ ? promise
2976
3218
  : null;
2977
3219
  })()
2978
3220
  : null;
2979
3221
  if (promiseType === null)
2980
3222
  return null;
2981
3223
  let receiver = lowerer.lowerExpr(access.expression);
3224
+ if (receiver.type.kind === "union" && access.questionDotToken) {
3225
+ const arms = lowerer.unions.get(receiver.type.unionId)?.arms ?? [];
3226
+ if (arms.every((arm) => arm.kind === "promise" || isUnitType(arm))) {
3227
+ return nullishBranches(lowerer, receiver, receiver.type, arms, result.type, locOf(access), () => result.type.kind === "bool"
3228
+ ? { kind: "boolLit", value: false, type: BOOL, loc: result.loc }
3229
+ : { kind: "strLit", value: "undefined", type: STRING, loc: result.loc }, () => result);
3230
+ }
3231
+ }
2982
3232
  if (receiver.type.kind === "union") {
2983
3233
  const optional = lowerer.runtimeOptionalPropertyReceiver(access.expression, receiver, promiseType, access.name.text);
2984
3234
  if (optional !== null)
@@ -2990,6 +3240,29 @@ function lowerPromiseThenPresence(lowerer, access, result) {
2990
3240
  receiver = { kind: "call", callee: helper, args: [receiver], type: promiseType, loc: receiver.loc };
2991
3241
  }
2992
3242
  }
3243
+ if (receiver.type.kind === "dyn") {
3244
+ // A specialized JS parameter can retain checked storage while JSDoc
3245
+ // describes a promise. Probe its actual value, preserving nullish
3246
+ // guards and own-property reads on ordinary thenable objects.
3247
+ const loc = locOf(access);
3248
+ const local = lowerer.declareHiddenLocal("%thenReceiver", DYN);
3249
+ const stable = varRef(local.id, DYN, loc);
3250
+ const member = {
3251
+ kind: "dynKeyGet", value: stable,
3252
+ key: { kind: "strLit", value: "then", type: STRING, loc },
3253
+ ...(access.questionDotToken ? { optional: true } : {}), type: DYN, loc,
3254
+ };
3255
+ const other = result.type.kind === "bool"
3256
+ ? { kind: "dynTest", test: "truthy", value: member, type: BOOL, loc }
3257
+ : { kind: "libCall", fn: "dyn.typeof", args: [member], type: STRING, loc };
3258
+ return {
3259
+ kind: "seqExpr", stmts: [{ kind: "varDecl", localId: local.id, init: receiver, loc }],
3260
+ result: {
3261
+ kind: "ternary", cond: { kind: "dynTest", test: "promise", value: stable, type: BOOL, loc },
3262
+ then: result, else_: other, type: result.type, loc,
3263
+ }, type: result.type, loc,
3264
+ };
3265
+ }
2993
3266
  if (receiver.type.kind !== "promise")
2994
3267
  return null;
2995
3268
  if (isSafeToDiscard(receiver))
@@ -3012,15 +3285,107 @@ function runtimeOptionalLocalOf(lowerer, node) {
3012
3285
  return local && lowerer.runtimeOptionalLocals.has(lowerer.runtimeOptionalRootOf(local)) ? local : null;
3013
3286
  }
3014
3287
  export function runtimeOptionalTrueIds(lowerer, node) {
3015
- let expr = node;
3016
- while (ts.isParenthesizedExpression(expr))
3017
- expr = expr.expression;
3018
- if (ts.isBinaryExpression(expr) &&
3019
- expr.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) {
3020
- return [...runtimeOptionalTrueIds(lowerer, expr.left), ...runtimeOptionalTrueIds(lowerer, expr.right)];
3021
- }
3022
- const local = runtimeOptionalLocalOf(lowerer, expr);
3023
- return local === null ? [] : [local];
3288
+ return runtimeOptionalGuardIds(lowerer, node, true);
3289
+ }
3290
+ /** Presence proved by one outcome of a boolean guard. Negation flips
3291
+ * the outcome; all operands of a true AND or false OR have that outcome.
3292
+ * Assignments invalidate a syntactic proof even when checker types hide
3293
+ * the undefined produced by an out-of-bounds read. */
3294
+ export function runtimeOptionalGuardIds(lowerer, node, truthy) {
3295
+ const candidates = [];
3296
+ const collect = (expr, outcome) => {
3297
+ if (ts.isParenthesizedExpression(expr))
3298
+ return collect(expr.expression, outcome);
3299
+ if (ts.isPrefixUnaryExpression(expr) && expr.operator === ts.SyntaxKind.ExclamationToken) {
3300
+ return collect(expr.operand, outcome === null ? null : !outcome);
3301
+ }
3302
+ if (ts.isBinaryExpression(expr) &&
3303
+ (outcome === true && expr.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ||
3304
+ outcome === false && expr.operatorToken.kind === ts.SyntaxKind.BarBarToken)) {
3305
+ collect(expr.left, outcome);
3306
+ collect(expr.right, outcome);
3307
+ }
3308
+ else if (ts.isPropertyAccessExpression(expr) && !expr.questionDotToken) {
3309
+ // A completed ordinary member read checked its receiver even if
3310
+ // the property's value was falsy. Optional chains make no such
3311
+ // promise, and calls/captures retain their separate checked ABI.
3312
+ const local = runtimeOptionalLocalOf(lowerer, expr.expression);
3313
+ if (local !== null)
3314
+ candidates.push(local);
3315
+ else if (ts.isPropertyAccessExpression(expr.expression))
3316
+ collect(expr.expression, outcome);
3317
+ }
3318
+ else if (ts.isBinaryExpression(expr) &&
3319
+ expr.operatorToken.kind !== ts.SyntaxKind.AmpersandAmpersandToken &&
3320
+ expr.operatorToken.kind !== ts.SyntaxKind.BarBarToken &&
3321
+ expr.operatorToken.kind !== ts.SyntaxKind.QuestionQuestionToken) {
3322
+ // Both operands of a comparison/arithmetic operator are evaluated.
3323
+ // Only member reads prove presence; bare identifiers need truth.
3324
+ const member = (operand) => {
3325
+ while (ts.isParenthesizedExpression(operand))
3326
+ operand = operand.expression;
3327
+ if (ts.isPropertyAccessExpression(operand))
3328
+ collect(operand, outcome);
3329
+ };
3330
+ member(expr.left);
3331
+ member(expr.right);
3332
+ const op = expr.operatorToken.kind;
3333
+ const equal = op === ts.SyntaxKind.EqualsEqualsEqualsToken || op === ts.SyntaxKind.EqualsEqualsToken;
3334
+ const unequal = op === ts.SyntaxKind.ExclamationEqualsEqualsToken || op === ts.SyntaxKind.ExclamationEqualsToken;
3335
+ if ((equal && outcome === false) || (unequal && outcome === true)) {
3336
+ const present = (value, unit) => {
3337
+ while (ts.isParenthesizedExpression(unit))
3338
+ unit = unit.expression;
3339
+ const excludesUndefined = ts.isIdentifier(unit) && unit.text === "undefined" &&
3340
+ (lowerer.typeOf(unit).flags & ts.TypeFlags.Undefined) !== 0 ||
3341
+ unit.kind === ts.SyntaxKind.NullKeyword &&
3342
+ (op === ts.SyntaxKind.EqualsEqualsToken || op === ts.SyntaxKind.ExclamationEqualsToken);
3343
+ if (excludesUndefined) {
3344
+ const local = runtimeOptionalLocalOf(lowerer, value);
3345
+ if (local !== null)
3346
+ candidates.push(local);
3347
+ }
3348
+ };
3349
+ present(expr.left, expr.right);
3350
+ present(expr.right, expr.left);
3351
+ }
3352
+ }
3353
+ else if (outcome) {
3354
+ const local = runtimeOptionalLocalOf(lowerer, expr);
3355
+ if (local !== null)
3356
+ candidates.push(local);
3357
+ }
3358
+ };
3359
+ collect(node, truthy);
3360
+ if (candidates.length === 0)
3361
+ return candidates;
3362
+ const assigned = new Set();
3363
+ const visit = (part) => {
3364
+ if (ts.isFunctionLike(part))
3365
+ return;
3366
+ let target = null;
3367
+ if (ts.isBinaryExpression(part) && part.operatorToken.kind >= ts.SyntaxKind.FirstAssignment &&
3368
+ part.operatorToken.kind <= ts.SyntaxKind.LastAssignment)
3369
+ target = part.left;
3370
+ if ((ts.isPrefixUnaryExpression(part) || ts.isPostfixUnaryExpression(part)) &&
3371
+ (part.operator === ts.SyntaxKind.PlusPlusToken || part.operator === ts.SyntaxKind.MinusMinusToken))
3372
+ target = part.operand;
3373
+ if (target !== null) {
3374
+ // Destructuring assignments can contain several identifiers.
3375
+ const mark = (name) => {
3376
+ if (ts.isIdentifier(name)) {
3377
+ const local = lowerer.resolveLocal(name);
3378
+ if (local)
3379
+ assigned.add(lowerer.runtimeOptionalRootOf(local));
3380
+ }
3381
+ ts.forEachChild(name, mark);
3382
+ };
3383
+ mark(target);
3384
+ }
3385
+ ts.forEachChild(part, visit);
3386
+ };
3387
+ visit(node);
3388
+ return candidates.filter((local) => !assigned.has(lowerer.runtimeOptionalRootOf(local)));
3024
3389
  }
3025
3390
  export function withRuntimeOptionalNarrowed(lowerer, locals, fn) {
3026
3391
  if (locals.length === 0)
@@ -3102,8 +3467,60 @@ export function eqComparableUnion(lowerer, unionId) {
3102
3467
  * the receiver type AND the ambient-file provenance of the member are
3103
3468
  * verified — the name alone proves nothing. */
3104
3469
  export function lowerIntrinsicProperty(lowerer, expr) {
3470
+ for (const name of ["String", "Number", "Boolean", "BigInt"]) {
3471
+ if (!lowerer.chainBlocked(expr) && lowerer.isStdlibGlobal(expr, name)) {
3472
+ return primitiveCtorClosure(lowerer, name, locOf(expr), isJsSourceFile(expr.getSourceFile()));
3473
+ }
3474
+ }
3475
+ if (lowerer.stdlibGlobalMember(expr, "Reflect") === "ownKeys") {
3476
+ const value = lowerer.lowerNativeCallableValue({
3477
+ fn: "dyn.ownKeys", params: [DYN], result: DYN, valueParams: [{ mode: "required", type: DYN }],
3478
+ }, "Reflect.ownKeys", locOf(expr));
3479
+ return { kind: "dynFrom", value, fnName: "ownKeys", type: DYN, loc: locOf(expr) };
3480
+ }
3105
3481
  if (lowerer.chainBlocked(expr))
3106
3482
  return null;
3483
+ const builtinPrototype = builtinPrototypeMethod(lowerer, expr);
3484
+ if (builtinPrototype)
3485
+ return { kind: "libCall", fn: "dyn.builtinMethod", args: [
3486
+ { kind: "strLit", value: builtinPrototype, type: STRING, loc: locOf(expr) },
3487
+ { kind: "strLit", value: expr.name.text, type: STRING, loc: locOf(expr.name) },
3488
+ ], type: DYN, loc: locOf(expr) };
3489
+ if (ts.isPropertyAccessExpression(expr.expression) &&
3490
+ lowerer.stdlibGlobalMember(expr.expression, "Array") === "prototype") {
3491
+ return { kind: "dynKeyGet", value: { kind: "libCall", fn: "dyn.arrayPrototype", args: [], type: DYN, loc: locOf(expr.expression) },
3492
+ key: { kind: "strLit", value: expr.name.text, type: STRING, loc: locOf(expr.name) }, type: DYN, loc: locOf(expr) };
3493
+ }
3494
+ if (expr.name.text === "apply" && ts.isPropertyAccessExpression(expr.expression) &&
3495
+ lowerer.stdlibGlobalMember(expr.expression, "Function") === "prototype")
3496
+ return { kind: "libCall", fn: "dyn.functionApply", args: [], type: DYN, loc: locOf(expr) };
3497
+ if (lowerer.stdlibGlobalMember(expr, "Array") === "prototype")
3498
+ return { kind: "libCall", fn: "dyn.arrayPrototype", args: [], type: DYN, loc: locOf(expr) };
3499
+ if (lowerer.stdlibGlobalMember(expr, "Object") === "prototype")
3500
+ return { kind: "libCall", fn: "dyn.objectPrototype", args: [], type: DYN, loc: locOf(expr) };
3501
+ if (lowerer.stdlibGlobalMember(expr, "Array") === "isArray")
3502
+ return lowerArrayIsArrayValue(lowerer, locOf(expr));
3503
+ if (lowerer.stdlibGlobalMember(expr, "Array") === "from")
3504
+ return lowerArrayFromValue(lowerer, locOf(expr));
3505
+ const stringMember = lowerer.stdlibGlobalMember(expr, "String");
3506
+ if (stringMember === "fromCharCode" || stringMember === "fromCodePoint")
3507
+ return lowerStringCodesValue(lowerer, stringMember, locOf(expr));
3508
+ const numberMember = lowerer.stdlibGlobalMember(expr, "Number");
3509
+ if (numberMember === "parseInt" || numberMember === "parseFloat")
3510
+ return lowerNumberParserValue(lowerer, numberMember, locOf(expr));
3511
+ if (lowerer.stdlibGlobalMember(expr, "Buffer") === "isBuffer")
3512
+ return lowerCheckedPredicateValue(lowerer, "Buffer.isBuffer", "buffer", locOf(expr));
3513
+ if (lowerer.stdlibGlobalMember(expr, "Date") === "now")
3514
+ return lowerer.lowerNativeCallableValue({
3515
+ fn: "date.now", params: [], result: F64, valueParams: [],
3516
+ }, "Date.now", locOf(expr));
3517
+ if (stdlibGlobalNameOf(lowerer, expr.expression) === "Object") {
3518
+ if (expr.name.text === "assign")
3519
+ return lowerObjectAssignValue(lowerer, locOf(expr));
3520
+ const fn = own(OBJECT_CALLABLE_VALUES, expr.name.text);
3521
+ if (fn)
3522
+ return lowerer.lowerNativeCallableValue(fn, `Object.${expr.name.text}`, locOf(expr));
3523
+ }
3107
3524
  // A never-tainted JS receiver type lowered checked-dynamic
3108
3525
  // (neverTaintedJsType — `cmd.length` on `const cmd = ['pwd', []]`):
3109
3526
  // stand down so the dyn keyed read below the chain answers, instead
@@ -3137,8 +3554,19 @@ export function lowerIntrinsicProperty(lowerer, expr) {
3137
3554
  if (kind === "child" ? !isChildSurfaceMember(lowerer, expr) : !tupleLengthOnArray && !lowerer.isStdlibMember(expr))
3138
3555
  return null;
3139
3556
  const name = expr.name.text;
3557
+ if (kind === "array" && name === "constructor" && isJsSourceFile(expr.getSourceFile())) {
3558
+ const receiver = lowerer.lowerExpr(expr.expression);
3559
+ if (receiver.type.kind === "dyn")
3560
+ return { kind: "dynKeyGet", value: receiver,
3561
+ key: { kind: "strLit", value: name, type: STRING, loc: locOf(expr) }, type: DYN, loc: locOf(expr) };
3562
+ // Ordinary arrays keep the existing Array builtin identity token.
3563
+ const result = { kind: "strLit", value: "[builtin Array]", type: STRING, loc: locOf(expr) };
3564
+ return { kind: "seqExpr", stmts: [{ kind: "exprStmt", expr: receiver, loc: locOf(expr) }], result, type: STRING, loc: locOf(expr) };
3565
+ }
3140
3566
  const strictReceiver = (expected) => {
3141
3567
  const value = lowerer.lowerExpr(expr.expression);
3568
+ if (value.type.kind === "dyn")
3569
+ return lowerer.coerceInto(expr.expression, value, expected);
3142
3570
  return lowerer.runtimeOptionalPropertyReceiver(expr.expression, value, expected, name) ?? value;
3143
3571
  };
3144
3572
  if (kind === "child") {
@@ -3310,6 +3738,9 @@ export function lowerIntrinsicProperty(lowerer, expr) {
3310
3738
  lowerer.noLowering(`URLSearchParams.${name}`, expr, "get, getAll, set, append, delete, has, sort, size, toString(), forEach, and for-of iteration are the supported URLSearchParams members", lowerer.checker.getSymbolAtLocation(expr.name));
3311
3739
  }
3312
3740
  if (kind === "regex") {
3741
+ if (name === "lastIndex") {
3742
+ return { kind: "regexIntrinsic", method: "lastIndex", receiver: strictReceiver({ kind: "regex" }), args: [], type: F64, loc: locOf(expr) };
3743
+ }
3313
3744
  if (name === "source" || name === "flags") {
3314
3745
  const receiver = strictReceiver({ kind: "regex" });
3315
3746
  return { kind: "regexIntrinsic", method: name, receiver, args: [], type: STRING, loc: locOf(expr) };
@@ -3329,6 +3760,12 @@ export function lowerIntrinsicProperty(lowerer, expr) {
3329
3760
  }
3330
3761
  if (kind === "bytes") {
3331
3762
  const loc = locOf(expr);
3763
+ if (name === "constructor") {
3764
+ const expected = lowerer.mapTypeOf(lowerer.typeOf(expr.expression));
3765
+ const receiver = expected?.kind === "bytes" ? strictReceiver(expected) : lowerer.lowerExpr(expr.expression);
3766
+ return { kind: "dynKeyGet", value: lowerer.coerceInto(expr.expression, receiver, DYN),
3767
+ key: { kind: "strLit", value: name, type: STRING, loc }, type: DYN, loc };
3768
+ }
3332
3769
  if (name === "length" || name === "byteLength") {
3333
3770
  const expected = lowerer.mapTypeOf(lowerer.typeOf(expr.expression));
3334
3771
  let receiver = expected?.kind === "bytes"
@@ -3359,15 +3796,14 @@ export function lowerIntrinsicProperty(lowerer, expr) {
3359
3796
  // SEMANTICS.md notes the divergence from Node's Buffer pooling),
3360
3797
  // the view's real offset for a DataView. A runtime read, so the
3361
3798
  // receiver's evaluation is never discarded.
3362
- const receiver = lowerer.lowerExpr(expr.expression);
3799
+ const expected = lowerer.mapTypeOf(lowerer.typeOf(expr.expression));
3800
+ const receiver = expected?.kind === "bytes" ? strictReceiver(expected) : lowerer.lowerExpr(expr.expression);
3363
3801
  return { kind: "bytesIntrinsic", method: "byteOffset", receiver, args: [], type: F64, loc };
3364
3802
  }
3365
3803
  if (name === "buffer") {
3366
- // No ArrayBuffer VALUE exists — `.buffer` compiles only in the
3367
- // one composed position that consumes it, the DataView
3368
- // constructor's first argument (lowerDataViewNew peels it).
3369
- lowerer.unsupported("SC1090", expr, "'.buffer' outside new DataView(x.buffer, ...) / Buffer.from(x.buffer, ...) " +
3370
- "(no free-standing ArrayBuffer value exists; subarray() answers an aliasing view directly)");
3804
+ const expected = lowerer.mapTypeOf(lowerer.typeOf(expr.expression));
3805
+ const receiver = expected?.kind === "bytes" ? strictReceiver(expected) : lowerer.lowerExpr(expr.expression);
3806
+ return { kind: "bytesIntrinsic", method: "buffer", receiver, args: [], type: DYN, loc };
3371
3807
  }
3372
3808
  if (name === "slice" || name === "subarray" || name === "set" || name === "toString") {
3373
3809
  lowerer.unsupported("SC1090", expr, `typed-array methods as values (call '${name}' directly)`);
@@ -3376,7 +3812,8 @@ export function lowerIntrinsicProperty(lowerer, expr) {
3376
3812
  }
3377
3813
  if (kind === "map") {
3378
3814
  if (name === "size") {
3379
- const receiver = lowerer.lowerExpr(expr.expression);
3815
+ const expected = lowerer.mapTypeOf(lowerer.typeOf(expr.expression));
3816
+ const receiver = expected?.kind === "map" ? strictReceiver(expected) : lowerer.lowerExpr(expr.expression);
3380
3817
  return { kind: "mapIntrinsic", method: "size", receiver, args: [], type: F64, loc: locOf(expr) };
3381
3818
  }
3382
3819
  if (MAP_METHODS.has(name) || name === "forEach") {
@@ -3386,7 +3823,8 @@ export function lowerIntrinsicProperty(lowerer, expr) {
3386
3823
  }
3387
3824
  if (kind === "set") {
3388
3825
  if (name === "size") {
3389
- const receiver = lowerer.lowerExpr(expr.expression);
3826
+ const expected = lowerer.mapTypeOf(lowerer.typeOf(expr.expression));
3827
+ const receiver = expected?.kind === "set" ? strictReceiver(expected) : lowerer.lowerExpr(expr.expression);
3390
3828
  return { kind: "setIntrinsic", method: "size", receiver, args: [], type: F64, loc: locOf(expr) };
3391
3829
  }
3392
3830
  if (SET_METHODS.has(name)) {
@@ -3398,9 +3836,10 @@ export function lowerIntrinsicProperty(lowerer, expr) {
3398
3836
  const expected = kind === "string"
3399
3837
  ? STRING
3400
3838
  : lowerer.mapTypeOf(lowerer.typeOf(expr.expression));
3401
- let receiver = expected && (expected.kind === "string" || expected.kind === "array")
3402
- ? strictReceiver(expected)
3403
- : lowerer.lowerExpr(expr.expression);
3839
+ let receiver = lowerer.lowerExpr(expr.expression);
3840
+ if (receiver.type.kind !== "dyn" && expected && (expected.kind === "string" || expected.kind === "array")) {
3841
+ receiver = lowerer.runtimeOptionalPropertyReceiver(expr.expression, receiver, expected, name) ?? receiver;
3842
+ }
3404
3843
  if (receiver.type.kind === "union" && lowerer.armTag(receiver.type.unionId, UNDEFINED_T) >= 0 && kind === "array") {
3405
3844
  const present = lowerer.stripUndefinedArm(receiver.type);
3406
3845
  const helper = present.kind === "array"
@@ -3466,6 +3905,19 @@ export function lowerArrayLiteral(lowerer, expr, expected) {
3466
3905
  // destination can use the ordinary tuple-to-array coercion afterward.
3467
3906
  const tsType = underConstAssertion(expr) ? lowerer.typeOf(expr) : ctxType ?? lowerer.typeOf(expr);
3468
3907
  let mapped = expected ?? lowerer.mapTypeOf(tsType);
3908
+ // Inferred JavaScript arrays can contain unrelated constructors with
3909
+ // structurally equal checker types. Their native class ABIs still differ.
3910
+ if (!expected && isJsSourceFile(expr.getSourceFile()) && mapped?.kind === "array" && mapped.elem.kind === "classval")
3911
+ mapped = DYN;
3912
+ // An inferred destructuring pattern supplies a contextual [any, ...]
3913
+ // tuple. Its positions do not erase the literal's inferred element
3914
+ // types; explicit destinations already arrive through expected.
3915
+ if (expected === undefined && ctxType && lowerer.checker.isTupleType(ctxType) &&
3916
+ lowerer.checker.getTypeArguments(ctxType).some((type) => (type.flags & ts.TypeFlags.Any) !== 0)) {
3917
+ const inferred = lowerer.mapTypeOf(lowerer.typeOf(expr));
3918
+ if (inferred?.kind === "array" || inferred?.kind === "record" && lowerer.shapes.get(inferred.shapeId)?.tuple)
3919
+ mapped = inferred;
3920
+ }
3469
3921
  // A JS literal whose OWN inferred type is never-tainted
3470
3922
  // (neverTaintedJsType — the evolving `const gb = []`, the mixed
3471
3923
  // command tuple `['pwd', []]`) carries no element information: route
@@ -3562,6 +4014,12 @@ export function lowerArrayLiteral(lowerer, expr, expected) {
3562
4014
  mapped = arrayOf(first.type);
3563
4015
  }
3564
4016
  }
4017
+ if (!expected && isJsSourceFile(expr.getSourceFile()) && mapped?.kind === "array") {
4018
+ const elem = mapped.elem;
4019
+ const arms = elem.kind === "union" ? lowerer.unions.get(elem.unionId)?.arms : [elem];
4020
+ if (arms?.length && arms.every((arm) => arm.kind === "classval"))
4021
+ mapped = DYN;
4022
+ }
3565
4023
  // An EMPTY literal under a CONST ASSERTION whose type queries panicked
3566
4024
  // (tsgo's readonly-[] TupleType conversion — the facade answers `any`,
3567
4025
  // which maps to nothing statically and to an island value under
@@ -3573,6 +4031,11 @@ export function lowerArrayLiteral(lowerer, expr, expected) {
3573
4031
  underConstAssertion(expr)) {
3574
4032
  mapped = arrayOf(unitOnlyUnion(lowerer.unions));
3575
4033
  }
4034
+ // Heterogeneous JavaScript object arrays must retain each literal's
4035
+ // real keys. A structural union can otherwise drop descriptor fields.
4036
+ if (!expected && isJsSourceFile(expr.getSourceFile()) && mapped?.kind === "array" && mapped.elem.kind === "union" &&
4037
+ expr.elements.some(ts.isObjectLiteralExpression))
4038
+ mapped = DYN;
3576
4039
  // A TUPLE-typed slot (`const t: [string, number] = ["a", 1]`): the
3577
4040
  // literal constructs the tuple's record shape — one positional field
3578
4041
  // per element, source order (which IS index order, so evaluation order
@@ -3628,7 +4091,14 @@ export function lowerArrayLiteral(lowerer, expr, expected) {
3628
4091
  // `unknown[]` annotation or a collapsed `(string | object)[]` maps
3629
4092
  // to DYN wholesale now (mapType's dyn-element array rule), so the
3630
4093
  // literal IS the dyn array.
3631
- if (isJsSourceFile(expr.getSourceFile()) || mapped?.kind === "dyn") {
4094
+ if (isJsSourceFile(expr.getSourceFile()) || mapped?.kind === "dyn" || lowerer.checkerAnyArray(expr)) {
4095
+ if (expr.elements.some(ts.isSpreadElement)) {
4096
+ // Native collection drains return vectors of retained dyn
4097
+ // elements. Reuse the ordinary spread builder, then expose the
4098
+ // resulting array through the established unknown[] representation.
4099
+ const vector = lowerArrayLiteral(lowerer, expr, arrayOf(DYN));
4100
+ return lowerer.coerceInto(expr, vector, DYN);
4101
+ }
3632
4102
  const elems = expr.elements.map((el) => {
3633
4103
  if (ts.isSpreadElement(el)) {
3634
4104
  lowerer.unsupported("SC1090", el, "spread elements in a dynamic (unknown[]) array literal");
@@ -3679,19 +4149,7 @@ export function lowerArrayLiteral(lowerer, expr, expected) {
3679
4149
  (ts.isConditionalExpression(srcNode)
3680
4150
  ? lowerTernary(lowerer, srcNode, type)
3681
4151
  : lowerer.lowerExpr(el.expression));
3682
- // `[...someSet]`: a same-element Set drains into a fresh array in
3683
- // insertion order (setIntrinsic toArray); the spread machinery
3684
- // then copies like any array source.
3685
- if (src.type.kind === "set" && typeEquals(src.type.elem, type.elem)) {
3686
- src = {
3687
- kind: "setIntrinsic",
3688
- method: "toArray",
3689
- receiver: src,
3690
- args: [],
3691
- type: arrayOf(src.type.elem),
3692
- loc: locOf(el),
3693
- };
3694
- }
4152
+ src = lowerCollectionSpread(lowerer, src, el.expression) ?? src;
3695
4153
  // `[...new SymbolIterator]`: a CLASS ITERABLE drains through its
3696
4154
  // own protocol into a fresh element array (classIteratorDrainCall
3697
4155
  // — an infinite iterator loops forever, exactly Node), and the
@@ -3723,6 +4181,12 @@ export function lowerArrayLiteral(lowerer, expr, expected) {
3723
4181
  loc: locOf(el),
3724
4182
  };
3725
4183
  }
4184
+ // A native checked iterable can supply scalar elements even when
4185
+ // the checker inferred a typed array from its producer. Drain once
4186
+ // and validate the elements before the ordinary spread copy.
4187
+ if (src.type.kind === "dyn" && (type.elem.kind === "dyn" || type.elem.kind === "f64" || type.elem.kind === "string" || type.elem.kind === "bool")) {
4188
+ src = lowerer.coerceInto(el.expression, checkedIteratorPack(lowerer, src, locOf(el)), type);
4189
+ }
3726
4190
  // A same-family array whose ELEMENT lifts (string[] into a
3727
4191
  // (string | symbol)[] literal — per-element wrap/width copy):
3728
4192
  // the interned width helper reshapes before the spread copies.
@@ -3982,16 +4446,17 @@ export function lowerElementAccess(lowerer, expr) {
3982
4446
  lowerer.fenceStaticResponseMember(expr, "read");
3983
4447
  lowerer.fenceStaticHeadersMember(expr, "read");
3984
4448
  lowerer.fenceStaticReadableStreamMember(expr, "read");
3985
- // `globalThis[<expr>]` — the dynamic global probe (the harness's
3986
- // conditional-globals sweep): a compiled binary's globals are
3987
- // compile-time bindings, not runtime properties, and none of the
3988
- // probed web-platform objects exist here — the honest answer is
3989
- // undefined for every dynamic name (SEMANTICS.md documents the
3990
- // divergence: statically-known globals are not reachable this way;
3991
- // the PROPERTY spelling answers the identity token instead — see
3992
- // lowerPropertyAccess's globalThis rule).
4449
+ // Computed global reads share the stored global object's custom
4450
+ // properties. Symbol keys retain their identity-indexed store.
3993
4451
  if (!expr.questionDotToken && stdlibGlobalNameOf(lowerer, expr.expression) === "globalThis") {
3994
- return { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc: locOf(expr) };
4452
+ const symbol = globalSymbolKey(lowerer, expr.expression, expr.argumentExpression);
4453
+ if (symbol)
4454
+ return { kind: "libCall", fn: "dyn.globalSymbolGet", args: [symbol], type: DYN, loc: locOf(expr) };
4455
+ const key = lowerer.lowerExpr(expr.argumentExpression);
4456
+ const name = key.type.kind === "string" ? key : {
4457
+ kind: "libCall", fn: "dyn.toStringCoerce", args: [lowerer.coerceInto(expr.argumentExpression, key, DYN)], type: STRING, loc: locOf(expr),
4458
+ };
4459
+ return { kind: "dynKeyGet", value: lowerGlobalValue(lowerer, expr.expression), key: name, type: DYN, loc: locOf(expr) };
3995
4460
  }
3996
4461
  // `req.headers["x-name"]` — the computed twin of `req.headers.host`
3997
4462
  // (the server spoke owns both; the envGet precedent).
@@ -4004,7 +4469,7 @@ export function lowerElementAccess(lowerer, expr) {
4004
4469
  // lower to the ONE process.envGet intrinsic. The read narrows like any
4005
4470
  // union-typed expression when the checker narrowed this occurrence.
4006
4471
  if (lowerer.isProcessEnv(expr.expression)) {
4007
- const key = lowerer.lowerExpr(expr.argumentExpression);
4472
+ const key = lowerEnvironmentKey(lowerer, expr.argumentExpression);
4008
4473
  if (key.type.kind !== "string") {
4009
4474
  lowerer.unsupported("SC1090", expr.argumentExpression, "indexing process.env with non-string keys");
4010
4475
  }
@@ -4027,17 +4492,31 @@ export function lowerElementAccess(lowerer, expr) {
4027
4492
  return lowerer.maybeNarrow(ex, expr);
4028
4493
  }
4029
4494
  // Symbol-keyed property READS (`this[kLimit]`): when the key is a
4030
- // statically-resolvable unique-symbol const declared as a field of the
4031
- // receiver's class (uniqueSymbolKeyOf — the countdown.js idiom), the
4495
+ // statically-resolvable symbol declared as a field of the receiver's
4496
+ // class (classSymbolKeyOf — the countdown.js/OpenTUI idioms), the
4032
4497
  // read IS an ordinary field read of the hidden slot. Every other
4033
4498
  // symbol key stays fenced: record shapes, runtime-identity keys
4034
- // (symbol parameters, Symbol.for consts), and keys no class declares
4499
+ // (symbol parameters, reassigned bindings), and keys no class declares
4035
4500
  // — the layouts are compile-time field lists.
4036
4501
  if (lowerer.mapTypeOf(lowerer.typeOf(expr.argumentExpression))?.kind === "symbol") {
4037
4502
  const target = symbolFieldTarget(lowerer, expr);
4038
4503
  if (target)
4039
4504
  return lowerer.maybeNarrow(lowerer.fieldGetExpr(target, locOf(expr), expr), expr);
4040
- 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)");
4505
+ let receiver = lowerer.lowerExpr(expr.expression);
4506
+ const classInfo = receiver.type.kind === "classval" ? lowerer.classes.get(receiver.type.className) : undefined;
4507
+ if (receiver.type.kind === "func" || receiver.type.kind === "generator" ||
4508
+ ((receiver.type.kind === "array" || receiver.type.kind === "string" || receiver.type.kind === "bytes") && lowerer.dynConvertible(receiver.type)) ||
4509
+ (receiver.type.kind === "object" && lowerer.dynConvertible(receiver.type)) || isDynTypedRefType(receiver.type) ||
4510
+ (classInfo && (classInfo.callableBase || hasRuntimeStatics(classInfo))) || ((receiver.type.kind === "map" || receiver.type.kind === "set") && lowerer.dynConvertible(receiver.type))) {
4511
+ const fnName = jsFuncNameOf(expr.expression);
4512
+ receiver = { kind: "dynFrom", value: receiver, type: DYN, loc: locOf(expr),
4513
+ ...(fnName !== null ? { fnName } : {}) };
4514
+ }
4515
+ if (receiver.type.kind === "dyn") {
4516
+ const key = lowerer.lowerExprExpecting(expr.argumentExpression, DYN);
4517
+ return lowerer.maybeNarrow({ kind: "dynKeyGet", value: receiver, key, type: DYN, loc: locOf(expr) }, expr);
4518
+ }
4519
+ 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)");
4041
4520
  }
4042
4521
  // A never-tainted JS receiver type (neverTaintedJsType — `cmd[1]` on
4043
4522
  // `const cmd = ['pwd', []]`, whose binding lowered checked-dynamic)
@@ -4046,6 +4525,22 @@ export function lowerElementAccess(lowerer, expr) {
4046
4525
  let receiverIr = neverTaintedJsType(lowerer, expr.expression, lowerer.typeOf(expr.expression))
4047
4526
  ? null
4048
4527
  : lowerer.mapTypeOf(lowerer.typeOf(expr.expression));
4528
+ if (isJsSourceFile(expr.getSourceFile())) {
4529
+ const receiver = tryLowerExpression(lowerer, expr.expression);
4530
+ if (receiver?.type.kind === "dyn") {
4531
+ const key = lowerer.lowerExprExpecting(expr.argumentExpression, DYN);
4532
+ const optional = hasOptionalChainGuard(expr.expression);
4533
+ return lowerer.maybeNarrow({ kind: "dynKeyGet", value: receiver, key,
4534
+ ...(optional ? { optional: true } : {}), type: DYN, loc: locOf(expr) }, expr);
4535
+ }
4536
+ if (receiver?.type.kind === "func" && lowerer.dynConvertible(receiver.type)) {
4537
+ const loc = locOf(expr);
4538
+ const fnName = jsFuncNameOf(expr.expression);
4539
+ const boxed = { kind: "dynFrom", value: receiver, type: DYN, loc, ...(fnName !== null ? { fnName } : {}) };
4540
+ const key = lowerer.lowerExprExpecting(expr.argumentExpression, DYN);
4541
+ return lowerer.maybeNarrow({ kind: "dynKeyGet", key, value: boxed, type: DYN, loc }, expr);
4542
+ }
4543
+ }
4049
4544
  // A readonly tuple union under Array.isArray is checker-typed as an
4050
4545
  // intersection with any[], whose structural mapping is not the tuple
4051
4546
  // shape. maybeNarrow on the receiver uses the runtime tag proof to
@@ -4054,6 +4549,19 @@ export function lowerElementAccess(lowerer, expr) {
4054
4549
  const checkerArray = lowerer.checkerArrayValue(expr.expression);
4055
4550
  if (checkerArray)
4056
4551
  receiverIr = checkerArray.type;
4552
+ if (receiverIr?.kind === "object") {
4553
+ const key = recordKeyLiteralText(expr.argumentExpression) ?? recordKeyTypeLiteralText(lowerer, expr.argumentExpression);
4554
+ if (key !== null) {
4555
+ const target = classFieldTarget(lowerer, expr.expression, receiverIr, key);
4556
+ if (target)
4557
+ return lowerer.maybeNarrow(lowerer.fieldGetExpr(target, locOf(expr), expr), expr);
4558
+ }
4559
+ if (receiverIr.className === "%Error" || isDynTypedRefType(receiverIr)) {
4560
+ const receiver = lowerer.coerceToExpected(lowerer.lowerExpr(expr.expression), DYN);
4561
+ const key = lowerer.lowerExprExpecting(expr.argumentExpression, DYN);
4562
+ return lowerer.maybeNarrow({ kind: "dynKeyGet", value: receiver, key, type: DYN, loc: locOf(expr) }, expr);
4563
+ }
4564
+ }
4057
4565
  if (receiverIr?.kind === "jsval") {
4058
4566
  // Dispatch follows the RUNTIME world (383(d)): a checker-'any'
4059
4567
  // receiver whose value LOWERED checked-dynamic (`bag.list[0]` where
@@ -4079,6 +4587,8 @@ export function lowerElementAccess(lowerer, expr) {
4079
4587
  // read undefined — divergence 4's policy).
4080
4588
  if (receiverIr?.kind === "bytes") {
4081
4589
  let recv = lowerer.lowerExpr(expr.expression);
4590
+ if (recv.type.kind === "dyn")
4591
+ recv = lowerer.coerceInto(expr.expression, recv, receiverIr);
4082
4592
  if (recv.type.kind === "union" && lowerer.armTag(recv.type.unionId, UNDEFINED_T) >= 0) {
4083
4593
  const present = lowerer.stripUndefinedArm(recv.type);
4084
4594
  const helper = present.kind === "bytes"
@@ -4095,6 +4605,14 @@ export function lowerElementAccess(lowerer, expr) {
4095
4605
  if (recv.type.kind === "jsval")
4096
4606
  return islandElementRead(lowerer, expr, recv);
4097
4607
  const index = lowerer.lowerExpr(expr.argumentExpression);
4608
+ if (isJsSourceFile(expr.getSourceFile()) && recv.type.kind === "bytes") {
4609
+ return { kind: "dynKeyGet", value: lowerer.coerceInto(expr.expression, recv, DYN),
4610
+ key: index.type.kind === "f64" ? { kind: "toString", operand: index, type: STRING, loc: locOf(expr) } : index,
4611
+ type: DYN, loc: locOf(expr) };
4612
+ }
4613
+ if (index.type.kind === "dyn" && recv.type.kind === "bytes") {
4614
+ return { kind: "dynKeyGet", value: lowerer.coerceInto(expr.expression, recv, DYN), key: index, type: DYN, loc: locOf(expr) };
4615
+ }
4098
4616
  if (index.type.kind !== "f64") {
4099
4617
  lowerer.unsupported("SC1090", expr.argumentExpression, "indexing with non-number keys");
4100
4618
  }
@@ -4116,6 +4634,17 @@ export function lowerElementAccess(lowerer, expr) {
4116
4634
  // like the array path.
4117
4635
  if (obj.type.kind === "jsval")
4118
4636
  return islandElementRead(lowerer, expr, obj);
4637
+ if (obj.type.kind === "dyn") {
4638
+ // Object.entries over a checked value stores each pair in the
4639
+ // dynamic tree even when the checker names a tuple type.
4640
+ const key = lowerRecordPropertyKey(lowerer, lowerer.lowerExpr(expr.argumentExpression), expr.argumentExpression);
4641
+ const optional = hasOptionalChainGuard(expr.expression);
4642
+ return lowerer.maybeNarrow({
4643
+ kind: "dynKeyGet", value: obj, key,
4644
+ ...(optional ? { optional: true } : {}),
4645
+ type: DYN, loc: locOf(expr),
4646
+ }, expr);
4647
+ }
4119
4648
  if (obj.type.kind === "union" && lowerer.armTag(obj.type.unionId, UNDEFINED_T) >= 0) {
4120
4649
  // The checker sees the outer `arrays[i]` as a tuple when
4121
4650
  // noUncheckedIndexedAccess is disabled, but the lowered read is
@@ -4171,21 +4700,33 @@ export function lowerElementAccess(lowerer, expr) {
4171
4700
  // (`pkg.workspaces.packages["0"]` — the lowering world types the
4172
4701
  // unknown-rooted chain `any`); a non-dyn lowering falls through to
4173
4702
  // the fences below (re-lowering is pure IR construction).
4174
- if (receiverIr?.kind === "dyn" || receiverIr === null) {
4703
+ if (receiverIr?.kind === "dyn" || receiverIr?.kind === "union" || receiverIr === null) {
4175
4704
  const obj = lowerer.lowerExpr(expr.expression);
4176
- if (obj.type.kind === "dyn") {
4705
+ // A generic mapped type can stay unresolved at this body use even
4706
+ // though its instantiated parameter has a concrete record ABI.
4707
+ // Dispatch from that stored shape, as the array fallback below does.
4708
+ if (receiverIr === null && obj.type.kind === "record") {
4709
+ const shape = lowerer.shapes.get(obj.type.shapeId);
4710
+ const index = tupleLiteralIndex(expr.argumentExpression);
4711
+ const field = shape?.tuple && index !== null ? shape.fields.find((field) => field.name === String(index)) : null;
4712
+ if (field)
4713
+ return lowerer.maybeNarrow({ kind: "recordGet", obj, shapeId: obj.type.shapeId, field: field.name, type: field.type, loc: locOf(expr) }, expr);
4714
+ if (shape && !shape.tuple) {
4715
+ return lowerer.lowerRecordKeyRead(expr, obj.type.shapeId, shape);
4716
+ }
4717
+ }
4718
+ if (obj.type.kind === "dyn" || obj.type.kind === "object" && obj.type.className === "%Error" || isDynTypedRefType(obj.type)) {
4177
4719
  const rawKey = lowerer.lowerExpr(expr.argumentExpression);
4178
4720
  // Number, bool, and DYN keys stringify (ToPropertyKey) — the
4179
4721
  // dyn-keyed read `catchWarning[warning.name]` where the property
4180
4722
  // chain itself lowered dyn.
4181
- const key = rawKey.type.kind === "string"
4182
- ? rawKey
4183
- : rawKey.type.kind === "f64" || rawKey.type.kind === "bool" || rawKey.type.kind === "dyn"
4184
- ? { kind: "toString", operand: rawKey, type: STRING, loc: rawKey.loc }
4185
- : null;
4186
- if (key) {
4723
+ const key = rawKey.type.kind === "dyn" || rawKey.type.kind === "symbol" ||
4724
+ rawKey.type.kind === "union" && lowerer.dynConvertible(rawKey.type)
4725
+ ? lowerer.coerceToExpected(rawKey, DYN)
4726
+ : lowerRecordPropertyKey(lowerer, rawKey, expr.argumentExpression);
4727
+ if (key.type.kind === "string" || key.type.kind === "dyn") {
4187
4728
  const opt = hasOptionalChainGuard(expr.expression);
4188
- return lowerer.maybeNarrow({ kind: "dynKeyGet", key, ...(opt ? { optional: true } : {}), value: obj, type: DYN, loc: locOf(expr) }, expr);
4729
+ return lowerer.maybeNarrow({ kind: "dynKeyGet", key, ...(opt ? { optional: true } : {}), value: lowerer.coerceToExpected(obj, DYN), type: DYN, loc: locOf(expr) }, expr);
4189
4730
  }
4190
4731
  }
4191
4732
  // A checker-`any` receiver that LOWERS to a static ARRAY
@@ -4195,6 +4736,9 @@ export function lowerElementAccess(lowerer, expr) {
4195
4736
  if (obj.type.kind === "array") {
4196
4737
  const index = lowerer.lowerExpr(expr.argumentExpression);
4197
4738
  if (index.type.kind === "f64") {
4739
+ const safe = lowerSafeIndexRead(lowerer, obj, index, locOf(expr));
4740
+ if (safe)
4741
+ return safe;
4198
4742
  return lowerer.maybeNarrow({ kind: "arrayGet", arr: obj, index, type: obj.type.elem, loc: locOf(expr) }, expr);
4199
4743
  }
4200
4744
  }
@@ -4217,7 +4761,7 @@ export function lowerElementAccess(lowerer, expr) {
4217
4761
  }
4218
4762
  }
4219
4763
  }
4220
- if (key.type.kind === "string") {
4764
+ if (key.type.kind === "string" || key.type.kind === "f64") {
4221
4765
  const lit = ts.isStringLiteral(expr.argumentExpression)
4222
4766
  ? expr.argumentExpression.text
4223
4767
  : null;
@@ -4225,33 +4769,20 @@ export function lowerElementAccess(lowerer, expr) {
4225
4769
  if (keyed)
4226
4770
  return lowerer.maybeNarrow(keyed, expr);
4227
4771
  }
4228
- // `u?.split(":")[0]` — a NUMBER index over an undefined-armed
4229
- // array union: the array arm answers its element (invalid indices
4230
- // trap — divergence 4's policy), unit arms answer undefined (the
4231
- // chain's short-circuit value). Every non-unit arm must be the
4232
- // same array type.
4233
- if (key.type.kind === "f64") {
4234
- const def = lowerer.unions.get(value.type.unionId);
4235
- const arrArms = def?.arms.filter((a) => a.kind === "array") ?? [];
4236
- if (def &&
4237
- arrArms.length > 0 &&
4238
- def.arms.every((a) => a.kind === "array" || isUnitType(a)) &&
4239
- arrArms.every((a) => typeEquals(a, arrArms[0]))) {
4240
- const elem = arrArms[0].elem;
4241
- const hasUnit = def.arms.some(isUnitType);
4242
- const t = !hasUnit
4243
- ? elem
4244
- : elem.kind === "union"
4245
- ? lowerer.withUndefinedArmOf(elem)
4246
- : lowerer.withUndefinedArm(elem);
4247
- if (t) {
4248
- return lowerer.maybeNarrow({ kind: "unionKeyGet", unionId: value.type.unionId, key, value, type: t, loc: locOf(expr) }, expr);
4249
- }
4250
- }
4251
- }
4252
4772
  }
4253
4773
  }
4254
4774
  if (receiverIr?.kind !== "array") {
4775
+ {
4776
+ const receiver = tryLowerExpression(lowerer, expr.expression);
4777
+ if (receiver?.type.kind === "func" &&
4778
+ canBoxFuncIntoDyn(receiver.type, (id) => lowerer.shapes.get(id), (id) => lowerer.unions.get(id))) {
4779
+ const key = lowerRecordPropertyKey(lowerer, lowerer.lowerExpr(expr.argumentExpression), expr.argumentExpression);
4780
+ if (key.type.kind === "string") {
4781
+ const boxed = { kind: "dynFrom", value: receiver, type: DYN, loc: locOf(expr.expression) };
4782
+ return lowerer.maybeNarrow({ kind: "dynKeyGet", key, value: boxed, type: DYN, loc: locOf(expr) }, expr);
4783
+ }
4784
+ }
4785
+ }
4255
4786
  if (receiverIr?.kind === "string") {
4256
4787
  // `s[i]` with a number index reads a UTF-16 code unit — charAt's
4257
4788
  // exact job (the UTF-16-exact runtime). Without
@@ -4259,16 +4790,23 @@ export function lowerElementAccess(lowerer, expr) {
4259
4790
  // and charAt's "" is the only string-typed answer for an
4260
4791
  // out-of-range or fractional index where JS reads `undefined` —
4261
4792
  // SEMANTICS.md documents the divergence; in-range integer reads
4262
- // (the loop pattern) are JS-exact. Under noUncheckedIndexedAccess
4263
- // the read types `string | undefined`, which charAt cannot honor —
4264
- // fenced.
4793
+ // (the loop pattern) are JS-exact. Optional results instead retain
4794
+ // undefined for missing string properties.
4795
+ const recv = lowerer.lowerExpr(expr.expression);
4265
4796
  const index = lowerer.lowerExpr(expr.argumentExpression);
4266
- if (index.type.kind === "f64" && lowerer.mapTypeOf(lowerer.typeOf(expr))?.kind === "string") {
4267
- const recv = lowerer.lowerExpr(expr.expression);
4268
- return { kind: "strIntrinsic", method: "charAt", receiver: recv, args: [index], type: STRING, loc: locOf(expr) };
4797
+ const resultType = lowerer.mapTypeOf(lowerer.typeOf(expr));
4798
+ if (index.type.kind === "f64" && recv.type.kind === "string") {
4799
+ if (resultType?.kind === "string") {
4800
+ return { kind: "strIntrinsic", method: "charAt", receiver: recv, args: [index], type: STRING, loc: locOf(expr) };
4801
+ }
4802
+ if (resultType?.kind === "union") {
4803
+ const arms = lowerer.unions.get(resultType.unionId)?.arms;
4804
+ if (arms?.length === 2 && lowerer.armTag(resultType.unionId, STRING) >= 0 && lowerer.armTag(resultType.unionId, UNDEFINED_T) >= 0) {
4805
+ return lowerOptionalStringIndex(lowerer, recv, index, resultType, locOf(expr));
4806
+ }
4807
+ }
4269
4808
  }
4270
- lowerer.unsupported("SC1090", expr, "string indexing with this index/result shape (a number index typed 'string' lowers to charAt; " +
4271
- "use .charAt(i) under noUncheckedIndexedAccess)");
4809
+ lowerer.unsupported("SC1090", expr, "string indexing with this index/result shape (expected a number index and a string or string | undefined result)");
4272
4810
  }
4273
4811
  lowerer.unsupported("SC1090", expr, "element access on non-array values");
4274
4812
  }
@@ -4346,14 +4884,22 @@ export function lowerElementAccess(lowerer, expr) {
4346
4884
  // can validate, and fences by name when it is not — an arrayGet over a
4347
4885
  // dyn receiver is never emitted (the validator ICE).
4348
4886
  if (arr.type.kind === "dyn") {
4349
- if (isJsonSafeType(receiverIr, (id) => lowerer.shapes.get(id), (id) => lowerer.unions.get(id))) {
4887
+ if (canDynCheckTo(receiverIr, (id) => lowerer.shapes.get(id), (id) => lowerer.unions.get(id))) {
4350
4888
  arr = { kind: "dynCheck", value: arr, type: receiverIr, loc: locOf(expr) };
4351
4889
  }
4352
4890
  else {
4353
4891
  lowerer.unsupported("SC1090", expr, `element access on checked-dynamic values with '${lowerer.fmt(receiverIr)}' elements`);
4354
4892
  }
4355
4893
  }
4356
- const index = lowerer.lowerExpr(expr.argumentExpression);
4894
+ // Array reads can themselves supply an index. A missing numeric value
4895
+ // is undefined, whose property lookup misses just like NaN; keep that
4896
+ // value instead of rejecting a checker-number index with optional IR.
4897
+ const index = lowerOptionalNumber(lowerer, lowerer.lowerExpr(expr.argumentExpression), locOf(expr.argumentExpression), expr.argumentExpression);
4898
+ if (arr.type.kind === "array" && (index.type.kind === "dyn" ||
4899
+ isJsSourceFile(expr.getSourceFile()) && index.type.kind !== "f64" && lowerer.dynConvertible(index.type))) {
4900
+ return { kind: "dynKeyGet", value: lowerer.coerceToExpected(arr, DYN),
4901
+ key: index, type: DYN, loc: locOf(expr) };
4902
+ }
4357
4903
  if (index.type.kind !== "f64") {
4358
4904
  lowerer.unsupported("SC1090", expr.argumentExpression, "indexing with non-number keys");
4359
4905
  }
@@ -4464,8 +5010,11 @@ export function lowerRecordKeyRead(lowerer, expr, shapeId, shape, includeUndefin
4464
5010
  // Number AND checked-dynamic keys ride the JS-exact formatter —
4465
5011
  // property keys ARE strings (o[k] is o[String(k)] in JS), and a dyn
4466
5012
  // key (agent.sockets[agent.getName(...)]) stringifies the same way.
4467
- dk = lowerRecordPropertyKey(lowerer, dk, keyNode);
4468
- if (dk.type.kind !== "string") {
5013
+ if (dk.type.kind === "union" && lowerer.dynConvertible(dk.type))
5014
+ dk = lowerer.coerceToExpected(dk, DYN);
5015
+ if (dk.type.kind !== "symbol" && dk.type.kind !== "dyn")
5016
+ dk = lowerRecordPropertyKey(lowerer, dk, keyNode);
5017
+ if (dk.type.kind !== "string" && dk.type.kind !== "symbol" && dk.type.kind !== "dyn") {
4469
5018
  lowerer.unsupported("SC1090", keyNode, "indexing records with non-string or non-number keys");
4470
5019
  }
4471
5020
  const read = { kind: "dynKeyGet", key: dk, value: obj, type: DYN, loc };
@@ -4475,6 +5024,16 @@ export function lowerRecordKeyRead(lowerer, expr, shapeId, shape, includeUndefin
4475
5024
  // to observe the missing value instead of throwing during that check.
4476
5025
  return includeUndefined ? read : lowerer.maybeNarrow(read, expr);
4477
5026
  }
5027
+ if (litKey !== null) {
5028
+ const nominal = representedClassFieldTarget(lowerer, expr.expression, litKey, obj);
5029
+ if (nominal) {
5030
+ const read = lowerer.fieldGetExpr(nominal, loc, expr);
5031
+ return includeUndefined ? read : lowerer.maybeNarrow(read, expr);
5032
+ }
5033
+ }
5034
+ if (hasClassPayload(lowerer, obj.type)) {
5035
+ lowerer.unsupported("SC1090", expr, "computed property reads through structural views of class instances (use a declared literal key)");
5036
+ }
4478
5037
  // Predicates can strengthen optional fields without changing the
4479
5038
  // receiver's stored layout. Use that layout for bracket reads just as
4480
5039
  // fieldTarget does for the corresponding dot reads.
@@ -4502,6 +5061,19 @@ export function lowerRecordKeyRead(lowerer, expr, shapeId, shape, includeUndefin
4502
5061
  let key = litKey !== null
4503
5062
  ? { kind: "strLit", value: litKey, type: STRING, loc: locOf(keyNode) }
4504
5063
  : lowerer.lowerExpr(keyNode);
5064
+ // Optional string/number keys need ToPropertyKey, not a boxed copy of
5065
+ // the receiver. Keep record reads on their native storage so nested
5066
+ // references retain identity across index-signature reads.
5067
+ if (key.type.kind === "union" &&
5068
+ !lowerer.unions.get(key.type.unionId)?.arms.some((arm) => arm.kind === "symbol" || arm.kind === "dyn")) {
5069
+ key = lowerRecordPropertyKey(lowerer, key, keyNode);
5070
+ }
5071
+ if ((key.type.kind === "symbol" || key.type.kind === "dyn" || key.type.kind === "union") &&
5072
+ lowerer.dynConvertible(key.type) && lowerer.dynConvertible(obj.type)) {
5073
+ const read = { kind: "dynKeyGet", value: lowerer.coerceToExpected(obj, DYN),
5074
+ key: lowerer.coerceToExpected(key, DYN), type: DYN, loc };
5075
+ return includeUndefined ? read : lowerer.maybeNarrow(read, expr);
5076
+ }
4505
5077
  // Runtime NUMBER keys ride the JS-exact formatter (o[n] is o[String(n)]
4506
5078
  // in JS — the number-keyed-signature access path).
4507
5079
  key = lowerRecordPropertyKey(lowerer, key, keyNode);
@@ -4530,7 +5102,7 @@ export function lowerRecordKeyRead(lowerer, expr, shapeId, shape, includeUndefin
4530
5102
  let declared = null;
4531
5103
  if (shape.indexValue) {
4532
5104
  declared = shape.indexValue;
4533
- if (includeUndefined || lowerer.program.getCompilerOptions().noUncheckedIndexedAccess) {
5105
+ if (declared.kind !== "dyn" && (includeUndefined || lowerer.program.getCompilerOptions().noUncheckedIndexedAccess)) {
4534
5106
  declared = lowerer.withUndefinedArmOf(declared);
4535
5107
  if (!declared)
4536
5108
  lowerer.badType(expr, lowerer.typeOf(expr));
@@ -4540,6 +5112,8 @@ export function lowerRecordKeyRead(lowerer, expr, shapeId, shape, includeUndefin
4540
5112
  shape.fields.every((f) => typeEquals(f.type, shape.fields[0].type))) {
4541
5113
  declared = shape.fields[0].type;
4542
5114
  }
5115
+ if (!declared && isJsSourceFile(expr.getSourceFile()) && recordKeyResultOk(lowerer, shape, DYN))
5116
+ declared = DYN;
4543
5117
  if (!declared) {
4544
5118
  lowerer.unsupported("SC1090", expr, `dynamic keyed reads of '${lowerer.fmt({ kind: "record", shapeId })}' (the declared fields have no one common type)`);
4545
5119
  }
@@ -4564,6 +5138,9 @@ export function lowerRecordKeyRead(lowerer, expr, shapeId, shape, includeUndefin
4564
5138
  * values remain real undefined values, whose JavaScript property spelling
4565
5139
  * is the string "undefined". */
4566
5140
  function lowerRecordPropertyKey(lowerer, key, node) {
5141
+ if (key.type.kind === "union" && lowerer.dynConvertible(key.type)) {
5142
+ return { kind: "libCall", fn: "dyn.toStringCoerce", args: [lowerer.coerceInto(node, key, DYN)], type: STRING, loc: key.loc };
5143
+ }
4567
5144
  if (key.type.kind === "f64" || key.type.kind === "bool" || key.type.kind === "dyn" ||
4568
5145
  lowerer.runtimeOptionalWidening(key.type, STRING) !== null) {
4569
5146
  return lowerer.ensureString(key, node);
@@ -4584,6 +5161,10 @@ export function lowerAbsenceProbe(lowerer, node) {
4584
5161
  }
4585
5162
  if (ts.isPropertyAccessExpression(expr)) {
4586
5163
  const target = lowerer.fieldTarget(expr);
5164
+ if (target?.container === "class" && target.fieldType.kind === "union" &&
5165
+ lowerer.unions.get(target.fieldType.unionId)?.arms.some(isUnitType)) {
5166
+ return lowerer.fieldGetExpr(target, locOf(expr), expr);
5167
+ }
4587
5168
  if (target?.container !== "recordOvf")
4588
5169
  return null;
4589
5170
  // A JS file-scope object-literal global is record-shaped to the
@@ -4685,6 +5266,7 @@ function recordKeyTypeLiteralText(lowerer, node) {
4685
5266
  function recordKeyResultOk(lowerer, shape, type) {
4686
5267
  const surfaces = (t) => typeEquals(t, type) ||
4687
5268
  (type.kind === "union" && lowerer.armTag(type.unionId, t) >= 0) ||
5269
+ exactUnionWidening(lowerer, t, type) ||
4688
5270
  (type.kind === "dyn" && lowerer.dynConvertible(t));
4689
5271
  if (!shape.fields.every((f) => surfaces(f.type)))
4690
5272
  return false;
@@ -4696,6 +5278,13 @@ function recordKeyResultOk(lowerer, shape, type) {
4696
5278
  }
4697
5279
  return true;
4698
5280
  }
5281
+ function exactUnionWidening(lowerer, from, to) {
5282
+ if (from.kind !== "union" || to.kind !== "union")
5283
+ return false;
5284
+ const source = lowerer.unions.get(from.unionId);
5285
+ const target = lowerer.unions.get(to.unionId);
5286
+ return !!source && !!target && unionWideningTags(source.arms, target.arms) !== null;
5287
+ }
4699
5288
  /** The literal index of a tuple access, or null when the expression isn't
4700
5289
  * a non-negative integer literal (parentheses tolerated — tsc's own
4701
5290
  * literal-index typing accepts them). */
@@ -4717,11 +5306,29 @@ export function lowerElementCompound(lowerer, expr, op) {
4717
5306
  const target = expr.left;
4718
5307
  fenceNodeModuleMutation(lowerer, target, "assignment");
4719
5308
  const loc = locOf(expr);
4720
- const receiverType = lowerer.mapTypeOf(lowerer.typeOf(target.expression));
5309
+ const checked = lowerer.lowerExpr(target.expression);
5310
+ if (checked?.type.kind === "dyn" && isJsSourceFile(expr.getSourceFile())) {
5311
+ const stmts = [];
5312
+ const save = (value, name) => {
5313
+ const local = lowerer.declareHiddenLocal(name, value.type);
5314
+ stmts.push({ kind: "varDecl", localId: local.id, init: value, loc });
5315
+ return varRef(local.id, value.type, loc);
5316
+ };
5317
+ const receiver = save(checked, "%compoundReceiver");
5318
+ const key = save(lowerer.lowerExprExpecting(target.argumentExpression, DYN), "%compoundKey");
5319
+ const old = save({ kind: "dynKeyGet", value: receiver, key, type: DYN, loc }, "%compoundOld");
5320
+ const rhs = lowerer.lowerExprExpecting(expr.right, DYN);
5321
+ const numeric = ["-", "*", "/", "%", "**"].includes(op);
5322
+ const value = save({ kind: "libCall", fn: op === "+" ? "dyn.add" : numeric ? "dyn.arithmetic" : "dyn.bitwise",
5323
+ args: op === "+" ? [old, rhs] : [old, rhs, strLit(op, loc)], type: DYN, loc }, "%compoundResult");
5324
+ stmts.push({ kind: "exprStmt", expr: { kind: "libCall", fn: "dyn.keySetComputed", args: [receiver, key, value], type: VOID, loc }, loc });
5325
+ return { kind: "seqExpr", stmts, result: value, type: DYN, loc };
5326
+ }
5327
+ const receiverType = checked.type.kind === "array" || checked.type.kind === "bytes" ? checked.type : lowerer.mapTypeOf(lowerer.typeOf(target.expression));
4721
5328
  if (receiverType?.kind !== "array" && receiverType?.kind !== "bytes") {
4722
5329
  lowerer.unsupported("SC1090", target, "compound assignment to non-array elements");
4723
5330
  }
4724
- let receiver = lowerer.lowerExpr(target.expression);
5331
+ let receiver = checked;
4725
5332
  if (receiver.type.kind === "union" && lowerer.armTag(receiver.type.unionId, UNDEFINED_T) >= 0) {
4726
5333
  const present = lowerer.stripUndefinedArm(receiver.type);
4727
5334
  const helper = present.kind === receiverType.kind
@@ -4758,6 +5365,12 @@ export function lowerElementCompound(lowerer, expr, op) {
4758
5365
  else if (valueType.kind === "f64" && numericRhs.type.kind === "f64") {
4759
5366
  computed = { kind: "bin", op, left: lowerOptionalNumber(lowerer, oldRef(), loc), right: numericRhs, type: F64, loc };
4760
5367
  }
5368
+ else if (valueType.kind === "dyn" && isJsSourceFile(expr.getSourceFile())) {
5369
+ const left = lowerer.coerceToExpected(oldRef(), DYN), right = lowerer.coerceToExpected(rhs, DYN);
5370
+ const numeric = ["-", "*", "/", "%", "**"].includes(op);
5371
+ computed = { kind: "libCall", fn: op === "+" ? "dyn.add" : numeric ? "dyn.arithmetic" : "dyn.bitwise",
5372
+ args: op === "+" ? [left, right] : [left, right, strLit(op, loc)], type: DYN, loc };
5373
+ }
4761
5374
  else {
4762
5375
  lowerer.unsupported("SC1043", expr);
4763
5376
  }
@@ -4780,16 +5393,71 @@ export function lowerElementCompound(lowerer, expr, op) {
4780
5393
  loc,
4781
5394
  };
4782
5395
  }
4783
- /** `a[i] = v` in statement position → arraySet (element writes, like local
4784
- * assignment, produce no value in our subset). */
5396
+ /** Checked member assignment evaluates the reference before its RHS and
5397
+ * yields the original assigned value, including in a chained assignment. */
5398
+ function lowerDynMemberAssignment(lowerer, expr, recv) {
5399
+ const loc = locOf(expr);
5400
+ const target = expr.left;
5401
+ const receiver = lowerer.declareHiddenLocal("%setReceiver", DYN);
5402
+ const keyValue = ts.isElementAccessExpression(target)
5403
+ ? lowerer.lowerExprExpecting(target.argumentExpression, DYN)
5404
+ : lowerer.coerceToExpected({ kind: "strLit", value: target.name.text, type: STRING, loc }, DYN);
5405
+ const key = lowerer.declareHiddenLocal("%setKey", DYN);
5406
+ const raw = lowerer.lowerExpr(expr.right);
5407
+ const value = raw.type.kind === "void"
5408
+ ? {
5409
+ kind: "seqExpr", stmts: [{ kind: "exprStmt", expr: raw, loc }],
5410
+ result: dynUndefinedExpr(loc), type: DYN, loc,
5411
+ }
5412
+ : isUnitType(raw.type) ? lowerer.coerceInto(expr.right, raw, DYN) : raw;
5413
+ const assigned = lowerer.declareHiddenLocal("%setValue", value.type);
5414
+ const result = varRef(assigned.id, value.type, loc);
5415
+ const stored = lowerer.coerceInto(expr.right, result, DYN);
5416
+ // Evaluate the reference before the RHS, but defer key coercion until
5417
+ // PutValue. Yield the RHS without rereading a setter-backed property.
5418
+ return {
5419
+ kind: "seqExpr", stmts: [
5420
+ { kind: "varDecl", localId: receiver.id, init: recv, loc },
5421
+ { kind: "varDecl", localId: key.id, init: keyValue, loc },
5422
+ { kind: "varDecl", localId: assigned.id, init: value, loc },
5423
+ { kind: "exprStmt", expr: { kind: "libCall", fn: "dyn.keySetComputed", args: [varRef(receiver.id, DYN, loc), varRef(key.id, DYN, loc), stored], type: VOID, loc }, loc },
5424
+ ], result, type: result.type, loc,
5425
+ };
5426
+ }
5427
+ /** JavaScript callable members live on the closure, so aliases and chained
5428
+ * writes share the same storage as Object.defineProperties. */
5429
+ export function lowerNativeFunctionAssignment(lowerer, expr) {
5430
+ const target = expr.left;
5431
+ if (!isJsSourceFile(expr.getSourceFile()) ||
5432
+ (!ts.isPropertyAccessExpression(target) && !ts.isElementAccessExpression(target)) ||
5433
+ target.questionDotToken)
5434
+ return null;
5435
+ const receiver = tryLowerExpression(lowerer, target.expression);
5436
+ if (receiver?.type.kind !== "func" ||
5437
+ !canBoxFuncIntoDyn(receiver.type, (id) => lowerer.shapes.get(id), (id) => lowerer.unions.get(id)))
5438
+ return null;
5439
+ fenceNodeModuleMutation(lowerer, target, "assignment");
5440
+ const fnName = jsFuncNameOf(target.expression);
5441
+ const boxed = {
5442
+ kind: "dynFrom", value: receiver, type: DYN, loc: locOf(target.expression),
5443
+ ...(fnName !== null ? { fnName } : {}),
5444
+ };
5445
+ return lowerDynMemberAssignment(lowerer, expr, boxed);
5446
+ }
4785
5447
  export function lowerElementWrite(lowerer, expr) {
4786
5448
  const target = expr.left;
4787
5449
  fenceNodeModuleMutation(lowerer, target, "assignment");
5450
+ const globalKey = globalSymbolKey(lowerer, target.expression, target.argumentExpression);
5451
+ if (globalKey) {
5452
+ const value = lowerer.lowerExprExpecting(expr.right, DYN);
5453
+ const loc = locOf(expr);
5454
+ return { kind: "exprStmt", expr: { kind: "libCall", fn: "dyn.globalSymbolSet", args: [globalKey, value], type: VOID, loc }, loc };
5455
+ }
4788
5456
  // `process.env[key] = v` — the computed twin of the dotted env write:
4789
5457
  // setenv(3), string keys and string values only.
4790
5458
  if (lowerer.isProcessEnv(target.expression) && !target.questionDotToken) {
4791
5459
  const loc = locOf(expr);
4792
- const key = lowerer.lowerExpr(target.argumentExpression);
5460
+ const key = lowerEnvironmentKey(lowerer, target.argumentExpression);
4793
5461
  if (key.type.kind !== "string") {
4794
5462
  lowerer.unsupported("SC1090", target.argumentExpression, "indexing process.env with non-string keys");
4795
5463
  }
@@ -4813,9 +5481,32 @@ export function lowerElementWrite(lowerer, expr) {
4813
5481
  const value = lowerer.lowerExprExpecting(expr.right, fieldT.fieldType);
4814
5482
  return lowerer.fieldSetStmt(fieldT, value, locOf(expr), target);
4815
5483
  }
4816
- 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)");
5484
+ const receiver = lowerer.lowerExpr(target.expression);
5485
+ if (receiver.type.kind === "dyn")
5486
+ return { kind: "exprStmt", expr: lowerDynMemberAssignment(lowerer, expr, receiver), loc: locOf(expr) };
5487
+ if (isJsSourceFile(expr.getSourceFile()) && receiver.type.kind === "object" && lowerer.dynConvertible(receiver.type)) {
5488
+ return { kind: "exprStmt", expr: lowerDynMemberAssignment(lowerer, expr, lowerer.coerceToExpected(receiver, DYN)), loc: locOf(expr) };
5489
+ }
5490
+ 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)");
5491
+ }
5492
+ const checkedReceiver = tryLowerExpression(lowerer, target.expression);
5493
+ if (checkedReceiver?.type.kind === "dyn") {
5494
+ return { kind: "exprStmt", expr: lowerDynMemberAssignment(lowerer, expr, checkedReceiver), loc: locOf(expr) };
5495
+ }
5496
+ if (checkedReceiver?.type.kind === "object" && isJsSourceFile(expr.getSourceFile())) {
5497
+ const info = lowerer.classes.get(checkedReceiver.type.className);
5498
+ const key = recordKeyLiteralText(target.argumentExpression) ?? recordKeyTypeLiteralText(lowerer, target.argumentExpression);
5499
+ if (info && key !== null && isClassCallback(lowerer, info, key)) {
5500
+ const receiver = lowerer.coerceInto(target.expression, checkedReceiver, DYN);
5501
+ return { kind: "exprStmt", expr: lowerDynMemberAssignment(lowerer, expr, receiver), loc: locOf(expr) };
5502
+ }
5503
+ }
5504
+ let receiverIr = lowerer.mapTypeOf(lowerer.typeOf(target.expression));
5505
+ if (receiverIr === null || receiverIr.kind === "dyn") {
5506
+ const receiver = tryLowerExpression(lowerer, target.expression);
5507
+ if (receiver?.type.kind === "array")
5508
+ receiverIr = receiver.type;
4817
5509
  }
4818
- const receiverIr = lowerer.mapTypeOf(lowerer.typeOf(target.expression));
4819
5510
  if (receiverIr?.kind === "jsval") {
4820
5511
  const obj = lowerer.lowerExpr(target.expression);
4821
5512
  // Dispatch follows the RUNTIME world (383(d)): a checker-'any'
@@ -4857,9 +5548,7 @@ export function lowerElementWrite(lowerer, expr) {
4857
5548
  const obj = lowerer.lowerExpr(target.expression);
4858
5549
  if (obj.type.kind === "dyn") {
4859
5550
  const loc = locOf(expr);
4860
- let key = lowerer.lowerExpr(target.argumentExpression);
4861
- if (key.type.kind === "f64")
4862
- key = lowerer.ensureString(key, target.argumentExpression);
5551
+ const key = lowerRecordPropertyKey(lowerer, lowerer.lowerExpr(target.argumentExpression), target.argumentExpression);
4863
5552
  if (key.type.kind !== "string") {
4864
5553
  lowerer.unsupported("SC1090", target.argumentExpression, "indexing with non-string or non-number keys");
4865
5554
  }
@@ -4870,9 +5559,25 @@ export function lowerElementWrite(lowerer, expr) {
4870
5559
  return { kind: "exprStmt", expr: { kind: "libCall", fn: "dyn.keySet", args: [obj, key, value], type: VOID, loc }, loc };
4871
5560
  }
4872
5561
  }
5562
+ if (receiverIr?.kind === "object" || receiverIr?.kind === "record") {
5563
+ const key = recordKeyLiteralText(target.argumentExpression) ?? recordKeyTypeLiteralText(lowerer, target.argumentExpression);
5564
+ if (key !== null) {
5565
+ const receiver = lowerer.lowerExpr(target.expression);
5566
+ const field = representedClassFieldTarget(lowerer, target.expression, key, receiver);
5567
+ if (field) {
5568
+ const value = lowerer.lowerExprExpecting(expr.right, field.fieldType);
5569
+ return lowerer.fieldSetStmt(field, value, locOf(expr), target);
5570
+ }
5571
+ if (hasClassPayload(lowerer, receiver.type)) {
5572
+ if (receiver.type.kind === "object" && receiver.type.className === "%Error") {
5573
+ return { kind: "exprStmt", expr: lowerDynMemberAssignment(lowerer, expr, lowerer.coerceInto(target.expression, receiver, DYN)), loc: locOf(expr) };
5574
+ }
5575
+ lowerer.unsupported("SC1090", target, "writing undeclared properties through structural views of class instances");
5576
+ }
5577
+ }
5578
+ }
4873
5579
  // Typed-array element write `b[i] = v` — bytesSet: the value is an f64
4874
- // the runtime coerces JS-exactly; invalid indices trap (no appends —
4875
- // typed arrays are fixed-length; JS would ignore the write).
5580
+ // the runtime coerces JS-exactly; invalid indices are ignored.
4876
5581
  if (receiverIr?.kind === "bytes") {
4877
5582
  const recv = lowerer.lowerExpr(target.expression);
4878
5583
  // The write twin: a typed-array .d.ts surface whose VALUE is a
@@ -4884,6 +5589,9 @@ export function lowerElementWrite(lowerer, expr) {
4884
5589
  return { kind: "exprStmt", expr: { kind: "jsOp", op: "setIdx", args: [recv, key, value], type: VOID, loc }, loc };
4885
5590
  }
4886
5591
  const index = lowerer.lowerExpr(target.argumentExpression);
5592
+ if (index.type.kind === "dyn" && recv.type.kind === "bytes") {
5593
+ return { kind: "exprStmt", expr: lowerDynMemberAssignment(lowerer, expr, lowerer.coerceInto(target.expression, recv, DYN)), loc: locOf(expr) };
5594
+ }
4887
5595
  if (index.type.kind !== "f64") {
4888
5596
  lowerer.unsupported("SC1090", target.argumentExpression, "indexing with non-number keys");
4889
5597
  }
@@ -4899,6 +5607,9 @@ export function lowerElementWrite(lowerer, expr) {
4899
5607
  // a recordSet/recordKeySet over a jsval.
4900
5608
  if (shape) {
4901
5609
  const obj = lowerer.lowerExpr(target.expression);
5610
+ if (hasClassPayload(lowerer, obj.type)) {
5611
+ lowerer.unsupported("SC1090", target, "computed property writes through structural views of class instances (use a declared literal key)");
5612
+ }
4902
5613
  if (obj.type.kind === "jsval") {
4903
5614
  const key = lowerer.jsvalIn(lowerer.lowerExpr(target.argumentExpression), target.argumentExpression);
4904
5615
  const value = lowerer.jsvalIn(lowerer.lowerExpr(expr.right), expr.right);
@@ -5050,9 +5761,9 @@ export function lowerElementWrite(lowerer, expr) {
5050
5761
  // type (static `any` — mapTypeOf answers null without --dynamic)
5051
5762
  // probes the receiver's own lowered world: a dyn value takes the same
5052
5763
  // write, anything else falls through to the fences.
5053
- if (receiverIr?.kind === "dyn" || receiverIr === null) {
5764
+ if (receiverIr?.kind === "dyn" || receiverIr?.kind === "object" || receiverIr === null) {
5054
5765
  const obj = receiverIr !== null ? lowerer.lowerExpr(target.expression) : tryLowerExpression(lowerer, target.expression);
5055
- if (obj !== null && obj.type.kind === "dyn") {
5766
+ if (obj !== null && (obj.type.kind === "dyn" || obj.type.kind === "object" && obj.type.className === "%Error" || isDynTypedRefType(obj.type))) {
5056
5767
  const loc = locOf(expr);
5057
5768
  const litKey = recordKeyLiteralText(target.argumentExpression);
5058
5769
  let key = litKey !== null
@@ -5066,7 +5777,7 @@ export function lowerElementWrite(lowerer, expr) {
5066
5777
  if (value.type.kind !== "dyn") {
5067
5778
  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)`);
5068
5779
  }
5069
- return { kind: "exprStmt", expr: { kind: "libCall", fn: "dyn.keySet", args: [obj, key, value], type: VOID, loc }, loc };
5780
+ return { kind: "exprStmt", expr: { kind: "libCall", fn: "dyn.keySet", args: [lowerer.coerceToExpected(obj, DYN), key, value], type: VOID, loc }, loc };
5070
5781
  }
5071
5782
  }
5072
5783
  if (receiverIr?.kind !== "array") {
@@ -5096,19 +5807,45 @@ export function lowerElementWrite(lowerer, expr) {
5096
5807
  if (arr.type.kind !== "array") {
5097
5808
  lowerer.unsupported("SC1090", target.expression, "assignment through a possibly missing nested array receiver");
5098
5809
  }
5099
- const index = lowerer.lowerExpr(target.argumentExpression);
5810
+ const index = lowerOptionalNumber(lowerer, lowerer.lowerExpr(target.argumentExpression), locOf(target.argumentExpression), target.argumentExpression);
5811
+ if (index.type.kind === "dyn" && lowerer.dynConvertible(arr.type)) {
5812
+ return { kind: "exprStmt", expr: lowerDynMemberAssignment(lowerer, expr, lowerer.coerceInto(target.expression, arr, DYN)), loc: locOf(expr) };
5813
+ }
5100
5814
  if (index.type.kind !== "f64") {
5101
5815
  lowerer.unsupported("SC1090", target.argumentExpression, "indexing with non-number keys");
5102
5816
  }
5103
5817
  // Optional array reads carry the source array's payload type plus an
5104
5818
  // undefined arm. arrayValueStore turns that arm into the runtime's
5105
5819
  // UNDEFINED state while keeping number[]/string[] payload storage scalar.
5106
- const value = lowerer.lowerExpr(expr.right);
5107
- return arrayValueStore(lowerer, arr, index, value, receiverIr.elem, locOf(expr));
5820
+ let literal = expr.right;
5821
+ while (ts.isParenthesizedExpression(literal) || ts.isSatisfiesExpression(literal))
5822
+ literal = literal.expression;
5823
+ const value = ts.isObjectLiteralExpression(literal) || ts.isArrayLiteralExpression(literal)
5824
+ ? lowerer.lowerExprExpecting(literal, arr.type.elem) : lowerer.lowerExpr(expr.right);
5825
+ // Validate the payload before emitting a native store. A dormant JS
5826
+ // method can contradict its JSDoc element type; retain its call-time
5827
+ // refusal instead of leaving invalid IR in a newly reachable method.
5828
+ const expected = value.type.kind === "undefinedT" ||
5829
+ value.type.kind === "union" && lowerer.armTag(value.type.unionId, UNDEFINED_T) >= 0
5830
+ ? arrayValueType(lowerer, arr.type.elem) : arr.type.elem;
5831
+ return arrayValueStore(lowerer, arr, index, lowerer.coerceInto(expr.right, value, expected), arr.type.elem, locOf(expr));
5832
+ }
5833
+ /** Environment property names use ToPrimitive with the string hint, so
5834
+ * checked objects must execute their own conversion hooks. */
5835
+ export function lowerEnvironmentKey(lowerer, node) {
5836
+ const key = lowerer.lowerExpr(node);
5837
+ if (key.type.kind === "dyn")
5838
+ return { kind: "libCall", fn: "dyn.toStringCoerce", args: [key], type: STRING, loc: key.loc };
5839
+ return lowerRecordPropertyKey(lowerer, key, node);
5108
5840
  }
5109
5841
  export function ensureString(lowerer, e, node) {
5110
5842
  if (e.type.kind === "string")
5111
5843
  return e;
5844
+ if (e.type.kind === "url")
5845
+ return { kind: "libCall", fn: "url.href", args: [e], type: STRING, loc: e.loc };
5846
+ if (e.type.kind === "regex") {
5847
+ return { kind: "regexIntrinsic", method: "toString", receiver: e, args: [], type: STRING, loc: e.loc };
5848
+ }
5112
5849
  if (e.type.kind === "bigint") {
5113
5850
  return {
5114
5851
  kind: "libCall",
@@ -5124,18 +5861,21 @@ export function ensureString(lowerer, e, node) {
5124
5861
  // strings verbatim, arrays via join, objects as "[object Object]").
5125
5862
  return { kind: "toString", operand: e, type: STRING, loc: e.loc };
5126
5863
  }
5864
+ if (e.type.kind === "bytes") {
5865
+ // Preserve the runtime brand: typed arrays join numeric elements,
5866
+ // while a Buffer stored in a Uint8Array slot decodes its bytes.
5867
+ const boxed = { kind: "dynFrom", value: e, type: DYN, loc: e.loc };
5868
+ return { kind: "toString", operand: boxed, type: STRING, loc: e.loc };
5869
+ }
5127
5870
  if (e.type.kind === "jsval") {
5128
5871
  // String(v) in the engine — JS-exact (and Node-exact in templates).
5129
5872
  return { kind: "jsOp", op: "toStr", args: [e], type: STRING, loc: e.loc };
5130
5873
  }
5131
5874
  if (e.type.kind === "object") {
5132
- // String(err) / `${err}` over the Error hierarchy: Error.prototype
5133
- // .toString — the ONE runtime implementation (overriding it is
5134
- // fenced), "name: message" with the empty-side elisions, exactly
5135
- // Node's String(err), which carries no stack either.
5875
+ // String(err) / `${err}` uses the same dispatch as err.toString().
5136
5876
  for (let c = lowerer.classes.get(e.type.className) ?? null; c; c = c.base) {
5137
5877
  if (c.builtinError) {
5138
- return { kind: "libCall", fn: "error.toString", args: [lowerer.upcastTo(e, "%Error")], type: STRING, loc: e.loc };
5878
+ return errorToStringCall(lowerer, e);
5139
5879
  }
5140
5880
  }
5141
5881
  }
@@ -5326,6 +6066,38 @@ export function lowerAsExpression(lowerer, expr) {
5326
6066
  return lowerer.jsvalIn(inner, expr.expression);
5327
6067
  }
5328
6068
  const target = lowerer.mapTypeOf(targetTs0);
6069
+ // Unknown-only record views use the checked-dynamic representation.
6070
+ // A native record asserted to such a view still denotes the original
6071
+ // object. Box a live reference instead of erasing the assertion and
6072
+ // handing native storage to a checked-dynamic property operation.
6073
+ if (target?.kind === "dyn" && (targetTs0.flags & ts.TypeFlags.Object) !== 0) {
6074
+ const sourceShape = inner.type.kind === "record" ? lowerer.shapes.get(inner.type.shapeId) : undefined;
6075
+ if (hasClassPayload(lowerer, inner.type) || sourceShape && shapeHasAccessorSlots(sourceShape)) {
6076
+ lowerer.unsupported("SC1090", expr, "opaque structural views of classes or accessor records (name the property's concrete type to retain native accessor dispatch)");
6077
+ }
6078
+ const boxed = lowerer.coerceInto(expr, inner, DYN);
6079
+ return boxed.kind === "dynFrom" ? { ...boxed, liveRef: true } : boxed;
6080
+ }
6081
+ // Collection assertions are interface views only: their backing
6082
+ // value must already have the same native key/element/value layout.
6083
+ // Erasing a mock or a different instantiation here would let member
6084
+ // lowering interpret a record (or another ABI) as a native handle.
6085
+ if (target?.kind === "map" || target?.kind === "set") {
6086
+ return lowerer.coerceInto(expr, inner, target);
6087
+ }
6088
+ // A class-derived interface still names the class's native layout.
6089
+ // An assertion must not let a structural mock acquire that layout;
6090
+ // unions retain their checked extraction below, and real instances
6091
+ // keep the ordinary class narrowing path.
6092
+ if (target?.kind === "object" && inner.type.kind !== "object" && inner.type.kind !== "union") {
6093
+ return lowerer.coerceInto(expr, inner, target);
6094
+ }
6095
+ if (target?.kind === "object" && inner.type.kind === "object" &&
6096
+ !typeEquals(target, inner.type) &&
6097
+ !lowerer.isSubclassOf(inner.type.className, target.className) &&
6098
+ !lowerer.isSubclassOf(target.className, inner.type.className)) {
6099
+ return lowerer.coerceInto(expr, inner, target);
6100
+ }
5329
6101
  // Static assertions normally erase, but record layouts are
5330
6102
  // monomorphic: a consumer selected from the asserted shape must see
5331
6103
  // that shape physically. Reuse the ordinary slot coercion so plain
@@ -5373,12 +6145,26 @@ export function lowerAsExpression(lowerer, expr) {
5373
6145
  return { kind: "jsExit", value: inner, type: target, loc: locOf(expr) };
5374
6146
  }
5375
6147
  const targetTs = lowerer.checker.getTypeFromTypeNode(expr.type);
5376
- const target = lowerer.mapTypeOf(targetTs);
6148
+ const target = lowerer.mapTypeOf(targetTs) ?? dynFallbackType(lowerer, expr.type, targetTs);
5377
6149
  if (!target)
5378
6150
  lowerer.badType(expr.type, targetTs);
5379
6151
  if (target.kind === "dyn")
5380
6152
  return inner; // `as unknown`: erasure
5381
- if (target.kind === "void" || !lowerer.jsonSafe(target)) {
6153
+ // An all-`unknown`-fields record target (`err as { code?: unknown }`
6154
+ // — the errno-probing idiom): there is nothing to validate (every
6155
+ // field is unknown, exactly what the dyn value already answers) and
6156
+ // nothing to build — the cast is pure typing, so it ERASES and the
6157
+ // reads ride the dyn keyed read.
6158
+ if (target.kind === "record") {
6159
+ const shape = lowerer.shapes.get(target.shapeId);
6160
+ if (shape &&
6161
+ !shape.tuple &&
6162
+ shape.fields.every((f) => f.type.kind === "dyn") &&
6163
+ (!shape.indexValue || shape.indexValue.kind === "dyn")) {
6164
+ return inner;
6165
+ }
6166
+ }
6167
+ if (target.kind === "void" || !canDynCheckTo(target, (id) => lowerer.shapes.get(id), (id) => lowerer.unions.get(id))) {
5382
6168
  // Bare undefined-armed targets pass when every OTHER arm is
5383
6169
  // JSON-safe: the checked-dynamic tree holds a first-class undefined value now
5384
6170
  // (index-signature overflow reads produce it for missing keys), and
@@ -5414,20 +6200,6 @@ export function lowerAsExpression(lowerer, expr) {
5414
6200
  if (DYN_HANDLE_KINDS.has(target.kind)) {
5415
6201
  return { kind: "dynCheck", value: inner, type: target, loc: locOf(expr) };
5416
6202
  }
5417
- // An all-`unknown`-fields record target (`err as { code?: unknown }`
5418
- // — the errno-probing idiom): there is nothing to validate (every
5419
- // field is unknown, exactly what the dyn value already answers) and
5420
- // nothing to build — the cast is pure typing, so it ERASES and the
5421
- // reads ride the dyn keyed read.
5422
- if (target.kind === "record") {
5423
- const shape = lowerer.shapes.get(target.shapeId);
5424
- if (shape &&
5425
- !shape.tuple &&
5426
- shape.fields.every((f) => f.type.kind === "dyn") &&
5427
- (!shape.indexValue || shape.indexValue.kind === "dyn")) {
5428
- return inner;
5429
- }
5430
- }
5431
6203
  lowerer.unsupported("SC1090", expr, `a checked cast of 'unknown' to '${target.kind === "void" ? "void" : lowerer.fmt(target)}' ` +
5432
6204
  `(a dynamic value can only be validated against JSON-representable types: ` +
5433
6205
  `number, string, boolean, records, arrays, and unions of those)`);
@@ -5445,7 +6217,10 @@ export function lowerPrefixUnary(lowerer, expr) {
5445
6217
  if (raw.type.kind === "bigint") {
5446
6218
  return { kind: "libCall", fn: "bigint.neg", args: [raw], type: BIGINT_T, loc };
5447
6219
  }
5448
- const operand = lowerOptionalNumber(lowerer, raw, loc, expr.operand);
6220
+ // Match the checked numeric boundary used by JS binary arithmetic.
6221
+ const operand = raw.type.kind === "dyn" && isJsSourceFile(expr.getSourceFile())
6222
+ ? { kind: "dynCheck", value: raw, type: F64, loc }
6223
+ : lowerOptionalNumber(lowerer, raw, loc, expr.operand);
5449
6224
  if (operand.type.kind !== "f64")
5450
6225
  lowerer.unsupported("SC1043", expr);
5451
6226
  if (operand.kind === "numLit")
@@ -5454,6 +6229,9 @@ export function lowerPrefixUnary(lowerer, expr) {
5454
6229
  }
5455
6230
  case ts.SyntaxKind.PlusToken: {
5456
6231
  const raw = lowerer.lowerExpr(expr.operand);
6232
+ if (raw.type.kind === "dyn") {
6233
+ return { kind: "libCall", fn: "dyn.toNumberCoerce", args: [raw], type: F64, loc };
6234
+ }
5457
6235
  // Unary + is ToNumber; on an already-number operand it's identity,
5458
6236
  // and a STRING operand runs the runtime's ECMA-exact StringToNumber
5459
6237
  // (num.fromString — Number(aString)'s lowering, scr_string.c).
@@ -5479,14 +6257,16 @@ export function lowerPrefixUnary(lowerer, expr) {
5479
6257
  if (raw.type.kind === "bigint") {
5480
6258
  return { kind: "libCall", fn: "bigint.not", args: [raw], type: BIGINT_T, loc };
5481
6259
  }
6260
+ if (raw.type.kind === "dyn")
6261
+ return {
6262
+ kind: "libCall", fn: "dyn.bitwise", args: [raw, dynUndefinedExpr(loc), { kind: "strLit", value: "~", type: STRING, loc }], type: DYN, loc,
6263
+ };
5482
6264
  // A checked-dynamic value retains enough JavaScript structure to
5483
6265
  // run exact ToNumber, including the object valueOf/toString
5484
6266
  // protocol. This is the static `any`/JS-lane answer for idioms
5485
6267
  // such as `while (~index)`, where inference may have widened an
5486
6268
  // otherwise numeric slot to the checked-dynamic tree.
5487
- const operand = raw.type.kind === "dyn"
5488
- ? { kind: "libCall", fn: "dyn.toNumberCoerce", args: [raw], type: F64, loc }
5489
- : lowerOptionalNumber(lowerer, raw, loc, expr.operand);
6269
+ const operand = lowerOptionalNumber(lowerer, raw, loc, expr.operand);
5490
6270
  if (operand.type.kind !== "f64")
5491
6271
  lowerer.unsupported("SC1043", expr);
5492
6272
  return { kind: "unary", op: "~", operand, type: F64, loc };
@@ -5496,7 +6276,7 @@ export function lowerPrefixUnary(lowerer, expr) {
5496
6276
  // `++x` / `--x` in expression position: yields the NEW value.
5497
6277
  return lowerIncDec(lowerer, expr, true);
5498
6278
  }
5499
- lowerer.unsupported("SC1090", expr, `syntax '${ts.SyntaxKind[expr.kind]}'`);
6279
+ lowerer.unsupported("SC1090", expr, `syntax '${ts.syntaxKindName(expr.kind)}'`);
5500
6280
  }
5501
6281
  /** `x++`/`x--`/`++x`/`--x` in EXPRESSION position — the incDec node:
5502
6282
  * read, write ±1, yield old (postfix) or new (prefix) value. Receivers
@@ -5508,6 +6288,14 @@ function lowerIncDec(lowerer, expr, prefix) {
5508
6288
  const loc = locOf(expr);
5509
6289
  const op = expr.operator === ts.SyntaxKind.PlusPlusToken ? "+" : "-";
5510
6290
  if (!ts.isIdentifier(expr.operand)) {
6291
+ if (ts.isPropertyAccessExpression(expr.operand)) {
6292
+ const target = staticFieldWriteTarget(lowerer, expr.operand);
6293
+ if (target) {
6294
+ if (target.type.kind !== "f64")
6295
+ lowerer.unsupported("SC1043", expr);
6296
+ return { kind: "incDec", op, prefix, localId: target.id, type: F64, loc };
6297
+ }
6298
+ }
5511
6299
  // CLASS-FIELD receivers (`if (--this.limit === 0)` — countdown.js's
5512
6300
  // dec(); `--this[kLimit]` — its symbol-keyed spelling): a single-
5513
6301
  // evaluation read-modify-write over the instance, yielding old/new
@@ -5519,6 +6307,25 @@ function lowerIncDec(lowerer, expr, prefix) {
5519
6307
  // accessor-backed names keep the fence.
5520
6308
  if ((ts.isPropertyAccessExpression(expr.operand) || ts.isElementAccessExpression(expr.operand)) &&
5521
6309
  !expr.operand.questionDotToken) {
6310
+ if (ts.isPropertyAccessExpression(expr.operand) && isJsSourceFile(expr.getSourceFile())) {
6311
+ const receiver = lowerer.lowerExpr(expr.operand.expression);
6312
+ if (receiver.type.kind === "dyn") {
6313
+ const local = lowerer.declareHiddenLocal("%incrementReceiver", DYN);
6314
+ const old = lowerer.declareHiddenLocal("%incrementOld", DYN);
6315
+ const next = lowerer.declareHiddenLocal("%incrementNext", DYN);
6316
+ const value = varRef(local.id, DYN, loc);
6317
+ const key = { kind: "strLit", value: expr.operand.name.text, type: STRING, loc };
6318
+ return { kind: "seqExpr", stmts: [
6319
+ { kind: "varDecl", localId: local.id, init: receiver, loc },
6320
+ { kind: "varDecl", localId: old.id, init: { kind: "libCall", fn: "dyn.toNumeric", args: [
6321
+ { kind: "dynKeyGet", value, key, type: DYN, loc },
6322
+ ], type: DYN, loc }, loc },
6323
+ { kind: "varDecl", localId: next.id, init: { kind: "libCall", fn: "dyn.increment", args: [varRef(old.id, DYN, loc),
6324
+ { kind: "boolLit", value: op === "+", type: BOOL, loc }], type: DYN, loc }, loc },
6325
+ { kind: "exprStmt", expr: { kind: "libCall", fn: "dyn.keySet", args: [value, key, varRef(next.id, DYN, loc)], type: VOID, loc }, loc },
6326
+ ], result: varRef(prefix ? next.id : old.id, DYN, loc), type: DYN, loc };
6327
+ }
6328
+ }
5522
6329
  const target = ts.isPropertyAccessExpression(expr.operand)
5523
6330
  ? lowerer.fieldTarget(expr.operand)
5524
6331
  : symbolFieldTarget(lowerer, expr.operand);
@@ -5548,6 +6355,16 @@ function lowerIncDec(lowerer, expr, prefix) {
5548
6355
  if (!target) {
5549
6356
  lowerer.rejectUnresolved(expr.operand, `increment/decrement of '${expr.operand.text}' (not a writable local or module global)`);
5550
6357
  }
6358
+ if (target.type.kind === "dyn") {
6359
+ const old = lowerer.declareHiddenLocal("%numericOld", DYN);
6360
+ const next = lowerer.declareHiddenLocal("%numericNext", DYN);
6361
+ const read = lowerer.lowerExpr(expr.operand);
6362
+ return { kind: "seqExpr", stmts: [
6363
+ { kind: "varDecl", localId: old.id, init: { kind: "libCall", fn: "dyn.toNumeric", args: [read], type: DYN, loc }, loc },
6364
+ { 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 },
6365
+ { kind: "assign", localId: target.id, value: varRef(next.id, DYN, loc), loc },
6366
+ ], result: varRef(prefix ? next.id : old.id, DYN, loc), type: DYN, loc };
6367
+ }
5551
6368
  if (target.type.kind !== "f64")
5552
6369
  lowerer.unsupported("SC1043", expr);
5553
6370
  return { kind: "incDec", op, prefix, localId: target.id, type: F64, loc };
@@ -5599,7 +6416,7 @@ function lowerAnyBinaryInIsland(lowerer, expr, left, right, loc) {
5599
6416
  };
5600
6417
  const jop = JS_BIN[op];
5601
6418
  if (jop === undefined) {
5602
- lowerer.unsupported("SC1090", expr, `operator '${ts.tokenToString(op) ?? ts.SyntaxKind[op]}' on 'any' values`);
6419
+ lowerer.unsupported("SC1090", expr, `operator '${ts.tokenToString(op) ?? ts.syntaxKindName(op)}' on 'any' values`);
5603
6420
  }
5604
6421
  const type = jsOpResultKind(jop) === "bool" ? BOOL : JSVAL;
5605
6422
  return {
@@ -5608,6 +6425,40 @@ function lowerAnyBinaryInIsland(lowerer, expr, left, right, loc) {
5608
6425
  type, loc,
5609
6426
  };
5610
6427
  }
6428
+ export function lowerCompoundValueToTarget(lowerer, expr, compound, target) {
6429
+ const loc = locOf(expr);
6430
+ const read = { kind: "varRef", localId: target.id, type: target.type, loc: locOf(expr.left) };
6431
+ const rhs = lowerer.lowerExpr(expr.right);
6432
+ const numericRhs = lowerOptionalNumber(lowerer, rhs, loc);
6433
+ if (target.type.kind === "jsval" || rhs.type.kind === "jsval") {
6434
+ const JS_COMPOUND = { "+": "add", "-": "sub", "*": "mul", "/": "div", "%": "mod", "**": "pow" };
6435
+ const jop = JS_COMPOUND[compound];
6436
+ if (jop === undefined)
6437
+ lowerer.unsupported("SC1043", expr);
6438
+ const wrapped = {
6439
+ kind: "jsOp", op: jop,
6440
+ args: [lowerer.jsvalIn(read, expr.left), lowerer.jsvalIn(rhs, expr.right)],
6441
+ type: JSVAL, loc,
6442
+ };
6443
+ return lowerer.coerceInto(expr, wrapped, target.type);
6444
+ }
6445
+ if (compound === "+" && target.type.kind === "string") {
6446
+ return { kind: "strConcat", left: read, right: lowerer.ensureString(rhs, expr.right), type: STRING, loc };
6447
+ }
6448
+ if (target.type.kind === "f64" && numericRhs.type.kind === "f64") {
6449
+ return { kind: "bin", op: compound, left: read, right: numericRhs, type: F64, loc };
6450
+ }
6451
+ if ((target.type.kind === "dyn" || rhs.type.kind === "dyn") &&
6452
+ isJsSourceFile(expr.getSourceFile())) {
6453
+ const left = lowerer.coerceInto(expr.left, read, DYN);
6454
+ const right = lowerer.coerceInto(expr.right, rhs, DYN);
6455
+ const numeric = ["-", "*", "/", "%", "**"].includes(compound);
6456
+ const computed = { kind: "libCall", fn: compound === "+" ? "dyn.add" : numeric ? "dyn.arithmetic" : "dyn.bitwise",
6457
+ args: compound === "+" ? [left, right] : [left, right, { kind: "strLit", value: compound, type: STRING, loc }], type: DYN, loc };
6458
+ return lowerer.coerceInto(expr, computed, target.type);
6459
+ }
6460
+ lowerer.unsupported("SC1043", expr);
6461
+ }
5611
6462
  export function lowerBinary(lowerer, expr) {
5612
6463
  const loc = locOf(expr);
5613
6464
  const op = expr.operatorToken.kind;
@@ -5615,6 +6466,15 @@ export function lowerBinary(lowerer, expr) {
5615
6466
  if (cacheHas)
5616
6467
  return cacheHas;
5617
6468
  if (op === ts.SyntaxKind.EqualsToken || (op >= ts.SyntaxKind.FirstCompoundAssignment && op <= ts.SyntaxKind.LastCompoundAssignment)) {
6469
+ if (op === ts.SyntaxKind.EqualsToken) {
6470
+ const callableWrite = lowerNativeFunctionAssignment(lowerer, expr);
6471
+ if (callableWrite)
6472
+ return callableWrite;
6473
+ }
6474
+ if (op === ts.SyntaxKind.QuestionQuestionEqualsToken ||
6475
+ op === ts.SyntaxKind.AmpersandAmpersandEqualsToken || op === ts.SyntaxKind.BarBarEqualsToken) {
6476
+ return lowerShortCircuitAssignment(lowerer, expr);
6477
+ }
5618
6478
  if (ts.isPropertyAccessExpression(expr.left) || ts.isElementAccessExpression(expr.left)) {
5619
6479
  fenceNodeModuleMutation(lowerer, expr.left, "assignment");
5620
6480
  }
@@ -5640,8 +6500,29 @@ export function lowerBinary(lowerer, expr) {
5640
6500
  }
5641
6501
  const indexedCompound = COMPOUND_ASSIGN_OPS[op];
5642
6502
  if (indexedCompound !== undefined && ts.isElementAccessExpression(expr.left)) {
6503
+ if (symbolFieldInfo(lowerer, expr.left))
6504
+ return lowerFieldCompoundValue(lowerer, expr.left, indexedCompound, expr.right, loc);
5643
6505
  return lowerElementCompound(lowerer, expr, indexedCompound);
5644
6506
  }
6507
+ if (indexedCompound !== undefined && ts.isIdentifier(expr.left)) {
6508
+ const target = lowerer.resolveWritable(expr.left);
6509
+ if (!target) {
6510
+ lowerer.rejectUnresolved(expr.left, "assignment to an unresolved variable");
6511
+ }
6512
+ const value = lowerCompoundValueToTarget(lowerer, expr, indexedCompound, target);
6513
+ return { kind: "assignExpr", localId: target.id, value, type: target.type, loc };
6514
+ }
6515
+ if (indexedCompound !== undefined &&
6516
+ (ts.isPropertyAccessExpression(expr.left) || ts.isElementAccessExpression(expr.left)) &&
6517
+ !(ts.isPropertyAccessExpression(expr.left) && expr.left.questionDotToken)) {
6518
+ const target = expandoWritableTarget(lowerer, expr.left) ??
6519
+ (ts.isPropertyAccessExpression(expr.left) ? nsWritableTarget(lowerer, expr.left) : null);
6520
+ if (target) {
6521
+ const value = lowerCompoundValueToTarget(lowerer, expr, indexedCompound, target);
6522
+ return { kind: "assignExpr", localId: target.id, value, type: target.type, loc };
6523
+ }
6524
+ return lowerFieldCompoundValue(lowerer, expr.left, indexedCompound, expr.right, loc);
6525
+ }
5645
6526
  // `events.defaultMaxListeners = v` — the module-property write
5646
6527
  // Node validates (validateNumber(n, 'defaultMaxListeners', 0)):
5647
6528
  // the value crosses into the checked-dynamic tree and the runtime ladder throws
@@ -5694,6 +6575,37 @@ export function lowerBinary(lowerer, expr) {
5694
6575
  const value = lowerer.lowerExprExpecting(expr.right, target.type);
5695
6576
  return { kind: "assignExpr", localId: target.id, value, type: target.type, loc };
5696
6577
  }
6578
+ // Bundled numeric enums use e[e["name"] = value] = "name".
6579
+ // Capture the receiver and key before the RHS. PutValue coerces the
6580
+ // key afterward, then yields the RHS without rereading the property.
6581
+ if (ts.isElementAccessExpression(expr.left) && !expr.left.questionDotToken) {
6582
+ const recv = tryLowerExpression(lowerer, expr.left.expression);
6583
+ if (recv && (recv.type.kind === "dyn" ||
6584
+ recv.type.kind === "array" && isJsSourceFile(expr.getSourceFile()) && lowerer.dynConvertible(recv.type))) {
6585
+ return lowerDynMemberAssignment(lowerer, expr, lowerer.coerceInto(expr.left.expression, recv, DYN));
6586
+ }
6587
+ if (recv?.type.kind === "array" || recv?.type.kind === "bytes") {
6588
+ const receiver = lowerer.declareHiddenLocal("%setArray", recv.type);
6589
+ const index = lowerer.lowerExpr(expr.left.argumentExpression);
6590
+ if (index.type.kind !== "f64")
6591
+ lowerer.unsupported("SC1090", expr.left.argumentExpression, "indexing with non-number keys");
6592
+ const key = lowerer.declareHiddenLocal("%setIndex", F64);
6593
+ const raw = lowerer.lowerExpr(expr.right);
6594
+ const value = lowerer.declareHiddenLocal("%setValue", raw.type);
6595
+ const result = varRef(value.id, raw.type, loc);
6596
+ const arr = varRef(receiver.id, recv.type, loc);
6597
+ const idx = varRef(key.id, F64, loc);
6598
+ const write = recv.type.kind === "array"
6599
+ ? arrayValueStore(lowerer, arr, idx, result, recv.type.elem, loc)
6600
+ : { kind: "bytesSet", arr, index: idx, value: lowerer.coerceInto(expr.right, result, F64), loc };
6601
+ return { kind: "seqExpr", stmts: [
6602
+ { kind: "varDecl", localId: receiver.id, init: recv, loc },
6603
+ { kind: "varDecl", localId: key.id, init: index, loc },
6604
+ { kind: "varDecl", localId: value.id, init: raw, loc },
6605
+ write,
6606
+ ], result, type: result.type, loc };
6607
+ }
6608
+ }
5697
6609
  // `h.k = v` on an ISLAND receiver in VALUE position: the engine
5698
6610
  // property write (setProp throws the engine's TypeErrors on
5699
6611
  // nullish receivers, bridged catchably), the RHS value threaded
@@ -5734,30 +6646,22 @@ export function lowerBinary(lowerer, expr) {
5734
6646
  // consumer sees exactly what the checker typed.
5735
6647
  if (ts.isPropertyAccessExpression(expr.left) && !expr.left.questionDotToken) {
5736
6648
  const recv = tryLowerExpression(lowerer, expr.left.expression);
5737
- if (recv && recv.type.kind === "dyn") {
5738
- const recvTmp = lowerer.declareHiddenLocal("%setRecv", DYN);
5739
- const rhsVal = lowerer.lowerExpr(expr.right);
5740
- const valTmp = lowerer.declareHiddenLocal("%setVal", rhsVal.type);
5741
- const valRef = () => ({ kind: "varRef", localId: valTmp.id, type: rhsVal.type, loc });
5742
- const stored = lowerer.coerceToExpected(valRef(), DYN);
5743
- if (stored.type.kind !== "dyn") {
5744
- lowerer.unsupported("SC1101", expr.right, `assigning '${lowerer.fmt(rhsVal.type)}' values into a checked-dynamic member`);
5745
- }
5746
- const key = { kind: "strLit", value: expr.left.name.text, type: STRING, loc: locOf(expr.left.name) };
6649
+ if (recv && recv.type.kind === "dyn")
6650
+ return lowerDynMemberAssignment(lowerer, expr, recv);
6651
+ const field = lowerer.fieldTarget(expr.left);
6652
+ if (field) {
6653
+ const recvTmp = lowerer.declareHiddenLocal("%setReceiver", field.obj.type);
6654
+ const receiver = field.obj;
6655
+ field.obj = varRef(recvTmp.id, receiver.type, loc);
6656
+ const value = lowerer.lowerExprExpecting(expr.right, field.fieldType);
6657
+ const valTmp = lowerer.declareHiddenLocal("%setValue", value.type);
6658
+ const result = varRef(valTmp.id, value.type, loc);
5747
6659
  return {
5748
- kind: "seqExpr",
5749
- stmts: [
5750
- { kind: "varDecl", localId: recvTmp.id, init: recv, loc },
5751
- { kind: "varDecl", localId: valTmp.id, init: rhsVal, loc },
5752
- {
5753
- kind: "exprStmt",
5754
- expr: { kind: "libCall", fn: "dyn.keySet", args: [{ kind: "varRef", localId: recvTmp.id, type: DYN, loc }, key, stored], type: VOID, loc },
5755
- loc,
5756
- },
5757
- ],
5758
- result: valRef(),
5759
- type: rhsVal.type,
5760
- loc,
6660
+ kind: "seqExpr", stmts: [
6661
+ { kind: "varDecl", localId: recvTmp.id, init: receiver, loc },
6662
+ { kind: "varDecl", localId: valTmp.id, init: value, loc },
6663
+ lowerer.fieldSetStmt(field, result, loc, expr.left),
6664
+ ], result, type: result.type, loc,
5761
6665
  };
5762
6666
  }
5763
6667
  }
@@ -5786,221 +6690,7 @@ export function lowerBinary(lowerer, expr) {
5786
6690
  lowerer.unsupported("SC1040", expr, undefined, "object-to-primitive loose equality can call user-defined valueOf/toString methods — compare explicit primitive conversions instead");
5787
6691
  }
5788
6692
  if (op === ts.SyntaxKind.AmpersandAmpersandToken || op === ts.SyntaxKind.BarBarToken) {
5789
- if (op === ts.SyntaxKind.BarBarToken) {
5790
- const fallback = lowerAbsenceAwareOrChain(lowerer, expr);
5791
- if (fallback)
5792
- return fallback;
5793
- }
5794
- // JS value semantics: `a && b` is `toBool(a) ? b : a` — the result is
5795
- // an operand value, so both operands must share one IR kind (which is
5796
- // also what tsc's type says, modulo literal-union collapsing that
5797
- // mapType already handles). Mixed kinds (`n && s`) stay rejected.
5798
- const left = lowerAbsenceProbe(lowerer, expr.left) ?? lowerer.lowerExpr(expr.left);
5799
- const right = lowerer.lowerExpr(expr.right);
5800
- // A LITERAL-unit left operand (a compile-time undefined/null — the
5801
- // capability-probe members: `process.features.inspector ||
5802
- // !flag.startsWith('--inspect')` reads undefined in a compiled
5803
- // binary): statically falsy, so the operator folds JS-exactly —
5804
- // `unit || X` IS X, `unit && X` IS the unit (X's side effects never
5805
- // run in JS either; the lowered right is simply dropped). Literal
5806
- // units only — computed unit-typed values keep the fences below.
5807
- if (left.kind === "unitLit") {
5808
- return op === ts.SyntaxKind.BarBarToken ? right : left;
5809
- }
5810
- // JS objects and symbols are always truthy. In `ref || fallback`
5811
- // the fallback is therefore unreachable and the result is the
5812
- // already-evaluated left value — the browser-fallback idiom used by
5813
- // packages for `process.argv || []` and `process.env || {}`. The
5814
- // operand itself remains in the IR, preserving reads/calls that
5815
- // produce the reference; only the unreachable right lowering drops.
5816
- if (op === ts.SyntaxKind.BarBarToken && REF_TRUTHY_KINDS.has(left.type.kind)) {
5817
- return left;
5818
- }
5819
- if (left.type.kind === "dyn" || right.type.kind === "dyn") {
5820
- // A checked-dynamic operand (`fn.name || '<anonymous>'` —
5821
- // test/common's _mustCallInner): both sides live in the checked-dynamic tree and
5822
- // the deciding test is ToBoolean over the dyn kind
5823
- // (scr_dyn_truthy) — JS value semantics exactly, result dyn. The
5824
- // non-dyn side converts through the usual boundary; a value with
5825
- // no dyn representation keeps the fence.
5826
- const l = lowerer.coerceToExpected(left, DYN);
5827
- const r = lowerer.coerceToExpected(right, DYN);
5828
- if (l.type.kind !== "dyn" || r.type.kind !== "dyn") {
5829
- lowerer.unsupported("SC1100", expr, "logical operators on 'unknown' values");
5830
- }
5831
- return {
5832
- kind: "logical",
5833
- op: op === ts.SyntaxKind.AmpersandAmpersandToken ? "&&" : "||",
5834
- left: l,
5835
- right: r,
5836
- type: DYN, loc,
5837
- };
5838
- }
5839
- if (left.type.kind === "jsval" || right.type.kind === "jsval") {
5840
- return {
5841
- kind: "logical",
5842
- op: op === ts.SyntaxKind.AmpersandAmpersandToken ? "&&" : "||",
5843
- left: lowerer.jsvalIn(left, expr.left),
5844
- right: lowerer.jsvalIn(right, expr.right),
5845
- type: JSVAL, loc,
5846
- };
5847
- }
5848
- if (left.type.kind === "union" || right.type.kind === "union") {
5849
- // JS value semantics over a union: the deciding test is the ARM
5850
- // value's ToBoolean (a per-union helper), and the result is the
5851
- // deciding operand. Supported when the checker's type of the whole
5852
- // expression maps to ONE union both operands coerce into (same
5853
- // union passes through; a plain arm wraps — `u || undefined`).
5854
- // Everything else (`u && flag`, whose value would need a wider
5855
- // re-tagged union) stays fenced — in CONDITION position those
5856
- // shapes lower through lowerCondition's bool descent instead.
5857
- let target = lowerer.mapTypeOf(lowerer.typeOf(expr));
5858
- // A broad-JSDoc npm-static body can leave the CHECKER result `any`
5859
- // even after declaration-backed specialization has recovered both
5860
- // operands (`helpOption && args.find(...)` in commander). When the
5861
- // left consists only of always-truthy references and falsy units,
5862
- // the static result is exactly the right operand plus those units.
5863
- if (target === null &&
5864
- lowerer.implicitParamTypes !== null &&
5865
- npmStaticPackageOfPath(expr.getSourceFile().fileName) !== null &&
5866
- (lowerer.typeOf(expr).flags & ts.TypeFlags.Any) !== 0 &&
5867
- left.type.kind === "union") {
5868
- const leftArms = lowerer.unions.get(left.type.unionId)?.arms;
5869
- const rightArms = right.type.kind === "union"
5870
- ? lowerer.unions.get(right.type.unionId)?.arms
5871
- : [right.type];
5872
- const rightIsStatic = rightArms?.every((arm) => arm.kind !== "dyn" && arm.kind !== "caught" && arm.kind !== "jsval" && arm.kind !== "void") ?? false;
5873
- if (leftArms && rightArms && rightIsStatic &&
5874
- leftArms.some(isUnitType) &&
5875
- leftArms.every((arm) => isUnitType(arm) || REF_TRUTHY_KINDS.has(arm.kind))) {
5876
- const byKey = new Map();
5877
- for (const arm of [...rightArms, ...leftArms.filter(isUnitType)]) {
5878
- byKey.set(typeKey(arm), arm);
5879
- }
5880
- const arms = [...byKey.values()].sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1));
5881
- if (arms.length > 1) {
5882
- target = { kind: "union", unionId: lowerer.unions.intern(arms) };
5883
- }
5884
- }
5885
- }
5886
- if (target?.kind === "union") {
5887
- // `&&` whose left does NOT fit the result union: the checker
5888
- // built that result by DROPPING left arms which are always
5889
- // truthy (`Option | undefined && string | undefined` answers
5890
- // `string | undefined`, not `Option | string | undefined`).
5891
- // Evaluate the left once, test it in its own union, and retag it
5892
- // only on the falsy path. Any stranded reference arms are
5893
- // unreachable there because every JS object is truthy.
5894
- if (op === ts.SyntaxKind.AmpersandAmpersandToken && left.type.kind === "union" && !typeEquals(left.type, target)) {
5895
- const def = lowerer.unions.get(left.type.unionId);
5896
- const trappable = new Set();
5897
- def?.arms.forEach((arm, tag) => {
5898
- if (lowerer.armTag(target.unionId, arm) < 0 && REF_TRUTHY_KINDS.has(arm.kind)) {
5899
- trappable.add(tag);
5900
- }
5901
- });
5902
- const retag = trappable.size === 0
5903
- ? null
5904
- : lowerer.unionRetagHelper(left.type.unionId, target.unionId, loc, trappable);
5905
- if (retag) {
5906
- lowerer.requireTruthyUnion(left.type.unionId, expr);
5907
- const stmts = [];
5908
- let stable = left;
5909
- if (!isSafeToRepeat(left)) {
5910
- const local = lowerer.declareHiddenLocal("%and", left.type);
5911
- stmts.push({ kind: "varDecl", localId: local.id, init: left, loc });
5912
- stable = varRef(local.id, left.type, loc);
5913
- }
5914
- const result = {
5915
- kind: "ternary",
5916
- cond: lowerer.ensureBool(stable, expr.left),
5917
- then: lowerer.coerceInto(expr.right, right, target),
5918
- else_: { kind: "call", callee: retag, args: [stable], type: target, loc },
5919
- type: target,
5920
- loc,
5921
- };
5922
- return stmts.length === 0 ? result : { kind: "seqExpr", stmts, result, type: target, loc };
5923
- }
5924
- }
5925
- // `||` whose left does NOT fit the result union: the checker built
5926
- // that result by DROPPING the left's falsy arms (`process.env.X ||
5927
- // null` is `string | null`, `|| 3000` is `string | number` — the
5928
- // `undefined` is gone from both), so coercing the left eagerly, as
5929
- // the shared shape below does, retags an arm the test is about to
5930
- // rule out and throws where Node yields the default. Single-eval
5931
- // instead: test the left in its OWN union and retag only on the
5932
- // truthy side, where the dropped arms are unreachable.
5933
- if (op === ts.SyntaxKind.BarBarToken && left.type.kind === "union" && !typeEquals(left.type, target)) {
5934
- const retag = lowerer.unionRetagHelper(left.type.unionId, target.unionId, loc);
5935
- if (retag) {
5936
- lowerer.requireTruthyUnion(left.type.unionId, expr);
5937
- return {
5938
- kind: "orDefault",
5939
- left,
5940
- right: lowerer.coerceInto(expr.right, right, target),
5941
- retag,
5942
- type: target,
5943
- loc,
5944
- };
5945
- }
5946
- }
5947
- lowerer.requireTruthyUnion(target.unionId, expr);
5948
- return {
5949
- kind: "logical",
5950
- op: op === ts.SyntaxKind.AmpersandAmpersandToken ? "&&" : "||",
5951
- left: lowerer.coerceInto(expr.left, left, target),
5952
- right: lowerer.coerceInto(expr.right, right, target),
5953
- type: target, loc,
5954
- };
5955
- }
5956
- // `u || d` NARROWED by the default: the checker types the result
5957
- // as u's single non-unit arm (`marker() || "default"`) — evaluate
5958
- // u once, truthy extracts the arm, falsy takes d lazily (the
5959
- // orDefault node, nullish's truthiness sibling). An UNMAPPABLE
5960
- // checker result takes the same rule (`options.runner ||
5961
- // defaultRunner` — tsc's union of two structurally-compatible
5962
- // function types has no representation, while the left's one
5963
- // non-unit arm is the only representable answer): the default
5964
- // must coerce into the arm or fence on its own.
5965
- if (op === ts.SyntaxKind.BarBarToken &&
5966
- left.type.kind === "union") {
5967
- const def = lowerer.unions.get(left.type.unionId);
5968
- const rest = def ? def.arms.filter((a) => !isUnitType(a)) : [];
5969
- const funcArmDefault = rest[0]?.kind === "func" && (target === null || target.kind === "func");
5970
- if (rest.length === 1 && ((target !== null && typeEquals(target, rest[0])) || funcArmDefault)) {
5971
- lowerer.requireTruthyUnion(left.type.unionId, expr);
5972
- const dflt = lowerer.lowerExprExpecting(expr.right, rest[0]);
5973
- return { kind: "orDefault", left, right: dflt, type: rest[0], loc };
5974
- }
5975
- }
5976
- lowerer.unsupported("SC1090", expr, `logical operators on union-typed values outside conditions (${NARROW_FIRST})`);
5977
- }
5978
- const kind = left.type.kind;
5979
- if (kind !== right.type.kind ||
5980
- (kind !== "f64" && kind !== "string" && kind !== "bool")) {
5981
- // Mixed PLAIN operands (`value || null`, `flag || undefined`,
5982
- // `s || 0`): JS value semantics still compile when the checker
5983
- // types the RESULT as one union both operands coerce into — the
5984
- // same lift the union-operand path above takes, arriving here
5985
- // with two plain arm values instead.
5986
- const target = lowerer.mapTypeOf(lowerer.typeOf(expr));
5987
- if (target?.kind === "union") {
5988
- lowerer.requireTruthyUnion(target.unionId, expr);
5989
- return {
5990
- kind: "logical",
5991
- op: op === ts.SyntaxKind.AmpersandAmpersandToken ? "&&" : "||",
5992
- left: lowerer.coerceInto(expr.left, left, target),
5993
- right: lowerer.coerceInto(expr.right, right, target),
5994
- type: target, loc,
5995
- };
5996
- }
5997
- lowerer.unsupported("SC1042", expr);
5998
- }
5999
- return {
6000
- kind: "logical",
6001
- op: op === ts.SyntaxKind.AmpersandAmpersandToken ? "&&" : "||",
6002
- left, right, type: left.type, loc,
6003
- };
6693
+ return lowerLogicalChain(lowerer, expr);
6004
6694
  }
6005
6695
  if (op === ts.SyntaxKind.QuestionQuestionToken) {
6006
6696
  return lowerer.lowerNullishCoalesce(expr, loc);
@@ -6011,7 +6701,7 @@ export function lowerBinary(lowerer, expr) {
6011
6701
  // runs for EFFECT exactly as its statement lowering (JS discards its
6012
6702
  // value), the right operand is the expression's value — a seqExpr.
6013
6703
  // The validator restricts seqExpr statements to straight-line writes
6014
- // (the C emission point is mid-expression), so a left operand whose
6704
+ // (the lowering point is mid-expression), so a left operand whose
6015
6705
  // statement lowering needs real control flow keeps a pointed fence
6016
6706
  // instead of tripping the validator downstream.
6017
6707
  const effect = lowerer.lowerExprStatement(expr.left);
@@ -6052,7 +6742,7 @@ export function lowerBinary(lowerer, expr) {
6052
6742
  op === ts.SyntaxKind.ExclamationEqualsEqualsToken) {
6053
6743
  const negated = op === ts.SyntaxKind.ExclamationEqualsEqualsToken;
6054
6744
  const unit = left.kind === "unitLit" ? left : right.kind === "unitLit" ? right : null;
6055
- const other = unit === left ? right : left;
6745
+ const other = left.kind === "unitLit" ? right : left;
6056
6746
  if (unit && other.type.kind === "dyn") {
6057
6747
  return {
6058
6748
  kind: "dynTest",
@@ -6069,12 +6759,30 @@ export function lowerBinary(lowerer, expr) {
6069
6759
  // answers false). dyn vs dyn keeps the fence below.
6070
6760
  const dynSide = left.type.kind === "dyn" ? left : right;
6071
6761
  const scalarSide = dynSide === left ? right : left;
6762
+ const reference = scalarSide.type;
6763
+ if ((isDynTypedRefType(reference) || reference.kind === "record" || reference.kind === "array" ||
6764
+ reference.kind === "bytes" || reference.kind === "func" || reference.kind === "classval" || reference.kind === "regex" || reference.kind === "url" ||
6765
+ reference.kind === "bigint" || reference.kind === "symbol" || reference.kind === "map" || reference.kind === "set" || reference.kind === "union" || DYN_HANDLE_KINDS.has(reference.kind)) &&
6766
+ lowerer.dynConvertible(reference)) {
6767
+ const boxed = {
6768
+ kind: "dynFrom", value: scalarSide, type: DYN, loc: scalarSide.loc,
6769
+ ...(reference.kind === "record" || reference.kind === "array" || reference.kind === "bytes" ? { liveRef: true } : {}),
6770
+ };
6771
+ return {
6772
+ kind: "dynScalarEq",
6773
+ left: scalarSide === left ? boxed : left,
6774
+ right: scalarSide === right ? boxed : right,
6775
+ ...(negated ? { negated: true } : {}),
6776
+ type: BOOL,
6777
+ loc,
6778
+ };
6779
+ }
6072
6780
  if (dynSide.type.kind === "dyn" &&
6073
6781
  (scalarSide.type.kind === "f64" || scalarSide.type.kind === "string" || scalarSide.type.kind === "bool" ||
6074
6782
  // dyn vs dyn (`context.actual !== context.exact` —
6075
6783
  // test/common's exit accounting): the runtime's whole-dyn
6076
6784
  // strict equality — scalars by value, units by kind,
6077
- // reference kinds by node identity (scr_dyn_strict_eq).
6785
+ // reference kinds by identity (scr_dyn_strict_eq).
6078
6786
  scalarSide.type.kind === "dyn")) {
6079
6787
  return {
6080
6788
  kind: "dynScalarEq",
@@ -6086,16 +6794,26 @@ export function lowerBinary(lowerer, expr) {
6086
6794
  };
6087
6795
  }
6088
6796
  }
6089
- // JS `any`-origin operands (the checked-dynamic declaration story):
6090
- // arithmetic and ordering CHECK the dyn side to the static side's
6091
- // scalar kind (dynCheck — a catchable TypeError on mismatch) and
6092
- // compute natively. Node would ToNumber-coerce instead — the honest
6093
- // divergence is loud (a throw), never a silent wrong answer
6094
- // (SEMANTICS.md). tsc rejects these forms on real `unknown`, so only
6095
- // any-typed JS reaches this; when BOTH sides are dyn a number
6096
- // context is the only honest guess for arithmetic — both check.
6097
- if (isJsSourceFile(expr.getSourceFile())) {
6098
- const checkNum = (e) => e.type.kind === "dyn" ? { kind: "dynCheck", value: e, type: F64, loc: e.loc } : e;
6797
+ const bitwise = {
6798
+ [ts.SyntaxKind.AmpersandToken]: "&", [ts.SyntaxKind.BarToken]: "|", [ts.SyntaxKind.CaretToken]: "^",
6799
+ [ts.SyntaxKind.LessThanLessThanToken]: "<<", [ts.SyntaxKind.GreaterThanGreaterThanToken]: ">>",
6800
+ [ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken]: ">>>",
6801
+ };
6802
+ if (bitwise[op]) {
6803
+ const l = lowerer.coerceToExpected(left, DYN);
6804
+ const r = lowerer.coerceToExpected(right, DYN);
6805
+ if (l.type.kind === "dyn" && r.type.kind === "dyn") {
6806
+ const value = { kind: "libCall", fn: "dyn.bitwise", args: [l, r, { kind: "strLit", value: bitwise[op], type: STRING, loc }], type: DYN, loc };
6807
+ return left.type.kind === "f64" || right.type.kind === "f64" ? { kind: "dynCheck", value, type: F64, loc } : value;
6808
+ }
6809
+ }
6810
+ // Numeric operators dispatch on ToNumeric's actual Number/BigInt
6811
+ // result, including values flowing through native callable properties.
6812
+ if (isJsSourceFile(expr.getSourceFile()) || op === ts.SyntaxKind.PlusToken ||
6813
+ [ts.SyntaxKind.LessThanToken, ts.SyntaxKind.LessThanEqualsToken,
6814
+ ts.SyntaxKind.GreaterThanToken, ts.SyntaxKind.GreaterThanEqualsToken].includes(op) ||
6815
+ [ts.SyntaxKind.MinusToken, ts.SyntaxKind.AsteriskToken, ts.SyntaxKind.SlashToken,
6816
+ ts.SyntaxKind.PercentToken, ts.SyntaxKind.AsteriskAsteriskToken].includes(op)) {
6099
6817
  const other = left.type.kind === "dyn" ? right : left;
6100
6818
  const NUM_BIN = {
6101
6819
  [ts.SyntaxKind.MinusToken]: "-",
@@ -6112,26 +6830,30 @@ export function lowerBinary(lowerer, expr) {
6112
6830
  };
6113
6831
  const arith = NUM_BIN[op];
6114
6832
  const cmp = NUM_CMP[op];
6115
- if ((arith || cmp) && (other.type.kind === "f64" || other.type.kind === "dyn")) {
6116
- const l = checkNum(left);
6117
- const r = checkNum(right);
6118
- if (arith)
6119
- return { kind: "bin", op: arith, left: l, right: r, type: F64, loc };
6120
- return { kind: "bin", op: cmp, left: l, right: r, type: BOOL, loc };
6121
- }
6122
- // `+`: number when the OTHER side is a number, string concat when
6123
- // it is a string — the two static homes; dyn+dyn stays a number.
6124
- if (op === ts.SyntaxKind.PlusToken) {
6125
- if (other.type.kind === "f64" || other.type.kind === "dyn") {
6126
- return { kind: "bin", op: "+", left: checkNum(left), right: checkNum(right), type: F64, loc };
6833
+ if (arith) {
6834
+ const l = lowerer.coerceToExpected(left, DYN);
6835
+ const r = lowerer.coerceToExpected(right, DYN);
6836
+ if (l.type.kind === "dyn" && r.type.kind === "dyn") {
6837
+ const value = { kind: "libCall", fn: "dyn.arithmetic",
6838
+ args: [l, r, { kind: "strLit", value: arith, type: STRING, loc }], type: DYN, loc };
6839
+ const numeric = left.type.kind === "bigint" || right.type.kind === "bigint" ? BIGINT_T
6840
+ : left.type.kind === "f64" || right.type.kind === "f64" ? F64 : null;
6841
+ return numeric ? { kind: "dynCheck", value, type: numeric, loc } : value;
6127
6842
  }
6128
- if (other.type.kind === "string") {
6129
- // String-context `+`: JS's answer is String(unknown) — the
6130
- // JS-exact dyn walker (numbers format, arrays join, objects
6131
- // print [object Object], handles the same) — never a checked
6132
- // cast: `'status ' + res.statusCode` concatenates like Node.
6133
- const strOf = (e) => e.type.kind === "dyn" ? { kind: "toString", operand: e, type: STRING, loc: e.loc } : e;
6134
- return { kind: "strConcat", left: strOf(left), right: strOf(right), type: STRING, loc };
6843
+ }
6844
+ if (cmp && lowerer.dynConvertible(left.type) && lowerer.dynConvertible(right.type)) {
6845
+ return { kind: "libCall", fn: "dyn.compare", args: [lowerer.coerceToExpected(left, DYN),
6846
+ lowerer.coerceToExpected(right, DYN), { kind: "strLit", value: cmp, type: STRING, loc }], type: BOOL, loc };
6847
+ }
6848
+ // Untyped addition chooses concatenation or numeric addition only
6849
+ // after both operands have undergone ToPrimitive at runtime.
6850
+ if (op === ts.SyntaxKind.PlusToken) {
6851
+ const l = lowerer.coerceToExpected(left, DYN);
6852
+ const r = lowerer.coerceToExpected(right, DYN);
6853
+ if (l.type.kind === "dyn" && r.type.kind === "dyn") {
6854
+ const value = { kind: "libCall", fn: "dyn.add", args: [l, r], type: DYN, loc };
6855
+ // A statically known string operand guarantees a string result.
6856
+ return other.type.kind === "string" ? { kind: "dynCheck", value, type: STRING, loc } : value;
6135
6857
  }
6136
6858
  }
6137
6859
  }
@@ -6150,7 +6872,7 @@ export function lowerBinary(lowerer, expr) {
6150
6872
  // retain the SC2011 promise that --dynamic lifts this site.
6151
6873
  if (lowerer.dynamic)
6152
6874
  return lowerAnyBinaryInIsland(lowerer, expr, left, right, loc);
6153
- lowerer.anyOpFence(`the '${ts.tokenToString(op) ?? ts.SyntaxKind[op]}' operator`, expr);
6875
+ lowerer.anyOpFence(`the '${ts.tokenToString(op) ?? ts.syntaxKindName(op)}' operator`, expr);
6154
6876
  }
6155
6877
  // tsc allows ===/!== on unknown (arithmetic/comparisons it rejects
6156
6878
  // itself); a dynamic equality would need a dyn walk — validate first.
@@ -6353,6 +7075,14 @@ export function lowerBinary(lowerer, expr) {
6353
7075
  const optionalStringPlus = lowerOptionalStringPlus();
6354
7076
  if (optionalStringPlus)
6355
7077
  return optionalStringPlus;
7078
+ if (isJsSourceFile(expr.getSourceFile())) {
7079
+ const primitive = (type) => type.kind === "f64" || type.kind === "string" || type.kind === "bool" || isUnitType(type) ||
7080
+ (type.kind === "union" && (lowerer.unions.get(type.unionId)?.arms.every(primitive) ?? false));
7081
+ if ((left.type.kind === "union" || right.type.kind === "union") && primitive(left.type) && primitive(right.type)) {
7082
+ lowerer.runtimeOptionalArithmeticTypes.set(expr, DYN);
7083
+ return { kind: "libCall", fn: "dyn.add", args: [lowerer.coerceToExpected(left, DYN), lowerer.coerceToExpected(right, DYN)], type: DYN, loc };
7084
+ }
7085
+ }
6356
7086
  lowerer.unsupported("SC1043", expr);
6357
7087
  break;
6358
7088
  case ts.SyntaxKind.MinusToken:
@@ -6395,8 +7125,11 @@ export function lowerBinary(lowerer, expr) {
6395
7125
  return indexed;
6396
7126
  if (plainBothNum)
6397
7127
  return { kind: "bin", op: negated ? "!==" : "===", left, right, type: BOOL, loc };
6398
- if (bothStr)
7128
+ if (bothStr) {
7129
+ if (left.kind === "strLit" && right.kind === "strLit")
7130
+ return { kind: "boolLit", value: (left.value === right.value) !== negated, type: BOOL, loc };
6399
7131
  return { kind: "strEq", negated, left, right, type: BOOL, loc };
7132
+ }
6400
7133
  // bool === bool: a plain value compare (the config-drift checks'
6401
7134
  // `desired.lanMode !== actual.lanMode` shape).
6402
7135
  if (left.type.kind === "bool" && right.type.kind === "bool") {
@@ -6520,6 +7253,8 @@ export function lowerBinary(lowerer, expr) {
6520
7253
  if ((idLeft.type.kind === "array" ||
6521
7254
  idLeft.type.kind === "map" ||
6522
7255
  idLeft.type.kind === "set" ||
7256
+ idLeft.type.kind === "regex" ||
7257
+ idLeft.type.kind === "url" ||
6523
7258
  idLeft.type.kind === "object" ||
6524
7259
  idLeft.type.kind === "record" ||
6525
7260
  // Symbols ARE identity: `Symbol('a') === Symbol('a')` is false,
@@ -6540,6 +7275,10 @@ export function lowerBinary(lowerer, expr) {
6540
7275
  if (DYN_HANDLE_KINDS.has(idLeft.type.kind) && typeEquals(idLeft.type, idRight.type)) {
6541
7276
  return { kind: "bin", op: negated ? "!==" : "===", left: idLeft, right: idRight, type: BOOL, loc };
6542
7277
  }
7278
+ if (lowerer.dynConvertible(idLeft.type) && lowerer.dynConvertible(idRight.type)) {
7279
+ return { kind: "dynScalarEq", left: lowerer.coerceToExpected(idLeft, DYN),
7280
+ right: lowerer.coerceToExpected(idRight, DYN), ...(negated ? { negated: true } : {}), type: BOOL, loc };
7281
+ }
6543
7282
  lowerer.unsupported("SC1043", expr);
6544
7283
  break;
6545
7284
  }
@@ -6554,27 +7293,308 @@ export function lowerBinary(lowerer, expr) {
6554
7293
  return { kind: "bin", op: cmpOp, left: arithmeticLeft, right: arithmeticRight, type: BOOL, loc };
6555
7294
  if (bothStr)
6556
7295
  return { kind: "strCmp", op: cmpOp, left, right, type: BOOL, loc };
7296
+ if (lowerer.dynConvertible(left.type) && lowerer.dynConvertible(right.type)) {
7297
+ return { kind: "libCall", fn: "dyn.compare", args: [lowerer.coerceToExpected(left, DYN),
7298
+ lowerer.coerceToExpected(right, DYN), { kind: "strLit", value: cmpOp, type: STRING, loc }], type: BOOL, loc };
7299
+ }
6557
7300
  lowerer.unsupported("SC1043", expr);
6558
7301
  break;
6559
7302
  }
6560
7303
  default:
6561
7304
  break;
6562
7305
  }
6563
- lowerer.unsupported("SC1090", expr, `operator '${ts.tokenToString(op) ?? ts.SyntaxKind[op]}'`);
7306
+ lowerer.unsupported("SC1090", expr, `operator '${ts.tokenToString(op) ?? ts.syntaxKindName(op)}'`);
7307
+ }
7308
+ function lowerLogicalChain(lowerer, expr) {
7309
+ // Logical chains have a left-nested AST. Keep the native stack bounded
7310
+ // while retaining each pair's checker type and lazy operand evaluation.
7311
+ const parents = [];
7312
+ let current = expr;
7313
+ let result;
7314
+ while (true) {
7315
+ if (current.operatorToken.kind === ts.SyntaxKind.BarBarToken) {
7316
+ const fallback = lowerAbsenceAwareOrChain(lowerer, current);
7317
+ if (fallback !== null) {
7318
+ result = fallback;
7319
+ break;
7320
+ }
7321
+ }
7322
+ const left = current.left;
7323
+ if (ts.isBinaryExpression(left) &&
7324
+ (left.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ||
7325
+ left.operatorToken.kind === ts.SyntaxKind.BarBarToken) &&
7326
+ !lowerer.chainRecvByNode.has(left)) {
7327
+ parents.push(current);
7328
+ current = left;
7329
+ continue;
7330
+ }
7331
+ result = lowerLogicalPair(lowerer, current, lowerAbsenceProbe(lowerer, left) ?? lowerer.lowerExpr(left));
7332
+ break;
7333
+ }
7334
+ for (let i = parents.length - 1; i >= 0; i--) {
7335
+ result = lowerLogicalPair(lowerer, parents[i], result);
7336
+ }
7337
+ return result;
7338
+ }
7339
+ function lowerLogicalPair(lowerer, expr, left) {
7340
+ const op = expr.operatorToken.kind;
7341
+ const loc = locOf(expr);
7342
+ // Preserve short-circuiting during lowering too: an unreachable member
7343
+ // on a specialized value must not poison the surrounding JS statement.
7344
+ if (left.kind === "boolLit") {
7345
+ const takeRight = op === ts.SyntaxKind.AmpersandAmpersandToken ? left.value : !left.value;
7346
+ return takeRight ? lowerer.lowerExpr(expr.right) : left;
7347
+ }
7348
+ if (left.kind === "unitLit" && op === ts.SyntaxKind.AmpersandAmpersandToken)
7349
+ return left;
7350
+ if (op === ts.SyntaxKind.BarBarToken && REF_TRUTHY_KINDS.has(left.type.kind))
7351
+ return left;
7352
+ const right = lowerer.lowerExpr(expr.right);
7353
+ // A LITERAL-unit left operand (a compile-time undefined/null — the
7354
+ // capability-probe members: `process.features.inspector ||
7355
+ // !flag.startsWith('--inspect')` reads undefined in a compiled
7356
+ // binary): statically falsy, so the operator folds JS-exactly —
7357
+ // `unit || X` IS X, `unit && X` IS the unit (X's side effects never
7358
+ // run in JS either; the lowered right is simply dropped). Literal
7359
+ // units only — computed unit-typed values keep the fences below.
7360
+ if (left.kind === "unitLit") {
7361
+ return op === ts.SyntaxKind.BarBarToken ? right : left;
7362
+ }
7363
+ // JS objects and symbols are always truthy. In `ref || fallback`
7364
+ // the fallback is therefore unreachable and the result is the
7365
+ // already-evaluated left value — the browser-fallback idiom used by
7366
+ // packages for `process.argv || []` and `process.env || {}`. The
7367
+ // operand itself remains in the IR, preserving reads/calls that
7368
+ // produce the reference; only the unreachable right lowering drops.
7369
+ if (op === ts.SyntaxKind.BarBarToken && REF_TRUTHY_KINDS.has(left.type.kind)) {
7370
+ return left;
7371
+ }
7372
+ if (left.type.kind === "dyn" || right.type.kind === "dyn") {
7373
+ // A checked-dynamic operand (`fn.name || '<anonymous>'` —
7374
+ // test/common's _mustCallInner): both sides live in the checked-dynamic tree and
7375
+ // the deciding test is ToBoolean over the dyn kind
7376
+ // (scr_dyn_truthy) — JS value semantics exactly, result dyn. The
7377
+ // non-dyn side converts through the usual boundary; a value with
7378
+ // no dyn representation keeps the fence.
7379
+ const l = lowerer.coerceToExpected(left, DYN);
7380
+ const r = lowerer.coerceToExpected(right, DYN);
7381
+ if (l.type.kind !== "dyn" || r.type.kind !== "dyn") {
7382
+ lowerer.unsupported("SC1100", expr, "logical operators on 'unknown' values");
7383
+ }
7384
+ return {
7385
+ kind: "logical",
7386
+ op: op === ts.SyntaxKind.AmpersandAmpersandToken ? "&&" : "||",
7387
+ left: l,
7388
+ right: r,
7389
+ type: DYN, loc,
7390
+ };
7391
+ }
7392
+ if (left.type.kind === "jsval" || right.type.kind === "jsval") {
7393
+ return {
7394
+ kind: "logical",
7395
+ op: op === ts.SyntaxKind.AmpersandAmpersandToken ? "&&" : "||",
7396
+ left: lowerer.jsvalIn(left, expr.left),
7397
+ right: lowerer.jsvalIn(right, expr.right),
7398
+ type: JSVAL, loc,
7399
+ };
7400
+ }
7401
+ if (isJsSourceFile(expr.getSourceFile()) && op === ts.SyntaxKind.AmpersandAmpersandToken &&
7402
+ left.type.kind === "union" && (lowerer.unions.get(left.type.unionId)?.arms.some((arm) => arm.kind === "bool")) &&
7403
+ !typeEquals(left.type, right.type) && lowerer.dynConvertible(left.type) && lowerer.dynConvertible(right.type)) {
7404
+ return { kind: "logical", op: "&&", left: lowerer.coerceToExpected(left, DYN), right: lowerer.coerceToExpected(right, DYN), type: DYN, loc };
7405
+ }
7406
+ if (left.type.kind === "union" || right.type.kind === "union") {
7407
+ // Test the deciding operand in its own representation. The result
7408
+ // contains only the values that can survive short-circuit evaluation.
7409
+ let target = lowerer.mapTypeOf(lowerer.typeOf(expr));
7410
+ // Index reads can be absent without noUncheckedIndexedAccess. Their
7411
+ // explicit nullish fallback still runs, even if the checker discarded it.
7412
+ if (target !== null) {
7413
+ const returned = [right.type, ...(op === ts.SyntaxKind.AmpersandAmpersandToken ? [left.type] : [])];
7414
+ for (const type of returned) {
7415
+ const arms = type.kind === "union" ? lowerer.unions.get(type.unionId)?.arms ?? [] : [type];
7416
+ for (const arm of arms) {
7417
+ if (arm.kind === "undefinedT" && !isUnitType(target))
7418
+ target = lowerer.runtimeOptionalType(target);
7419
+ if (arm.kind === "nullT")
7420
+ target = withUnitArm(target, arm.kind, lowerer.unions) ?? target;
7421
+ }
7422
+ }
7423
+ }
7424
+ // A broad-JSDoc npm-static body can leave the CHECKER result `any`
7425
+ // even after declaration-backed specialization has recovered both
7426
+ // operands (`helpOption && args.find(...)` in commander). When the
7427
+ // left consists only of always-truthy references and falsy units,
7428
+ // the static result is exactly the right operand plus those units.
7429
+ if (op === ts.SyntaxKind.AmpersandAmpersandToken &&
7430
+ target === null &&
7431
+ lowerer.implicitParamTypes !== null &&
7432
+ npmStaticPackageOfPath(expr.getSourceFile().fileName) !== null &&
7433
+ (lowerer.typeOf(expr).flags & ts.TypeFlags.Any) !== 0 &&
7434
+ left.type.kind === "union") {
7435
+ const leftArms = lowerer.unions.get(left.type.unionId)?.arms;
7436
+ const rightArms = right.type.kind === "union"
7437
+ ? lowerer.unions.get(right.type.unionId)?.arms
7438
+ : [right.type];
7439
+ const rightIsStatic = rightArms?.every((arm) => arm.kind !== "dyn" && arm.kind !== "caught" && arm.kind !== "jsval" && arm.kind !== "void") ?? false;
7440
+ if (leftArms && rightArms && rightIsStatic &&
7441
+ leftArms.some(isUnitType) &&
7442
+ leftArms.every((arm) => isUnitType(arm) || REF_TRUTHY_KINDS.has(arm.kind))) {
7443
+ const byKey = new Map();
7444
+ for (const arm of [...rightArms, ...leftArms.filter(isUnitType)]) {
7445
+ byKey.set(typeKey(arm), arm);
7446
+ }
7447
+ const arms = [...byKey.values()].sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1));
7448
+ if (arms.length > 1) {
7449
+ const source = right.type.kind === "union" ? lowerer.unions.get(right.type.unionId) : undefined;
7450
+ target = { kind: "union", unionId: source
7451
+ ? lowerer.unions.transform(source, arms)
7452
+ : lowerer.unions.intern(arms) };
7453
+ }
7454
+ }
7455
+ }
7456
+ if (target?.kind === "union") {
7457
+ // `&&` whose left does NOT fit the result union: the checker
7458
+ // built that result by DROPPING left arms which are always
7459
+ // truthy (`Option | undefined && string | undefined` answers
7460
+ // `string | undefined`, not `Option | string | undefined`).
7461
+ // Evaluate the left once, test it in its own union, and retag it
7462
+ // only on the falsy path. Any stranded reference arms are
7463
+ // unreachable there because every JS object is truthy.
7464
+ if (op === ts.SyntaxKind.AmpersandAmpersandToken && left.type.kind === "union" && !typeEquals(left.type, target)) {
7465
+ const def = lowerer.unions.get(left.type.unionId);
7466
+ const trappable = new Set();
7467
+ def?.arms.forEach((arm, tag) => {
7468
+ if (lowerer.armTag(target.unionId, arm) < 0 && REF_TRUTHY_KINDS.has(arm.kind)) {
7469
+ trappable.add(tag);
7470
+ }
7471
+ });
7472
+ const retag = trappable.size === 0
7473
+ ? null
7474
+ : lowerer.unionRetagHelper(left.type.unionId, target.unionId, loc, trappable);
7475
+ if (retag) {
7476
+ lowerer.requireTruthyUnion(left.type.unionId, expr);
7477
+ const stmts = [];
7478
+ let stable = left;
7479
+ if (!isSafeToRepeat(left)) {
7480
+ const local = lowerer.declareHiddenLocal("%and", left.type);
7481
+ stmts.push({ kind: "varDecl", localId: local.id, init: left, loc });
7482
+ stable = varRef(local.id, left.type, loc);
7483
+ }
7484
+ const result = {
7485
+ kind: "ternary",
7486
+ cond: lowerer.ensureBool(stable, expr.left),
7487
+ then: lowerer.coerceInto(expr.right, right, target),
7488
+ else_: { kind: "call", callee: retag, args: [stable], type: target, loc },
7489
+ type: target,
7490
+ loc,
7491
+ };
7492
+ return stmts.length === 0 ? result : { kind: "seqExpr", stmts, result, type: target, loc };
7493
+ }
7494
+ }
7495
+ // `||` whose left does NOT fit the result union: the checker built
7496
+ // that result by DROPPING the left's falsy arms (`process.env.X ||
7497
+ // null` is `string | null`, `|| 3000` is `string | number` — the
7498
+ // `undefined` is gone from both), so coercing the left eagerly, as
7499
+ // the shared shape below does, retags an arm the test is about to
7500
+ // rule out and throws where Node yields the default. Single-eval
7501
+ // instead: test the left in its OWN union and retag only on the
7502
+ // truthy side, where the dropped arms are unreachable.
7503
+ if (op === ts.SyntaxKind.BarBarToken && left.type.kind === "union" && !typeEquals(left.type, target)) {
7504
+ const retag = lowerer.unionRetagHelper(left.type.unionId, target.unionId, loc);
7505
+ if (retag) {
7506
+ lowerer.requireTruthyUnion(left.type.unionId, expr);
7507
+ return {
7508
+ kind: "orDefault",
7509
+ left,
7510
+ right: lowerer.coerceInto(expr.right, right, target),
7511
+ retag,
7512
+ type: target,
7513
+ loc,
7514
+ };
7515
+ }
7516
+ }
7517
+ lowerer.requireTruthyUnion(target.unionId, expr);
7518
+ return {
7519
+ kind: "logical",
7520
+ op: op === ts.SyntaxKind.AmpersandAmpersandToken ? "&&" : "||",
7521
+ left: lowerer.coerceInto(expr.left, left, target),
7522
+ right: lowerer.coerceInto(expr.right, right, target),
7523
+ type: target, loc,
7524
+ };
7525
+ }
7526
+ // `u || d` NARROWED by the default: the checker types the result
7527
+ // as u's single non-unit arm (`marker() || "default"`) — evaluate
7528
+ // u once, truthy extracts the arm, falsy takes d lazily (the
7529
+ // orDefault node, nullish's truthiness sibling). An UNMAPPABLE
7530
+ // checker result takes the same rule (`options.runner ||
7531
+ // defaultRunner` — tsc's union of two structurally-compatible
7532
+ // function types has no representation, while the left's one
7533
+ // non-unit arm is the only representable answer): the default
7534
+ // must coerce into the arm or fence on its own.
7535
+ if (op === ts.SyntaxKind.BarBarToken &&
7536
+ left.type.kind === "union") {
7537
+ const def = lowerer.unions.get(left.type.unionId);
7538
+ const rest = def ? def.arms.filter((a) => !isUnitType(a)) : [];
7539
+ const funcArmDefault = rest[0]?.kind === "func" && (target === null || target.kind === "func");
7540
+ if (rest.length === 1 && ((target !== null && typeEquals(target, rest[0])) || funcArmDefault)) {
7541
+ lowerer.requireTruthyUnion(left.type.unionId, expr);
7542
+ const dflt = lowerer.lowerExprExpecting(expr.right, rest[0]);
7543
+ return { kind: "orDefault", left, right: dflt, type: rest[0], loc };
7544
+ }
7545
+ }
7546
+ // JavaScript defaults can join differently inferred object layouts.
7547
+ // When both operands have native checked representations, preserve
7548
+ // the deciding value and short-circuit evaluation in that domain.
7549
+ if (isJsSourceFile(expr.getSourceFile()) &&
7550
+ lowerer.dynConvertible(left.type) && lowerer.dynConvertible(right.type)) {
7551
+ return {
7552
+ kind: "logical", op: op === ts.SyntaxKind.AmpersandAmpersandToken ? "&&" : "||",
7553
+ left: lowerer.coerceInto(expr.left, left, DYN),
7554
+ right: lowerer.coerceInto(expr.right, right, DYN), type: DYN, loc,
7555
+ };
7556
+ }
7557
+ lowerer.unsupported("SC1090", expr, `logical operators on union-typed values outside conditions (${NARROW_FIRST})`);
7558
+ }
7559
+ const kind = left.type.kind;
7560
+ if (kind !== right.type.kind ||
7561
+ (kind !== "f64" && kind !== "string" && kind !== "bool")) {
7562
+ // Mixed PLAIN operands (`value || null`, `flag || undefined`,
7563
+ // `s || 0`): JS value semantics still compile when the checker
7564
+ // types the RESULT as one union both operands coerce into — the
7565
+ // same lift the union-operand path above takes, arriving here
7566
+ // with two plain arm values instead.
7567
+ const target = lowerer.mapTypeOf(lowerer.typeOf(expr));
7568
+ if (target?.kind === "union") {
7569
+ lowerer.requireTruthyUnion(target.unionId, expr);
7570
+ return {
7571
+ kind: "logical",
7572
+ op: op === ts.SyntaxKind.AmpersandAmpersandToken ? "&&" : "||",
7573
+ left: lowerer.coerceInto(expr.left, left, target),
7574
+ right: lowerer.coerceInto(expr.right, right, target),
7575
+ type: target, loc,
7576
+ };
7577
+ }
7578
+ lowerer.unsupported("SC1042", expr);
7579
+ }
7580
+ return {
7581
+ kind: "logical",
7582
+ op: op === ts.SyntaxKind.AmpersandAmpersandToken ? "&&" : "||",
7583
+ left, right, type: left.type, loc,
7584
+ };
6564
7585
  }
6565
7586
  function lowerAbsenceAwareOrChain(lowerer, expr) {
6566
7587
  const nodes = [];
6567
- const collect = (node) => {
6568
- if (ts.isBinaryExpression(node) &&
6569
- node.operatorToken.kind === ts.SyntaxKind.BarBarToken) {
6570
- collect(node.left);
6571
- collect(node.right);
7588
+ const pending = [expr];
7589
+ while (pending.length !== 0) {
7590
+ const node = pending.pop();
7591
+ if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.BarBarToken) {
7592
+ pending.push(node.right, node.left);
6572
7593
  }
6573
7594
  else {
6574
7595
  nodes.push(node);
6575
7596
  }
6576
- };
6577
- collect(expr);
7597
+ }
6578
7598
  if (nodes.length < 3)
6579
7599
  return null;
6580
7600
  const lowered = nodes.map((node) => lowerAbsenceProbe(lowerer, node) ?? lowerer.lowerExpr(node));
@@ -6645,10 +7665,7 @@ function errorRootedObjectOf(lowerer, t) {
6645
7665
  * shape, so the sibling typeof lowerings keep trying. */
6646
7666
  function lowerErrorCodeTypeofTest(lowerer, expr, loc) {
6647
7667
  const negated = expr.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken;
6648
- for (const [a, b] of [
6649
- [expr.left, expr.right],
6650
- [expr.right, expr.left],
6651
- ]) {
7668
+ for (const [a, b] of operandPairs(expr)) {
6652
7669
  if (!ts.isTypeOfExpression(a))
6653
7670
  continue;
6654
7671
  let prop = a.expression;
@@ -6708,10 +7725,7 @@ function lowerErrorCodeTypeofTest(lowerer, expr, loc) {
6708
7725
  }
6709
7726
  export function lowerCaughtTypeofTest(lowerer, expr, loc) {
6710
7727
  const negated = expr.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken;
6711
- for (const [a, b] of [
6712
- [expr.left, expr.right],
6713
- [expr.right, expr.left],
6714
- ]) {
7728
+ for (const [a, b] of operandPairs(expr)) {
6715
7729
  if (!ts.isTypeOfExpression(a))
6716
7730
  continue;
6717
7731
  const local = lowerer.caughtLocalOf(a.expression);
@@ -6834,12 +7848,13 @@ export function aliasTypeofNarrows(lowerer, cond, polarity) {
6834
7848
  walk(cond, polarity);
6835
7849
  return out;
6836
7850
  }
7851
+ /** Visit both operand orders while keeping the sequence an array of pairs. */
7852
+ function operandPairs(expr) {
7853
+ return [[expr.left, expr.right], [expr.right, expr.left]];
7854
+ }
6837
7855
  /** One ===/!== leaf of aliasTypeofNarrows — the qualification battery. */
6838
7856
  function aliasTypeofArmOf(lowerer, e, strip) {
6839
- for (const [a0, b] of [
6840
- [e.left, e.right],
6841
- [e.right, e.left],
6842
- ]) {
7857
+ for (const [a0, b] of operandPairs(e)) {
6843
7858
  if (!ts.isStringLiteral(b))
6844
7859
  continue;
6845
7860
  const a = strip(a0);
@@ -6868,7 +7883,7 @@ function aliasTypeofArmOf(lowerer, e, strip) {
6868
7883
  if (!bindingNeverReassigned(lowerer, valSym, valDecl))
6869
7884
  continue;
6870
7885
  const fn = valDecl.parent;
6871
- if (!(e.pos >= fn.pos && e.end <= fn.end))
7886
+ if (fn === undefined || !(e.pos >= fn.pos && e.end <= fn.end))
6872
7887
  continue;
6873
7888
  const valT = lowerer.typeOf(opnd); // override-aware: implicit bindings compose
6874
7889
  const mapped = lowerer.mapTypeOf(valT);
@@ -6910,10 +7925,7 @@ function aliasTypeofArmOf(lowerer, e, strip) {
6910
7925
  * bare-typeof value form then composes with strEq for pure operands). */
6911
7926
  function lowerUnionTypeofTest(lowerer, expr, loc) {
6912
7927
  const negated = expr.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken;
6913
- for (const [a, b] of [
6914
- [expr.left, expr.right],
6915
- [expr.right, expr.left],
6916
- ]) {
7928
+ for (const [a, b] of operandPairs(expr)) {
6917
7929
  if (!ts.isTypeOfExpression(a))
6918
7930
  continue;
6919
7931
  if (lowerer.mapTypeOf(lowerer.typeOf(a.expression))?.kind !== "union")
@@ -6961,12 +7973,13 @@ function lowerUnionTypeofTest(lowerer, expr, loc) {
6961
7973
  * side is a typeof over a dyn-typed operand (not this pattern). */
6962
7974
  function lowerDynTypeofTest(lowerer, expr, loc) {
6963
7975
  const negated = expr.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken;
6964
- for (const [a, b] of [
6965
- [expr.left, expr.right],
6966
- [expr.right, expr.left],
6967
- ]) {
7976
+ for (const [a, b] of operandPairs(expr)) {
6968
7977
  if (!ts.isTypeOfExpression(a))
6969
7978
  continue;
7979
+ // Non-literal comparisons use the ordinary typeof string value,
7980
+ // preserving both operands' evaluation order without a narrowing test.
7981
+ if (!ts.isStringLiteral(b))
7982
+ continue;
6970
7983
  const declared = lowerer.mapTypeOf(lowerer.typeOf(a.expression));
6971
7984
  let value;
6972
7985
  if (declared?.kind === "dyn") {
@@ -7024,10 +8037,7 @@ function lowerDynTypeofTest(lowerer, expr, loc) {
7024
8037
  value = probed;
7025
8038
  }
7026
8039
  }
7027
- if (!ts.isStringLiteral(b)) {
7028
- lowerer.unsupported("SC1090", expr, "'typeof' tests on 'unknown' values against non-literal strings");
7029
- }
7030
- if (b.text === "string" || b.text === "number" || b.text === "boolean" || b.text === "undefined") {
8040
+ if (b.text === "string" || b.text === "number" || b.text === "boolean" || b.text === "undefined" || b.text === "bigint" || b.text === "symbol") {
7031
8041
  return {
7032
8042
  kind: "dynTest",
7033
8043
  test: b.text,
@@ -7064,14 +8074,8 @@ function lowerDynTypeofTest(lowerer, expr, loc) {
7064
8074
  loc,
7065
8075
  };
7066
8076
  }
7067
- // Kinds a dyn box can NEVER hold — no conversion into 'unknown'
7068
- // exists for bigints or symbols (dynFrom's domain is JSON-safe data
7069
- // + bytes + Error + functions), so the test's answer is a
7070
- // compile-time constant: false for ===, true for !==. The capability
7071
- // probes this settles (`typeof ms === 'bigint'` in dual-mode number
7072
- // helpers) then FOLD their impossible arm (lowerTernary), which is
7073
- // what lets the reachable arm compile statically.
7074
- if (b.text === "bigint" || b.text === "symbol") {
8077
+ // Native checked values do not yet carry symbols.
8078
+ if (b.text === "symbol") {
7075
8079
  return { kind: "boolLit", value: negated, type: BOOL, loc };
7076
8080
  }
7077
8081
  lowerer.unsupported("SC1090", b, `'typeof' tests against "${b.text}" on 'unknown' values (only "string"/"number"/` +
@@ -7093,7 +8097,7 @@ function lowerReplacedExportsRead(lowerer, expr) {
7093
8097
  if (!ts.isIdentifier(recv) || recv.text !== "exports")
7094
8098
  return null;
7095
8099
  const sf = expr.getSourceFile();
7096
- if (!isJsSourceFile(sf) || isNodeEsmFile(sf))
8100
+ if (!isJsSourceFile(sf) || isNodeEsmFile(sf, lowerer.program))
7097
8101
  return null;
7098
8102
  if (lowerer.peekLocal(recv) || lowerer.globalOf(recv))
7099
8103
  return null; // a user binding shadows
@@ -7270,6 +8274,40 @@ export function lowerInstanceOf(lowerer, expr, loc) {
7270
8274
  const right = lowerer.lowerExpr(expr.right);
7271
8275
  return { kind: "jsOp", op: "instanceOf", args: [left, right], type: BOOL, loc };
7272
8276
  }
8277
+ if (!lowerer.dynamic && (lowerer.isStdlibGlobal(expr.right, "Set") || lowerer.isStdlibGlobal(expr.right, "Map"))) {
8278
+ const collection = lowerer.isStdlibGlobal(expr.right, "Map") ? "map" : "set";
8279
+ const value = lowerer.lowerExpr(expr.left);
8280
+ if (value.type.kind === "dyn")
8281
+ return { kind: "libCall", fn: collection === "map" ? "dyn.nativeMapIs" : "dyn.nativeSetIs", args: [value], type: BOOL, loc };
8282
+ if (value.type.kind === collection)
8283
+ return { kind: "seqExpr", stmts: [{ kind: "exprStmt", expr: value, loc }],
8284
+ result: { kind: "boolLit", value: true, type: BOOL, loc }, type: BOOL, loc };
8285
+ return { kind: "libCall", fn: collection === "map" ? "dyn.nativeMapIs" : "dyn.nativeSetIs",
8286
+ args: [lowerer.coerceInto(expr.left, value, DYN)], type: BOOL, loc };
8287
+ }
8288
+ if (lowerer.isStdlibGlobal(expr.right, "WeakMap") || lowerer.isStdlibGlobal(expr.right, "WeakSet")) {
8289
+ if (lowerer.dynamic) {
8290
+ const value = lowerer.jsvalIn(lowerer.lowerExpr(expr.left), expr.left);
8291
+ const ctor = { kind: "jsOp", op: "globalGet", name: lowerer.isStdlibGlobal(expr.right, "WeakMap") ? "WeakMap" : "WeakSet", args: [], type: JSVAL, loc };
8292
+ return { kind: "jsOp", op: "instanceOf", args: [value, ctor], type: BOOL, loc };
8293
+ }
8294
+ const value = lowerer.coerceInto(expr.left, lowerer.lowerExpr(expr.left), DYN);
8295
+ return { kind: "libCall", fn: lowerer.isStdlibGlobal(expr.right, "WeakMap") ? "weakMap.is" : "weakSet.is", args: [value], type: BOOL, loc };
8296
+ }
8297
+ const rhsBuiltin = ts.isIdentifier(expr.right) ? lowerer.builtinImportOf(expr.right) :
8298
+ ts.isPropertyAccessExpression(expr.right) ? lowerer.builtinMemberOf(expr.right) : null;
8299
+ if (lowerer.isStdlibGlobal(expr.right, "URL") || rhsBuiltin?.module === "url" && rhsBuiltin.member === "URL") {
8300
+ const value = lowerer.coerceInto(expr.left, lowerer.lowerExpr(expr.left), DYN);
8301
+ return { kind: "libCall", fn: "dyn.nativeUrlIs", args: [value], type: BOOL, loc };
8302
+ }
8303
+ if (lowerer.isStdlibGlobal(expr.right, "DataView")) {
8304
+ const value = lowerer.coerceInto(expr.left, lowerer.lowerExpr(expr.left), DYN);
8305
+ return { kind: "libCall", fn: "dyn.dataViewIs", args: [value], type: BOOL, loc };
8306
+ }
8307
+ if (lowerer.isStdlibGlobal(expr.right, "ArrayBuffer")) {
8308
+ const value = lowerer.coerceInto(expr.left, lowerer.lowerExpr(expr.left), DYN);
8309
+ return { kind: "libCall", fn: "arrayBuffer.is", args: [value], type: BOOL, loc };
8310
+ }
7273
8311
  const rhsSymbol = ts.isIdentifier(expr.right) ? lowerer.resolveValueSymbol(expr.right) : null;
7274
8312
  // `x instanceof events.EventEmitter` — the namespace-member spelling
7275
8313
  // resolves to the same ambient class as the named import.
@@ -7303,29 +8341,46 @@ export function lowerInstanceOf(lowerer, expr, loc) {
7303
8341
  // decoration result — fall to the class-VALUE path below, whose
7304
8342
  // instanceOfValue reads the interval off the bound class object.
7305
8343
  const directRhs = rhsSymbol ? lowerer.classBySymbol.get(rhsSymbol) : undefined;
7306
- const target = (directRhs?.classDecorators?.valueGlobalId !== undefined ? undefined : directRhs) ??
8344
+ const target = (directRhs?.localClass || directRhs?.classDecorators?.valueGlobalId !== undefined ? undefined : directRhs) ??
7307
8345
  rhsNsClass ??
7308
8346
  lowerer.builtinErrorInfoOf(rhsSymbol) ??
8347
+ lowerer.builtinErrorInfoOf(rhsMemberSymbol) ??
7309
8348
  lowerer.builtinEmitterInfoOf(rhsSymbol) ??
7310
8349
  lowerer.builtinEmitterInfoOf(rhsMemberSymbol) ??
7311
8350
  lowerer.builtinStreamInfoOf(rhsSymbol) ??
7312
8351
  lowerer.builtinStreamInfoOf(rhsMemberSymbol) ??
7313
8352
  undefined;
7314
8353
  if (!target) {
7315
- // `u instanceof Uint8Array` on an `unknown` value: the checked-dynamic tree carries a
7316
- // bytes kind — one runtime tag test, and tsc's narrowing types the
7317
- // true branch (reads bridge through maybeNarrow's validated
7318
- // extraction, like the typeof tests). Node's Buffer IS a Uint8Array
7319
- // subclass and rides the same bytes kind, so both worlds answer true
7320
- // for Buffer payloads — Node-exact (the bytes kind's other
7321
- // divergences are SEMANTICS.md 45). Catch bindings stay out (their
7322
- // payload is a typed snapshot, not a dyn).
7323
- if (ts.isIdentifier(expr.right) &&
7324
- lowerer.isStdlibGlobal(expr.right, "Uint8Array") &&
7325
- !lowerer.caughtLocalOf(expr.left)) {
7326
- const left = lowerer.lowerExpr(expr.left);
7327
- if (left.type.kind === "dyn") {
7328
- return { kind: "dynTest", test: "bytes", value: left, type: BOOL, loc };
8354
+ const date = lowerer.isStdlibGlobal(expr.right, "Date");
8355
+ const url = lowerer.isStdlibGlobal(expr.right, "URL");
8356
+ if ((date || url) && !lowerer.caughtLocalOf(expr.left)) {
8357
+ const value = lowerer.lowerExpr(expr.left);
8358
+ if (value.type.kind === "dyn")
8359
+ return { kind: "libCall", fn: date ? "dyn.nativeDateIs" : "dyn.nativeUrlIs", args: [value], type: BOOL, loc };
8360
+ if (value.type.kind === "union")
8361
+ return { kind: "libCall", fn: date ? "dyn.nativeDateIs" : "dyn.nativeUrlIs",
8362
+ args: [lowerer.coerceInto(expr.left, value, DYN)], type: BOOL, loc };
8363
+ return { kind: "seqExpr", stmts: [{ kind: "exprStmt", expr: value, loc }],
8364
+ result: { kind: "boolLit", value: value.type.kind === (date ? "date" : "url"), type: BOOL, loc }, type: BOOL, loc };
8365
+ }
8366
+ const webBrand = ["Request", "Response", "Headers"].find((name) => lowerer.isStdlibGlobal(expr.right, name));
8367
+ if (webBrand)
8368
+ return { kind: "libCall", fn: "fetch.webIs", args: [lowerer.lowerExprExpecting(expr.left, DYN),
8369
+ { kind: "strLit", value: webBrand, type: STRING, loc }], type: BOOL, loc };
8370
+ if (lowerer.isStdlibGlobal(expr.right, "ReadableStream")) {
8371
+ const value = lowerer.lowerExprExpecting(expr.left, DYN);
8372
+ return { kind: "libCall", fn: "fetch.streamIs", args: [value], type: BOOL, loc };
8373
+ }
8374
+ // Unknown storage retains each numeric typed array's exact native brand.
8375
+ if (!lowerer.caughtLocalOf(expr.left)) {
8376
+ const elem = Object.keys(BYTES_ELEMENT_NAME).find((kind) => lowerer.isStdlibGlobal(expr.right, BYTES_ELEMENT_NAME[kind]));
8377
+ if (elem !== undefined) {
8378
+ const left = lowerer.lowerExpr(expr.left);
8379
+ if (left.type.kind === "dyn") {
8380
+ return { kind: "dynTest", test: "bytes", bytesElem: elem, value: left, type: BOOL, loc };
8381
+ }
8382
+ return { kind: "dynTest", test: "bytes", bytesElem: elem,
8383
+ value: lowerer.coerceInto(expr.left, left, DYN), type: BOOL, loc };
7329
8384
  }
7330
8385
  }
7331
8386
  // `x instanceof RegExp` over a union with a regex arm (the
@@ -7338,6 +8393,8 @@ export function lowerInstanceOf(lowerer, expr, loc) {
7338
8393
  lowerer.isStdlibGlobal(expr.right, "RegExp") &&
7339
8394
  !lowerer.caughtLocalOf(expr.left)) {
7340
8395
  const left = lowerer.lowerExpr(expr.left);
8396
+ if (left.type.kind === "dyn")
8397
+ return { kind: "libCall", fn: "dyn.nativeRegexIs", args: [left], type: BOOL, loc };
7341
8398
  if (left.type.kind === "union") {
7342
8399
  const def = lowerer.unions.get(left.type.unionId);
7343
8400
  const tag = def ? def.arms.findIndex((a) => a.kind === "regex") : -1;
@@ -7345,9 +8402,7 @@ export function lowerInstanceOf(lowerer, expr, loc) {
7345
8402
  return { kind: "unionIsTag", unionId: left.type.unionId, tag, negated: false, value: left, type: BOOL, loc };
7346
8403
  }
7347
8404
  }
7348
- if (left.type.kind === "regex" && left.kind === "varRef" && ts.isIdentifier(expr.left)) {
7349
- return { kind: "boolLit", value: true, type: BOOL, loc };
7350
- }
8405
+ return { kind: "libCall", fn: "dyn.nativeRegexIs", args: [lowerer.coerceInto(expr.left, left, DYN)], type: BOOL, loc };
7351
8406
  }
7352
8407
  // `x instanceof X` where X is a class VALUE (a classval-typed
7353
8408
  // binding): the target is DYNAMIC — a hierarchy target reads its
@@ -7355,7 +8410,7 @@ export function lowerInstanceOf(lowerer, expr, loc) {
7355
8410
  // STANDALONE target class has exactly one possible runtime value
7356
8411
  // (itself — no descendants can flow into the slot), so the answer
7357
8412
  // folds statically exactly like the named-target folds below.
7358
- const rhsClassval = lowerer.mapTypeOf(lowerer.typeOf(expr.right));
8413
+ const rhsClassval = storedClassValueType(lowerer, expr.right);
7359
8414
  if (rhsClassval?.kind === "classval" && !lowerer.caughtLocalOf(expr.left)) {
7360
8415
  const targetInfo = lowerer.classes.get(rhsClassval.className);
7361
8416
  if (!targetInfo) {
@@ -7366,13 +8421,18 @@ export function lowerInstanceOf(lowerer, expr, loc) {
7366
8421
  lowerer.unsupported("SC1090", expr, "'instanceof' against a class value whose class has no lowering (the class declaration itself was rejected — see its own diagnostic)");
7367
8422
  }
7368
8423
  const left = lowerer.lowerExpr(expr.left);
8424
+ if (left.type.kind === "dyn") {
8425
+ const classValue = lowerer.lowerExpr(expr.right);
8426
+ return classInstanceOf(lowerer, left, targetInfo, loc, classValue);
8427
+ }
7369
8428
  if (left.type.kind !== "object") {
7370
8429
  lowerer.unsupported("SC1090", expr, "'instanceof' on values other than class instances (narrow union-typed values first)");
7371
8430
  }
7372
8431
  const lhsInfo = lowerer.classes.get(left.type.className);
7373
8432
  if (!lhsInfo)
7374
8433
  throw new InternalCompilerError(`lowerer bug: unknown class ${left.type.className}`);
7375
- if (lowerer.inHierarchy(targetInfo) && lowerer.inHierarchy(lhsInfo)) {
8434
+ if ((lowerer.inHierarchy(targetInfo) && lowerer.inHierarchy(lhsInfo)) ||
8435
+ (targetInfo.localClass !== undefined && lhsInfo.localClass !== undefined)) {
7376
8436
  const classValue = lowerer.lowerExpr(expr.right);
7377
8437
  if (classValue.type.kind !== "classval")
7378
8438
  lowerer.badType(expr.right, lowerer.typeOf(expr.right));
@@ -7389,6 +8449,11 @@ export function lowerInstanceOf(lowerer, expr, loc) {
7389
8449
  }
7390
8450
  lowerer.unsupported("SC1090", expr, "statically-decided 'instanceof' on computed operands (bind the values to variables first)");
7391
8451
  }
8452
+ const constructor = tryLowerExpression(lowerer, expr.right);
8453
+ if (constructor?.type.kind === "dyn" && isJsSourceFile(expr.getSourceFile())) {
8454
+ const value = lowerer.coerceInto(expr.left, lowerer.lowerExpr(expr.left), DYN);
8455
+ return checkedClassInstanceOf(lowerer, value, constructor, loc);
8456
+ }
7392
8457
  lowerer.unsupported("SC1090", expr.right, "'instanceof' right-hand sides other than classes declared in the program");
7393
8458
  }
7394
8459
  // A catch binding on the left: the runtime test against the class's
@@ -7419,6 +8484,9 @@ export function lowerInstanceOf(lowerer, expr, loc) {
7419
8484
  // (name strings are user-writable), so `u instanceof TypeError` keeps
7420
8485
  // the fence. Reads past the narrow bridge through maybeNarrow's
7421
8486
  // validated %Error extraction. SEMANTICS.md 67.
8487
+ if (left.type.kind === "dyn" && !target.def.runtime && !target.builtinError && !target.builtinEmitter && !target.builtinStream && !target.localClass) {
8488
+ return classInstanceOf(lowerer, left, target, loc);
8489
+ }
7422
8490
  if (left.type.kind === "dyn" && target.def.name === "%Error") {
7423
8491
  return { kind: "dynTest", test: "error", value: left, type: BOOL, loc };
7424
8492
  }
@@ -7441,8 +8509,47 @@ export function lowerInstanceOf(lowerer, expr, loc) {
7441
8509
  if (left.type.kind === "dyn") {
7442
8510
  lowerer.unsupported("SC1090", expr, `'instanceof ${target.def.name.replace(/^%/, "")}' on 'unknown' values (only the Error classes answer — test 'instanceof Error' and read '.name')`);
7443
8511
  }
8512
+ if (left.type.kind === "union") {
8513
+ const unionId = left.type.unionId;
8514
+ const arms = lowerer.unions.get(unionId)?.arms;
8515
+ // Ordinary records and scalar values do not carry a class prototype.
8516
+ // Dynamic/opaque runtime objects keep their existing instanceof fences.
8517
+ if (arms?.every((arm) => arm.kind === "object" || arm.kind === "record" ||
8518
+ arm.kind === "f64" || arm.kind === "string" || arm.kind === "bool" ||
8519
+ arm.kind === "bigint" || arm.kind === "symbol" || isUnitType(arm))) {
8520
+ const key = `instanceof.union:${unionId}:${target.def.name}`;
8521
+ let helper = lowerer.widthHelpers.get(key);
8522
+ if (!helper) {
8523
+ helper = `%instanceof.union.${lowerer.widthHelpers.size}`;
8524
+ lowerer.widthHelpers.set(key, helper);
8525
+ const value = { kind: "varRef", localId: "value.0", type: left.type, loc };
8526
+ const body = [];
8527
+ arms.forEach((arm, tag) => {
8528
+ if (arm.kind !== "object")
8529
+ return;
8530
+ const lhsInfo = lowerer.classes.get(arm.className);
8531
+ if (!lhsInfo)
8532
+ throw new InternalCompilerError(`lowerer bug: unknown class ${arm.className}`);
8533
+ const result = lowerer.inHierarchy(lhsInfo) && lowerer.inHierarchy(target)
8534
+ ? { kind: "instanceOf", value: { kind: "unionNarrow", unionId, tag, value, type: arm, loc }, className: target.def.name, type: BOOL, loc }
8535
+ : { kind: "boolLit", value: arm.className === target.def.name || lowerer.isSubclassOf(arm.className, target.def.name), type: BOOL, loc };
8536
+ body.push({
8537
+ kind: "if", cond: { kind: "unionIsTag", unionId, tag, negated: false, value, type: BOOL, loc },
8538
+ then: [{ kind: "return", value: result, loc }], else_: null, loc,
8539
+ });
8540
+ });
8541
+ body.push({ kind: "return", value: { kind: "boolLit", value: false, type: BOOL, loc }, loc });
8542
+ lowerer.liftedFns.push({
8543
+ name: helper, params: [{ localId: "value.0", name: "value", type: left.type }], returnType: BOOL,
8544
+ locals: [{ id: "value.0", name: "value", type: left.type, mutable: false }], body, loc,
8545
+ });
8546
+ }
8547
+ return { kind: "call", callee: helper, args: [left], type: BOOL, loc };
8548
+ }
8549
+ }
7444
8550
  if (left.type.kind !== "object") {
7445
- lowerer.unsupported("SC1090", expr, "'instanceof' on values other than class instances (narrow union-typed values first)");
8551
+ return { kind: "seqExpr", stmts: [{ kind: "exprStmt", expr: left, loc }],
8552
+ result: { kind: "boolLit", value: false, type: BOOL, loc }, type: BOOL, loc };
7446
8553
  }
7447
8554
  const lhsInfo = lowerer.classes.get(left.type.className);
7448
8555
  if (!lhsInfo)
@@ -7613,6 +8720,12 @@ function lowerPrivateIn(lowerer, expr, priv, loc) {
7613
8720
  * and dyn/unknown stay fenced. Keys are literal strings — a computed key
7614
8721
  * over a shape would need the runtime key table. */
7615
8722
  function lowerInExpression(lowerer, expr, loc) {
8723
+ if (lowerer.isStdlibGlobal(expr.right, "process") && lowerer.foldedStringKeyOf(expr.left) === "hrtime") {
8724
+ return { kind: "boolLit", value: true, type: BOOL, loc };
8725
+ }
8726
+ const globalKey = globalSymbolKey(lowerer, expr.right, expr.left);
8727
+ if (globalKey)
8728
+ return { kind: "libCall", fn: "dyn.globalSymbolHas", args: [globalKey], type: BOOL, loc };
7616
8729
  // `#name in obj` — the ergonomic brand check (ES2022) — resolves
7617
8730
  // before any string-key machinery: the left operand is a private
7618
8731
  // NAME, not a value.
@@ -7664,14 +8777,13 @@ function lowerInExpression(lowerer, expr, loc) {
7664
8777
  // `in` shares the coercion).
7665
8778
  {
7666
8779
  const probed = tryLowerExpression(lowerer, expr.right);
7667
- if (probed?.type.kind === "dyn") {
7668
- let k = lowerer.lowerExpr(expr.left); // JS order: the key evaluates first
7669
- if (k.type.kind === "f64" || k.type.kind === "string" || k.type.kind === "dyn") {
7670
- k = lowerer.ensureString(k, expr.left);
7671
- const recvD = lowerer.lowerExpr(expr.right);
7672
- if (recvD.type.kind === "dyn") {
7673
- return { kind: "libCall", fn: "dyn.hasKey", args: [recvD, k], type: BOOL, loc };
7674
- }
8780
+ if (probed && lowerer.dynConvertible(probed.type)) {
8781
+ const k = lowerer.lowerExpr(expr.left); // JS order: the key evaluates first
8782
+ if (lowerer.dynConvertible(k.type)) {
8783
+ const boxed = lowerer.coerceToExpected(k, DYN);
8784
+ const keyLocal = lowerer.declareHiddenLocal("%inKey", DYN);
8785
+ return { kind: "seqExpr", stmts: [{ kind: "varDecl", localId: keyLocal.id, init: boxed, loc }],
8786
+ 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 };
7675
8787
  }
7676
8788
  }
7677
8789
  }
@@ -7697,7 +8809,18 @@ function lowerInExpression(lowerer, expr, loc) {
7697
8809
  };
7698
8810
  return { kind: "unionIsTag", unionId: envType.unionId, tag: undefTag, negated: true, value: read, type: BOOL, loc };
7699
8811
  }
7700
- const recv = lowerer.lowerExpr(expr.right);
8812
+ let recv = lowerer.lowerExpr(expr.right);
8813
+ const classInfo = recv.type.kind === "classval" ? lowerer.classes.get(recv.type.className) : undefined;
8814
+ if ((classInfo && (classInfo.callableBase || hasRuntimeStatics(classInfo))) ||
8815
+ (isJsSourceFile(expr.getSourceFile()) && recv.type.kind === "func" && lowerer.dynConvertible(recv.type))) {
8816
+ recv = lowerer.coerceToExpected(recv, DYN);
8817
+ }
8818
+ const siteType = lowerer.mapTypeOf(lowerer.typeOf(expr.right));
8819
+ if (recv.type.kind === "union" && siteType?.kind === "union" && !typeEquals(recv.type, siteType)) {
8820
+ const helper = lowerer.narrowedRetagHelper(expr.right, recv.type.unionId, siteType.unionId, loc);
8821
+ if (helper)
8822
+ recv = { kind: "call", callee: helper, args: [recv], type: siteType, loc };
8823
+ }
7701
8824
  // Error-rooted receivers (builtin or user subclass — the isErrnoException
7702
8825
  // predicate's `err instanceof Error && "code" in err` shape): `code`
7703
8826
  // answers from the runtime error's code slot (stamped by fs/system/
@@ -7722,6 +8845,9 @@ function lowerInExpression(lowerer, expr, loc) {
7722
8845
  while (info && info.base)
7723
8846
  info = info.base;
7724
8847
  if (info?.def.name === "%Error") {
8848
+ if (key === "cause") {
8849
+ return { kind: "libCall", fn: "error.hasCause", args: [recv], type: BOOL, loc };
8850
+ }
7725
8851
  if (key === "code") {
7726
8852
  const codeType = lowerer.envValueType();
7727
8853
  if (codeType.kind !== "union")
@@ -7935,8 +9061,8 @@ function lowerRuntimeKeyIn(lowerer, expr, loc) {
7935
9061
  }
7936
9062
  return { kind: "call", callee: helper, args: [keyIr, recv], type: BOOL, loc };
7937
9063
  }
7938
- /** A regex literal `/ab+c/gi` → regexLit (interned per (pattern, flags)
7939
- * by the backend). The TS parser has already syntax-checked the literal;
9064
+ /** A regex literal `/ab+c/gi` → regexLit (fresh state per evaluation,
9065
+ * shared bytecode per (pattern, flags) pair). The TS parser has already syntax-checked the literal;
7940
9066
  * what remains here is the flag-alphabet fence (d and v are
7941
9067
  * declared-valid TS flags outside this slice). Named capture groups
7942
9068
  * `(?<name>...)` and `\k<name>` backreferences compile — libregexp
@@ -8077,14 +9203,52 @@ function constInitializerOf(lowerer, ident) {
8077
9203
  * caller keeps its fence), or the groups list (possibly empty — a
8078
9204
  * traced regex WITHOUT named groups is the Node-undefined case). */
8079
9205
  export function matchResultNamedGroupsOf(lowerer, e) {
9206
+ let producer = e;
9207
+ while (ts.isParenthesizedExpression(producer) || ts.isNonNullExpression(producer))
9208
+ producer = producer.expression;
9209
+ if (ts.isIdentifier(producer)) {
9210
+ const init = constInitializerOf(lowerer, producer);
9211
+ if (init !== null)
9212
+ return matchResultNamedGroupsOf(lowerer, init);
9213
+ }
8080
9214
  const reExpr = matchProducerRegexOf(lowerer, e);
8081
9215
  if (reExpr === null)
8082
9216
  return null;
9217
+ if (ts.isCallExpression(producer) && ts.isPropertyAccessExpression(producer.expression) &&
9218
+ producer.expression.name.text === "match") {
9219
+ const flags = staticRegexFlagsOf(lowerer, reExpr);
9220
+ if (flags === null)
9221
+ return null;
9222
+ // Global String.match returns whole matches without a groups
9223
+ // property, even when the pattern contains named captures.
9224
+ if (flags.includes("g"))
9225
+ return [];
9226
+ }
8083
9227
  const pattern = staticRegexPatternOf(lowerer, reExpr);
8084
9228
  if (pattern === null)
8085
9229
  return null;
8086
9230
  return namedCaptureGroupsOfPattern(pattern);
8087
9231
  }
9232
+ function staticRegexFlagsOf(lowerer, node) {
9233
+ let expr = node;
9234
+ while (ts.isParenthesizedExpression(expr) || ts.isNonNullExpression(expr))
9235
+ expr = expr.expression;
9236
+ if (ts.isRegularExpressionLiteral(expr))
9237
+ return expr.text.slice(expr.text.lastIndexOf("/") + 1);
9238
+ if (ts.isIdentifier(expr)) {
9239
+ const init = constInitializerOf(lowerer, expr);
9240
+ return init !== null ? staticRegexFlagsOf(lowerer, init) : null;
9241
+ }
9242
+ if (ts.isNewExpression(expr) && ts.isIdentifier(expr.expression) &&
9243
+ lowerer.isStdlibGlobal(expr.expression, "RegExp")) {
9244
+ const flags = expr.arguments?.[1];
9245
+ if (!flags)
9246
+ return "";
9247
+ if (ts.isStringLiteral(flags) || ts.isNoSubstitutionTemplateLiteral(flags))
9248
+ return flags.text;
9249
+ }
9250
+ return null;
9251
+ }
8088
9252
  /** The regex EXPRESSION whose match produced `e` (see
8089
9253
  * matchResultNamedGroupsOf for the traced shapes). */
8090
9254
  function matchProducerRegexOf(lowerer, e) {
@@ -8366,12 +9530,24 @@ export function lowerGroupsProjection(lowerer, recv, groups, loc) {
8366
9530
  }
8367
9531
  return { kind: "call", callee: helper, args: [recv], type: recordT, loc };
8368
9532
  }
8369
- /** Field read `obj.f` on class-instance and record receivers, through the
8370
- * shared FieldTarget union (fieldGet / recordGet). Bound method references
8371
- * on classes are rejected specifically; func-typed record fields are
8372
- * ordinary closure values, so bare references to them work (unlike class
8373
- * methods, which have no bound-value form). */
9533
+ /** Field reads use the shared FieldTarget union. Native method reads select
9534
+ * an interned unbound callable; invocation supplies its receiver separately. */
8374
9535
  export function lowerFieldRead(lowerer, expr) {
9536
+ // A checked receiver already owns its property table. An ambient
9537
+ // generic method signature must not divert its value into native
9538
+ // object-literal specialization (Array.prototype.map is one example).
9539
+ if (ts.isIdentifier(expr.expression)) {
9540
+ const stored = lowerer.peekLocal(expr.expression) ?? lowerer.globalOf(expr.expression);
9541
+ if (stored?.type.kind === "dyn") {
9542
+ const value = lowerer.lowerExpr(expr.expression);
9543
+ if (value.type.kind === "dyn")
9544
+ return {
9545
+ kind: "dynKeyGet", value,
9546
+ key: { kind: "strLit", value: expr.name.text, type: STRING, loc: locOf(expr.name) },
9547
+ ...(expr.questionDotToken || hasOptionalChainGuard(expr.expression) ? { optional: true } : {}), type: DYN, loc: locOf(expr),
9548
+ };
9549
+ }
9550
+ }
8375
9551
  const target = lowerer.fieldTarget(expr);
8376
9552
  if (target)
8377
9553
  return lowerer.fieldGetExpr(target, locOf(expr), expr);
@@ -8388,7 +9564,9 @@ export function lowerFieldRead(lowerer, expr) {
8388
9564
  if (receiverIr?.kind === "object" &&
8389
9565
  (lowerer.findMethodOn(lowerer.classes.get(receiverIr.className) ?? null, expr.name.text) ||
8390
9566
  findGenericMethodOn(lowerer, lowerer.classes.get(receiverIr.className) ?? null, expr.name.text))) {
8391
- lowerer.unsupported("SC1090", expr, `bound method references (call '${expr.name.text}' directly)`);
9567
+ const info = lowerer.classes.get(receiverIr.className);
9568
+ if (info)
9569
+ return lowerClassMethodValue(lowerer, expr, info);
8392
9570
  }
8393
9571
  // An object-literal GENERIC method as a VALUE (`o.m` — the member is
8394
9572
  // excluded from the record shape): the pinned-value rule verbatim when
@@ -8448,16 +9626,22 @@ export function lowerFieldRead(lowerer, expr) {
8448
9626
  * and `switch (r.kind)` work without dedicated test nodes. Anything else
8449
9627
  * on a union receiver is rejected specifically (narrow first). */
8450
9628
  export function lowerUnionProperty(lowerer, expr) {
8451
- if (expr.questionDotToken)
9629
+ if (lowerer.chainBlocked(expr))
8452
9630
  return null;
8453
9631
  const receiverIr = lowerer.mapTypeOf(lowerer.typeOf(expr.expression));
8454
9632
  if (receiverIr?.kind !== "union")
8455
9633
  return null;
8456
- // Lower the receiver FIRST and read its actual IR union: a partially
8457
- // narrowed receiver (checker type = a SUB-union of the binding's union)
8458
- // stays the full union at runtime, and the tag switch must cover the
8459
- // full union's arms.
8460
- const value = lowerer.lowerExpr(expr.expression);
9634
+ // Lower the receiver first: its runtime value can still carry the full
9635
+ // binding union even when the checker has removed several variants.
9636
+ let value = lowerer.lowerExpr(expr.expression);
9637
+ // A guard can remove several variants without selecting one record.
9638
+ // Materialize that sub-union before looking for common fields; arms
9639
+ // ruled out by the guard must not veto a field shared by the survivors.
9640
+ if (value.type.kind === "union" && !typeEquals(value.type, receiverIr)) {
9641
+ const helper = lowerer.narrowedRetagHelper(expr.expression, value.type.unionId, receiverIr.unionId, locOf(expr));
9642
+ if (helper)
9643
+ value = { kind: "call", callee: helper, args: [value], type: receiverIr, loc: locOf(expr) };
9644
+ }
8461
9645
  // A checker-union receiver whose VALUE lowered to a plain RECORD (the
8462
9646
  // merged-signature fiction — `runner(cmd, args)` where runner joined
8463
9647
  // a structural runner type with spawnSync's, and the local adopted
@@ -8553,20 +9737,29 @@ export function lowerUnionProperty(lowerer, expr) {
8553
9737
  const keyed = lowerUnionKeyedRead(lowerer, expr, value.type.unionId, value, key, field);
8554
9738
  if (keyed)
8555
9739
  return keyed;
9740
+ // JavaScript overloads commonly inspect a class brand on a scalar-or-
9741
+ // instance argument (e.g. Matrix4.makeTranslation). Preserve primitive
9742
+ // missing-property semantics and dispatch class data through its native
9743
+ // capsule instead of demanding a field shared by every union arm.
9744
+ if (isJsSourceFile(expr.getSourceFile()) && lowerer.dynConvertible(value.type)) {
9745
+ return { kind: "dynKeyGet", value: lowerer.coerceToExpected(value, DYN), key,
9746
+ ...(hasOptionalChainGuard(expr.expression) ? { optional: true } : {}), type: DYN, loc: locOf(expr) };
9747
+ }
8556
9748
  lowerer.unsupported("SC1090", expr, `reading '${field}' on a union-typed value (every arm must be an object/record ` +
8557
9749
  `with a same-typed field '${field}'; ` +
8558
9750
  `${NARROW_FIRST})`);
8559
9751
  }
8560
9752
  /** The unionDisc generalization: a keyed read on a union receiver whose
8561
9753
  * arms answer DIFFERENT (but joinable) types. Each arm contributes its
8562
- * declared answer — a declared field's type (literal keys), an
9754
+ * declared answer — an array element's type (number keys), a declared field's type (literal keys), an
8563
9755
  * index-signature arm's value type (plus its declared fields' types for
8564
9756
  * runtime keys, which reach them through the keyed-read helper), and
8565
9757
  * UNDEFINED for unit arms (reachable only through optional-chain tails,
8566
9758
  * where JS short-circuits to undefined; a unit arm the checker narrowed
8567
9759
  * away is simply unreachable). The result type is the JOIN of those
8568
- * answers; every arm's answer must be the join itself or one of its
8569
- * arms (sub-union RE-TAGGING between distinct unions stays fenced).
9760
+ * answers; every arm's answer must be the join itself, one of its
9761
+ * arms, or a sub-union whose payload representations are unchanged.
9762
+ * Array reads retain the ordinary invalid-index trap policy.
8570
9763
  * Returns null when any arm cannot answer — the caller owns the fence
8571
9764
  * message. The caller (the property/element dispatch) maybeNarrows. */
8572
9765
  function lowerUnionKeyedRead(lowerer, expr, unionId, value, key, literalField) {
@@ -8602,6 +9795,11 @@ function lowerUnionKeyedRead(lowerer, expr, unionId, value, key, literalField) {
8602
9795
  push(UNDEFINED_T);
8603
9796
  continue;
8604
9797
  }
9798
+ if (key.type.kind === "f64") {
9799
+ if (arm.kind !== "array" || !pushAnswer(arm.elem))
9800
+ return null;
9801
+ continue;
9802
+ }
8605
9803
  if (arm.kind !== "record")
8606
9804
  return null;
8607
9805
  const shape = lowerer.shapes.get(arm.shapeId);
@@ -8631,13 +9829,11 @@ function lowerUnionKeyedRead(lowerer, expr, unionId, value, key, literalField) {
8631
9829
  if (joinArms.length === 0)
8632
9830
  return null;
8633
9831
  // The join must be a BUILDABLE union: arm kinds the union invariants
8634
- // admit (no map/dyn/jsval/generator arms; func arms only beside
8635
- // func/unit siblings — unionFuncSetArmsOk, the validator's rule). A
8636
- // join mixing, say, a func answer with data answers has no union to
8637
- // surface as; the caller owns the fence message.
9832
+ // admit (no dyn/jsval/generator arms; containers only beside unit
9833
+ // siblings). The caller owns the diagnostic for a refused join.
8638
9834
  if (joinArms.length > 1 &&
8639
- (!unionFuncSetArmsOk(joinArms) ||
8640
- joinArms.some((a) => a.kind === "map" || a.kind === "dyn" || a.kind === "jsval" || a.kind === "generator" || a.kind === "caught"))) {
9835
+ (!unionContainerArmsOk(joinArms) ||
9836
+ joinArms.some((a) => a.kind === "dyn" || a.kind === "jsval" || a.kind === "generator" || a.kind === "caught"))) {
8641
9837
  return null;
8642
9838
  }
8643
9839
  joinArms.sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1));
@@ -8645,13 +9841,19 @@ function lowerUnionKeyedRead(lowerer, expr, unionId, value, key, literalField) {
8645
9841
  // Pass 2: every arm's answer must SURFACE as the join, and index arms
8646
9842
  // must pass the single-record keyed-read constraints (the helper is
8647
9843
  // shared with recordKeyGet, its missing-key policy included).
8648
- const surfaces = (t) => typeEquals(t, type) || (type.kind === "union" && lowerer.armTag(type.unionId, t) >= 0);
9844
+ const surfaces = (t) => typeEquals(t, type) || (type.kind === "union" && lowerer.armTag(type.unionId, t) >= 0) ||
9845
+ exactUnionWidening(lowerer, t, type);
8649
9846
  for (const arm of def.arms) {
8650
9847
  if (isUnitType(arm)) {
8651
9848
  if (!(type.kind === "union" && lowerer.armTag(type.unionId, UNDEFINED_T) >= 0))
8652
9849
  return null;
8653
9850
  continue;
8654
9851
  }
9852
+ if (key.type.kind === "f64") {
9853
+ if (arm.kind !== "array" || !surfaces(arm.elem))
9854
+ return null;
9855
+ continue;
9856
+ }
8655
9857
  if (arm.kind !== "record")
8656
9858
  return null;
8657
9859
  const shape = lowerer.shapes.get(arm.shapeId);
@@ -8669,6 +9871,75 @@ function lowerUnionKeyedRead(lowerer, expr, unionId, value, key, literalField) {
8669
9871
  }
8670
9872
  return { kind: "unionKeyGet", unionId, key, value, type, loc: locOf(expr) };
8671
9873
  }
9874
+ /** A structural TypeScript view does not change a nominal object's layout
9875
+ * or accessor dispatch. Resolve the property from the represented class;
9876
+ * the optional checker view must not turn its getter into a record read. */
9877
+ function classFieldTarget(lowerer, receiverNode, receiverType, fieldName, saved) {
9878
+ const info = lowerer.classes.get(receiverType.className);
9879
+ if (!info) {
9880
+ // A receiver typed as a class whose collection deferred: the
9881
+ // deferred diagnostics are what explains the miss.
9882
+ lowerer.flushDeferredClass(receiverType.className);
9883
+ return null;
9884
+ }
9885
+ const fieldType = info.fields.get(fieldName);
9886
+ const lowerObjectReceiver = () => {
9887
+ let obj = saved ?? lowerer.lowerExpr(receiverNode);
9888
+ if (obj.type.kind === "union") {
9889
+ const helper = lowerer.narrowedArmHelper(obj.type.unionId, receiverType, locOf(receiverNode));
9890
+ obj = helper
9891
+ ? { kind: "call", callee: helper, args: [obj], type: receiverType, loc: locOf(receiverNode) }
9892
+ : lowerer.maybeNarrow(obj, receiverNode);
9893
+ }
9894
+ return obj.type.kind === "dyn" ? lowerer.coerceInto(receiverNode, obj, receiverType) : obj;
9895
+ };
9896
+ if (fieldName === "cause" && receiverType.className !== "%DOMException") {
9897
+ let root = info;
9898
+ while (root.base)
9899
+ root = root.base;
9900
+ if (root.def.name === "%Error") {
9901
+ return { container: "errorCause", obj: lowerObjectReceiver(), field: "cause", fieldType: DYN };
9902
+ }
9903
+ }
9904
+ if (fieldType) {
9905
+ const obj = lowerObjectReceiver();
9906
+ return { container: "class", obj, className: receiverType.className, field: fieldName, fieldType };
9907
+ }
9908
+ // Accessor property: either half declared anywhere on the chain
9909
+ // makes the name an accessor target (fields and accessors share a
9910
+ // namespace — tsc rejects mixing them, so the halves agree on kind).
9911
+ const getF = lowerer.findMethodOn(info, `get:${fieldName}`);
9912
+ const setF = lowerer.findMethodOn(info, `set:${fieldName}`);
9913
+ if (getF || setF) {
9914
+ const obj = lowerObjectReceiver();
9915
+ return {
9916
+ container: "accessor",
9917
+ obj,
9918
+ className: receiverType.className,
9919
+ field: fieldName,
9920
+ fieldType: getF ? getF.sig.ret : setF.sig.params[0].type,
9921
+ };
9922
+ }
9923
+ return null;
9924
+ }
9925
+ /** A structural assertion can leave the value in its original class
9926
+ * representation, including an unchecked array read's undefined arm. Only
9927
+ * a single nominal payload has an unambiguous property layout. Validate
9928
+ * that payload before dispatching a field or accessor; an absent receiver
9929
+ * must never reach the backend as an object pointer. */
9930
+ function hasClassPayload(lowerer, type) {
9931
+ return type.kind === "object" || type.kind === "union" &&
9932
+ (lowerer.unions.get(type.unionId)?.arms.some((arm) => arm.kind === "object") ?? false);
9933
+ }
9934
+ function representedClassFieldTarget(lowerer, receiverNode, fieldName, value) {
9935
+ const arms = value.type.kind === "union"
9936
+ ? lowerer.unions.get(value.type.unionId)?.arms.filter((arm) => !isUnitType(arm))
9937
+ : [value.type];
9938
+ const objectType = arms?.length === 1 ? arms[0] : null;
9939
+ return objectType?.kind === "object"
9940
+ ? classFieldTarget(lowerer, receiverNode, objectType, fieldName, value)
9941
+ : null;
9942
+ }
8672
9943
  /** Recognizes `obj.field` as an assignable field target: receiver is a
8673
9944
  * known class instance OR a record, and the member is a field or (class
8674
9945
  * receivers) a declared accessor property. Returns the pieces of a
@@ -8676,55 +9947,26 @@ function lowerUnionKeyedRead(lowerer, expr, unionId, value, key, literalField) {
8676
9947
  export function fieldTarget(lowerer, access) {
8677
9948
  if (lowerer.chainBlocked(access))
8678
9949
  return null;
8679
- const receiverIr = lowerer.mapTypeOf(lowerer.typeOf(access.expression));
9950
+ if (access.name.text === "stackTraceLimit" && lowerer.isStdlibGlobal(access.expression, "Error")) {
9951
+ return { container: "errorStackLimit", obj: { kind: "numLit", value: 0, type: F64, loc: locOf(access) }, field: "stackTraceLimit", fieldType: F64 };
9952
+ }
9953
+ const stored = ts.isIdentifier(access.expression)
9954
+ ? lowerer.peekLocal(access.expression)?.type ?? lowerer.globalOf(access.expression)?.type : undefined;
9955
+ const receiverIr = stored?.kind === "record" || stored?.kind === "object" ? stored : lowerer.mapTypeOf(lowerer.typeOf(access.expression));
8680
9956
  if (receiverIr?.kind === "object") {
8681
- const info = lowerer.classes.get(receiverIr.className);
8682
- if (!info) {
8683
- // A receiver typed as a class whose collection deferred: the
8684
- // deferred diagnostics are what explains the miss.
8685
- lowerer.flushDeferredClass(receiverIr.className);
8686
- return null;
8687
- }
8688
- const fieldType = info.fields.get(access.name.text);
8689
- const lowerObjectReceiver = () => {
8690
- let obj = lowerer.lowerExpr(access.expression);
8691
- if (obj.type.kind === "union" && lowerer.armTag(obj.type.unionId, UNDEFINED_T) >= 0) {
8692
- const present = lowerer.stripUndefinedArm(obj.type);
8693
- const helper = present.kind === "object"
8694
- ? lowerer.narrowedArmHelper(obj.type.unionId, present, locOf(access.expression))
8695
- : null;
8696
- obj = helper
8697
- ? { kind: "call", callee: helper, args: [obj], type: present, loc: locOf(access.expression) }
8698
- : lowerer.maybeNarrow(obj, access.expression);
8699
- }
8700
- return obj;
8701
- };
8702
- if (fieldType) {
8703
- const obj = lowerObjectReceiver();
8704
- return { container: "class", obj, className: receiverIr.className, field: access.name.text, fieldType };
8705
- }
8706
- // Accessor property: either half declared anywhere on the chain
8707
- // makes the name an accessor target (fields and accessors share a
8708
- // namespace — tsc rejects mixing them, so the halves agree on kind).
8709
- const getF = lowerer.findMethodOn(info, `get:${access.name.text}`);
8710
- const setF = lowerer.findMethodOn(info, `set:${access.name.text}`);
8711
- if (getF || setF) {
8712
- const obj = lowerObjectReceiver();
8713
- return {
8714
- container: "accessor",
8715
- obj,
8716
- className: receiverIr.className,
8717
- field: access.name.text,
8718
- fieldType: getF ? getF.sig.ret : setF.sig.params[0].type,
8719
- };
8720
- }
8721
- return null;
9957
+ return classFieldTarget(lowerer, access.expression, receiverIr, access.name.text);
9958
+ }
9959
+ if (receiverIr?.kind === "union" && hasClassPayload(lowerer, receiverIr)) {
9960
+ return representedClassFieldTarget(lowerer, access.expression, access.name.text, lowerer.lowerExpr(access.expression));
8722
9961
  }
8723
9962
  if (receiverIr?.kind === "record") {
8724
9963
  const shape = lowerer.shapes.get(receiverIr.shapeId);
8725
9964
  const fieldType = shape?.fields.find((f) => f.name === access.name.text)?.type;
8726
9965
  if (fieldType) {
8727
9966
  let obj = lowerer.lowerExpr(access.expression);
9967
+ const nominal = representedClassFieldTarget(lowerer, access.expression, access.name.text, obj);
9968
+ if (nominal)
9969
+ return nominal;
8728
9970
  // A checker-record receiver whose VALUE stayed dyn (the erased
8729
9971
  // all-unknown-fields cast — `(err as { code?: unknown }).code`):
8730
9972
  // decline, and the dyn keyed-read fallback answers.
@@ -8858,7 +10100,7 @@ export function uniqueSymbolKeyOf(lowerer, key) {
8858
10100
  // Module level only: a unique-symbol const inside a FUNCTION is a
8859
10101
  // fresh runtime identity per call — one static slot would conflate
8860
10102
  // what JS keeps distinct.
8861
- if (!ts.isVariableStatement(decl.parent.parent) || !ts.isSourceFile(decl.parent.parent.parent))
10103
+ if (!ts.isVariableStatement(decl.parent?.parent) || !ts.isSourceFile(decl.parent?.parent?.parent))
8862
10104
  return null;
8863
10105
  const init = decl.initializer;
8864
10106
  if (!init || !ts.isCallExpression(init) || init.questionDotToken)
@@ -8891,10 +10133,10 @@ export function symbolFieldInfo(lowerer, expr) {
8891
10133
  lowerer.flushDeferredClass(receiverIr.className);
8892
10134
  return null;
8893
10135
  }
8894
- const key = uniqueSymbolKeyOf(lowerer, expr.argumentExpression);
10136
+ const key = classSymbolKeyOf(lowerer, expr.argumentExpression);
8895
10137
  if (!key)
8896
10138
  return null;
8897
- const field = info.symbolFields?.get(key.sym);
10139
+ const field = info.symbolFields?.get(key.identity);
8898
10140
  if (field === undefined)
8899
10141
  return null;
8900
10142
  const fieldType = info.fields.get(field);
@@ -8916,6 +10158,14 @@ function symbolFieldTarget(lowerer, expr) {
8916
10158
  * tsc-clean (the property types as the setter's param), but Node yields
8917
10159
  * undefined, which these property types cannot represent. */
8918
10160
  export function fieldGetExpr(lowerer, target, loc, blame) {
10161
+ if (target.container === "class" && (target.field === "message" || target.field === "name") && (target.className === "%Error" || lowerer.isSubclassOf(target.className, "%Error")))
10162
+ return errorPropertyRead(lowerer, target.obj, target.field);
10163
+ if (target.container === "errorStackLimit") {
10164
+ return { kind: "libCall", fn: "error.stackLimitGet", args: [], type: F64, loc };
10165
+ }
10166
+ if (target.container === "errorCause") {
10167
+ return { kind: "libCall", fn: "error.cause", args: [target.obj], type: DYN, loc };
10168
+ }
8919
10169
  // A record-shaped CHECKER target whose receiver VALUE lives in the checked-dynamic tree
8920
10170
  // (a JS file-scope object-literal global): the checked-dynamic keyed
8921
10171
  // read — dynKeyGet (a missing key answers the dyn undefined, exactly
@@ -8935,6 +10185,11 @@ export function fieldGetExpr(lowerer, target, loc, blame) {
8935
10185
  if (!getF) {
8936
10186
  lowerer.unsupported("SC1090", blame, `reading a property that has only a setter ('${target.field}' — Node would yield undefined)`);
8937
10187
  }
10188
+ if (!target.field.startsWith("#") && isJsSourceFile(blame.getSourceFile()) && lowerer.dynConvertible(getF.sig.ret)) {
10189
+ const value = { kind: "dynKeyGet", value: lowerer.coerceToExpected(target.obj, DYN),
10190
+ key: { kind: "strLit", value: target.field, type: STRING, loc }, type: DYN, loc };
10191
+ return lowerer.coerceToExpected(value, getF.sig.ret);
10192
+ }
8938
10193
  return lowerer.accessorCall(target.className, `get:${target.field}`, target.obj, [], getF.sig.ret, loc);
8939
10194
  }
8940
10195
  // Record accessor properties: the read IS a call of the %get: closure
@@ -9012,6 +10267,14 @@ export function fieldGetExpr(lowerer, target, loc, blame) {
9012
10267
  * call). A write to a getter-only property never gets here in a clean
9013
10268
  * program (tsc's TS2540 is the fence); the rejection is the backstop. */
9014
10269
  export function fieldSetStmt(lowerer, target, value, loc, blame) {
10270
+ if (target.container === "class" && (target.field === "message" || target.field === "name") && (target.className === "%Error" || lowerer.isSubclassOf(target.className, "%Error")))
10271
+ return errorPropertyWrite(lowerer, target.obj, value, target.field);
10272
+ if (target.container === "errorStackLimit") {
10273
+ return { kind: "exprStmt", expr: { kind: "libCall", fn: "error.stackLimitSet", args: [value], type: VOID, loc }, loc };
10274
+ }
10275
+ if (target.container === "errorCause") {
10276
+ return { kind: "exprStmt", expr: { kind: "libCall", fn: "error.setCause", args: [target.obj, value], type: VOID, loc }, loc };
10277
+ }
9015
10278
  // A record-shaped CHECKER target whose receiver VALUE lives in the checked-dynamic tree:
9016
10279
  // the checked-dynamic keyed write — dyn.keySet (later writes win,
9017
10280
  // insertion order; Node's TypeErrors on non-object receivers), the
@@ -9113,13 +10376,13 @@ export function lowerPromiseRejectCall(lowerer, call, access) {
9113
10376
  // identity survives to catch/unhandledRejection observers), and the
9114
10377
  // result is promise<dyn> when no context names a concrete one.
9115
10378
  const reason = lowerer.lowerExpr(reasonNode);
9116
- if (reason.type.kind === "dyn") {
10379
+ if (reason.type.kind === "dyn" || lowerer.dynConvertible(reason.type) && reason.type.kind !== "object") {
9117
10380
  // The result only ever REJECTS, so the inner type is unobservable:
9118
10381
  // adopt the context's promise type when one names it, else the
9119
10382
  // checker's own Promise<never> reading (promise<void> — exactly the
9120
10383
  // Error-reason form below).
9121
10384
  const resultT = type?.kind === "promise" ? type : { kind: "promise", inner: VOID };
9122
- return { kind: "intrinsic", name: "promise.reject", args: [reason], type: resultT, loc };
10385
+ return { kind: "intrinsic", name: "promise.reject", args: [lowerer.coerceInto(reasonNode, reason, DYN)], type: resultT, loc };
9123
10386
  }
9124
10387
  // An Error reason with NO context-named promise type (a bare
9125
10388
  // top-level `Promise.reject(new Error())` — the unhandled-rejection
@@ -9180,61 +10443,73 @@ export function lowerPromiseAllTupleCall(lowerer, call, access) {
9180
10443
  }
9181
10444
  /** `obj.f op= e` (and `obj.f++` with rhs null ≡ 1) — the element spelling
9182
10445
  * `obj[k] op= e` included when k is a declared symbol-keyed field.
9183
- * Restricted to side-effect-free receivers (identifier or `this`)
9184
- * because the desugar evaluates the receiver twice. */
10446
+ * Save the receiver and old value before the RHS, then write through
10447
+ * that same receiver even if the RHS replaces its original binding. */
9185
10448
  export function lowerFieldCompound(lowerer, access, op, rhsNode, loc) {
10449
+ return { kind: "exprStmt", expr: lowerFieldCompoundValue(lowerer, access, op, rhsNode, loc), loc };
10450
+ }
10451
+ function lowerFieldCompoundValue(lowerer, access, op, rhsNode, loc) {
9186
10452
  if (access.expression.kind === ts.SyntaxKind.SuperKeyword) {
9187
10453
  lowerer.unsupported("SC1090", access, "compound assignment through 'super' (read and write separately)");
9188
10454
  }
9189
- if (!ts.isIdentifier(access.expression) && access.expression.kind !== ts.SyntaxKind.ThisKeyword) {
9190
- lowerer.unsupported("SC1090", access, "compound assignment to fields of computed receivers");
9191
- }
10455
+ const body = [];
10456
+ const save = (value, name) => {
10457
+ const local = lowerer.declareHiddenLocal(name, value.type);
10458
+ body.push({ kind: "varDecl", localId: local.id, init: value, loc: value.loc });
10459
+ return varRef(local.id, value.type, value.loc);
10460
+ };
9192
10461
  // A CHECKED-DYNAMIC receiver (`context.actual++` — test/common's call
9193
10462
  // accounting; dot spelling only — symbol-keyed element targets are
9194
10463
  // static fields): read the member (dynKeyGet), VALIDATE it as a
9195
10464
  // number (dynCheck — a non-number member throws the catchable
9196
10465
  // TypeError where JS would ToNumber-coerce; loud, never a silent
9197
10466
  // NaN — SEMANTICS.md), combine, write back (dyn.keySet). The
9198
- // receiver is an identifier (checked above), so evaluating it for
9199
- // read and write matches JS's once-evaluation observably.
10467
+ // receiver and member read are saved before evaluating the RHS.
9200
10468
  if (ts.isPropertyAccessExpression(access)) {
9201
10469
  const probed = tryLowerExpression(lowerer, access.expression);
9202
10470
  if (probed?.type.kind === "dyn") {
10471
+ const receiver = save(probed, "%compoundReceiver");
9203
10472
  const key = { kind: "strLit", value: access.name.text, type: STRING, loc: locOf(access.name) };
9204
- const read = { kind: "dynKeyGet", key, value: probed, type: DYN, loc };
10473
+ const read = save({ kind: "dynKeyGet", key, value: receiver, type: DYN, loc }, "%compoundOld");
9205
10474
  const cur = { kind: "dynCheck", value: read, type: F64, loc };
9206
- const rhs = rhsNode
10475
+ const rhs = save(rhsNode
9207
10476
  ? lowerer.lowerExpr(rhsNode)
9208
- : { kind: "numLit", value: 1, type: F64, loc };
9209
- if (rhs.type.kind !== "f64")
10477
+ : { kind: "numLit", value: 1, type: F64, loc }, "%compoundRhs");
10478
+ const numericRhs = rhs.type.kind === "dyn"
10479
+ ? { kind: "dynCheck", value: rhs, type: F64, loc: rhs.loc }
10480
+ : lowerOptionalNumber(lowerer, rhs, loc);
10481
+ if (numericRhs.type.kind !== "f64")
9210
10482
  lowerer.unsupported("SC1043", access);
9211
- const value = { kind: "bin", op, left: cur, right: rhs, type: F64, loc };
9212
- const recv2 = lowerer.lowerExpr(access.expression);
10483
+ const value = save({ kind: "bin", op, left: cur, right: numericRhs, type: F64, loc }, "%compoundResult");
9213
10484
  const boxed = { kind: "dynFrom", value, type: DYN, loc };
9214
- return {
10485
+ body.push({
9215
10486
  kind: "exprStmt",
9216
- expr: { kind: "libCall", fn: "dyn.keySet", args: [recv2, { ...key }, boxed], type: VOID, loc },
10487
+ expr: { kind: "libCall", fn: "dyn.keySet", args: [receiver, { ...key }, boxed], type: VOID, loc },
9217
10488
  loc,
9218
- };
10489
+ });
10490
+ return { kind: "seqExpr", stmts: body, result: value, type: value.type, loc };
9219
10491
  }
9220
10492
  }
9221
10493
  const targetOf = () => ts.isPropertyAccessExpression(access) ? lowerer.fieldTarget(access) : symbolFieldTarget(lowerer, access);
9222
10494
  const target = targetOf();
9223
10495
  if (!target)
9224
10496
  lowerer.unsupported("SC1090", access, "compound assignment to unsupported field targets");
9225
- // Through an accessor target this desugars to get, op, set — with the
9226
- // receiver an identifier/this, the observable order matches JS exactly:
9227
- // getter, rhs side effects, setter (verified against Node).
9228
- const read = lowerer.fieldGetExpr(target, locOf(access), access);
9229
- const rhs = rhsNode
10497
+ target.obj = save(target.obj, "%compoundReceiver");
10498
+ // Accessors observe getter, RHS side effects, then setter, all through
10499
+ // the saved receiver.
10500
+ const read = save(lowerer.fieldGetExpr(target, locOf(access), access), "%compoundOld");
10501
+ const rhs = save(rhsNode
9230
10502
  ? lowerer.lowerExpr(rhsNode)
9231
- : { kind: "numLit", value: 1, type: F64, loc };
10503
+ : { kind: "numLit", value: 1, type: F64, loc }, "%compoundRhs");
10504
+ const numericRhs = rhs.type.kind === "dyn" && isJsSourceFile(access.getSourceFile())
10505
+ ? { kind: "dynCheck", value: rhs, type: F64, loc: rhs.loc }
10506
+ : lowerOptionalNumber(lowerer, rhs, loc);
9232
10507
  let value;
9233
10508
  if (op === "+" && target.fieldType.kind === "string") {
9234
10509
  value = { kind: "strConcat", left: read, right: lowerer.ensureString(rhs, rhsNode ?? access), type: STRING, loc };
9235
10510
  }
9236
- else if (target.fieldType.kind === "f64" && rhs.type.kind === "f64") {
9237
- value = { kind: "bin", op, left: read, right: rhs, type: F64, loc };
10511
+ else if (target.fieldType.kind === "f64" && numericRhs.type.kind === "f64") {
10512
+ value = { kind: "bin", op, left: read, right: numericRhs, type: F64, loc };
9238
10513
  }
9239
10514
  else if (target.fieldType.kind === "dyn" &&
9240
10515
  isJsSourceFile(access.getSourceFile()) &&
@@ -9270,9 +10545,9 @@ export function lowerFieldCompound(lowerer, access, op, rhsNode, loc) {
9270
10545
  else {
9271
10546
  lowerer.unsupported("SC1043", access);
9272
10547
  }
9273
- // Second, independent evaluation of the (side-effect-free) receiver.
9274
- const reevaluatedTarget = targetOf();
9275
- return lowerer.fieldSetStmt(reevaluatedTarget, value, loc, access);
10548
+ const result = save(value, "%compoundResult");
10549
+ body.push(lowerer.fieldSetStmt(target, result, loc, access));
10550
+ return { kind: "seqExpr", stmts: body, result, type: result.type, loc };
9276
10551
  }
9277
10552
  /** Stream-rooted receivers' property surface (readableEnded, destroyed,
9278
10553
  * ...): the receiver's mapped class dispatches into lower-stream.ts.
@@ -9298,8 +10573,8 @@ function lowerStreamObjectProperty(lowerer, expr) {
9298
10573
  * Interning makes every reference under one ABI the SAME immortal closure,
9299
10574
  * so `opt.type === String` compares like JS function identity. */
9300
10575
  function primitiveCtorClosure(lowerer, name, loc, dynamicInput) {
9301
- const ret = name === "String" ? STRING : name === "Number" ? F64 : BOOL;
9302
- const input = dynamicInput ? DYN : STRING;
10576
+ const ret = name === "String" ? STRING : name === "Number" ? F64 : name === "BigInt" ? BIGINT_T : BOOL;
10577
+ const input = dynamicInput || name === "BigInt" ? DYN : STRING;
9303
10578
  const fnT = funcOf([input], ret);
9304
10579
  const key = `${name}:${dynamicInput ? "dyn" : "string"}`;
9305
10580
  let fnName = lowerer.primitiveCtorFns.get(key);
@@ -9308,16 +10583,20 @@ function primitiveCtorClosure(lowerer, name, loc, dynamicInput) {
9308
10583
  lowerer.primitiveCtorFns.set(key, fnName);
9309
10584
  const v = { kind: "varRef", localId: "v.0", type: input, loc };
9310
10585
  const value = dynamicInput
9311
- ? name === "String"
9312
- ? { kind: "libCall", fn: "dyn.toStringCoerce", args: [v], type: STRING, loc }
9313
- : name === "Number"
9314
- ? { kind: "libCall", fn: "dyn.toNumberCoerce", args: [v], type: F64, loc }
9315
- : { kind: "dynTest", test: "truthy", value: v, type: BOOL, loc }
9316
- : name === "String"
9317
- ? v
9318
- : name === "Number"
9319
- ? { kind: "libCall", fn: "num.fromString", args: [v], type: F64, loc }
9320
- : { kind: "strEq", negated: true, left: v, right: { kind: "strLit", value: "", type: STRING, loc }, type: BOOL, loc };
10586
+ ? name === "BigInt"
10587
+ ? { kind: "libCall", fn: "dyn.bigintConstructor", args: [v], type: BIGINT_T, loc }
10588
+ : name === "String"
10589
+ ? { kind: "libCall", fn: "dyn.toStringCoerce", args: [v], type: STRING, loc }
10590
+ : name === "Number"
10591
+ ? { kind: "libCall", fn: "dyn.numberConstructor", args: [v], type: F64, loc }
10592
+ : { kind: "dynTest", test: "truthy", value: v, type: BOOL, loc }
10593
+ : name === "BigInt"
10594
+ ? { kind: "libCall", fn: "dyn.bigintConstructor", args: [v], type: BIGINT_T, loc }
10595
+ : name === "String"
10596
+ ? v
10597
+ : name === "Number"
10598
+ ? { kind: "libCall", fn: "num.fromString", args: [v], type: F64, loc }
10599
+ : { kind: "strEq", negated: true, left: v, right: { kind: "strLit", value: "", type: STRING, loc }, type: BOOL, loc };
9321
10600
  const fn = {
9322
10601
  name: fnName,
9323
10602
  params: [{ localId: "v.0", name: "value", type: input }],
@@ -9330,4 +10609,25 @@ function primitiveCtorClosure(lowerer, name, loc, dynamicInput) {
9330
10609
  }
9331
10610
  return { kind: "closure", fnName, captures: [], type: fnT, loc };
9332
10611
  }
10612
+ /** Drain using checked dispatch so native class and generator iterators keep their methods. */
10613
+ function checkedIteratorPack(lowerer, source, loc) {
10614
+ const name = "%iterator.pack.checked";
10615
+ if (!lowerer.liftedFns.some((fn) => fn.name === name)) {
10616
+ const ref = (id) => ({ kind: "varRef", localId: id, type: DYN, loc });
10617
+ const property = (key) => ({ kind: "dynKeyGet", value: ref("step"), key: { kind: "strLit", value: key, type: STRING, loc }, type: DYN, loc });
10618
+ lowerer.liftedFns.push({ name, params: [{ localId: "source", name: "source", type: DYN }], returnType: DYN,
10619
+ locals: ["source", "iterator", "step", "result"].map((id) => ({ id, name: id, type: DYN, mutable: id !== "source" })),
10620
+ body: [
10621
+ { kind: "varDecl", localId: "iterator", init: { kind: "libCall", fn: "dyn.iterator", args: [ref("source"), { kind: "strLit", value: "value", type: STRING, loc }], type: DYN, loc }, loc },
10622
+ { kind: "varDecl", localId: "result", init: { kind: "dynArrLit", elems: [], type: DYN, loc }, loc },
10623
+ { kind: "while", cond: { kind: "boolLit", value: true, type: BOOL, loc }, body: [
10624
+ { kind: "varDecl", localId: "step", init: { kind: "dynInvoke", recv: ref("iterator"), method: "next", calleeName: "iterator.next", args: [], type: DYN, loc }, loc },
10625
+ { kind: "if", cond: { kind: "dynTest", test: "truthy", value: property("done"), type: BOOL, loc }, then: [{ kind: "return", value: ref("result"), loc }], else_: null, loc },
10626
+ { kind: "exprStmt", expr: { kind: "libCall", fn: "dyn.packPush", args: [ref("result"), property("value")], type: VOID, loc }, loc },
10627
+ ], loc },
10628
+ { kind: "return", value: ref("result"), loc },
10629
+ ], loc });
10630
+ }
10631
+ return { kind: "call", callee: name, args: [source], type: DYN, loc };
10632
+ }
9333
10633
  //# sourceMappingURL=lower-exprs.js.map