redscript-mc 2.6.2 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (636) hide show
  1. package/.github/workflows/ci.yml +11 -0
  2. package/CHANGELOG.md +18 -9
  3. package/README-benchmarks.md +48 -0
  4. package/README-vscode-test.md +251 -0
  5. package/RELEASE_NOTES.md +74 -0
  6. package/ROADMAP.md +131 -167
  7. package/benchmarks/_shared.ts +468 -0
  8. package/benchmarks/baseline.json +2816 -0
  9. package/benchmarks/baseline.md +13 -0
  10. package/benchmarks/compiler-perf.report.json +207 -0
  11. package/benchmarks/compiler-perf.ts +76 -0
  12. package/benchmarks/results.md +13 -0
  13. package/benchmarks/stdlib-complexity.report.json +2606 -0
  14. package/benchmarks/stdlib-complexity.ts +54 -0
  15. package/benchmarks/stdlib-size.md +57 -0
  16. package/benchmarks/stdlib-size.ts +91 -0
  17. package/coverage-report.md +177 -0
  18. package/dist/src/__tests__/budget.test.js +4 -0
  19. package/dist/src/__tests__/cache/cache-behavior.test.d.ts +10 -0
  20. package/dist/src/__tests__/cache/cache-behavior.test.js +425 -0
  21. package/dist/src/__tests__/cache-extra.test.d.ts +5 -0
  22. package/dist/src/__tests__/cache-extra.test.js +211 -0
  23. package/dist/src/__tests__/cli-init.test.d.ts +1 -0
  24. package/dist/src/__tests__/cli-init.test.js +97 -0
  25. package/dist/src/__tests__/cli-publish.test.d.ts +9 -0
  26. package/dist/src/__tests__/cli-publish.test.js +189 -0
  27. package/dist/src/__tests__/cli.test.js +76 -0
  28. package/dist/src/__tests__/compile-preprocess.test.d.ts +11 -0
  29. package/dist/src/__tests__/compile-preprocess.test.js +328 -0
  30. package/dist/src/__tests__/compiler/break-stmt.test.d.ts +1 -0
  31. package/dist/src/__tests__/compiler/break-stmt.test.js +58 -0
  32. package/dist/src/__tests__/compiler/const-decl.test.d.ts +1 -0
  33. package/dist/src/__tests__/compiler/const-decl.test.js +123 -0
  34. package/dist/src/__tests__/compiler/continue-stmt.test.d.ts +1 -0
  35. package/dist/src/__tests__/compiler/continue-stmt.test.js +67 -0
  36. package/dist/src/__tests__/compiler/coroutine-extended.test.d.ts +17 -0
  37. package/dist/src/__tests__/compiler/coroutine-extended.test.js +565 -0
  38. package/dist/src/__tests__/compiler/deprecated.test.d.ts +4 -0
  39. package/dist/src/__tests__/compiler/deprecated.test.js +285 -0
  40. package/dist/src/__tests__/compiler/do-while.test.d.ts +1 -0
  41. package/dist/src/__tests__/compiler/do-while.test.js +120 -0
  42. package/dist/src/__tests__/compiler/enum-payload.test.d.ts +9 -0
  43. package/dist/src/__tests__/compiler/enum-payload.test.js +272 -0
  44. package/dist/src/__tests__/compiler/interface.test.d.ts +10 -0
  45. package/dist/src/__tests__/compiler/interface.test.js +258 -0
  46. package/dist/src/__tests__/compiler/labeled-loops.test.d.ts +1 -0
  47. package/dist/src/__tests__/compiler/labeled-loops.test.js +263 -0
  48. package/dist/src/__tests__/compiler/match-string.test.d.ts +1 -0
  49. package/dist/src/__tests__/compiler/match-string.test.js +43 -0
  50. package/dist/src/__tests__/compiler/memoize.test.d.ts +1 -0
  51. package/dist/src/__tests__/compiler/memoize.test.js +113 -0
  52. package/dist/src/__tests__/compiler/method-chain.test.d.ts +5 -0
  53. package/dist/src/__tests__/compiler/method-chain.test.js +115 -0
  54. package/dist/src/__tests__/compiler/module-import.test.d.ts +12 -0
  55. package/dist/src/__tests__/compiler/module-import.test.js +261 -0
  56. package/dist/src/__tests__/compiler/option-extensions.test.d.ts +6 -0
  57. package/dist/src/__tests__/compiler/option-extensions.test.js +191 -0
  58. package/dist/src/__tests__/compiler/profile-decorator.test.d.ts +1 -0
  59. package/dist/src/__tests__/compiler/profile-decorator.test.js +69 -0
  60. package/dist/src/__tests__/compiler/string-advanced.test.d.ts +7 -0
  61. package/dist/src/__tests__/compiler/string-advanced.test.js +281 -0
  62. package/dist/src/__tests__/compiler/struct-extends.test.d.ts +1 -0
  63. package/dist/src/__tests__/compiler/struct-extends.test.js +95 -0
  64. package/dist/src/__tests__/compiler/throttle-retry.test.d.ts +1 -0
  65. package/dist/src/__tests__/compiler/throttle-retry.test.js +166 -0
  66. package/dist/src/__tests__/compiler/tuple-type.test.d.ts +10 -0
  67. package/dist/src/__tests__/compiler/tuple-type.test.js +229 -0
  68. package/dist/src/__tests__/compiler/watch-decorator.test.d.ts +1 -0
  69. package/dist/src/__tests__/compiler/watch-decorator.test.js +65 -0
  70. package/dist/src/__tests__/config/project-config.test.d.ts +1 -0
  71. package/dist/src/__tests__/config/project-config.test.js +199 -0
  72. package/dist/src/__tests__/config-decorator.test.d.ts +8 -0
  73. package/dist/src/__tests__/config-decorator.test.js +142 -0
  74. package/dist/src/__tests__/diagnostics-extra.test.d.ts +6 -0
  75. package/dist/src/__tests__/diagnostics-extra.test.js +132 -0
  76. package/dist/src/__tests__/emit/compile-branches.test.d.ts +1 -0
  77. package/dist/src/__tests__/emit/compile-branches.test.js +123 -0
  78. package/dist/src/__tests__/emit/compile-coverage.test.d.ts +25 -0
  79. package/dist/src/__tests__/emit/compile-coverage.test.js +617 -0
  80. package/dist/src/__tests__/emit/compile-extra-branches.test.d.ts +12 -0
  81. package/dist/src/__tests__/emit/compile-extra-branches.test.js +225 -0
  82. package/dist/src/__tests__/emit/compile-mocked-branches.test.d.ts +0 -0
  83. package/dist/src/__tests__/emit/compile-mocked-branches.test.js +238 -0
  84. package/dist/src/__tests__/emit/execute-chain.test.d.ts +10 -0
  85. package/dist/src/__tests__/emit/execute-chain.test.js +94 -0
  86. package/dist/src/__tests__/emit/index.test.js +2 -1
  87. package/dist/src/__tests__/emit/modules-branches.test.d.ts +1 -0
  88. package/dist/src/__tests__/emit/modules-branches.test.js +88 -0
  89. package/dist/src/__tests__/emit/modules-coverage.test.d.ts +15 -0
  90. package/dist/src/__tests__/emit/modules-coverage.test.js +221 -0
  91. package/dist/src/__tests__/emit/modules-errors.test.d.ts +12 -0
  92. package/dist/src/__tests__/emit/modules-errors.test.js +169 -0
  93. package/dist/src/__tests__/emit/modules-rewrite.test.d.ts +17 -0
  94. package/dist/src/__tests__/emit/modules-rewrite.test.js +204 -0
  95. package/dist/src/__tests__/emit/source-map.test.d.ts +1 -0
  96. package/dist/src/__tests__/emit/source-map.test.js +167 -0
  97. package/dist/src/__tests__/enum.test.js +9 -4
  98. package/dist/src/__tests__/error-recovery.test.d.ts +7 -0
  99. package/dist/src/__tests__/error-recovery.test.js +217 -0
  100. package/dist/src/__tests__/events-types-extra.test.d.ts +10 -0
  101. package/dist/src/__tests__/events-types-extra.test.js +91 -0
  102. package/dist/src/__tests__/events-types.test.d.ts +4 -0
  103. package/dist/src/__tests__/events-types.test.js +56 -0
  104. package/dist/src/__tests__/formatter.test.js +13 -5
  105. package/dist/src/__tests__/hir/lower-extra.test.d.ts +9 -0
  106. package/dist/src/__tests__/hir/lower-extra.test.js +140 -0
  107. package/dist/src/__tests__/hir/monomorphize-extra.test.d.ts +15 -0
  108. package/dist/src/__tests__/hir/monomorphize-extra.test.js +200 -0
  109. package/dist/src/__tests__/hir/monomorphize-extra2.test.d.ts +16 -0
  110. package/dist/src/__tests__/hir/monomorphize-extra2.test.js +316 -0
  111. package/dist/src/__tests__/incremental.test.js +10 -2
  112. package/dist/src/__tests__/index-extra.test.d.ts +10 -0
  113. package/dist/src/__tests__/index-extra.test.js +71 -0
  114. package/dist/src/__tests__/lexer.test.js +2 -2
  115. package/dist/src/__tests__/lint/rules.test.d.ts +5 -0
  116. package/dist/src/__tests__/lint/rules.test.js +208 -0
  117. package/dist/src/__tests__/lir/lower.test.js +29 -0
  118. package/dist/src/__tests__/lir/verify.test.js +30 -0
  119. package/dist/src/__tests__/lsp/completion.test.d.ts +7 -0
  120. package/dist/src/__tests__/lsp/completion.test.js +583 -0
  121. package/dist/src/__tests__/lsp/definition.test.d.ts +7 -0
  122. package/dist/src/__tests__/lsp/definition.test.js +454 -0
  123. package/dist/src/__tests__/lsp/diagnostics.test.d.ts +10 -0
  124. package/dist/src/__tests__/lsp/diagnostics.test.js +98 -0
  125. package/dist/src/__tests__/lsp/hover-docs.test.d.ts +10 -0
  126. package/dist/src/__tests__/lsp/hover-docs.test.js +210 -0
  127. package/dist/src/__tests__/lsp.test.js +4 -1
  128. package/dist/src/__tests__/mc-integration/item-entity-events.test.js +4 -0
  129. package/dist/src/__tests__/mc-integration/stdlib-coverage-2.test.js +4 -0
  130. package/dist/src/__tests__/mc-integration/stdlib-coverage-3.test.d.ts +13 -0
  131. package/dist/src/__tests__/mc-integration/stdlib-coverage-3.test.js +1227 -0
  132. package/dist/src/__tests__/mc-integration/stdlib-coverage-4.test.d.ts +13 -0
  133. package/dist/src/__tests__/mc-integration/stdlib-coverage-4.test.js +1509 -0
  134. package/dist/src/__tests__/mc-integration/stdlib-coverage-5.test.d.ts +14 -0
  135. package/dist/src/__tests__/mc-integration/stdlib-coverage-5.test.js +1374 -0
  136. package/dist/src/__tests__/mc-integration/stdlib-coverage-6.test.d.ts +10 -0
  137. package/dist/src/__tests__/mc-integration/stdlib-coverage-6.test.js +759 -0
  138. package/dist/src/__tests__/mc-integration/stdlib-coverage-7.test.d.ts +13 -0
  139. package/dist/src/__tests__/mc-integration/stdlib-coverage-7.test.js +855 -0
  140. package/dist/src/__tests__/mc-integration/stdlib-coverage.test.js +4 -0
  141. package/dist/src/__tests__/mc-integration/syntax-coverage.test.js +4 -0
  142. package/dist/src/__tests__/mc-validator-coverage.test.d.ts +13 -0
  143. package/dist/src/__tests__/mc-validator-coverage.test.js +296 -0
  144. package/dist/src/__tests__/mc-validator-extra.test.d.ts +13 -0
  145. package/dist/src/__tests__/mc-validator-extra.test.js +245 -0
  146. package/dist/src/__tests__/mir/lower-extra.test.d.ts +20 -0
  147. package/dist/src/__tests__/mir/lower-extra.test.js +361 -0
  148. package/dist/src/__tests__/mir/lower-extra2.test.d.ts +17 -0
  149. package/dist/src/__tests__/mir/lower-extra2.test.js +317 -0
  150. package/dist/src/__tests__/mir/lower-extra3.test.d.ts +19 -0
  151. package/dist/src/__tests__/mir/lower-extra3.test.js +249 -0
  152. package/dist/src/__tests__/mir/lower-extra4.test.d.ts +23 -0
  153. package/dist/src/__tests__/mir/lower-extra4.test.js +606 -0
  154. package/dist/src/__tests__/mir/lower-extra5.test.d.ts +25 -0
  155. package/dist/src/__tests__/mir/lower-extra5.test.js +543 -0
  156. package/dist/src/__tests__/mir/lower-extra6.test.d.ts +16 -0
  157. package/dist/src/__tests__/mir/lower-extra6.test.js +471 -0
  158. package/dist/src/__tests__/mir/lower-extra7.test.d.ts +35 -0
  159. package/dist/src/__tests__/mir/lower-extra7.test.js +921 -0
  160. package/dist/src/__tests__/mir/lower-extra8.test.d.ts +19 -0
  161. package/dist/src/__tests__/mir/lower-extra8.test.js +626 -0
  162. package/dist/src/__tests__/mir/lower-extra9.test.d.ts +14 -0
  163. package/dist/src/__tests__/mir/lower-extra9.test.js +717 -0
  164. package/dist/src/__tests__/optimizer/auto-inline.test.d.ts +1 -0
  165. package/dist/src/__tests__/optimizer/auto-inline.test.js +176 -0
  166. package/dist/src/__tests__/optimizer/cse.test.d.ts +4 -0
  167. package/dist/src/__tests__/optimizer/cse.test.js +178 -0
  168. package/dist/src/__tests__/optimizer/inline_fn.test.d.ts +1 -0
  169. package/dist/src/__tests__/optimizer/inline_fn.test.js +221 -0
  170. package/dist/src/__tests__/optimizer/licm.test.d.ts +1 -0
  171. package/dist/src/__tests__/optimizer/licm.test.js +244 -0
  172. package/dist/src/__tests__/optimizer/optimizer-extended.test.d.ts +12 -0
  173. package/dist/src/__tests__/optimizer/optimizer-extended.test.js +993 -0
  174. package/dist/src/__tests__/optimizer/strength-reduction.test.d.ts +1 -0
  175. package/dist/src/__tests__/optimizer/strength-reduction.test.js +86 -0
  176. package/dist/src/__tests__/optimizer/tco.test.d.ts +14 -0
  177. package/dist/src/__tests__/optimizer/tco.test.js +203 -0
  178. package/dist/src/__tests__/parser-coverage.test.d.ts +25 -0
  179. package/dist/src/__tests__/parser-coverage.test.js +491 -0
  180. package/dist/src/__tests__/parser-extra.test.d.ts +6 -0
  181. package/dist/src/__tests__/parser-extra.test.js +451 -0
  182. package/dist/src/__tests__/parser.test.js +12 -0
  183. package/dist/src/__tests__/repl-extra.test.d.ts +13 -0
  184. package/dist/src/__tests__/repl-extra.test.js +174 -0
  185. package/dist/src/__tests__/repl-server-extra.test.d.ts +10 -0
  186. package/dist/src/__tests__/repl-server-extra.test.js +161 -0
  187. package/dist/src/__tests__/repl-server.test.d.ts +6 -0
  188. package/dist/src/__tests__/repl-server.test.js +146 -0
  189. package/dist/src/__tests__/runtime-extra.test.d.ts +15 -0
  190. package/dist/src/__tests__/runtime-extra.test.js +732 -0
  191. package/dist/src/__tests__/singleton-decorator.test.d.ts +11 -0
  192. package/dist/src/__tests__/singleton-decorator.test.js +260 -0
  193. package/dist/src/__tests__/sourcemap.test.js +1 -1
  194. package/dist/src/__tests__/stdlib/advanced.test.d.ts +5 -0
  195. package/dist/src/__tests__/stdlib/advanced.test.js +301 -0
  196. package/dist/src/__tests__/stdlib/bigint.test.d.ts +4 -0
  197. package/dist/src/__tests__/stdlib/bigint.test.js +83 -0
  198. package/dist/src/__tests__/stdlib/bits.test.d.ts +4 -0
  199. package/dist/src/__tests__/stdlib/bits.test.js +96 -0
  200. package/dist/src/__tests__/stdlib/bossbar.test.d.ts +4 -0
  201. package/dist/src/__tests__/stdlib/bossbar.test.js +72 -0
  202. package/dist/src/__tests__/stdlib/color.test.d.ts +4 -0
  203. package/dist/src/__tests__/stdlib/color.test.js +84 -0
  204. package/dist/src/__tests__/stdlib/combat.test.d.ts +4 -0
  205. package/dist/src/__tests__/stdlib/combat.test.js +64 -0
  206. package/dist/src/__tests__/stdlib/cooldown.test.d.ts +4 -0
  207. package/dist/src/__tests__/stdlib/cooldown.test.js +64 -0
  208. package/dist/src/__tests__/stdlib/dialog.test.js +15 -7
  209. package/dist/src/__tests__/stdlib/ecs.test.d.ts +4 -0
  210. package/dist/src/__tests__/stdlib/ecs.test.js +81 -0
  211. package/dist/src/__tests__/stdlib/effects.test.d.ts +4 -0
  212. package/dist/src/__tests__/stdlib/effects.test.js +72 -0
  213. package/dist/src/__tests__/stdlib/events.test.d.ts +4 -0
  214. package/dist/src/__tests__/stdlib/events.test.js +55 -0
  215. package/dist/src/__tests__/stdlib/expr.test.d.ts +4 -0
  216. package/dist/src/__tests__/stdlib/expr.test.js +77 -0
  217. package/dist/src/__tests__/stdlib/fft.test.d.ts +4 -0
  218. package/dist/src/__tests__/stdlib/fft.test.js +82 -0
  219. package/dist/src/__tests__/stdlib/graph.test.d.ts +4 -0
  220. package/dist/src/__tests__/stdlib/graph.test.js +102 -0
  221. package/dist/src/__tests__/stdlib/interactions.test.d.ts +4 -0
  222. package/dist/src/__tests__/stdlib/interactions.test.js +60 -0
  223. package/dist/src/__tests__/stdlib/inventory.test.d.ts +4 -0
  224. package/dist/src/__tests__/stdlib/inventory.test.js +68 -0
  225. package/dist/src/__tests__/stdlib/linalg.test.d.ts +5 -0
  226. package/dist/src/__tests__/stdlib/linalg.test.js +78 -0
  227. package/dist/src/__tests__/stdlib/map.test.d.ts +1 -0
  228. package/dist/src/__tests__/stdlib/map.test.js +84 -0
  229. package/dist/src/__tests__/stdlib/math.test.js +19 -6
  230. package/dist/src/__tests__/stdlib/math_hp.test.d.ts +4 -0
  231. package/dist/src/__tests__/stdlib/math_hp.test.js +80 -0
  232. package/dist/src/__tests__/stdlib/mobs.test.d.ts +4 -0
  233. package/dist/src/__tests__/stdlib/mobs.test.js +61 -0
  234. package/dist/src/__tests__/stdlib/noise.test.d.ts +4 -0
  235. package/dist/src/__tests__/stdlib/noise.test.js +73 -0
  236. package/dist/src/__tests__/stdlib/ode.test.d.ts +4 -0
  237. package/dist/src/__tests__/stdlib/ode.test.js +68 -0
  238. package/dist/src/__tests__/stdlib/parabola.test.d.ts +4 -0
  239. package/dist/src/__tests__/stdlib/parabola.test.js +77 -0
  240. package/dist/src/__tests__/stdlib/particles.test.d.ts +4 -0
  241. package/dist/src/__tests__/stdlib/particles.test.js +68 -0
  242. package/dist/src/__tests__/stdlib/physics.test.d.ts +4 -0
  243. package/dist/src/__tests__/stdlib/physics.test.js +76 -0
  244. package/dist/src/__tests__/stdlib/player.test.d.ts +4 -0
  245. package/dist/src/__tests__/stdlib/player.test.js +64 -0
  246. package/dist/src/__tests__/stdlib/quaternion.test.d.ts +4 -0
  247. package/dist/src/__tests__/stdlib/quaternion.test.js +73 -0
  248. package/dist/src/__tests__/stdlib/queue.test.d.ts +1 -0
  249. package/dist/src/__tests__/stdlib/queue.test.js +97 -0
  250. package/dist/src/__tests__/stdlib/random.test.d.ts +4 -0
  251. package/dist/src/__tests__/stdlib/random.test.js +76 -0
  252. package/dist/src/__tests__/stdlib/result.test.d.ts +12 -0
  253. package/dist/src/__tests__/stdlib/result.test.js +329 -0
  254. package/dist/src/__tests__/stdlib/scheduler.test.js +19 -8
  255. package/dist/src/__tests__/stdlib/set_int.test.d.ts +1 -0
  256. package/dist/src/__tests__/stdlib/set_int.test.js +88 -0
  257. package/dist/src/__tests__/stdlib/sets.test.d.ts +6 -0
  258. package/dist/src/__tests__/stdlib/sets.test.js +60 -0
  259. package/dist/src/__tests__/stdlib/signal.test.d.ts +4 -0
  260. package/dist/src/__tests__/stdlib/signal.test.js +84 -0
  261. package/dist/src/__tests__/stdlib/spawn.test.d.ts +4 -0
  262. package/dist/src/__tests__/stdlib/spawn.test.js +68 -0
  263. package/dist/src/__tests__/stdlib/string.test.d.ts +12 -0
  264. package/dist/src/__tests__/stdlib/string.test.js +231 -0
  265. package/dist/src/__tests__/stdlib/strings.test.d.ts +4 -0
  266. package/dist/src/__tests__/stdlib/strings.test.js +83 -0
  267. package/dist/src/__tests__/stdlib/tags.test.d.ts +4 -0
  268. package/dist/src/__tests__/stdlib/tags.test.js +57 -0
  269. package/dist/src/__tests__/stdlib/teams.test.d.ts +4 -0
  270. package/dist/src/__tests__/stdlib/teams.test.js +72 -0
  271. package/dist/src/__tests__/stdlib/timer.test.d.ts +4 -0
  272. package/dist/src/__tests__/stdlib/timer.test.js +79 -0
  273. package/dist/src/__tests__/stdlib/vec.test.d.ts +5 -0
  274. package/dist/src/__tests__/stdlib/vec.test.js +94 -0
  275. package/dist/src/__tests__/stdlib/world.test.d.ts +4 -0
  276. package/dist/src/__tests__/stdlib/world.test.js +72 -0
  277. package/dist/src/__tests__/struct-display.test.d.ts +1 -0
  278. package/dist/src/__tests__/struct-display.test.js +64 -0
  279. package/dist/src/__tests__/test-framework/runner.test.d.ts +10 -0
  280. package/dist/src/__tests__/test-framework/runner.test.js +193 -0
  281. package/dist/src/__tests__/tuner/adapters.test.d.ts +14 -0
  282. package/dist/src/__tests__/tuner/adapters.test.js +194 -0
  283. package/dist/src/__tests__/tuner/simulator-extra.test.d.ts +4 -0
  284. package/dist/src/__tests__/tuner/simulator-extra.test.js +193 -0
  285. package/dist/src/__tests__/typechecker-coverage.test.d.ts +30 -0
  286. package/dist/src/__tests__/typechecker-coverage.test.js +627 -0
  287. package/dist/src/__tests__/typechecker.test.js +3 -3
  288. package/dist/src/__tests__/watch-decorator.test.d.ts +1 -0
  289. package/dist/src/__tests__/watch-decorator.test.js +54 -0
  290. package/dist/src/ast/types.d.ts +102 -3
  291. package/dist/src/cache/incremental.d.ts +13 -14
  292. package/dist/src/cache/incremental.js +106 -89
  293. package/dist/src/cache/index.d.ts +8 -2
  294. package/dist/src/cache/index.js +18 -6
  295. package/dist/src/cli.d.ts +1 -0
  296. package/dist/src/cli.js +466 -17
  297. package/dist/src/config/project-config.d.ts +29 -0
  298. package/dist/src/config/project-config.js +180 -0
  299. package/dist/src/diagnostics/index.d.ts +9 -0
  300. package/dist/src/diagnostics/index.js +18 -1
  301. package/dist/src/emit/compile.d.ts +10 -0
  302. package/dist/src/emit/compile.js +395 -50
  303. package/dist/src/emit/index.d.ts +40 -0
  304. package/dist/src/emit/index.js +307 -14
  305. package/dist/src/emit/modules.js +21 -3
  306. package/dist/src/emit/sourcemap.d.ts +23 -27
  307. package/dist/src/emit/sourcemap.js +52 -30
  308. package/dist/src/formatter/index.js +33 -8
  309. package/dist/src/hir/deprecated.d.ts +13 -0
  310. package/dist/src/hir/deprecated.js +218 -0
  311. package/dist/src/hir/lower.js +114 -8
  312. package/dist/src/hir/monomorphize.js +22 -2
  313. package/dist/src/hir/types.d.ts +65 -1
  314. package/dist/src/index.d.ts +6 -0
  315. package/dist/src/index.js +18 -3
  316. package/dist/src/lexer/index.d.ts +2 -1
  317. package/dist/src/lexer/index.js +39 -3
  318. package/dist/src/lint/index.d.ts +45 -0
  319. package/dist/src/lint/index.js +930 -0
  320. package/dist/src/lir/lower.js +29 -2
  321. package/dist/src/lir/types.d.ts +2 -0
  322. package/dist/src/lsp/server.js +92 -5
  323. package/dist/src/mir/lower.js +775 -34
  324. package/dist/src/mir/macro.js +36 -2
  325. package/dist/src/mir/types.d.ts +12 -0
  326. package/dist/src/mir/verify.js +9 -0
  327. package/dist/src/optimizer/auto-inline.d.ts +2 -0
  328. package/dist/src/optimizer/auto-inline.js +67 -0
  329. package/dist/src/optimizer/cse.d.ts +20 -0
  330. package/dist/src/optimizer/cse.js +234 -0
  331. package/dist/src/optimizer/inline.d.ts +26 -0
  332. package/dist/src/optimizer/inline.js +286 -0
  333. package/dist/src/optimizer/interprocedural.js +4 -0
  334. package/dist/src/optimizer/licm.d.ts +32 -0
  335. package/dist/src/optimizer/licm.js +371 -0
  336. package/dist/src/optimizer/pipeline.js +12 -2
  337. package/dist/src/optimizer/strength_reduction.d.ts +15 -0
  338. package/dist/src/optimizer/strength_reduction.js +90 -0
  339. package/dist/src/optimizer/tco.d.ts +53 -0
  340. package/dist/src/optimizer/tco.js +238 -0
  341. package/dist/src/parser/index.d.ts +32 -0
  342. package/dist/src/parser/index.js +421 -59
  343. package/dist/src/repl-server.d.ts +13 -0
  344. package/dist/src/repl-server.js +127 -0
  345. package/dist/src/structs/expand.d.ts +15 -0
  346. package/dist/src/structs/expand.js +46 -0
  347. package/dist/src/testing/runner.d.ts +40 -0
  348. package/dist/src/testing/runner.js +237 -0
  349. package/dist/src/typechecker/index.d.ts +3 -0
  350. package/dist/src/typechecker/index.js +254 -9
  351. package/dist/tsconfig.tsbuildinfo +1 -1
  352. package/doc-drafts/redscript-docs/docs/en/stdlib/graph.md +104 -0
  353. package/doc-drafts/redscript-docs/docs/en/stdlib/parabola.md +113 -0
  354. package/doc-drafts/redscript-docs/docs/en/stdlib/pathfind.md +104 -0
  355. package/doc-drafts/redscript-docs/docs/en/stdlib/physics.md +134 -0
  356. package/doc-drafts/redscript-docs/docs/en/stdlib/quaternion.md +135 -0
  357. package/doc-drafts/redscript-docs/docs/zh/stdlib/graph.md +104 -0
  358. package/doc-drafts/redscript-docs/docs/zh/stdlib/parabola.md +113 -0
  359. package/doc-drafts/redscript-docs/docs/zh/stdlib/pathfind.md +104 -0
  360. package/doc-drafts/redscript-docs/docs/zh/stdlib/physics.md +134 -0
  361. package/doc-drafts/redscript-docs/docs/zh/stdlib/quaternion.md +135 -0
  362. package/docs/stdlib/result.md +156 -0
  363. package/docs/stdlib/result.zh.md +156 -0
  364. package/editors/vscode/fixtures/test.mcrs +7 -0
  365. package/editors/vscode/out/extension.js +2095 -225
  366. package/editors/vscode/out/lsp-server.js +519 -51
  367. package/editors/vscode/package-lock.json +9 -4
  368. package/editors/vscode/package.json +1 -1
  369. package/examples/display-demo.mcrs +64 -0
  370. package/examples/game/racing.mcrs +301 -0
  371. package/examples/game/tower_defense.mcrs +311 -0
  372. package/examples/math/physics_sim.mcrs +322 -0
  373. package/examples/rpg/boss_fight.mcrs +313 -0
  374. package/examples/rpg/health_system.mcrs +237 -0
  375. package/examples/rpg/inventory.mcrs +265 -0
  376. package/examples/util/debug_hud.mcrs +279 -0
  377. package/jest.config.js +10 -0
  378. package/package.json +12 -3
  379. package/playground/index.html +823 -0
  380. package/scripts/gen-docs.ts +533 -0
  381. package/scripts/update-redscript-docs-stdlib.sh +770 -0
  382. package/src/__tests__/budget.test.ts +5 -0
  383. package/src/__tests__/cache/cache-behavior.test.ts +480 -0
  384. package/src/__tests__/cache-extra.test.ts +199 -0
  385. package/src/__tests__/cli-docs.test.ts +77 -0
  386. package/src/__tests__/cli-init.test.ts +91 -0
  387. package/src/__tests__/cli-publish.test.ts +190 -0
  388. package/src/__tests__/cli.test.ts +117 -1
  389. package/src/__tests__/compile-preprocess.test.ts +366 -0
  390. package/src/__tests__/compiler/break-stmt.test.ts +66 -0
  391. package/src/__tests__/compiler/const-decl.test.ts +141 -0
  392. package/src/__tests__/compiler/continue-stmt.test.ts +81 -0
  393. package/src/__tests__/compiler/coroutine-extended.test.ts +723 -0
  394. package/src/__tests__/compiler/deprecated.test.ts +305 -0
  395. package/src/__tests__/compiler/do-while.test.ts +130 -0
  396. package/src/__tests__/compiler/enum-payload.test.ts +299 -0
  397. package/src/__tests__/compiler/interface.test.ts +287 -0
  398. package/src/__tests__/compiler/labeled-loops.test.ts +279 -0
  399. package/src/__tests__/compiler/match-string.test.ts +45 -0
  400. package/src/__tests__/compiler/memoize.test.ts +126 -0
  401. package/src/__tests__/compiler/method-chain.test.ts +121 -0
  402. package/src/__tests__/compiler/module-import.test.ts +240 -0
  403. package/src/__tests__/compiler/option-extensions.test.ts +207 -0
  404. package/src/__tests__/compiler/profile-decorator.test.ts +79 -0
  405. package/src/__tests__/compiler/string-advanced.test.ts +310 -0
  406. package/src/__tests__/compiler/struct-extends.test.ts +109 -0
  407. package/src/__tests__/compiler/throttle-retry.test.ts +191 -0
  408. package/src/__tests__/compiler/tuple-type.test.ts +263 -0
  409. package/src/__tests__/compiler/watch-decorator.test.ts +72 -0
  410. package/src/__tests__/config/project-config.test.ts +181 -0
  411. package/src/__tests__/config-decorator.test.ts +157 -0
  412. package/src/__tests__/diagnostics-extra.test.ts +155 -0
  413. package/src/__tests__/emit/compile-branches.test.ts +135 -0
  414. package/src/__tests__/emit/compile-coverage.test.ts +696 -0
  415. package/src/__tests__/emit/compile-extra-branches.test.ts +228 -0
  416. package/src/__tests__/emit/compile-mocked-branches.test.ts +249 -0
  417. package/src/__tests__/emit/compile.test.ts +6 -1
  418. package/src/__tests__/emit/execute-chain.test.ts +114 -0
  419. package/src/__tests__/emit/index.test.ts +2 -1
  420. package/src/__tests__/emit/modules-branches.test.ts +90 -0
  421. package/src/__tests__/emit/modules-coverage.test.ts +241 -0
  422. package/src/__tests__/emit/modules-errors.test.ts +192 -0
  423. package/src/__tests__/emit/modules-rewrite.test.ts +232 -0
  424. package/src/__tests__/emit/source-map.test.ts +152 -0
  425. package/src/__tests__/enum.test.ts +9 -4
  426. package/src/__tests__/error-recovery.test.ts +226 -0
  427. package/src/__tests__/events-types-extra.test.ts +110 -0
  428. package/src/__tests__/events-types.test.ts +66 -0
  429. package/src/__tests__/formatter.test.ts +15 -5
  430. package/src/__tests__/generics.test.ts +16 -9
  431. package/src/__tests__/hir/lower-extra.test.ts +151 -0
  432. package/src/__tests__/hir/monomorphize-coverage.test.ts +432 -0
  433. package/src/__tests__/hir/monomorphize-extra.test.ts +220 -0
  434. package/src/__tests__/hir/monomorphize-extra2.test.ts +350 -0
  435. package/src/__tests__/impl.test.ts +12 -8
  436. package/src/__tests__/incremental.test.ts +10 -2
  437. package/src/__tests__/index-extra.test.ts +79 -0
  438. package/src/__tests__/lexer.test.ts +2 -2
  439. package/src/__tests__/lint/hir-coverage.test.ts +1716 -0
  440. package/src/__tests__/lint/rules-coverage.test.ts +598 -0
  441. package/src/__tests__/lint/rules.test.ts +230 -0
  442. package/src/__tests__/lir/lower.test.ts +33 -0
  443. package/src/__tests__/lir/verify.test.ts +33 -0
  444. package/src/__tests__/lsp/completion.test.ts +687 -0
  445. package/src/__tests__/lsp/definition.test.ts +499 -0
  446. package/src/__tests__/lsp/diagnostics.test.ts +108 -0
  447. package/src/__tests__/lsp/hover-docs.test.ts +222 -0
  448. package/src/__tests__/lsp.test.ts +4 -1
  449. package/src/__tests__/mc-integration/item-entity-events.test.ts +5 -0
  450. package/src/__tests__/mc-integration/stdlib-coverage-2.test.ts +5 -0
  451. package/src/__tests__/mc-integration/stdlib-coverage-3.test.ts +1105 -0
  452. package/src/__tests__/mc-integration/stdlib-coverage-4.test.ts +1366 -0
  453. package/src/__tests__/mc-integration/stdlib-coverage-5.test.ts +1245 -0
  454. package/src/__tests__/mc-integration/stdlib-coverage-6.test.ts +755 -0
  455. package/src/__tests__/mc-integration/stdlib-coverage-7.test.ts +771 -0
  456. package/src/__tests__/mc-integration/stdlib-coverage.test.ts +5 -0
  457. package/src/__tests__/mc-integration/syntax-coverage.test.ts +5 -0
  458. package/src/__tests__/mc-validator-coverage.test.ts +325 -0
  459. package/src/__tests__/mc-validator-extra.test.ts +252 -0
  460. package/src/__tests__/mir/lower-extra.test.ts +402 -0
  461. package/src/__tests__/mir/lower-extra2.test.ts +348 -0
  462. package/src/__tests__/mir/lower-extra3.test.ts +277 -0
  463. package/src/__tests__/mir/lower-extra4.test.ts +636 -0
  464. package/src/__tests__/mir/lower-extra5.test.ts +612 -0
  465. package/src/__tests__/mir/lower-extra6.test.ts +520 -0
  466. package/src/__tests__/mir/lower-extra7.test.ts +1045 -0
  467. package/src/__tests__/mir/lower-extra8.test.ts +704 -0
  468. package/src/__tests__/mir/lower-extra9.test.ts +821 -0
  469. package/src/__tests__/optimizer/auto-inline.test.ts +206 -0
  470. package/src/__tests__/optimizer/cse.test.ts +195 -0
  471. package/src/__tests__/optimizer/inline_fn.test.ts +263 -0
  472. package/src/__tests__/optimizer/licm.test.ts +358 -0
  473. package/src/__tests__/optimizer/nbt-coalesce.test.ts +147 -0
  474. package/src/__tests__/optimizer/optimizer-extended.test.ts +1081 -0
  475. package/src/__tests__/optimizer/scoreboard-batch.test.ts +141 -0
  476. package/src/__tests__/optimizer/strength-reduction.test.ts +111 -0
  477. package/src/__tests__/optimizer/tco-coverage.test.ts +309 -0
  478. package/src/__tests__/optimizer/tco.test.ts +238 -0
  479. package/src/__tests__/option.test.ts +14 -7
  480. package/src/__tests__/parser-coverage.test.ts +576 -0
  481. package/src/__tests__/parser-extra.test.ts +531 -0
  482. package/src/__tests__/parser.test.ts +14 -0
  483. package/src/__tests__/repl-extra.test.ts +195 -0
  484. package/src/__tests__/repl-server-extra.test.ts +150 -0
  485. package/src/__tests__/repl-server.test.ts +122 -0
  486. package/src/__tests__/runtime-extra.test.ts +862 -0
  487. package/src/__tests__/singleton-decorator.test.ts +285 -0
  488. package/src/__tests__/sourcemap.test.ts +1 -1
  489. package/src/__tests__/stdlib/advanced.test.ts +312 -0
  490. package/src/__tests__/stdlib/bigint.test.ts +57 -0
  491. package/src/__tests__/stdlib/bits.test.ts +75 -0
  492. package/src/__tests__/stdlib/bossbar.test.ts +45 -0
  493. package/src/__tests__/stdlib/color.test.ts +60 -0
  494. package/src/__tests__/stdlib/combat.test.ts +35 -0
  495. package/src/__tests__/stdlib/cooldown.test.ts +35 -0
  496. package/src/__tests__/stdlib/dialog.test.ts +14 -6
  497. package/src/__tests__/stdlib/ecs.test.ts +54 -0
  498. package/src/__tests__/stdlib/effects.test.ts +45 -0
  499. package/src/__tests__/stdlib/events.test.ts +23 -0
  500. package/src/__tests__/stdlib/expr.test.ts +48 -0
  501. package/src/__tests__/stdlib/fft.test.ts +54 -0
  502. package/src/__tests__/stdlib/graph.test.ts +77 -0
  503. package/src/__tests__/stdlib/interactions.test.ts +30 -0
  504. package/src/__tests__/stdlib/inventory.test.ts +40 -0
  505. package/src/__tests__/stdlib/linalg.test.ts +52 -0
  506. package/src/__tests__/stdlib/map.test.ts +55 -0
  507. package/src/__tests__/stdlib/math.test.ts +19 -5
  508. package/src/__tests__/stdlib/math_hp.test.ts +55 -0
  509. package/src/__tests__/stdlib/mobs.test.ts +40 -0
  510. package/src/__tests__/stdlib/noise.test.ts +46 -0
  511. package/src/__tests__/stdlib/ode.test.ts +40 -0
  512. package/src/__tests__/stdlib/parabola.test.ts +51 -0
  513. package/src/__tests__/stdlib/particles.test.ts +40 -0
  514. package/src/__tests__/stdlib/physics.test.ts +50 -0
  515. package/src/__tests__/stdlib/player.test.ts +35 -0
  516. package/src/__tests__/stdlib/quaternion.test.ts +46 -0
  517. package/src/__tests__/stdlib/queue.test.ts +73 -0
  518. package/src/__tests__/stdlib/random.test.ts +50 -0
  519. package/src/__tests__/stdlib/result.test.ts +326 -0
  520. package/src/__tests__/stdlib/scheduler.test.ts +18 -7
  521. package/src/__tests__/stdlib/set_int.test.ts +62 -0
  522. package/src/__tests__/stdlib/sets.test.ts +28 -0
  523. package/src/__tests__/stdlib/signal.test.ts +60 -0
  524. package/src/__tests__/stdlib/spawn.test.ts +40 -0
  525. package/src/__tests__/stdlib/string.test.ts +224 -0
  526. package/src/__tests__/stdlib/strings.test.ts +55 -0
  527. package/src/__tests__/stdlib/tags.test.ts +32 -0
  528. package/src/__tests__/stdlib/teams.test.ts +45 -0
  529. package/src/__tests__/stdlib/timer.test.ts +53 -0
  530. package/src/__tests__/stdlib/vec.test.ts +72 -0
  531. package/src/__tests__/stdlib/world.test.ts +45 -0
  532. package/src/__tests__/struct-display.test.ts +69 -0
  533. package/src/__tests__/test-framework/runner.test.ts +208 -0
  534. package/src/__tests__/tuner/adapters.test.ts +232 -0
  535. package/src/__tests__/tuner/simulator-extra.test.ts +222 -0
  536. package/src/__tests__/tuple.test.ts +11 -4
  537. package/src/__tests__/typechecker-coverage.test.ts +671 -0
  538. package/src/__tests__/typechecker.test.ts +4 -3
  539. package/src/__tests__/watch-decorator.test.ts +59 -0
  540. package/src/ast/types.ts +65 -3
  541. package/src/cache/incremental.ts +128 -99
  542. package/src/cache/index.ts +35 -8
  543. package/src/cli.ts +538 -29
  544. package/src/config/project-config.ts +176 -0
  545. package/src/diagnostics/index.ts +22 -0
  546. package/src/docs.ts +98 -0
  547. package/src/emit/compile.ts +408 -51
  548. package/src/emit/index.ts +366 -18
  549. package/src/emit/modules.ts +19 -3
  550. package/src/emit/sourcemap.ts +64 -43
  551. package/src/formatter/index.ts +35 -8
  552. package/src/hir/deprecated.ts +212 -0
  553. package/src/hir/lower.ts +128 -8
  554. package/src/hir/monomorphize.ts +24 -2
  555. package/src/hir/types.ts +26 -1
  556. package/src/index.ts +23 -3
  557. package/src/lexer/index.ts +45 -6
  558. package/src/lint/index.ts +922 -0
  559. package/src/lir/lower.ts +30 -2
  560. package/src/lir/types.ts +4 -0
  561. package/src/lsp/server.ts +100 -1
  562. package/src/mir/lower.ts +785 -40
  563. package/src/mir/macro.ts +30 -2
  564. package/src/mir/types.ts +13 -0
  565. package/src/mir/verify.ts +10 -2
  566. package/src/optimizer/auto-inline.ts +86 -0
  567. package/src/optimizer/copy_prop.ts +2 -2
  568. package/src/optimizer/coroutine.ts +3 -3
  569. package/src/optimizer/cse.ts +205 -0
  570. package/src/optimizer/dce.ts +2 -2
  571. package/src/optimizer/inline.ts +335 -0
  572. package/src/optimizer/interprocedural.ts +5 -1
  573. package/src/optimizer/licm.ts +454 -0
  574. package/src/optimizer/nbt-coalesce.ts +109 -0
  575. package/src/optimizer/pipeline.ts +16 -2
  576. package/src/optimizer/scoreboard-batch.ts +52 -0
  577. package/src/optimizer/strength_reduction.ts +95 -0
  578. package/src/optimizer/tco.ts +267 -0
  579. package/src/optimizer/unroll.ts +2 -2
  580. package/src/parser/index.ts +426 -53
  581. package/src/repl-server.ts +102 -0
  582. package/src/stdlib/advanced.mcrs +271 -101
  583. package/src/stdlib/bigint.mcrs +97 -11
  584. package/src/stdlib/bits.mcrs +75 -12
  585. package/src/stdlib/bossbar.mcrs +37 -8
  586. package/src/stdlib/calculus.mcrs +82 -26
  587. package/src/stdlib/color.mcrs +98 -16
  588. package/src/stdlib/combat.mcrs +23 -5
  589. package/src/stdlib/cooldown.mcrs +19 -0
  590. package/src/stdlib/dialog.mcrs +45 -7
  591. package/src/stdlib/easing.mcrs +132 -12
  592. package/src/stdlib/ecs.mcrs +142 -25
  593. package/src/stdlib/effects.mcrs +88 -12
  594. package/src/stdlib/events.mcrs +21 -2
  595. package/src/stdlib/expr.mcrs +18 -3
  596. package/src/stdlib/fft.mcrs +66 -56
  597. package/src/stdlib/geometry.mcrs +137 -39
  598. package/src/stdlib/graph.mcrs +73 -0
  599. package/src/stdlib/heap.mcrs +49 -8
  600. package/src/stdlib/i18n/zh.yaml +2891 -0
  601. package/src/stdlib/interactions.mcrs +43 -20
  602. package/src/stdlib/inventory.mcrs +14 -3
  603. package/src/stdlib/linalg.mcrs +185 -30
  604. package/src/stdlib/list.mcrs +168 -18
  605. package/src/stdlib/map.mcrs +112 -0
  606. package/src/stdlib/math.mcrs +68 -18
  607. package/src/stdlib/math_hp.mcrs +124 -33
  608. package/src/stdlib/matrix.mcrs +133 -20
  609. package/src/stdlib/mobs.mcrs +87 -0
  610. package/src/stdlib/noise.mcrs +65 -21
  611. package/src/stdlib/ode.mcrs +96 -0
  612. package/src/stdlib/parabola.mcrs +104 -29
  613. package/src/stdlib/particles.mcrs +78 -21
  614. package/src/stdlib/pathfind.mcrs +89 -35
  615. package/src/stdlib/physics.mcrs +134 -26
  616. package/src/stdlib/player.mcrs +18 -0
  617. package/src/stdlib/quaternion.mcrs +213 -9
  618. package/src/stdlib/queue.mcrs +123 -0
  619. package/src/stdlib/random.mcrs +63 -18
  620. package/src/stdlib/result.mcrs +111 -0
  621. package/src/stdlib/scheduler.mcrs +59 -10
  622. package/src/stdlib/set_int.mcrs +240 -0
  623. package/src/stdlib/sets.mcrs +49 -19
  624. package/src/stdlib/signal.mcrs +151 -79
  625. package/src/stdlib/sort.mcrs +44 -24
  626. package/src/stdlib/spawn.mcrs +30 -7
  627. package/src/stdlib/state.mcrs +40 -5
  628. package/src/stdlib/strings.mcrs +131 -3
  629. package/src/stdlib/tags.mcrs +2 -2
  630. package/src/stdlib/teams.mcrs +22 -10
  631. package/src/stdlib/timer.mcrs +36 -6
  632. package/src/stdlib/vec.mcrs +44 -9
  633. package/src/stdlib/world.mcrs +57 -25
  634. package/src/structs/expand.ts +64 -0
  635. package/src/testing/runner.ts +271 -0
  636. package/src/typechecker/index.ts +273 -9
@@ -0,0 +1,1081 @@
1
+ /**
2
+ * Extended optimizer coverage tests.
3
+ *
4
+ * Targets uncovered branches in:
5
+ * - coroutine.ts (fnContainsMacroCalls paths, macro-skip warning)
6
+ * - unroll.ts (two-step latch pattern, initializesTo0 copy path, copy-init path)
7
+ * - interprocedural.ts (call_macro isSelfRecursive, param mismatch guard, rewriteCallSites guards)
8
+ * - lir/const_imm.ts (score_swap / store_nbt_to_score / macro_line read slots, extractSlotsFromRaw)
9
+ * - block_merge.ts (branch terminator — no merge, target === fn.entry guard)
10
+ * - constant_fold.ts (evalCmp default branch)
11
+ */
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Imports
15
+ // ---------------------------------------------------------------------------
16
+
17
+ import { coroutineTransform, type CoroutineInfo } from '../../optimizer/coroutine'
18
+ import { loopUnroll } from '../../optimizer/unroll'
19
+ import { interproceduralConstProp } from '../../optimizer/interprocedural'
20
+ import { constImmFold } from '../../optimizer/lir/const_imm'
21
+ import { blockMerge } from '../../optimizer/block_merge'
22
+ import { constantFold } from '../../optimizer/constant_fold'
23
+ import type { MIRFunction, MIRModule, MIRBlock, MIRInstr, Operand } from '../../mir/types'
24
+ import type { LIRFunction, LIRInstr, Slot } from '../../lir/types'
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // MIR helpers
28
+ // ---------------------------------------------------------------------------
29
+
30
+ const c = (v: number): Operand => ({ kind: 'const', value: v })
31
+ const t = (n: string): Operand => ({ kind: 'temp', name: n })
32
+
33
+ function mirBlock(id: string, instrs: MIRInstr[], term: MIRInstr, preds: string[] = []): MIRBlock {
34
+ return { id, instrs, term, preds }
35
+ }
36
+
37
+ function mirFn(name: string, params: string[], blocks: MIRBlock[], isMacro = false): MIRFunction {
38
+ return {
39
+ name,
40
+ params: params.map(p => ({ name: p, isMacroParam: false })),
41
+ blocks,
42
+ entry: 'entry',
43
+ isMacro,
44
+ }
45
+ }
46
+
47
+ function mirMod(functions: MIRFunction[]): MIRModule {
48
+ return { functions, namespace: 'test', objective: '__test' }
49
+ }
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // LIR helpers
53
+ // ---------------------------------------------------------------------------
54
+
55
+ const obj = '__test'
56
+ function slot(player: string): Slot { return { player, obj } }
57
+ function lirFn(instructions: LIRInstr[]): LIRFunction {
58
+ return { name: 'test', instructions, isMacro: false, macroParams: [] }
59
+ }
60
+
61
+ // ===========================================================================
62
+ // 1. coroutine.ts — uncovered branches
63
+ // ===========================================================================
64
+
65
+ describe('coroutine — fnContainsMacroCalls skip path', () => {
66
+ function mkBlock(id: string, instrs: MIRInstr[], term: MIRInstr): MIRBlock {
67
+ return { id, instrs, term, preds: [] }
68
+ }
69
+
70
+ test('skips transform and emits warning when function has call_macro', () => {
71
+ const fn: MIRFunction = {
72
+ name: 'test:worker',
73
+ params: [],
74
+ blocks: [
75
+ mkBlock('entry', [
76
+ // call_macro instruction — should trigger the skip path
77
+ { kind: 'call_macro', dst: null, fn: 'test:mfn', args: [] },
78
+ ], { kind: 'return', value: null }),
79
+ ],
80
+ entry: 'entry',
81
+ isMacro: false,
82
+ }
83
+ const mod = mirMod([fn])
84
+ const info: CoroutineInfo = { fnName: 'test:worker', batch: 10 }
85
+ const result = coroutineTransform(mod, [info])
86
+
87
+ // Function should be kept unchanged (not transformed)
88
+ expect(result.module.functions).toHaveLength(1)
89
+ expect(result.module.functions[0].name).toBe('test:worker')
90
+ // No tick functions generated
91
+ expect(result.generatedTickFunctions).toHaveLength(0)
92
+ // Warning should be emitted
93
+ expect(result.warnings).toHaveLength(1)
94
+ expect(result.warnings[0]).toContain('test:worker')
95
+ expect(result.warnings[0]).toContain('@coroutine')
96
+ })
97
+
98
+ test('skips transform when function has __raw: call with ${interpolation}', () => {
99
+ const fn: MIRFunction = {
100
+ name: 'test:interpolated',
101
+ params: [],
102
+ blocks: [
103
+ mkBlock('entry', [
104
+ // A raw call with variable interpolation
105
+ { kind: 'call', dst: null, fn: '__raw:say ${name}', args: [] },
106
+ ], { kind: 'return', value: null }),
107
+ ],
108
+ entry: 'entry',
109
+ isMacro: false,
110
+ }
111
+ const mod = mirMod([fn])
112
+ const info: CoroutineInfo = { fnName: 'test:interpolated', batch: 10 }
113
+ const result = coroutineTransform(mod, [info])
114
+
115
+ expect(result.module.functions).toHaveLength(1)
116
+ expect(result.generatedTickFunctions).toHaveLength(0)
117
+ expect(result.warnings).toHaveLength(1)
118
+ })
119
+
120
+ test('skips transform when function has __raw:\\x01 (builtin with macro)', () => {
121
+ const fn: MIRFunction = {
122
+ name: 'test:builtin_macro',
123
+ params: [],
124
+ blocks: [
125
+ mkBlock('entry', [
126
+ { kind: 'call', dst: null, fn: '__raw:\x01particle dust', args: [] },
127
+ ], { kind: 'return', value: null }),
128
+ ],
129
+ entry: 'entry',
130
+ isMacro: false,
131
+ }
132
+ const mod = mirMod([fn])
133
+ const info: CoroutineInfo = { fnName: 'test:builtin_macro', batch: 5 }
134
+ const result = coroutineTransform(mod, [info])
135
+
136
+ expect(result.generatedTickFunctions).toHaveLength(0)
137
+ expect(result.warnings).toHaveLength(1)
138
+ })
139
+
140
+ test('transforms when __raw: call has no interpolation', () => {
141
+ // __raw: without ${ or \x01 should NOT block the transform
142
+ const fn: MIRFunction = {
143
+ name: 'test:plain_raw',
144
+ params: [],
145
+ blocks: [
146
+ mkBlock('entry', [
147
+ { kind: 'call', dst: null, fn: '__raw:say hello', args: [] },
148
+ ], { kind: 'return', value: null }),
149
+ ],
150
+ entry: 'entry',
151
+ isMacro: false,
152
+ }
153
+ const mod = mirMod([fn])
154
+ const info: CoroutineInfo = { fnName: 'test:plain_raw', batch: 5 }
155
+ const result = coroutineTransform(mod, [info])
156
+
157
+ // Should be transformed (no warning)
158
+ expect(result.warnings).toHaveLength(0)
159
+ expect(result.generatedTickFunctions).toHaveLength(1)
160
+ })
161
+
162
+ test('call_macro in terminator position also triggers skip', () => {
163
+ // The loop in fnContainsMacroCalls iterates [...block.instrs, block.term]
164
+ // so a call_macro as the terminator should also trigger the skip
165
+ const fn: MIRFunction = {
166
+ name: 'test:macro_term',
167
+ params: [],
168
+ blocks: [
169
+ {
170
+ id: 'entry',
171
+ instrs: [],
172
+ // call_macro as terminator
173
+ term: { kind: 'call_macro', dst: null, fn: 'test:m', args: [] } as MIRInstr,
174
+ preds: [],
175
+ },
176
+ ],
177
+ entry: 'entry',
178
+ isMacro: false,
179
+ }
180
+ const mod = mirMod([fn])
181
+ const info: CoroutineInfo = { fnName: 'test:macro_term', batch: 5 }
182
+ const result = coroutineTransform(mod, [info])
183
+
184
+ expect(result.warnings).toHaveLength(1)
185
+ expect(result.generatedTickFunctions).toHaveLength(0)
186
+ })
187
+ })
188
+
189
+ // ===========================================================================
190
+ // 2. unroll.ts — uncovered branches
191
+ // ===========================================================================
192
+
193
+ describe('loopUnroll — uncovered branches', () => {
194
+ function buildLoopWithBlocks(blocks: MIRBlock[]): MIRFunction {
195
+ return { name: 'test', params: [], blocks, entry: 'entry', isMacro: false }
196
+ }
197
+
198
+ test('two-step latch pattern: add t_tmp i 1; copy i t_tmp', () => {
199
+ // Pattern 2 in latchIncrementsBy1
200
+ const fn = buildLoopWithBlocks([
201
+ mirBlock('entry', [
202
+ { kind: 'const', dst: 'i', value: 0 },
203
+ ], { kind: 'jump', target: 'loop_header_0' }),
204
+ mirBlock('loop_header_0', [
205
+ { kind: 'cmp', dst: 't_cmp', op: 'lt', a: t('i'), b: c(3) },
206
+ ], { kind: 'branch', cond: t('t_cmp'), then: 'loop_body_0', else: 'loop_exit_0' }),
207
+ mirBlock('loop_body_0', [
208
+ { kind: 'call', dst: null, fn: 'test:body', args: [t('i')] },
209
+ ], { kind: 'jump', target: 'loop_latch_0' }),
210
+ mirBlock('loop_latch_0', [
211
+ // Two-step increment: t_tmp = i + 1; i = t_tmp
212
+ { kind: 'add', dst: 't_tmp', a: t('i'), b: c(1) },
213
+ { kind: 'copy', dst: 'i', src: t('t_tmp') },
214
+ ], { kind: 'jump', target: 'loop_header_0' }),
215
+ mirBlock('loop_exit_0', [], { kind: 'return', value: null }),
216
+ ])
217
+
218
+ const result = loopUnroll(fn)
219
+
220
+ // Should have unrolled — loop_header gone
221
+ expect(result.blocks.some(b => b.id.startsWith('loop_header'))).toBe(false)
222
+ const entry = result.blocks.find(b => b.id === 'entry')!
223
+ const calls = entry.instrs.filter(i => i.kind === 'call')
224
+ expect(calls).toHaveLength(3)
225
+ // i substituted as 0, 1, 2
226
+ const callInstrs = calls as Extract<MIRInstr, { kind: 'call' }>[]
227
+ expect(callInstrs[0].args[0]).toEqual(c(0))
228
+ expect(callInstrs[1].args[0]).toEqual(c(1))
229
+ expect(callInstrs[2].args[0]).toEqual(c(2))
230
+ })
231
+
232
+ test('initializesTo0 via copy instruction (copy i, const 0)', () => {
233
+ // Tests the `copy` branch in initializesTo0
234
+ const fn = buildLoopWithBlocks([
235
+ mirBlock('entry', [
236
+ // Initialize via copy of const 0 instead of direct const
237
+ { kind: 'copy', dst: 'i', src: c(0) },
238
+ ], { kind: 'jump', target: 'loop_header_0' }),
239
+ mirBlock('loop_header_0', [
240
+ { kind: 'cmp', dst: 't_cmp', op: 'lt', a: t('i'), b: c(2) },
241
+ ], { kind: 'branch', cond: t('t_cmp'), then: 'loop_body_0', else: 'loop_exit_0' }),
242
+ mirBlock('loop_body_0', [
243
+ { kind: 'call', dst: null, fn: 'test:body', args: [t('i')] },
244
+ ], { kind: 'jump', target: 'loop_latch_0' }),
245
+ mirBlock('loop_latch_0', [
246
+ { kind: 'add', dst: 'i', a: t('i'), b: c(1) },
247
+ ], { kind: 'jump', target: 'loop_header_0' }),
248
+ mirBlock('loop_exit_0', [], { kind: 'return', value: null }),
249
+ ])
250
+
251
+ const result = loopUnroll(fn)
252
+ // Should unroll since copy i, 0 is recognized as init-to-0
253
+ expect(result.blocks.some(b => b.id.startsWith('loop_header'))).toBe(false)
254
+ const entry = result.blocks.find(b => b.id === 'entry')!
255
+ const calls = entry.instrs.filter(i => i.kind === 'call')
256
+ expect(calls).toHaveLength(2)
257
+ })
258
+
259
+ test('initializesTo0 copy with non-zero const does not unroll', () => {
260
+ // copy i, const 5 — not init to 0
261
+ const fn = buildLoopWithBlocks([
262
+ mirBlock('entry', [
263
+ { kind: 'copy', dst: 'i', src: c(5) },
264
+ ], { kind: 'jump', target: 'loop_header_0' }),
265
+ mirBlock('loop_header_0', [
266
+ { kind: 'cmp', dst: 't_cmp', op: 'lt', a: t('i'), b: c(8) },
267
+ ], { kind: 'branch', cond: t('t_cmp'), then: 'loop_body_0', else: 'loop_exit_0' }),
268
+ mirBlock('loop_body_0', [
269
+ { kind: 'call', dst: null, fn: 'test:body', args: [t('i')] },
270
+ ], { kind: 'jump', target: 'loop_latch_0' }),
271
+ mirBlock('loop_latch_0', [
272
+ { kind: 'add', dst: 'i', a: t('i'), b: c(1) },
273
+ ], { kind: 'jump', target: 'loop_header_0' }),
274
+ mirBlock('loop_exit_0', [], { kind: 'return', value: null }),
275
+ ])
276
+
277
+ const result = loopUnroll(fn)
278
+ expect(result).toBe(fn) // unchanged
279
+ })
280
+
281
+ test('latch with wrong temp in two-step does not unroll', () => {
282
+ // Pattern 2 but copy is from a different temp than the add destination
283
+ const fn = buildLoopWithBlocks([
284
+ mirBlock('entry', [
285
+ { kind: 'const', dst: 'i', value: 0 },
286
+ ], { kind: 'jump', target: 'loop_header_0' }),
287
+ mirBlock('loop_header_0', [
288
+ { kind: 'cmp', dst: 't_cmp', op: 'lt', a: t('i'), b: c(3) },
289
+ ], { kind: 'branch', cond: t('t_cmp'), then: 'loop_body_0', else: 'loop_exit_0' }),
290
+ mirBlock('loop_body_0', [
291
+ { kind: 'call', dst: null, fn: 'test:body', args: [t('i')] },
292
+ ], { kind: 'jump', target: 'loop_latch_0' }),
293
+ mirBlock('loop_latch_0', [
294
+ // Two-step but the copy sources from a different unrelated temp
295
+ { kind: 'add', dst: 't_tmp', a: t('i'), b: c(1) },
296
+ { kind: 'copy', dst: 'i', src: t('other_tmp') }, // wrong source
297
+ ], { kind: 'jump', target: 'loop_header_0' }),
298
+ mirBlock('loop_exit_0', [], { kind: 'return', value: null }),
299
+ ])
300
+
301
+ const result = loopUnroll(fn)
302
+ // Should NOT unroll — two-step mismatch
303
+ expect(result).toBe(fn)
304
+ })
305
+
306
+ test('multiple loops in function: unrolls both iteratively', () => {
307
+ // Two sequential loops, both N=2 — fixpoint iteration handles both
308
+ const fn = buildLoopWithBlocks([
309
+ mirBlock('entry', [
310
+ { kind: 'const', dst: 'i', value: 0 },
311
+ ], { kind: 'jump', target: 'loop_header_0' }),
312
+ mirBlock('loop_header_0', [
313
+ { kind: 'cmp', dst: 't0', op: 'lt', a: t('i'), b: c(2) },
314
+ ], { kind: 'branch', cond: t('t0'), then: 'loop_body_0', else: 'mid' }),
315
+ mirBlock('loop_body_0', [
316
+ { kind: 'call', dst: null, fn: 'test:a', args: [t('i')] },
317
+ ], { kind: 'jump', target: 'loop_latch_0' }),
318
+ mirBlock('loop_latch_0', [
319
+ { kind: 'add', dst: 'i', a: t('i'), b: c(1) },
320
+ ], { kind: 'jump', target: 'loop_header_0' }),
321
+ mirBlock('mid', [
322
+ { kind: 'const', dst: 'j', value: 0 },
323
+ ], { kind: 'jump', target: 'loop_header_1' }),
324
+ mirBlock('loop_header_1', [
325
+ { kind: 'cmp', dst: 't1', op: 'lt', a: t('j'), b: c(2) },
326
+ ], { kind: 'branch', cond: t('t1'), then: 'loop_body_1', else: 'loop_exit_1' }),
327
+ mirBlock('loop_body_1', [
328
+ { kind: 'call', dst: null, fn: 'test:b', args: [t('j')] },
329
+ ], { kind: 'jump', target: 'loop_latch_1' }),
330
+ mirBlock('loop_latch_1', [
331
+ { kind: 'add', dst: 'j', a: t('j'), b: c(1) },
332
+ ], { kind: 'jump', target: 'loop_header_1' }),
333
+ mirBlock('loop_exit_1', [], { kind: 'return', value: null }),
334
+ ])
335
+
336
+ const result = loopUnroll(fn)
337
+ // Both loops should be unrolled
338
+ expect(result.blocks.some(b => b.id.startsWith('loop_header'))).toBe(false)
339
+ })
340
+ })
341
+
342
+ // ===========================================================================
343
+ // 3. interprocedural.ts — uncovered branches
344
+ // ===========================================================================
345
+
346
+ describe('interproceduralConstProp — uncovered branches', () => {
347
+ test('isSelfRecursive: function calling itself via call_macro is not specialized', () => {
348
+ // A function that is self-recursive via call_macro should be skipped
349
+ const selfRecursiveFn = mirFn('test:recur', ['n'], [
350
+ mirBlock('entry', [
351
+ // call_macro to itself
352
+ { kind: 'call_macro', dst: null, fn: 'test:recur', args: [] },
353
+ ], { kind: 'return', value: null }),
354
+ ])
355
+
356
+ const callerFn = mirFn('test:main', [], [
357
+ mirBlock('entry', [
358
+ { kind: 'call', dst: null, fn: 'test:recur', args: [c(5)] },
359
+ ], { kind: 'return', value: null }),
360
+ ])
361
+
362
+ const mod = mirMod([callerFn, selfRecursiveFn])
363
+ const result = interproceduralConstProp(mod)
364
+
365
+ // Should NOT create specialized version — self-recursive via call_macro
366
+ expect(result.functions.some(f => f.name.includes('__const_'))).toBe(false)
367
+ })
368
+
369
+ test('param count mismatch: callee.params.length !== instr.args.length skips', () => {
370
+ // Callee expects 2 params but call site provides 1 — should not specialize
371
+ const addFn = mirFn('test:add', ['a', 'b'], [
372
+ mirBlock('entry', [
373
+ { kind: 'add', dst: 'r', a: t('a'), b: t('b') },
374
+ ], { kind: 'return', value: t('r') }),
375
+ ])
376
+
377
+ const callerFn = mirFn('test:main', [], [
378
+ mirBlock('entry', [
379
+ // Only 1 arg but callee expects 2
380
+ { kind: 'call', dst: null, fn: 'test:add', args: [c(3)] },
381
+ ], { kind: 'return', value: null }),
382
+ ])
383
+
384
+ const mod = mirMod([callerFn, addFn])
385
+ const result = interproceduralConstProp(mod)
386
+ expect(result.functions.some(f => f.name.includes('__const_'))).toBe(false)
387
+ })
388
+
389
+ test('rewriteCallSites skips call_macro instructions (not rewritten)', () => {
390
+ // rewriteCallSites only rewrites 'call' not 'call_macro'
391
+ const helperFn = mirFn('test:helper', ['x'], [
392
+ mirBlock('entry', [], { kind: 'return', value: null }),
393
+ ])
394
+
395
+ const callerFn = mirFn('test:main', [], [
396
+ mirBlock('entry', [
397
+ // call_macro — should not be rewritten even if specialized fn exists
398
+ { kind: 'call_macro', dst: null, fn: 'test:helper', args: [] },
399
+ ], { kind: 'return', value: null }),
400
+ ])
401
+
402
+ const mod = mirMod([callerFn, helperFn])
403
+ const result = interproceduralConstProp(mod)
404
+
405
+ const main = result.functions.find(f => f.name === 'test:main')!
406
+ const macroCall = main.blocks[0].instrs.find(i => i.kind === 'call_macro')
407
+ // Should remain unchanged
408
+ expect(macroCall).toBeDefined()
409
+ })
410
+
411
+ test('rewriteCallSites: call with self-same name is not rewritten', () => {
412
+ // When callee.name === fn.name (self-call) in rewriteCallSites, skip
413
+ const selfCallFn = mirFn('test:fib', ['n'], [
414
+ mirBlock('entry', [
415
+ // direct self-call with constant arg — rewriteCallSites should skip
416
+ { kind: 'call', dst: null, fn: 'test:fib', args: [c(5)] },
417
+ ], { kind: 'return', value: null }),
418
+ ])
419
+
420
+ const mod = mirMod([selfCallFn])
421
+ const result = interproceduralConstProp(mod)
422
+
423
+ const fib = result.functions.find(f => f.name === 'test:fib')!
424
+ const call = fib.blocks[0].instrs.find(i => i.kind === 'call') as Extract<MIRInstr, { kind: 'call' }>
425
+ // Self call should remain unchanged
426
+ expect(call.fn).toBe('test:fib')
427
+ })
428
+
429
+ test('rewriteCallSites: call with non-all-const args is not rewritten', () => {
430
+ const addFn = mirFn('test:add', ['a', 'b'], [
431
+ mirBlock('entry', [
432
+ { kind: 'add', dst: 'r', a: t('a'), b: t('b') },
433
+ ], { kind: 'return', value: t('r') }),
434
+ ])
435
+
436
+ const callerFn = mirFn('test:main', [], [
437
+ mirBlock('entry', [
438
+ // Mixed args — not all const
439
+ { kind: 'call', dst: null, fn: 'test:add', args: [t('x'), c(4)] },
440
+ ], { kind: 'return', value: null }),
441
+ ])
442
+
443
+ const mod = mirMod([callerFn, addFn])
444
+ const result = interproceduralConstProp(mod)
445
+
446
+ const main = result.functions.find(f => f.name === 'test:main')!
447
+ const call = main.blocks[0].instrs.find(i => i.kind === 'call') as Extract<MIRInstr, { kind: 'call' }>
448
+ // Should remain as 'test:add' (not rewritten)
449
+ expect(call.fn).toBe('test:add')
450
+ })
451
+
452
+ test('callee with multiple blocks is not specialized', () => {
453
+ // Only single-block callees are specialized
454
+ const multiFn = mirFn('test:multi', ['x'], [
455
+ mirBlock('entry', [
456
+ { kind: 'cmp', dst: 'cond', op: 'gt', a: t('x'), b: c(0) },
457
+ ], { kind: 'branch', cond: t('cond'), then: 'pos', else: 'neg' }),
458
+ mirBlock('pos', [], { kind: 'return', value: c(1) }),
459
+ mirBlock('neg', [], { kind: 'return', value: c(0) }),
460
+ ])
461
+
462
+ const callerFn = mirFn('test:main', [], [
463
+ mirBlock('entry', [
464
+ { kind: 'call', dst: null, fn: 'test:multi', args: [c(5)] },
465
+ ], { kind: 'return', value: null }),
466
+ ])
467
+
468
+ const mod = mirMod([callerFn, multiFn])
469
+ const result = interproceduralConstProp(mod)
470
+ expect(result.functions.some(f => f.name.includes('__const_'))).toBe(false)
471
+ })
472
+ })
473
+
474
+ // ===========================================================================
475
+ // 4. lir/const_imm.ts — uncovered branches
476
+ // ===========================================================================
477
+
478
+ describe('constImmFold — getReadSlots uncovered branches', () => {
479
+ test('score_swap: both slots a and b are read slots', () => {
480
+ // score_swap uses both a and b — getReadSlots returns [a, b]
481
+ // If one of them is the $__const_ slot, use count is > 1 → no fold
482
+ const constSlot = slot('$__const_5')
483
+ const fn = lirFn([
484
+ { kind: 'score_set', dst: constSlot, value: 5 },
485
+ { kind: 'score_swap', a: constSlot, b: slot('$x') },
486
+ { kind: 'score_add', dst: slot('$y'), src: constSlot },
487
+ ])
488
+ const result = constImmFold(fn)
489
+ // $__const_5 is used twice (swap + add) → no fold
490
+ expect(result.instructions).toHaveLength(3)
491
+ expect(result).toBe(fn)
492
+ })
493
+
494
+ test('store_nbt_to_score: does not count as reading a slot', () => {
495
+ // store_nbt_to_score reads from NBT, not a score slot — getReadSlots returns []
496
+ // So $__const_5 with only score_add as user → folds
497
+ const constSlot = slot('$__const_5')
498
+ const fn = lirFn([
499
+ { kind: 'score_set', dst: constSlot, value: 5 },
500
+ // store_nbt_to_score does NOT read constSlot
501
+ { kind: 'store_nbt_to_score', dst: slot('$z'), ns: 'test', path: 'data', scale: 1 },
502
+ { kind: 'score_add', dst: slot('$x'), src: constSlot },
503
+ ])
504
+ // Not adjacent (store_nbt_to_score is between) — no fold
505
+ const result = constImmFold(fn)
506
+ expect(result.instructions).toHaveLength(3)
507
+ })
508
+
509
+ test('macro_line: slots referenced in template are counted', () => {
510
+ // macro_line uses extractSlotsFromRaw on the template field
511
+ const constSlot = slot('$__const_5')
512
+ const fn = lirFn([
513
+ { kind: 'score_set', dst: constSlot, value: 5 },
514
+ // macro_line that references constSlot in template
515
+ { kind: 'macro_line', template: `$__const_5 ${obj}` },
516
+ { kind: 'score_add', dst: slot('$x'), src: constSlot },
517
+ ])
518
+ const result = constImmFold(fn)
519
+ // $__const_5 is used in macro_line template + score_add → 2 uses → no fold
520
+ expect(result).toBe(fn)
521
+ expect(result.instructions).toHaveLength(3)
522
+ })
523
+
524
+ test('extractSlotsFromRaw: raw cmd with player-obj pattern extracts slot', () => {
525
+ // raw instruction — slots extracted via regex /(\$[\w.:]+)\s+(\S+)/g
526
+ const constSlot = slot('$__const_7')
527
+ const fn = lirFn([
528
+ { kind: 'score_set', dst: constSlot, value: 7 },
529
+ // raw command that references constSlot
530
+ { kind: 'raw', cmd: `scoreboard players get $__const_7 ${obj}` },
531
+ { kind: 'score_add', dst: slot('$a'), src: constSlot },
532
+ ])
533
+ const result = constImmFold(fn)
534
+ // constSlot used in raw + score_add → 2 uses → no fold
535
+ expect(result).toBe(fn)
536
+ })
537
+
538
+ test('raw cmd with no matching slots: slot is not counted', () => {
539
+ // A raw cmd with no $-prefixed player references
540
+ const constSlot = slot('$__const_3')
541
+ const fn = lirFn([
542
+ { kind: 'score_set', dst: constSlot, value: 3 },
543
+ { kind: 'raw', cmd: 'say hello world' }, // no slot references
544
+ { kind: 'score_add', dst: slot('$x'), src: constSlot },
545
+ ])
546
+ // constSlot used only once (score_add), but not adjacent to score_set
547
+ const result = constImmFold(fn)
548
+ expect(result.instructions).toHaveLength(3) // not adjacent → no fold
549
+ })
550
+
551
+ test('call_if_score: both a and b slots are counted as reads', () => {
552
+ // call_if_score reads both a and b — if constSlot appears as either, use count++
553
+ const constSlot = slot('$__const_4')
554
+ const fn = lirFn([
555
+ { kind: 'score_set', dst: constSlot, value: 4 },
556
+ { kind: 'call_if_score', fn: 'test:f', a: constSlot, op: 'gt', b: slot('$x') },
557
+ { kind: 'score_add', dst: slot('$y'), src: constSlot },
558
+ ])
559
+ const result = constImmFold(fn)
560
+ // constSlot used in call_if_score (a) + score_add → 2 uses → no fold
561
+ expect(result).toBe(fn)
562
+ })
563
+
564
+ test('return_value: slot is counted as a read', () => {
565
+ // return_value reads the slot — if constSlot is returned, use count++
566
+ const constSlot = slot('$__const_9')
567
+ const fn = lirFn([
568
+ { kind: 'score_set', dst: constSlot, value: 9 },
569
+ { kind: 'score_add', dst: slot('$x'), src: constSlot },
570
+ { kind: 'return_value', slot: constSlot },
571
+ ])
572
+ // Two uses of constSlot (score_add + return_value) — no fold for the score_add pair
573
+ const result = constImmFold(fn)
574
+ expect(result).toBe(fn)
575
+ })
576
+
577
+ test('instructions length < 2: returns fn unchanged', () => {
578
+ const fn = lirFn([
579
+ { kind: 'score_set', dst: slot('$x'), value: 1 },
580
+ ])
581
+ const result = constImmFold(fn)
582
+ expect(result).toBe(fn)
583
+ })
584
+ })
585
+
586
+ // ===========================================================================
587
+ // 5. block_merge.ts — uncovered branch
588
+ // ===========================================================================
589
+
590
+ describe('blockMerge — uncovered branches', () => {
591
+ test('branch terminator: block with branch does not trigger merge', () => {
592
+ // The merge condition requires term.kind === 'jump'; a branch should not merge
593
+ const fn: MIRFunction = {
594
+ name: 'test',
595
+ params: [],
596
+ blocks: [
597
+ mirBlock('entry', [], {
598
+ kind: 'branch', cond: t('c'), then: 'b1', else: 'b2',
599
+ }),
600
+ mirBlock('b1', [], { kind: 'return', value: null }, ['entry']),
601
+ mirBlock('b2', [], { kind: 'return', value: null }, ['entry']),
602
+ ],
603
+ entry: 'entry',
604
+ isMacro: false,
605
+ }
606
+ const result = blockMerge(fn)
607
+ // Should have all 3 blocks — no merge possible
608
+ expect(result.blocks).toHaveLength(3)
609
+ })
610
+
611
+ test('jump to entry block: not merged (entry guard)', () => {
612
+ // A block jumping to 'entry' should not cause entry to be merged away
613
+ // (the `targetId !== fn.entry` guard)
614
+ const fn: MIRFunction = {
615
+ name: 'test',
616
+ params: [],
617
+ blocks: [
618
+ mirBlock('entry', [
619
+ { kind: 'const', dst: 'x', value: 1 },
620
+ ], { kind: 'return', value: t('x') }),
621
+ // A block that jumps to entry (unusual but valid for testing)
622
+ mirBlock('before', [], { kind: 'jump', target: 'entry' }),
623
+ ],
624
+ entry: 'before',
625
+ isMacro: false,
626
+ }
627
+ const result = blockMerge(fn)
628
+ // 'entry' has 1 pred (before) but is the fn.entry — guard prevents merge?
629
+ // Actually fn.entry = 'before', so 'entry' is NOT fn.entry here — it should merge
630
+ // Let's verify: before → entry (single pred), entry is not fn.entry ('before' is)
631
+ // So entry gets merged into before
632
+ expect(result.blocks.some(b => b.id === 'before')).toBe(true)
633
+ })
634
+
635
+ test('target not in blockMap: merge is skipped gracefully', () => {
636
+ // If target block doesn't exist in blockMap, blockMap.get returns undefined
637
+ // The `if (target && ...)` guard handles this
638
+ const fn: MIRFunction = {
639
+ name: 'test',
640
+ params: [],
641
+ blocks: [
642
+ mirBlock('entry', [], { kind: 'jump', target: 'nonexistent' }),
643
+ ],
644
+ entry: 'entry',
645
+ isMacro: false,
646
+ }
647
+ // Should not throw — target not found means no merge
648
+ const result = blockMerge(fn)
649
+ expect(result.blocks).toHaveLength(1)
650
+ })
651
+
652
+ test('already-removed block is skipped in iteration', () => {
653
+ // When A merges B, B is added to `removed`. If blocks array processes B again, it skips.
654
+ // A → B → C (all single-pred). B merges into A in first iteration;
655
+ // then A+B → C merges in second iteration.
656
+ const fn: MIRFunction = {
657
+ name: 'test',
658
+ params: [],
659
+ blocks: [
660
+ mirBlock('entry', [{ kind: 'const', dst: 'a', value: 1 }], { kind: 'jump', target: 'b1' }),
661
+ mirBlock('b1', [{ kind: 'const', dst: 'b', value: 2 }], { kind: 'jump', target: 'b2' }, ['entry']),
662
+ mirBlock('b2', [{ kind: 'const', dst: 'c', value: 3 }], { kind: 'return', value: null }, ['b1']),
663
+ ],
664
+ entry: 'entry',
665
+ isMacro: false,
666
+ }
667
+ const result = blockMerge(fn)
668
+ expect(result.blocks).toHaveLength(1)
669
+ expect(result.blocks[0].instrs).toHaveLength(3)
670
+ })
671
+ })
672
+
673
+ // ===========================================================================
674
+ // 2b. unroll.ts — additional uncovered branches
675
+ // ===========================================================================
676
+
677
+ describe('loopUnroll — additional edge cases', () => {
678
+ test('initializesTo0: another instr defines loopVar before const → returns false', () => {
679
+ // In initializesTo0, if getInstrDst returns loopVar for a non-const, non-copy instr → false
680
+ const fn: MIRFunction = {
681
+ name: 'test', params: [], entry: 'entry', isMacro: false,
682
+ blocks: [
683
+ mirBlock('entry', [
684
+ // Another instr defines 'i' — then const i 0 never reached in reverse scan
685
+ { kind: 'add', dst: 'i', a: c(1), b: c(1) }, // defines i = 2
686
+ { kind: 'const', dst: 'i', value: 0 }, // defines i = 0 (last def wins in reverse)
687
+ ], { kind: 'jump', target: 'loop_header_0' }),
688
+ mirBlock('loop_header_0', [
689
+ { kind: 'cmp', dst: 't_cmp', op: 'lt', a: t('i'), b: c(3) },
690
+ ], { kind: 'branch', cond: t('t_cmp'), then: 'loop_body_0', else: 'loop_exit_0' }),
691
+ mirBlock('loop_body_0', [
692
+ { kind: 'call', dst: null, fn: 'test:body', args: [t('i')] },
693
+ ], { kind: 'jump', target: 'loop_latch_0' }),
694
+ mirBlock('loop_latch_0', [
695
+ { kind: 'add', dst: 'i', a: t('i'), b: c(1) },
696
+ ], { kind: 'jump', target: 'loop_header_0' }),
697
+ mirBlock('loop_exit_0', [], { kind: 'return', value: null }),
698
+ ],
699
+ }
700
+ // Reverse scan hits const i 0 first → should unroll
701
+ const result = loopUnroll(fn)
702
+ expect(result.blocks.some(b => b.id.startsWith('loop_header'))).toBe(false)
703
+ })
704
+
705
+ test('initializesTo0: loopVar defined by non-zero non-copy instr → false', () => {
706
+ // In reverse scan: first hit is add dst=i → getInstrDst returns 'i' → return false
707
+ const fn: MIRFunction = {
708
+ name: 'test', params: [], entry: 'entry', isMacro: false,
709
+ blocks: [
710
+ mirBlock('entry', [
711
+ // Only a non-zero add defines i — no const 0 before it in reverse scan
712
+ { kind: 'add', dst: 'i', a: c(5), b: c(3) }, // defines i (not 0)
713
+ ], { kind: 'jump', target: 'loop_header_0' }),
714
+ mirBlock('loop_header_0', [
715
+ { kind: 'cmp', dst: 't_cmp', op: 'lt', a: t('i'), b: c(3) },
716
+ ], { kind: 'branch', cond: t('t_cmp'), then: 'loop_body_0', else: 'loop_exit_0' }),
717
+ mirBlock('loop_body_0', [
718
+ { kind: 'call', dst: null, fn: 'test:body', args: [t('i')] },
719
+ ], { kind: 'jump', target: 'loop_latch_0' }),
720
+ mirBlock('loop_latch_0', [
721
+ { kind: 'add', dst: 'i', a: t('i'), b: c(1) },
722
+ ], { kind: 'jump', target: 'loop_header_0' }),
723
+ mirBlock('loop_exit_0', [], { kind: 'return', value: null }),
724
+ ],
725
+ }
726
+ const result = loopUnroll(fn)
727
+ expect(result).toBe(fn) // not unrolled — not initialized to 0
728
+ })
729
+
730
+ test('findPreHeader: multiple predecessors to header → no unroll', () => {
731
+ // If loop_header has more than one non-latch predecessor, findPreHeader returns null
732
+ const fn: MIRFunction = {
733
+ name: 'test', params: [], entry: 'entry', isMacro: false,
734
+ blocks: [
735
+ mirBlock('entry', [
736
+ { kind: 'const', dst: 'i', value: 0 },
737
+ ], { kind: 'branch', cond: t('some_cond'), then: 'loop_header_0', else: 'alt' }),
738
+ mirBlock('alt', [
739
+ { kind: 'const', dst: 'i', value: 0 },
740
+ ], { kind: 'jump', target: 'loop_header_0' }),
741
+ // loop_header has two non-latch preds: entry and alt
742
+ mirBlock('loop_header_0', [
743
+ { kind: 'cmp', dst: 't_cmp', op: 'lt', a: t('i'), b: c(3) },
744
+ ], { kind: 'branch', cond: t('t_cmp'), then: 'loop_body_0', else: 'loop_exit_0' }),
745
+ mirBlock('loop_body_0', [
746
+ { kind: 'call', dst: null, fn: 'test:body', args: [t('i')] },
747
+ ], { kind: 'jump', target: 'loop_latch_0' }),
748
+ mirBlock('loop_latch_0', [
749
+ { kind: 'add', dst: 'i', a: t('i'), b: c(1) },
750
+ ], { kind: 'jump', target: 'loop_header_0' }),
751
+ mirBlock('loop_exit_0', [], { kind: 'return', value: null }),
752
+ ],
753
+ }
754
+ const result = loopUnroll(fn)
755
+ expect(result).toBe(fn) // not unrolled — ambiguous pre-header
756
+ })
757
+
758
+ test('body with nbt_write_dynamic substitutes both indexSrc and valueSrc', () => {
759
+ // Covers the nbt_write_dynamic case in substituteInstr
760
+ const fn: MIRFunction = {
761
+ name: 'test', params: [], entry: 'entry', isMacro: false,
762
+ blocks: [
763
+ mirBlock('entry', [
764
+ { kind: 'const', dst: 'i', value: 0 },
765
+ ], { kind: 'jump', target: 'loop_header_0' }),
766
+ mirBlock('loop_header_0', [
767
+ { kind: 'cmp', dst: 't_cmp', op: 'lt', a: t('i'), b: c(2) },
768
+ ], { kind: 'branch', cond: t('t_cmp'), then: 'loop_body_0', else: 'loop_exit_0' }),
769
+ mirBlock('loop_body_0', [
770
+ // nbt_write_dynamic with i as both indexSrc and valueSrc
771
+ {
772
+ kind: 'nbt_write_dynamic',
773
+ ns: 'test', pathPrefix: 'arr',
774
+ indexSrc: t('i'),
775
+ valueSrc: t('i'),
776
+ } as MIRInstr,
777
+ ], { kind: 'jump', target: 'loop_latch_0' }),
778
+ mirBlock('loop_latch_0', [
779
+ { kind: 'add', dst: 'i', a: t('i'), b: c(1) },
780
+ ], { kind: 'jump', target: 'loop_header_0' }),
781
+ mirBlock('loop_exit_0', [], { kind: 'return', value: null }),
782
+ ],
783
+ }
784
+ const result = loopUnroll(fn)
785
+ // Should unroll — 2 copies
786
+ expect(result.blocks.some(b => b.id.startsWith('loop_header'))).toBe(false)
787
+ const entry = result.blocks.find(b => b.id === 'entry')!
788
+ const writes = entry.instrs.filter(i => i.kind === 'nbt_write_dynamic')
789
+ expect(writes).toHaveLength(2)
790
+ // Both indexSrc and valueSrc should be substituted
791
+ const w0 = writes[0] as Extract<MIRInstr, { kind: 'nbt_write_dynamic' }>
792
+ expect(w0.indexSrc).toEqual(c(0))
793
+ expect(w0.valueSrc).toEqual(c(0))
794
+ })
795
+
796
+ test('body with nbt_write substitutes src', () => {
797
+ const fn: MIRFunction = {
798
+ name: 'test', params: [], entry: 'entry', isMacro: false,
799
+ blocks: [
800
+ mirBlock('entry', [
801
+ { kind: 'const', dst: 'i', value: 0 },
802
+ ], { kind: 'jump', target: 'loop_header_0' }),
803
+ mirBlock('loop_header_0', [
804
+ { kind: 'cmp', dst: 't_cmp', op: 'lt', a: t('i'), b: c(2) },
805
+ ], { kind: 'branch', cond: t('t_cmp'), then: 'loop_body_0', else: 'loop_exit_0' }),
806
+ mirBlock('loop_body_0', [
807
+ { kind: 'nbt_write', ns: 'test', path: 'val', type: 'int' as const, scale: 1, src: t('i') },
808
+ ], { kind: 'jump', target: 'loop_latch_0' }),
809
+ mirBlock('loop_latch_0', [
810
+ { kind: 'add', dst: 'i', a: t('i'), b: c(1) },
811
+ ], { kind: 'jump', target: 'loop_header_0' }),
812
+ mirBlock('loop_exit_0', [], { kind: 'return', value: null }),
813
+ ],
814
+ }
815
+ const result = loopUnroll(fn)
816
+ expect(result.blocks.some(b => b.id.startsWith('loop_header'))).toBe(false)
817
+ const entry = result.blocks.find(b => b.id === 'entry')!
818
+ const writes = entry.instrs.filter(i => i.kind === 'nbt_write') as Extract<MIRInstr, { kind: 'nbt_write' }>[]
819
+ expect(writes).toHaveLength(2)
820
+ expect(writes[0].src).toEqual(c(0))
821
+ expect(writes[1].src).toEqual(c(1))
822
+ })
823
+
824
+ test('body with call_macro substitutes arg values', () => {
825
+ // Covers the call_macro case in substituteInstr
826
+ const fn: MIRFunction = {
827
+ name: 'test', params: [], entry: 'entry', isMacro: false,
828
+ blocks: [
829
+ mirBlock('entry', [
830
+ { kind: 'const', dst: 'i', value: 0 },
831
+ ], { kind: 'jump', target: 'loop_header_0' }),
832
+ mirBlock('loop_header_0', [
833
+ { kind: 'cmp', dst: 't_cmp', op: 'lt', a: t('i'), b: c(2) },
834
+ ], { kind: 'branch', cond: t('t_cmp'), then: 'loop_body_0', else: 'loop_exit_0' }),
835
+ mirBlock('loop_body_0', [
836
+ {
837
+ kind: 'call_macro',
838
+ dst: null,
839
+ fn: 'test:macro_fn',
840
+ args: [{ name: 'x', value: t('i'), type: 'int' as const, scale: 1 }],
841
+ } as MIRInstr,
842
+ ], { kind: 'jump', target: 'loop_latch_0' }),
843
+ mirBlock('loop_latch_0', [
844
+ { kind: 'add', dst: 'i', a: t('i'), b: c(1) },
845
+ ], { kind: 'jump', target: 'loop_header_0' }),
846
+ mirBlock('loop_exit_0', [], { kind: 'return', value: null }),
847
+ ],
848
+ }
849
+ const result = loopUnroll(fn)
850
+ expect(result.blocks.some(b => b.id.startsWith('loop_header'))).toBe(false)
851
+ const entry = result.blocks.find(b => b.id === 'entry')!
852
+ const macroCalls = entry.instrs.filter(i => i.kind === 'call_macro') as Extract<MIRInstr, { kind: 'call_macro' }>[]
853
+ expect(macroCalls).toHaveLength(2)
854
+ expect(macroCalls[0].args[0].value).toEqual(c(0))
855
+ expect(macroCalls[1].args[0].value).toEqual(c(1))
856
+ })
857
+
858
+ test('body with return instr: substitutes value', () => {
859
+ // return with value — substituteInstr covers the return case
860
+ const fn: MIRFunction = {
861
+ name: 'test', params: [], entry: 'entry', isMacro: false,
862
+ blocks: [
863
+ mirBlock('entry', [
864
+ { kind: 'const', dst: 'i', value: 0 },
865
+ ], { kind: 'jump', target: 'loop_header_0' }),
866
+ mirBlock('loop_header_0', [
867
+ { kind: 'cmp', dst: 't_cmp', op: 'lt', a: t('i'), b: c(2) },
868
+ ], { kind: 'branch', cond: t('t_cmp'), then: 'loop_body_0', else: 'loop_exit_0' }),
869
+ // Body ending with a terminator substitution test via branch (cond=i)
870
+ mirBlock('loop_body_0', [
871
+ { kind: 'call', dst: null, fn: 'test:f', args: [] },
872
+ ], { kind: 'jump', target: 'loop_latch_0' }),
873
+ mirBlock('loop_latch_0', [
874
+ { kind: 'add', dst: 'i', a: t('i'), b: c(1) },
875
+ ], { kind: 'jump', target: 'loop_header_0' }),
876
+ mirBlock('loop_exit_0', [], { kind: 'return', value: t('i') }),
877
+ ],
878
+ }
879
+ const result = loopUnroll(fn)
880
+ // Unrolled — exit is kept with return value
881
+ expect(result.blocks.some(b => b.id.startsWith('loop_header'))).toBe(false)
882
+ })
883
+
884
+ test('loop with N=0: does not unroll (N <= 0 guard)', () => {
885
+ const fn: MIRFunction = {
886
+ name: 'test', params: [], entry: 'entry', isMacro: false,
887
+ blocks: [
888
+ mirBlock('entry', [
889
+ { kind: 'const', dst: 'i', value: 0 },
890
+ ], { kind: 'jump', target: 'loop_header_0' }),
891
+ mirBlock('loop_header_0', [
892
+ { kind: 'cmp', dst: 't_cmp', op: 'lt', a: t('i'), b: c(0) },
893
+ ], { kind: 'branch', cond: t('t_cmp'), then: 'loop_body_0', else: 'loop_exit_0' }),
894
+ mirBlock('loop_body_0', [
895
+ { kind: 'call', dst: null, fn: 'test:body', args: [] },
896
+ ], { kind: 'jump', target: 'loop_latch_0' }),
897
+ mirBlock('loop_latch_0', [
898
+ { kind: 'add', dst: 'i', a: t('i'), b: c(1) },
899
+ ], { kind: 'jump', target: 'loop_header_0' }),
900
+ mirBlock('loop_exit_0', [], { kind: 'return', value: null }),
901
+ ],
902
+ }
903
+ const result = loopUnroll(fn)
904
+ expect(result).toBe(fn) // N=0 is rejected
905
+ })
906
+
907
+ test('loop_header with no cmp instr: not unrolled', () => {
908
+ // header has branch but no cmp instruction for condName
909
+ const fn: MIRFunction = {
910
+ name: 'test', params: [], entry: 'entry', isMacro: false,
911
+ blocks: [
912
+ mirBlock('entry', [
913
+ { kind: 'const', dst: 'i', value: 0 },
914
+ ], { kind: 'jump', target: 'loop_header_0' }),
915
+ mirBlock('loop_header_0', [
916
+ // No cmp here — branch uses t_cmp but nothing defines it
917
+ ], { kind: 'branch', cond: t('t_cmp'), then: 'loop_body_0', else: 'loop_exit_0' }),
918
+ mirBlock('loop_body_0', [
919
+ { kind: 'call', dst: null, fn: 'test:body', args: [] },
920
+ ], { kind: 'jump', target: 'loop_latch_0' }),
921
+ mirBlock('loop_latch_0', [
922
+ { kind: 'add', dst: 'i', a: t('i'), b: c(1) },
923
+ ], { kind: 'jump', target: 'loop_header_0' }),
924
+ mirBlock('loop_exit_0', [], { kind: 'return', value: null }),
925
+ ],
926
+ }
927
+ const result = loopUnroll(fn)
928
+ expect(result).toBe(fn)
929
+ })
930
+
931
+ test('loop_header with const cond (not temp): not unrolled', () => {
932
+ // branch.cond must be temp — if it's const, skip
933
+ const fn: MIRFunction = {
934
+ name: 'test', params: [], entry: 'entry', isMacro: false,
935
+ blocks: [
936
+ mirBlock('entry', [
937
+ { kind: 'const', dst: 'i', value: 0 },
938
+ ], { kind: 'jump', target: 'loop_header_0' }),
939
+ mirBlock('loop_header_0', [
940
+ { kind: 'cmp', dst: 't_cmp', op: 'lt', a: t('i'), b: c(3) },
941
+ ], { kind: 'branch', cond: c(1), then: 'loop_body_0', else: 'loop_exit_0' }),
942
+ mirBlock('loop_body_0', [
943
+ { kind: 'call', dst: null, fn: 'test:body', args: [] },
944
+ ], { kind: 'jump', target: 'loop_latch_0' }),
945
+ mirBlock('loop_latch_0', [
946
+ { kind: 'add', dst: 'i', a: t('i'), b: c(1) },
947
+ ], { kind: 'jump', target: 'loop_header_0' }),
948
+ mirBlock('loop_exit_0', [], { kind: 'return', value: null }),
949
+ ],
950
+ }
951
+ const result = loopUnroll(fn)
952
+ expect(result).toBe(fn)
953
+ })
954
+ })
955
+
956
+ // ===========================================================================
957
+ // 6. constant_fold.ts — uncovered branch (evalCmp default)
958
+ // ===========================================================================
959
+
960
+ describe('constantFold — evalCmp default branch', () => {
961
+ test('unknown cmp op falls through to default (returns 0)', () => {
962
+ // The default case in evalCmp — an op that doesn't match any known op
963
+ // We test through constantFold by constructing a cmp with unknown op
964
+ const fn: MIRFunction = {
965
+ name: 'test',
966
+ params: [],
967
+ blocks: [
968
+ mirBlock('entry', [
969
+ // Use an unknown cmp op — TypeScript won't normally allow this
970
+ // but we cast to test the runtime default branch
971
+ {
972
+ kind: 'cmp',
973
+ dst: 'result',
974
+ op: 'unknown_op' as any,
975
+ a: c(5),
976
+ b: c(3),
977
+ } as MIRInstr,
978
+ ], { kind: 'return', value: t('result') }),
979
+ ],
980
+ entry: 'entry',
981
+ isMacro: false,
982
+ }
983
+
984
+ const result = constantFold(fn)
985
+ // Should have folded cmp(unknown_op, 5, 3) → const 0 (default case)
986
+ const instr = result.blocks[0].instrs[0]
987
+ expect(instr.kind).toBe('const')
988
+ if (instr.kind === 'const') {
989
+ expect(instr.value).toBe(0)
990
+ expect(instr.dst).toBe('result')
991
+ }
992
+ })
993
+
994
+ test('all six cmp ops fold correctly', () => {
995
+ // Regression: verify all known ops work (these are covered but good to have)
996
+ const ops = [
997
+ { op: 'eq', a: 5, b: 5, expected: 1 },
998
+ { op: 'eq', a: 5, b: 3, expected: 0 },
999
+ { op: 'ne', a: 5, b: 3, expected: 1 },
1000
+ { op: 'ne', a: 5, b: 5, expected: 0 },
1001
+ { op: 'lt', a: 3, b: 5, expected: 1 },
1002
+ { op: 'lt', a: 5, b: 3, expected: 0 },
1003
+ { op: 'le', a: 5, b: 5, expected: 1 },
1004
+ { op: 'le', a: 6, b: 5, expected: 0 },
1005
+ { op: 'gt', a: 5, b: 3, expected: 1 },
1006
+ { op: 'gt', a: 3, b: 5, expected: 0 },
1007
+ { op: 'ge', a: 5, b: 5, expected: 1 },
1008
+ { op: 'ge', a: 4, b: 5, expected: 0 },
1009
+ ] as const
1010
+
1011
+ for (const { op, a: av, b: bv, expected } of ops) {
1012
+ const fn: MIRFunction = {
1013
+ name: 'test',
1014
+ params: [],
1015
+ blocks: [
1016
+ mirBlock('entry', [
1017
+ { kind: 'cmp', dst: 'r', op, a: c(av), b: c(bv) },
1018
+ ], { kind: 'return', value: t('r') }),
1019
+ ],
1020
+ entry: 'entry',
1021
+ isMacro: false,
1022
+ }
1023
+ const result = constantFold(fn)
1024
+ const instr = result.blocks[0].instrs[0]
1025
+ expect(instr.kind).toBe('const')
1026
+ if (instr.kind === 'const') {
1027
+ expect(instr.value).toBe(expected)
1028
+ }
1029
+ }
1030
+ })
1031
+
1032
+ test('cmp with non-const operands is not folded', () => {
1033
+ const fn: MIRFunction = {
1034
+ name: 'test',
1035
+ params: [],
1036
+ blocks: [
1037
+ mirBlock('entry', [
1038
+ { kind: 'cmp', dst: 'r', op: 'lt', a: t('x'), b: c(5) },
1039
+ ], { kind: 'return', value: t('r') }),
1040
+ ],
1041
+ entry: 'entry',
1042
+ isMacro: false,
1043
+ }
1044
+ const result = constantFold(fn)
1045
+ // Not folded — a is temp
1046
+ expect(result.blocks[0].instrs[0].kind).toBe('cmp')
1047
+ })
1048
+
1049
+ test('div by zero is not folded', () => {
1050
+ const fn: MIRFunction = {
1051
+ name: 'test',
1052
+ params: [],
1053
+ blocks: [
1054
+ mirBlock('entry', [
1055
+ { kind: 'div', dst: 'r', a: c(10), b: c(0) },
1056
+ ], { kind: 'return', value: t('r') }),
1057
+ ],
1058
+ entry: 'entry',
1059
+ isMacro: false,
1060
+ }
1061
+ const result = constantFold(fn)
1062
+ // div by 0 should not be folded
1063
+ expect(result.blocks[0].instrs[0].kind).toBe('div')
1064
+ })
1065
+
1066
+ test('mod by zero is not folded', () => {
1067
+ const fn: MIRFunction = {
1068
+ name: 'test',
1069
+ params: [],
1070
+ blocks: [
1071
+ mirBlock('entry', [
1072
+ { kind: 'mod', dst: 'r', a: c(10), b: c(0) },
1073
+ ], { kind: 'return', value: t('r') }),
1074
+ ],
1075
+ entry: 'entry',
1076
+ isMacro: false,
1077
+ }
1078
+ const result = constantFold(fn)
1079
+ expect(result.blocks[0].instrs[0].kind).toBe('mod')
1080
+ })
1081
+ })