@typecad/cuttlefish 0.1.0-alpha.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 (377) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +79 -0
  3. package/dist/api/board-types.d.ts +1 -0
  4. package/dist/api/board-types.js +4 -0
  5. package/dist/api/capabilities.d.ts +12 -0
  6. package/dist/api/capabilities.js +1 -0
  7. package/dist/api/config.d.ts +120 -0
  8. package/dist/api/config.js +7 -0
  9. package/dist/api/index.d.ts +5 -0
  10. package/dist/api/index.js +2 -0
  11. package/dist/api/schema/builder.d.ts +14 -0
  12. package/dist/api/schema/builder.js +15 -0
  13. package/dist/api/schema/index.d.ts +2 -0
  14. package/dist/api/schema/index.js +1 -0
  15. package/dist/api/schema/types.d.ts +257 -0
  16. package/dist/api/schema/types.js +4 -0
  17. package/dist/api/shared/async-runtime-static.d.ts +8 -0
  18. package/dist/api/shared/async-runtime-static.js +201 -0
  19. package/dist/api/shared/async-types.d.ts +42 -0
  20. package/dist/api/shared/async-types.js +8 -0
  21. package/dist/api/shared/board-resolver.d.ts +12 -0
  22. package/dist/api/shared/board-resolver.js +7 -0
  23. package/dist/api/shared/cpp-type-ir.d.ts +229 -0
  24. package/dist/api/shared/cpp-type-ir.js +672 -0
  25. package/dist/api/shared/display-adapter.d.ts +12 -0
  26. package/dist/api/shared/display-adapter.js +145 -0
  27. package/dist/api/shared/display-adapters/eink-mono.d.ts +2 -0
  28. package/dist/api/shared/display-adapters/eink-mono.js +53 -0
  29. package/dist/api/shared/display-adapters/sdl.d.ts +2 -0
  30. package/dist/api/shared/display-adapters/sdl.js +452 -0
  31. package/dist/api/shared/display-adapters/ssd1309.d.ts +2 -0
  32. package/dist/api/shared/display-adapters/ssd1309.js +136 -0
  33. package/dist/api/shared/display-adapters/st7796.d.ts +2 -0
  34. package/dist/api/shared/display-adapters/st7796.js +132 -0
  35. package/dist/api/shared/display-capabilities.d.ts +45 -0
  36. package/dist/api/shared/display-capabilities.js +54 -0
  37. package/dist/api/shared/display-op-ir.d.ts +62 -0
  38. package/dist/api/shared/display-op-ir.js +19 -0
  39. package/dist/api/shared/display-profile.d.ts +265 -0
  40. package/dist/api/shared/display-profile.js +338 -0
  41. package/dist/api/shared/graphics-strategy.d.ts +29 -0
  42. package/dist/api/shared/graphics-strategy.js +11 -0
  43. package/dist/api/shared/hal-op-ir.d.ts +393 -0
  44. package/dist/api/shared/hal-op-ir.js +18 -0
  45. package/dist/api/shared/index.d.ts +26 -0
  46. package/dist/api/shared/index.js +21 -0
  47. package/dist/api/shared/ir-core.d.ts +363 -0
  48. package/dist/api/shared/ir-core.js +8 -0
  49. package/dist/api/shared/ir-declarations.d.ts +201 -0
  50. package/dist/api/shared/ir-declarations.js +26 -0
  51. package/dist/api/shared/ir.d.ts +85 -0
  52. package/dist/api/shared/ir.js +13 -0
  53. package/dist/api/shared/platform-strategy.d.ts +402 -0
  54. package/dist/api/shared/platform-strategy.js +14 -0
  55. package/dist/api/shared/polyfill-helper-registry.d.ts +11 -0
  56. package/dist/api/shared/polyfill-helper-registry.js +86 -0
  57. package/dist/api/shared/polyfill-types.d.ts +33 -0
  58. package/dist/api/shared/polyfill-types.js +76 -0
  59. package/dist/api/shared/promise-runtime.d.ts +10 -0
  60. package/dist/api/shared/promise-runtime.js +244 -0
  61. package/dist/api/shared/snprintf-types.d.ts +29 -0
  62. package/dist/api/shared/snprintf-types.js +8 -0
  63. package/dist/api/shared/string-method-registry.d.ts +53 -0
  64. package/dist/api/shared/string-method-registry.js +168 -0
  65. package/dist/api/shared/toolchain-types.d.ts +63 -0
  66. package/dist/api/shared/toolchain-types.js +71 -0
  67. package/dist/api/shared/types.d.ts +50 -0
  68. package/dist/api/shared/types.js +6 -0
  69. package/dist/ast/parse.d.ts +2 -0
  70. package/dist/ast/parse.js +4 -0
  71. package/dist/cache.d.ts +26 -0
  72. package/dist/cache.js +95 -0
  73. package/dist/cli-utils.d.ts +37 -0
  74. package/dist/cli-utils.js +250 -0
  75. package/dist/cli.d.ts +2 -0
  76. package/dist/cli.js +635 -0
  77. package/dist/config-loader.d.ts +78 -0
  78. package/dist/config-loader.js +508 -0
  79. package/dist/config-schema.d.ts +233 -0
  80. package/dist/config-schema.js +83 -0
  81. package/dist/create/board-checklist.d.ts +2 -0
  82. package/dist/create/board-checklist.js +52 -0
  83. package/dist/create/board-codegen.d.ts +9 -0
  84. package/dist/create/board-codegen.js +238 -0
  85. package/dist/create/board-generators.d.ts +12 -0
  86. package/dist/create/board-generators.js +652 -0
  87. package/dist/create/board-spec.d.ts +640 -0
  88. package/dist/create/board-spec.js +215 -0
  89. package/dist/create/eslint-rules-template.d.ts +2 -0
  90. package/dist/create/eslint-rules-template.js +672 -0
  91. package/dist/create/index.d.ts +11 -0
  92. package/dist/create/index.js +8 -0
  93. package/dist/create/init-scaffold.d.ts +27 -0
  94. package/dist/create/init-scaffold.js +150 -0
  95. package/dist/create/init-templates.d.ts +24 -0
  96. package/dist/create/init-templates.js +379 -0
  97. package/dist/create/init-wizard.d.ts +8 -0
  98. package/dist/create/init-wizard.js +168 -0
  99. package/dist/debug/breakpoint-loader.d.ts +24 -0
  100. package/dist/debug/breakpoint-loader.js +137 -0
  101. package/dist/debug/index.d.ts +3 -0
  102. package/dist/debug/index.js +7 -0
  103. package/dist/debug/preprocessor.d.ts +9 -0
  104. package/dist/debug/preprocessor.js +214 -0
  105. package/dist/debug/types.d.ts +32 -0
  106. package/dist/debug/types.js +6 -0
  107. package/dist/diagnostics/diagnostics-report.d.ts +51 -0
  108. package/dist/diagnostics/diagnostics-report.js +333 -0
  109. package/dist/diagnostics/json-schema.d.ts +138 -0
  110. package/dist/diagnostics/json-schema.js +27 -0
  111. package/dist/diagnostics/md-writer.d.ts +10 -0
  112. package/dist/diagnostics/md-writer.js +336 -0
  113. package/dist/diagnostics/mermaid-builder.d.ts +65 -0
  114. package/dist/diagnostics/mermaid-builder.js +681 -0
  115. package/dist/emit/cpp-emitter.d.ts +6 -0
  116. package/dist/emit/cpp-emitter.js +48 -0
  117. package/dist/emit/emitters/class-emitter.d.ts +2 -0
  118. package/dist/emit/emitters/class-emitter.js +523 -0
  119. package/dist/emit/emitters/emitter-context.d.ts +174 -0
  120. package/dist/emit/emitters/emitter-context.js +1 -0
  121. package/dist/emit/emitters/entrypoint-synthesizer.d.ts +2 -0
  122. package/dist/emit/emitters/entrypoint-synthesizer.js +175 -0
  123. package/dist/emit/emitters/function-emitter-impl.d.ts +19 -0
  124. package/dist/emit/emitters/function-emitter-impl.js +428 -0
  125. package/dist/emit/emitters/line-appender.d.ts +15 -0
  126. package/dist/emit/emitters/line-appender.js +344 -0
  127. package/dist/emit/emitters/namespace-emitter.d.ts +2 -0
  128. package/dist/emit/emitters/namespace-emitter.js +324 -0
  129. package/dist/emit/emitters/output-finalizer.d.ts +4 -0
  130. package/dist/emit/emitters/output-finalizer.js +267 -0
  131. package/dist/emit/emitters/setup.d.ts +3 -0
  132. package/dist/emit/emitters/setup.js +851 -0
  133. package/dist/emit/emitters/top-level-prep.d.ts +2 -0
  134. package/dist/emit/emitters/top-level-prep.js +706 -0
  135. package/dist/emit/emitters/type-decl-emitter.d.ts +2 -0
  136. package/dist/emit/emitters/type-decl-emitter.js +251 -0
  137. package/dist/emit/emitters/ui-emitter.d.ts +21 -0
  138. package/dist/emit/emitters/ui-emitter.js +564 -0
  139. package/dist/emit/enum-emitter.d.ts +1 -0
  140. package/dist/emit/enum-emitter.js +1 -0
  141. package/dist/emit/expression-renderer.d.ts +283 -0
  142. package/dist/emit/expression-renderer.js +1477 -0
  143. package/dist/emit/native-helpers-emitter.d.ts +11 -0
  144. package/dist/emit/native-helpers-emitter.js +34 -0
  145. package/dist/emit/route-hal-op.d.ts +5 -0
  146. package/dist/emit/route-hal-op.js +13 -0
  147. package/dist/emit/snprintf-helpers.d.ts +17 -0
  148. package/dist/emit/snprintf-helpers.js +462 -0
  149. package/dist/emit/statement-renderer.d.ts +189 -0
  150. package/dist/emit/statement-renderer.js +948 -0
  151. package/dist/emit/utils/async-state-machine.d.ts +42 -0
  152. package/dist/emit/utils/async-state-machine.js +343 -0
  153. package/dist/emit/utils/comment-helpers.d.ts +20 -0
  154. package/dist/emit/utils/comment-helpers.js +31 -0
  155. package/dist/emit/utils/cpp-helpers.d.ts +47 -0
  156. package/dist/emit/utils/cpp-helpers.js +85 -0
  157. package/dist/emit/utils/include-resolver.d.ts +58 -0
  158. package/dist/emit/utils/include-resolver.js +155 -0
  159. package/dist/emit/utils/index.d.ts +8 -0
  160. package/dist/emit/utils/index.js +12 -0
  161. package/dist/emit/utils/type-inference.d.ts +159 -0
  162. package/dist/emit/utils/type-inference.js +559 -0
  163. package/dist/eslint-check.d.ts +10 -0
  164. package/dist/eslint-check.js +102 -0
  165. package/dist/framework-package.d.ts +10 -0
  166. package/dist/framework-package.js +58 -0
  167. package/dist/framework-registry.d.ts +39 -0
  168. package/dist/framework-registry.js +30 -0
  169. package/dist/incremental-cache.d.ts +116 -0
  170. package/dist/incremental-cache.js +335 -0
  171. package/dist/ir/adc-range-validation.d.ts +11 -0
  172. package/dist/ir/adc-range-validation.js +250 -0
  173. package/dist/ir/ast-node-utils.d.ts +8 -0
  174. package/dist/ir/ast-node-utils.js +32 -0
  175. package/dist/ir/board-pin-utils.d.ts +13 -0
  176. package/dist/ir/board-pin-utils.js +65 -0
  177. package/dist/ir/board-resolver.d.ts +36 -0
  178. package/dist/ir/board-resolver.js +437 -0
  179. package/dist/ir/build-ir-state.d.ts +167 -0
  180. package/dist/ir/build-ir-state.js +306 -0
  181. package/dist/ir/build-ir.d.ts +2 -0
  182. package/dist/ir/build-ir.js +808 -0
  183. package/dist/ir/call-graph.d.ts +35 -0
  184. package/dist/ir/call-graph.js +194 -0
  185. package/dist/ir/compile-time-only.d.ts +2 -0
  186. package/dist/ir/compile-time-only.js +16 -0
  187. package/dist/ir/declaration-builders.d.ts +9 -0
  188. package/dist/ir/declaration-builders.js +608 -0
  189. package/dist/ir/entry-points.d.ts +30 -0
  190. package/dist/ir/entry-points.js +191 -0
  191. package/dist/ir/expression-to-ir.d.ts +5 -0
  192. package/dist/ir/expression-to-ir.js +2249 -0
  193. package/dist/ir/extract-property-chain.d.ts +10 -0
  194. package/dist/ir/extract-property-chain.js +30 -0
  195. package/dist/ir/feature-prescan.d.ts +3 -0
  196. package/dist/ir/feature-prescan.js +43 -0
  197. package/dist/ir/feature-registry.d.ts +30 -0
  198. package/dist/ir/feature-registry.js +816 -0
  199. package/dist/ir/filter.d.ts +7 -0
  200. package/dist/ir/filter.js +85 -0
  201. package/dist/ir/function-builder.d.ts +19 -0
  202. package/dist/ir/function-builder.js +670 -0
  203. package/dist/ir/hal/hal-emitter.d.ts +62 -0
  204. package/dist/ir/hal/hal-emitter.js +735 -0
  205. package/dist/ir/hal/hal-parser.d.ts +61 -0
  206. package/dist/ir/hal/hal-parser.js +473 -0
  207. package/dist/ir/hal/hal-plugins.d.ts +34 -0
  208. package/dist/ir/hal/hal-plugins.js +706 -0
  209. package/dist/ir/hal-resolver.d.ts +5 -0
  210. package/dist/ir/hal-resolver.js +5 -0
  211. package/dist/ir/heap-analysis.d.ts +8 -0
  212. package/dist/ir/heap-analysis.js +350 -0
  213. package/dist/ir/heap-array-validation.d.ts +24 -0
  214. package/dist/ir/heap-array-validation.js +29 -0
  215. package/dist/ir/identifier-collector.d.ts +18 -0
  216. package/dist/ir/identifier-collector.js +439 -0
  217. package/dist/ir/interrupt-analysis.d.ts +39 -0
  218. package/dist/ir/interrupt-analysis.js +467 -0
  219. package/dist/ir/memory-budget-validation.d.ts +10 -0
  220. package/dist/ir/memory-budget-validation.js +86 -0
  221. package/dist/ir/namespace-builder.d.ts +7 -0
  222. package/dist/ir/namespace-builder.js +152 -0
  223. package/dist/ir/ownership-analysis.d.ts +9 -0
  224. package/dist/ir/ownership-analysis.js +1207 -0
  225. package/dist/ir/peripheral-ownership.d.ts +3 -0
  226. package/dist/ir/peripheral-ownership.js +152 -0
  227. package/dist/ir/peripheral-symbols.d.ts +7 -0
  228. package/dist/ir/peripheral-symbols.js +57 -0
  229. package/dist/ir/peripheral-usage.d.ts +54 -0
  230. package/dist/ir/peripheral-usage.js +691 -0
  231. package/dist/ir/peripheral-validation.d.ts +8 -0
  232. package/dist/ir/peripheral-validation.js +77 -0
  233. package/dist/ir/pin-alias-conflict.d.ts +4 -0
  234. package/dist/ir/pin-alias-conflict.js +34 -0
  235. package/dist/ir/pin-capability-validation.d.ts +6 -0
  236. package/dist/ir/pin-capability-validation.js +413 -0
  237. package/dist/ir/pin-mode-validation.d.ts +10 -0
  238. package/dist/ir/pin-mode-validation.js +301 -0
  239. package/dist/ir/pin-safety.d.ts +11 -0
  240. package/dist/ir/pin-safety.js +58 -0
  241. package/dist/ir/program-analysis.d.ts +44 -0
  242. package/dist/ir/program-analysis.js +589 -0
  243. package/dist/ir/pulldown-validation.d.ts +7 -0
  244. package/dist/ir/pulldown-validation.js +49 -0
  245. package/dist/ir/pwm-timer-sharing.d.ts +4 -0
  246. package/dist/ir/pwm-timer-sharing.js +93 -0
  247. package/dist/ir/reachability.d.ts +68 -0
  248. package/dist/ir/reachability.js +224 -0
  249. package/dist/ir/register-decorators.d.ts +6 -0
  250. package/dist/ir/register-decorators.js +49 -0
  251. package/dist/ir/render-expr.d.ts +4 -0
  252. package/dist/ir/render-expr.js +127 -0
  253. package/dist/ir/resource-analysis.d.ts +19 -0
  254. package/dist/ir/resource-analysis.js +153 -0
  255. package/dist/ir/statement-to-ir.d.ts +23 -0
  256. package/dist/ir/statement-to-ir.js +349 -0
  257. package/dist/ir/symbol-table.d.ts +68 -0
  258. package/dist/ir/symbol-table.js +179 -0
  259. package/dist/ir/symbol-types.d.ts +56 -0
  260. package/dist/ir/symbol-types.js +107 -0
  261. package/dist/ir/timer0-pwm-timing-conflict.d.ts +4 -0
  262. package/dist/ir/timer0-pwm-timing-conflict.js +71 -0
  263. package/dist/ir/timing-validation.d.ts +7 -0
  264. package/dist/ir/timing-validation.js +95 -0
  265. package/dist/ir/transformers/array-methods.d.ts +8 -0
  266. package/dist/ir/transformers/array-methods.js +493 -0
  267. package/dist/ir/transformers/call-statement.d.ts +5 -0
  268. package/dist/ir/transformers/call-statement.js +758 -0
  269. package/dist/ir/transformers/callback-context-registry.d.ts +20 -0
  270. package/dist/ir/transformers/callback-context-registry.js +55 -0
  271. package/dist/ir/transformers/canvas-lowering.d.ts +42 -0
  272. package/dist/ir/transformers/canvas-lowering.js +249 -0
  273. package/dist/ir/transformers/control-flow.d.ts +8 -0
  274. package/dist/ir/transformers/control-flow.js +508 -0
  275. package/dist/ir/transformers/expressions.d.ts +7 -0
  276. package/dist/ir/transformers/expressions.js +406 -0
  277. package/dist/ir/transformers/hal-call-resolver.d.ts +29 -0
  278. package/dist/ir/transformers/hal-call-resolver.js +274 -0
  279. package/dist/ir/transformers/hal-emit-helpers.d.ts +14 -0
  280. package/dist/ir/transformers/hal-emit-helpers.js +63 -0
  281. package/dist/ir/transformers/namespace-methods.d.ts +20 -0
  282. package/dist/ir/transformers/namespace-methods.js +110 -0
  283. package/dist/ir/transformers/register-assignment.d.ts +5 -0
  284. package/dist/ir/transformers/register-assignment.js +56 -0
  285. package/dist/ir/transformers/ui-call-resolver.d.ts +149 -0
  286. package/dist/ir/transformers/ui-call-resolver.js +1136 -0
  287. package/dist/ir/transformers/ui-callback-lowering.d.ts +62 -0
  288. package/dist/ir/transformers/ui-callback-lowering.js +623 -0
  289. package/dist/ir/transformers/ui-mount.d.ts +20 -0
  290. package/dist/ir/transformers/ui-mount.js +35 -0
  291. package/dist/ir/transformers/ui-reactive.d.ts +68 -0
  292. package/dist/ir/transformers/ui-reactive.js +129 -0
  293. package/dist/ir/transformers/variables.d.ts +14 -0
  294. package/dist/ir/transformers/variables.js +1045 -0
  295. package/dist/ir/try-catch-validation.d.ts +26 -0
  296. package/dist/ir/try-catch-validation.js +128 -0
  297. package/dist/ir/type-resolution.d.ts +31 -0
  298. package/dist/ir/type-resolution.js +977 -0
  299. package/dist/ir/ui-element-auto-wire.d.ts +50 -0
  300. package/dist/ir/ui-element-auto-wire.js +218 -0
  301. package/dist/ir/unit-suspicion-validation.d.ts +12 -0
  302. package/dist/ir/unit-suspicion-validation.js +296 -0
  303. package/dist/ir/utils/walk-ir.d.ts +6 -0
  304. package/dist/ir/utils/walk-ir.js +162 -0
  305. package/dist/ir/validation-orchestrator.d.ts +4 -0
  306. package/dist/ir/validation-orchestrator.js +45 -0
  307. package/dist/libdef/base-class-resolver.d.ts +23 -0
  308. package/dist/libdef/base-class-resolver.js +50 -0
  309. package/dist/libdef/cpp-to-decl.d.ts +34 -0
  310. package/dist/libdef/cpp-to-decl.js +584 -0
  311. package/dist/libdef/header-parser.d.ts +53 -0
  312. package/dist/libdef/header-parser.js +269 -0
  313. package/dist/libdef/registry.d.ts +10 -0
  314. package/dist/libdef/registry.js +142 -0
  315. package/dist/mapping/peripheral-names.d.ts +23 -0
  316. package/dist/mapping/peripheral-names.js +63 -0
  317. package/dist/mapping/source-map.d.ts +46 -0
  318. package/dist/mapping/source-map.js +207 -0
  319. package/dist/orchestrator/dts-generator.d.ts +17 -0
  320. package/dist/orchestrator/dts-generator.js +83 -0
  321. package/dist/orchestrator/graph-builder.d.ts +18 -0
  322. package/dist/orchestrator/graph-builder.js +218 -0
  323. package/dist/orchestrator/semantic-facts-verifier.d.ts +38 -0
  324. package/dist/orchestrator/semantic-facts-verifier.js +121 -0
  325. package/dist/orchestrator/semantic-facts.d.ts +141 -0
  326. package/dist/orchestrator/semantic-facts.js +652 -0
  327. package/dist/orchestrator/type-checker.d.ts +39 -0
  328. package/dist/orchestrator/type-checker.js +760 -0
  329. package/dist/platform/async-runtime.d.ts +8 -0
  330. package/dist/platform/async-runtime.js +26 -0
  331. package/dist/platform/generic-strategy.d.ts +101 -0
  332. package/dist/platform/generic-strategy.js +289 -0
  333. package/dist/platform/index.d.ts +4 -0
  334. package/dist/platform/index.js +9 -0
  335. package/dist/platform/registry.d.ts +21 -0
  336. package/dist/platform/registry.js +42 -0
  337. package/dist/platform/toolchain.d.ts +4 -0
  338. package/dist/platform/toolchain.js +43 -0
  339. package/dist/preview/client.d.ts +1 -0
  340. package/dist/preview/client.js +137 -0
  341. package/dist/preview/server.d.ts +5 -0
  342. package/dist/preview/server.js +199 -0
  343. package/dist/profiler/index.d.ts +28 -0
  344. package/dist/profiler/index.js +28 -0
  345. package/dist/profiler/profiler.d.ts +95 -0
  346. package/dist/profiler/profiler.js +338 -0
  347. package/dist/profiler/types.d.ts +87 -0
  348. package/dist/profiler/types.js +12 -0
  349. package/dist/stores/display-profile-store.d.ts +16 -0
  350. package/dist/stores/display-profile-store.js +48 -0
  351. package/dist/stores/theme-store.d.ts +13 -0
  352. package/dist/stores/theme-store.js +30 -0
  353. package/dist/testing.d.ts +45 -0
  354. package/dist/testing.js +47 -0
  355. package/dist/transpile/resolution.d.ts +29 -0
  356. package/dist/transpile/resolution.js +282 -0
  357. package/dist/transpile.d.ts +3 -0
  358. package/dist/transpile.js +722 -0
  359. package/dist/types.d.ts +217 -0
  360. package/dist/types.js +1 -0
  361. package/dist/ui/ui-bridge.d.ts +4 -0
  362. package/dist/ui/ui-bridge.js +34 -0
  363. package/dist/ui-hook.d.ts +84 -0
  364. package/dist/ui-hook.js +45 -0
  365. package/dist/utils/cli.d.ts +3 -0
  366. package/dist/utils/cli.js +463 -0
  367. package/dist/utils/fs.d.ts +4 -0
  368. package/dist/utils/fs.js +23 -0
  369. package/dist/utils/logger.d.ts +2 -0
  370. package/dist/utils/logger.js +8 -0
  371. package/dist/utils/strings.d.ts +35 -0
  372. package/dist/utils/strings.js +142 -0
  373. package/dist/utils/ui.d.ts +66 -0
  374. package/dist/utils/ui.js +149 -0
  375. package/dist/watch.d.ts +35 -0
  376. package/dist/watch.js +111 -0
  377. package/package.json +127 -0
@@ -0,0 +1,1207 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Ownership & Borrowing Safety Analysis
3
+ //
4
+ // Validates Rust-inspired ownership rules at transpile time:
5
+ // 1. Use-after-move: error when reading a moved Owned variable
6
+ // 2. Assign-to-shared: error when assigning to a Shared (immutable borrow)
7
+ // 3. Mutable exclusivity: warning when multiple Mutable borrows exist
8
+ // 4. Borrow mismatch: error passing Shared where Mutable is expected
9
+ // 5. Const suggestion: warning for let variables never reassigned
10
+ //
11
+ // Opt-in: if no ownership types (Shared, Mutable, Owned) are used anywhere
12
+ // in the program, no diagnostics are generated.
13
+ // ---------------------------------------------------------------------------
14
+ import { parseCppType, parsedIsPointer } from '../api/shared/cpp-type-ir.js';
15
+ /** True for any `std::`-prefixed type (vector/map/set/tuple/variant/function/string).
16
+ * Replaces the historical `cppType.startsWith('std::')` check. */
17
+ function isStdContainerType(cppType) {
18
+ const ir = parseCppType(cppType);
19
+ switch (ir.kind) {
20
+ case "vector":
21
+ case "map":
22
+ case "set":
23
+ case "tuple":
24
+ case "variant":
25
+ case "function":
26
+ case "string":
27
+ case "smartPointer":
28
+ return true;
29
+ default:
30
+ return false;
31
+ }
32
+ }
33
+ /** Compile-time exhaustiveness check for switch statements on IR kinds. */
34
+ function assertNever(x) {
35
+ throw new Error(`Unhandled IR kind: ${JSON.stringify(x)}`);
36
+ }
37
+ /**
38
+ * Returns true for C++ scalar/primitive types passed cheaply by value.
39
+ * Non-primitives (std::vector, String, structs) should be passed by C++ reference
40
+ * when borrowed via Shared<T> or Mutable<T>.
41
+ */
42
+ function isPrimitiveCppType(cppType) {
43
+ const t = cppType.trim();
44
+ const primitives = new Set([
45
+ 'int', 'float', 'double', 'bool', 'char', 'long', 'void',
46
+ 'uint8_t', 'uint16_t', 'uint32_t', 'uint64_t',
47
+ 'int8_t', 'int16_t', 'int32_t', 'int64_t',
48
+ 'size_t', 'byte', 'word',
49
+ 'unsigned int', 'unsigned long', 'unsigned char',
50
+ 'signed int', 'signed long', 'signed char',
51
+ ]);
52
+ return primitives.has(t);
53
+ }
54
+ /** Scope tracker for ownership analysis. */
55
+ class OwnershipScope {
56
+ constructor(parent) {
57
+ this.parent = parent;
58
+ /** Variables declared in this scope, keyed by name. */
59
+ this.vars = new Map();
60
+ /** Set of variable names that have been moved. */
61
+ this.movedVars = new Set();
62
+ /** Track Mutable variables and their sources for exclusivity checking. */
63
+ this.mutRefSources = new Map(); // source -> [mutRefVarName, ...]
64
+ /** Track parameter ownership kinds for the current function. */
65
+ this.paramKinds = new Map();
66
+ /** By-value struct parameters — mutations on their fields are invisible to callers. */
67
+ this.byValueStructParams = new Set();
68
+ /** Whether any ownership type is used in this scope. */
69
+ this.usesOwnershipTypes = false;
70
+ }
71
+ /** Declare a variable in this scope. */
72
+ declare(name, ownershipKind, isLet, borrowSource, cppType) {
73
+ if (ownershipKind !== 'owned' || borrowSource) {
74
+ this.usesOwnershipTypes = true;
75
+ }
76
+ this.vars.set(name, {
77
+ name,
78
+ ownershipKind,
79
+ moved: false,
80
+ isLet,
81
+ everAssigned: false,
82
+ borrowSource,
83
+ cppType,
84
+ });
85
+ // Track Mutable sources for exclusivity checking
86
+ if (ownershipKind === 'mutable' && borrowSource) {
87
+ const existing = this.mutRefSources.get(borrowSource) ?? [];
88
+ existing.push(name);
89
+ this.mutRefSources.set(borrowSource, existing);
90
+ }
91
+ }
92
+ /** Mark a variable as moved. */
93
+ move(name) {
94
+ this.movedVars.add(name);
95
+ const v = this.resolve(name);
96
+ if (v) {
97
+ v.moved = true;
98
+ }
99
+ }
100
+ /** Mark a variable as having been assigned to. */
101
+ markAssigned(name) {
102
+ const v = this.resolve(name);
103
+ if (v) {
104
+ v.everAssigned = true;
105
+ }
106
+ }
107
+ /** Resolve a variable in this scope or parent scopes. */
108
+ resolve(name) {
109
+ const v = this.vars.get(name);
110
+ if (v)
111
+ return v;
112
+ if (this.parent)
113
+ return this.parent.resolve(name);
114
+ return undefined;
115
+ }
116
+ /** Check if a variable has been moved. */
117
+ isMoved(name) {
118
+ if (this.movedVars.has(name))
119
+ return true;
120
+ if (this.parent)
121
+ return this.parent.isMoved(name);
122
+ return false;
123
+ }
124
+ /** Get the ownership kind for a variable. */
125
+ getOwnershipKind(name) {
126
+ const v = this.resolve(name);
127
+ return v?.ownershipKind;
128
+ }
129
+ /** Collect all let variables in this scope (for const suggestion). */
130
+ collectLetVariables() {
131
+ const result = [];
132
+ for (const v of this.vars.values()) {
133
+ if (v.isLet && v.ownershipKind === 'owned') {
134
+ result.push(v);
135
+ }
136
+ }
137
+ return result;
138
+ }
139
+ }
140
+ /**
141
+ * Checks for mutations on by-value struct parameters (no Mutable<T> annotation).
142
+ * Emits ownership-mutate-copy warning — changes are local copies invisible to callers.
143
+ */
144
+ function checkByValueParamMutations(program, diagnostics) {
145
+ for (const fn of program.functions) {
146
+ const byValueParams = new Set();
147
+ for (const param of fn.parameters) {
148
+ if (!param.ownershipKind &&
149
+ param.cppType &&
150
+ !isPrimitiveCppType(param.cppType) &&
151
+ !parsedIsPointer(param.cppType) &&
152
+ !isStdContainerType(param.cppType)) {
153
+ byValueParams.add(param.name);
154
+ }
155
+ }
156
+ if (byValueParams.size === 0)
157
+ continue;
158
+ scanStatementsForByValueMutation(fn.statements, fn.originalName, byValueParams, diagnostics);
159
+ }
160
+ }
161
+ /**
162
+ * Recursively scans statement lists for assign/update to fields of by-value params.
163
+ */
164
+ function scanStatementsForByValueMutation(stmts, fnName, byValueParams, diagnostics) {
165
+ for (const stmt of stmts) {
166
+ if (stmt.kind === 'assign' || stmt.kind === 'update') {
167
+ const target = stmt.target;
168
+ if (target) {
169
+ const dotIdx = target.indexOf('.');
170
+ if (dotIdx > 0) {
171
+ const baseName = target.substring(0, dotIdx);
172
+ if (byValueParams.has(baseName)) {
173
+ diagnostics.push({
174
+ severity: 'warning',
175
+ message: `Mutating '${target}' on by-value parameter '${baseName}' in '${fnName}()' — changes are invisible to the caller.`,
176
+ hint: `Use '${baseName}: Mutable<T>' to pass by mutable reference (T&) instead of a copy.`,
177
+ line: stmt.sourceSpan.startLine,
178
+ column: stmt.sourceSpan.startColumn,
179
+ code: 'ownership-mutate-copy',
180
+ source: 'ownership-analysis',
181
+ });
182
+ }
183
+ }
184
+ }
185
+ }
186
+ // Recurse into nested statement blocks
187
+ if ('body' in stmt && Array.isArray(stmt.body))
188
+ scanStatementsForByValueMutation(stmt.body, fnName, byValueParams, diagnostics);
189
+ if ('thenBranch' in stmt && Array.isArray(stmt.thenBranch))
190
+ scanStatementsForByValueMutation(stmt.thenBranch, fnName, byValueParams, diagnostics);
191
+ if ('elseBranch' in stmt && Array.isArray(stmt.elseBranch))
192
+ scanStatementsForByValueMutation(stmt.elseBranch, fnName, byValueParams, diagnostics);
193
+ if ('cases' in stmt && Array.isArray(stmt.cases)) {
194
+ for (const c of stmt.cases) {
195
+ if (c.body)
196
+ scanStatementsForByValueMutation(c.body, fnName, byValueParams, diagnostics);
197
+ }
198
+ }
199
+ if ('tryBlock' in stmt && Array.isArray(stmt.tryBlock))
200
+ scanStatementsForByValueMutation(stmt.tryBlock, fnName, byValueParams, diagnostics);
201
+ if ('catchBlock' in stmt && Array.isArray(stmt.catchBlock))
202
+ scanStatementsForByValueMutation(stmt.catchBlock, fnName, byValueParams, diagnostics);
203
+ if ('finallyBlock' in stmt && Array.isArray(stmt.finallyBlock))
204
+ scanStatementsForByValueMutation(stmt.finallyBlock, fnName, byValueParams, diagnostics);
205
+ }
206
+ }
207
+ /**
208
+ * Validates ownership rules across the entire program.
209
+ *
210
+ * @param program - The program IR to analyze
211
+ * @returns Array of diagnostics for ownership violations
212
+ */
213
+ export function validateOwnership(program) {
214
+ const diagnostics = [];
215
+ // First pass: check if any ownership types are used anywhere
216
+ let usesOwnershipTypes = false;
217
+ const checkTypeForOwnership = (cppType) => {
218
+ return /\b(Shared|Mutable|Owned)</.test(cppType);
219
+ };
220
+ // Check top-level variable declarations
221
+ for (const stmt of program.topLevelStatements) {
222
+ if (stmt.kind === 'var_decl') {
223
+ if (stmt.ownershipKind) {
224
+ usesOwnershipTypes = true;
225
+ }
226
+ if (checkTypeForOwnership(stmt.cppType)) {
227
+ usesOwnershipTypes = true;
228
+ }
229
+ }
230
+ }
231
+ // Check function parameters and bodies
232
+ for (const fn of program.functions) {
233
+ for (const param of fn.parameters) {
234
+ if (param.ownershipKind) {
235
+ usesOwnershipTypes = true;
236
+ }
237
+ }
238
+ for (const stmt of fn.statements) {
239
+ if (stmt.kind === 'var_decl') {
240
+ if (stmt.ownershipKind) {
241
+ usesOwnershipTypes = true;
242
+ }
243
+ }
244
+ }
245
+ }
246
+ // If no ownership types are used, skip full ownership validation (opt-in).
247
+ // Still check for by-value struct param mutations — that diagnostic is
248
+ // useful whether or not the user has adopted ownership annotations.
249
+ checkByValueParamMutations(program, diagnostics);
250
+ if (!usesOwnershipTypes) {
251
+ // Still do const suggestion for all let variables
252
+ validateConstSuggestions(program, diagnostics);
253
+ return diagnostics;
254
+ }
255
+ // Second pass: full ownership validation
256
+ // Validate top-level statements
257
+ const globalScope = new OwnershipScope();
258
+ analyzeStatements(program.topLevelStatements, globalScope, diagnostics);
259
+ // Validate each function
260
+ for (const fn of program.functions) {
261
+ const fnScope = new OwnershipScope(globalScope);
262
+ // Register parameters — those with explicit ownership annotations get full
263
+ // borrow tracking; by-value non-primitive params are recorded so we can warn
264
+ // when their fields are mutated (changes won't be visible to the caller).
265
+ for (const param of fn.parameters) {
266
+ const kind = param.ownershipKind;
267
+ if (kind) {
268
+ fnScope.declare(param.name, kind, false, undefined, param.cppType);
269
+ fnScope.usesOwnershipTypes = true;
270
+ }
271
+ else if (param.cppType &&
272
+ !isPrimitiveCppType(param.cppType) &&
273
+ !parsedIsPointer(param.cppType) &&
274
+ !isStdContainerType(param.cppType)) {
275
+ fnScope.byValueStructParams.add(param.name);
276
+ }
277
+ }
278
+ analyzeStatements(fn.statements, fnScope, diagnostics);
279
+ }
280
+ // Const suggestions for ownership-aware code
281
+ validateConstSuggestions(program, diagnostics);
282
+ // Borrow mismatch: Shared<T> arg passed to Mutable<T> param
283
+ checkBorrowMismatch(program, diagnostics);
284
+ return diagnostics;
285
+ }
286
+ /**
287
+ * Analyze a list of statements for ownership violations.
288
+ */
289
+ function analyzeStatements(stmts, scope, diagnostics) {
290
+ for (const stmt of stmts) {
291
+ analyzeStatement(stmt, scope, diagnostics);
292
+ }
293
+ }
294
+ /**
295
+ * Analyze a single statement for ownership violations.
296
+ */
297
+ function analyzeStatement(stmt, scope, diagnostics) {
298
+ const span = stmt.sourceSpan;
299
+ switch (stmt.kind) {
300
+ case 'var_decl': {
301
+ const ownershipKind = stmt.ownershipKind;
302
+ // Detect borrow source from initializer
303
+ let borrowSource;
304
+ if (stmt.initializer && ownershipKind && ownershipKind !== 'owned') {
305
+ borrowSource = extractBorrowSource(stmt.initializer);
306
+ }
307
+ // Check initializer for use-after-move and detect moves from Owned variables.
308
+ // Order matters: analyzeExpression runs FIRST to detect pre-existing moves,
309
+ // then we register the new move. This avoids a false positive where we move
310
+ // the variable and then immediately check it again in analyzeExpression.
311
+ if (stmt.initializer) {
312
+ analyzeExpression(stmt.initializer, scope, diagnostics, span);
313
+ // If the initializer is a plain identifier referencing an explicitly Owned variable
314
+ // that hasn't already been moved, check whether this is a move or a borrow.
315
+ // Shared<T> and Mutable<T> destinations are borrows — the source stays alive.
316
+ // Only untyped or Owned<T> destinations trigger a move (ownership transfer).
317
+ const initName = extractIdentifier(stmt.initializer);
318
+ if (initName) {
319
+ const sourceVar = scope.resolve(initName);
320
+ if (sourceVar && sourceVar.ownershipKind === 'owned' && !scope.isMoved(initName)) {
321
+ const isBorrow = ownershipKind === 'shared' || ownershipKind === 'mutable';
322
+ if (!isBorrow) {
323
+ // Move ownership from source to this new variable
324
+ scope.move(initName);
325
+ // ownership-owned-copy: moving a non-primitive is a C++ copy, not a true move
326
+ if (!isPrimitiveCppType(stmt.cppType)) {
327
+ diagnostics.push({
328
+ severity: 'info',
329
+ message: `Moving '${initName}' into '${stmt.name}' creates a C++ copy.`,
330
+ hint: `const ${stmt.name}: Shared = ${initName}; // borrow by reference instead of copying`,
331
+ line: span.startLine,
332
+ column: span.startColumn,
333
+ code: 'ownership-owned-copy',
334
+ source: 'ownership-analysis',
335
+ });
336
+ }
337
+ }
338
+ }
339
+ // ownership-implicit-copy: unannotated non-primitive copied from a non-Owned variable
340
+ else if (!ownershipKind && !isPrimitiveCppType(stmt.cppType)) {
341
+ const srcVar = scope.resolve(initName);
342
+ if (srcVar && srcVar.ownershipKind !== 'owned') {
343
+ diagnostics.push({
344
+ severity: 'info',
345
+ message: `'${stmt.name}' silently copies '${initName}' — no borrow annotation.`,
346
+ hint: `const ${stmt.name}: Shared = ${initName}; // borrow by const reference, zero copy`,
347
+ line: span.startLine,
348
+ column: span.startColumn,
349
+ code: 'ownership-implicit-copy',
350
+ source: 'ownership-analysis',
351
+ });
352
+ }
353
+ }
354
+ }
355
+ // ownership-temp-ref-warn: Shared/Mutable assigned from a non-identifier non-primitive
356
+ if (ownershipKind && ownershipKind !== 'owned' && !isPrimitiveCppType(stmt.cppType)) {
357
+ if (stmt.initializer.kind !== 'identifier') {
358
+ const annotLabel = ownershipKind === 'shared' ? 'Shared' : 'Mutable';
359
+ const storageKw = ownershipKind === 'shared' ? 'const' : 'let';
360
+ diagnostics.push({
361
+ severity: 'warning',
362
+ message: `'${stmt.name}: ${annotLabel}' borrows a temporary — C++ cannot bind a reference to an rvalue. The emitter will fall back to a copy.`,
363
+ hint: `${storageKw} _tmp: Owned = ...;\nconst ${stmt.name}: ${annotLabel} = _tmp;`,
364
+ line: span.startLine,
365
+ column: span.startColumn,
366
+ code: 'ownership-temp-ref-warn',
367
+ source: 'ownership-analysis',
368
+ });
369
+ }
370
+ }
371
+ }
372
+ if (ownershipKind) {
373
+ scope.declare(stmt.name, ownershipKind, stmt.storage === 'let', borrowSource, stmt.cppType);
374
+ }
375
+ break;
376
+ }
377
+ case 'assign': {
378
+ // Check: assignment to Shared variable
379
+ const targetKind = scope.getOwnershipKind(stmt.target);
380
+ if (targetKind === 'shared') {
381
+ const targetVar = scope.resolve(stmt.target);
382
+ const typeAnnotation = (targetVar?.cppType && targetVar.cppType !== 'auto') ? `: Shared<${targetVar.cppType}>` : ': Shared';
383
+ diagnostics.push({
384
+ severity: 'error',
385
+ message: `Cannot assign to '${stmt.target}' — it is an immutable borrow.`,
386
+ hint: `change '${stmt.target}${typeAnnotation}' → '${stmt.target}: Mutable' // Mutable allows mutation`,
387
+ line: span.startLine,
388
+ column: span.startColumn,
389
+ code: 'ownership-assign-to-ref',
390
+ source: 'ownership-analysis',
391
+ });
392
+ }
393
+ // Check: assignment from Owned variable (move).
394
+ // Order matters: analyzeExpression runs FIRST to detect pre-existing moves,
395
+ // then we register the new move. This avoids a false positive.
396
+ if (stmt.value && stmt.operator === '=') {
397
+ analyzeExpression(stmt.value, scope, diagnostics, span);
398
+ // If the value is a plain identifier referencing an Owned variable that
399
+ // hasn't already been moved, register the move — but only if the target
400
+ // is not a borrow (Shared/Mutable). Borrows don't transfer ownership.
401
+ const sourceName = extractIdentifier(stmt.value);
402
+ if (sourceName) {
403
+ const sourceVar = scope.resolve(sourceName);
404
+ const targetOwnership = scope.getOwnershipKind(stmt.target);
405
+ const isTargetBorrow = targetOwnership === 'shared' || targetOwnership === 'mutable';
406
+ if (sourceVar && sourceVar.ownershipKind === 'owned' && !scope.isMoved(sourceName) && sourceVar.isLet && !isTargetBorrow) {
407
+ scope.move(sourceName);
408
+ }
409
+ if (isTargetBorrow && sourceVar && sourceVar.ownershipKind === 'owned') {
410
+ const targetVar = scope.resolve(stmt.target);
411
+ if (targetVar) {
412
+ targetVar.borrowSource = sourceName;
413
+ }
414
+ }
415
+ }
416
+ }
417
+ else if (stmt.value) {
418
+ analyzeExpression(stmt.value, scope, diagnostics, span);
419
+ }
420
+ scope.markAssigned(stmt.target);
421
+ // ownership-mutate-copy: mutating a field on a by-value struct parameter.
422
+ // Changes are local to the function and invisible to the caller.
423
+ const dotIdx = stmt.target.indexOf('.');
424
+ if (dotIdx > 0) {
425
+ const baseName = stmt.target.substring(0, dotIdx);
426
+ if (scope.byValueStructParams.has(baseName)) {
427
+ diagnostics.push({
428
+ severity: 'warning',
429
+ message: `Mutating '${stmt.target}' on by-value parameter '${baseName}' — changes are invisible to the caller.`,
430
+ hint: `Use '${baseName}: Mutable<T>' to pass by mutable reference (T&) instead of a copy.`,
431
+ line: span.startLine,
432
+ column: span.startColumn,
433
+ code: 'ownership-mutate-copy',
434
+ source: 'ownership-analysis',
435
+ });
436
+ }
437
+ }
438
+ break;
439
+ }
440
+ case 'update': {
441
+ // Check: update to Shared variable
442
+ const updateTargetKind = scope.getOwnershipKind(stmt.target);
443
+ if (updateTargetKind === 'shared') {
444
+ const updateTargetVar = scope.resolve(stmt.target);
445
+ const typeAnnotation = (updateTargetVar?.cppType && updateTargetVar.cppType !== 'auto') ? `: Shared<${updateTargetVar.cppType}>` : ': Shared';
446
+ diagnostics.push({
447
+ severity: 'error',
448
+ message: `Cannot update '${stmt.target}' — it is an immutable borrow.`,
449
+ hint: `change '${stmt.target}${typeAnnotation}' → '${stmt.target}: Mutable' // Mutable allows mutation`,
450
+ line: span.startLine,
451
+ column: span.startColumn,
452
+ code: 'ownership-assign-to-ref',
453
+ source: 'ownership-analysis',
454
+ });
455
+ }
456
+ // Check: update to moved variable
457
+ if (scope.isMoved(stmt.target)) {
458
+ diagnostics.push({
459
+ severity: 'error',
460
+ message: `'${stmt.target}' was moved and cannot be used again.`,
461
+ hint: `const ${stmt.target}_ref: Shared = ${stmt.target}; // add this before the move`,
462
+ line: span.startLine,
463
+ column: span.startColumn,
464
+ code: 'ownership-use-after-move',
465
+ source: 'ownership-analysis',
466
+ });
467
+ }
468
+ scope.markAssigned(stmt.target);
469
+ // ownership-mutate-copy (update path): same check as assign above
470
+ const upDotIdx = stmt.target.indexOf('.');
471
+ if (upDotIdx > 0) {
472
+ const upBaseName = stmt.target.substring(0, upDotIdx);
473
+ if (scope.byValueStructParams.has(upBaseName)) {
474
+ diagnostics.push({
475
+ severity: 'warning',
476
+ message: `Mutating '${stmt.target}' on by-value parameter '${upBaseName}' — changes are invisible to the caller.`,
477
+ hint: `Use '${upBaseName}: Mutable<T>' to pass by mutable reference (T&) instead of a copy.`,
478
+ line: span.startLine,
479
+ column: span.startColumn,
480
+ code: 'ownership-mutate-copy',
481
+ source: 'ownership-analysis',
482
+ });
483
+ }
484
+ }
485
+ break;
486
+ }
487
+ case 'call': {
488
+ // Check arguments for use-after-move
489
+ for (const arg of stmt.args) {
490
+ analyzeExpression(arg, scope, diagnostics, span);
491
+ }
492
+ break;
493
+ }
494
+ case 'return': {
495
+ if (stmt.value) {
496
+ analyzeExpression(stmt.value, scope, diagnostics, span);
497
+ // Check: returning a borrow whose source is an Owned variable — dangling reference
498
+ const retName = extractIdentifier(stmt.value);
499
+ if (retName) {
500
+ const retVar = scope.resolve(retName);
501
+ if (retVar && (retVar.ownershipKind === 'shared' || retVar.ownershipKind === 'mutable') && retVar.borrowSource) {
502
+ const source = scope.resolve(retVar.borrowSource);
503
+ if (source && source.ownershipKind === 'owned') {
504
+ diagnostics.push({
505
+ severity: 'error',
506
+ message: `Returning '${retName}' borrows '${retVar.borrowSource}' which will be destroyed when this function returns — dangling reference.`,
507
+ hint: `return ${retVar.borrowSource} directly as Owned, or change the function to accept '${retVar.borrowSource}: Shared' as a parameter`,
508
+ line: span.startLine,
509
+ column: span.startColumn,
510
+ code: 'ownership-return-local-ref',
511
+ source: 'ownership-analysis',
512
+ });
513
+ }
514
+ }
515
+ }
516
+ }
517
+ break;
518
+ }
519
+ case 'if': {
520
+ if (stmt.condition)
521
+ analyzeExpression(stmt.condition, scope, diagnostics, span);
522
+ const thenScope = new OwnershipScope(scope);
523
+ analyzeStatements(stmt.thenBranch, thenScope, diagnostics);
524
+ checkDanglingBorrowsOnScopeExit(thenScope, diagnostics, span);
525
+ if (stmt.elseBranch) {
526
+ const elseScope = new OwnershipScope(scope);
527
+ analyzeStatements(stmt.elseBranch, elseScope, diagnostics);
528
+ checkDanglingBorrowsOnScopeExit(elseScope, diagnostics, span);
529
+ }
530
+ break;
531
+ }
532
+ case 'while':
533
+ case 'do_while': {
534
+ if (stmt.condition)
535
+ analyzeExpression(stmt.condition, scope, diagnostics, span);
536
+ const loopScope = new OwnershipScope(scope);
537
+ analyzeStatements(stmt.body, loopScope, diagnostics);
538
+ checkDanglingBorrowsOnScopeExit(loopScope, diagnostics, span);
539
+ break;
540
+ }
541
+ case 'for': {
542
+ const forScope = new OwnershipScope(scope);
543
+ if (stmt.initializer)
544
+ analyzeStatement(stmt.initializer, forScope, diagnostics);
545
+ if (stmt.condition)
546
+ analyzeExpression(stmt.condition, forScope, diagnostics, span);
547
+ if (stmt.increment)
548
+ analyzeStatement(stmt.increment, forScope, diagnostics);
549
+ analyzeStatements(stmt.body, forScope, diagnostics);
550
+ checkDanglingBorrowsOnScopeExit(forScope, diagnostics, span);
551
+ break;
552
+ }
553
+ case 'for_of':
554
+ case 'for_in': {
555
+ const forScope = new OwnershipScope(scope);
556
+ if (stmt.variable)
557
+ analyzeStatement(stmt.variable, forScope, diagnostics);
558
+ if ('iterable' in stmt && stmt.iterable)
559
+ analyzeExpression(stmt.iterable, forScope, diagnostics, span);
560
+ if ('object' in stmt && stmt.object)
561
+ analyzeExpression(stmt.object, forScope, diagnostics, span);
562
+ analyzeStatements(stmt.body, forScope, diagnostics);
563
+ checkDanglingBorrowsOnScopeExit(forScope, diagnostics, span);
564
+ break;
565
+ }
566
+ case 'switch': {
567
+ if (stmt.expression)
568
+ analyzeExpression(stmt.expression, scope, diagnostics, span);
569
+ for (const c of stmt.cases) {
570
+ const caseScope = new OwnershipScope(scope);
571
+ if (c.value)
572
+ analyzeExpression(c.value, caseScope, diagnostics, span);
573
+ analyzeStatements(c.body, caseScope, diagnostics);
574
+ checkDanglingBorrowsOnScopeExit(caseScope, diagnostics, span);
575
+ }
576
+ break;
577
+ }
578
+ case 'block': {
579
+ const blockScope = new OwnershipScope(scope);
580
+ analyzeStatements(stmt.body, blockScope, diagnostics);
581
+ checkDanglingBorrowsOnScopeExit(blockScope, diagnostics, span);
582
+ break;
583
+ }
584
+ case 'labeled': {
585
+ const labelScope = new OwnershipScope(scope);
586
+ analyzeStatements(stmt.body, labelScope, diagnostics);
587
+ checkDanglingBorrowsOnScopeExit(labelScope, diagnostics, span);
588
+ break;
589
+ }
590
+ case 'try': {
591
+ const tryScope = new OwnershipScope(scope);
592
+ analyzeStatements(stmt.tryBlock, tryScope, diagnostics);
593
+ checkDanglingBorrowsOnScopeExit(tryScope, diagnostics, span);
594
+ if (stmt.catchBlock) {
595
+ const catchScope = new OwnershipScope(scope);
596
+ analyzeStatements(stmt.catchBlock, catchScope, diagnostics);
597
+ checkDanglingBorrowsOnScopeExit(catchScope, diagnostics, span);
598
+ }
599
+ if (stmt.finallyBlock) {
600
+ const finallyScope = new OwnershipScope(scope);
601
+ analyzeStatements(stmt.finallyBlock, finallyScope, diagnostics);
602
+ checkDanglingBorrowsOnScopeExit(finallyScope, diagnostics, span);
603
+ }
604
+ break;
605
+ }
606
+ case 'throw': {
607
+ if (stmt.value)
608
+ analyzeExpression(stmt.value, scope, diagnostics, span);
609
+ break;
610
+ }
611
+ case 'break':
612
+ case 'continue':
613
+ case 'hal-op':
614
+ case 'yield':
615
+ case 'super_call':
616
+ break;
617
+ default:
618
+ assertNever(stmt);
619
+ }
620
+ }
621
+ /**
622
+ * Analyze an expression for ownership violations (use-after-move).
623
+ *
624
+ * @param expr - The expression to analyze
625
+ * @param scope - The current ownership scope
626
+ * @param diagnostics - Accumulator for diagnostics
627
+ * @param fallbackSpan - SourceSpan from the enclosing statement, used when
628
+ * the expression itself doesn't carry location info
629
+ */
630
+ function analyzeExpression(expr, scope, diagnostics, fallbackSpan) {
631
+ if (!expr || typeof expr !== 'object' || !expr.kind)
632
+ return;
633
+ const span = expr.kind === 'callback' ? expr.sourceSpan : fallbackSpan;
634
+ switch (expr.kind) {
635
+ case 'identifier': {
636
+ const name = expr.value;
637
+ if (scope.isMoved(name)) {
638
+ diagnostics.push({
639
+ severity: 'error',
640
+ message: `'${name}' was moved and cannot be used again.`,
641
+ hint: `const ${name}_ref: Shared = ${name}; // add this before the move`,
642
+ line: span.startLine,
643
+ column: span.startColumn,
644
+ code: 'ownership-use-after-move',
645
+ source: 'ownership-analysis',
646
+ });
647
+ }
648
+ break;
649
+ }
650
+ case 'binary': {
651
+ analyzeExpression(expr.left, scope, diagnostics, span);
652
+ analyzeExpression(expr.right, scope, diagnostics, span);
653
+ break;
654
+ }
655
+ case 'unary': {
656
+ analyzeExpression(expr.operand, scope, diagnostics, span);
657
+ break;
658
+ }
659
+ case 'ternary': {
660
+ analyzeExpression(expr.condition, scope, diagnostics, span);
661
+ analyzeExpression(expr.whenTrue, scope, diagnostics, span);
662
+ analyzeExpression(expr.whenFalse, scope, diagnostics, span);
663
+ break;
664
+ }
665
+ case 'await': {
666
+ analyzeExpression(expr.value, scope, diagnostics, span);
667
+ break;
668
+ }
669
+ case 'array': {
670
+ for (const el of expr.elements) {
671
+ analyzeExpression(el, scope, diagnostics, span);
672
+ }
673
+ break;
674
+ }
675
+ case 'object': {
676
+ for (const field of expr.fields) {
677
+ analyzeExpression(field.value, scope, diagnostics, span);
678
+ }
679
+ break;
680
+ }
681
+ case 'string_concat': {
682
+ for (const part of expr.parts) {
683
+ analyzeExpression(part, scope, diagnostics, span);
684
+ }
685
+ break;
686
+ }
687
+ case 'template_string': {
688
+ analyzeExpression(expr.expression, scope, diagnostics, span);
689
+ break;
690
+ }
691
+ case 'property-access': {
692
+ analyzeExpression(expr.object, scope, diagnostics, span);
693
+ break;
694
+ }
695
+ case 'instanceof': {
696
+ analyzeExpression(expr.object, scope, diagnostics, span);
697
+ break;
698
+ }
699
+ case 'spread_array': {
700
+ analyzeExpression(expr.spreadExpr, scope, diagnostics, span);
701
+ for (const el of expr.additionalElements) {
702
+ analyzeExpression(el, scope, diagnostics, span);
703
+ }
704
+ break;
705
+ }
706
+ case 'raw': {
707
+ const matches = expr.value.match(/[A-Za-z_][A-Za-z0-9_]*/g);
708
+ if (matches) {
709
+ for (const match of matches) {
710
+ if (scope.isMoved(match)) {
711
+ diagnostics.push({
712
+ severity: 'error',
713
+ message: `'${match}' was moved and cannot be used again.`,
714
+ hint: `const ${match}_ref: Shared = ${match}; // add this before the move`,
715
+ line: span.startLine,
716
+ column: span.startColumn,
717
+ code: 'ownership-use-after-move',
718
+ source: 'ownership-analysis',
719
+ });
720
+ }
721
+ }
722
+ }
723
+ break;
724
+ }
725
+ case 'callback': {
726
+ for (const s of expr.statements) {
727
+ analyzeStatement(s, scope, diagnostics);
728
+ }
729
+ break;
730
+ }
731
+ case 'lambda': {
732
+ for (const s of expr.body) {
733
+ analyzeStatement(s, scope, diagnostics);
734
+ }
735
+ break;
736
+ }
737
+ case 'method-call': {
738
+ for (const arg of expr.args) {
739
+ analyzeExpression(arg, scope, diagnostics, span);
740
+ }
741
+ break;
742
+ }
743
+ case 'element-access': {
744
+ analyzeExpression(expr.object, scope, diagnostics, span);
745
+ analyzeExpression(expr.index, scope, diagnostics, span);
746
+ break;
747
+ }
748
+ case 'tuple-access': {
749
+ analyzeExpression(expr.object, scope, diagnostics, span);
750
+ break;
751
+ }
752
+ case 'paren': {
753
+ analyzeExpression(expr.inner, scope, diagnostics, span);
754
+ break;
755
+ }
756
+ case 'number':
757
+ case 'string':
758
+ case 'boolean':
759
+ case 'hal-expr':
760
+ break;
761
+ default:
762
+ assertNever(expr);
763
+ }
764
+ }
765
+ /**
766
+ * Extract the identifier name from a simple expression.
767
+ * Returns undefined for complex expressions.
768
+ */
769
+ function extractIdentifier(expr) {
770
+ if (expr.kind === 'identifier')
771
+ return expr.value;
772
+ return undefined;
773
+ }
774
+ /**
775
+ * Extract the borrow source from an initializer expression.
776
+ * For `let ref: Shared<T> = source`, returns "source".
777
+ */
778
+ function extractBorrowSource(expr) {
779
+ return extractIdentifier(expr);
780
+ }
781
+ /**
782
+ * Validate const suggestions: warn about `let` variables that are never reassigned.
783
+ * This runs regardless of whether ownership types are used.
784
+ */
785
+ function validateConstSuggestions(program, diagnostics) {
786
+ // Methods whose C++ lowering mutates the receiver (so a `const` binding
787
+ // mutated through them must be demoted to non-const).
788
+ const MUTATING_METHODS = new Set([
789
+ 'push', 'push_back', 'pop', 'pop_back', 'shift', '__tc_shift',
790
+ 'unshift', '__tc_unshift', 'splice', '__tc_splice1', '__tc_splice2',
791
+ 'sort', '__tc_sort', '__tc_sort_fn', 'fill', '__tc_fill',
792
+ '__tc_fill3', 'reverse', '__tc_reverse', 'clear',
793
+ // Set.add() -> insert, Map/Set.delete() -> erase (demo #15 fix C)
794
+ 'insert', 'erase',
795
+ ]);
796
+ // Every lexical statement body in the program. Demotion (const-content-
797
+ // mutated) and the suggest-const global-assignment scan must reach class
798
+ // methods, getters, setters, constructors, and namespace-scoped functions —
799
+ // not just top-level statements and free `function`s. Without this, a
800
+ // `const`-bound loop variable (or collection) mutated inside a class method
801
+ // is never demoted, so the emitter keeps `const T&` and g++ rejects the
802
+ // mutation (demo #17). Each body is analyzed with its own scope-local maps
803
+ // (see analyzeScope below), so this only enumerates the bodies; it does not
804
+ // merge their scopes.
805
+ const allStatementBodies = [program.topLevelStatements];
806
+ for (const fn of program.functions)
807
+ allStatementBodies.push(fn.statements);
808
+ for (const cls of program.classes) {
809
+ for (const method of cls.methods)
810
+ allStatementBodies.push(method.statements);
811
+ for (const getter of cls.getters)
812
+ allStatementBodies.push(getter.statements);
813
+ for (const setter of cls.setters)
814
+ allStatementBodies.push(setter.statements);
815
+ if (cls.constructor)
816
+ allStatementBodies.push(cls.constructor.statements);
817
+ }
818
+ // Namespaces are recursive (a namespace can nest classes/namespaces).
819
+ const collectNamespaceBodies = (ns) => {
820
+ for (const fn of ns.functions)
821
+ allStatementBodies.push(fn.statements);
822
+ for (const cls of ns.classes) {
823
+ for (const method of cls.methods)
824
+ allStatementBodies.push(method.statements);
825
+ for (const getter of cls.getters)
826
+ allStatementBodies.push(getter.statements);
827
+ for (const setter of cls.setters)
828
+ allStatementBodies.push(setter.statements);
829
+ if (cls.constructor)
830
+ allStatementBodies.push(cls.constructor.statements);
831
+ }
832
+ for (const child of ns.children ?? [])
833
+ collectNamespaceBodies(child);
834
+ };
835
+ for (const ns of program.namespaces)
836
+ collectNamespaceBodies(ns);
837
+ // Collect every plain-identifier assignment / update target across the whole
838
+ // program. A `let` declared in one scope may be reassigned by a *different*
839
+ // function that closes over it (e.g. a top-level `let counter` reassigned in
840
+ // `main()`), so the suggest-const check must be suppressed for any name that
841
+ // is assigned anywhere. Demotion (const-content-mutated), by contrast, is
842
+ // resolved scope-locally below — a mutation in one function must not demote a
843
+ // same-named `const` in a sibling function (demo #16 gap #2).
844
+ const globallyAssignedNames = new Set();
845
+ const collectAssignedNames = (stmts) => {
846
+ for (const stmt of stmts) {
847
+ if (stmt.kind === 'assign' || stmt.kind === 'update') {
848
+ // Only a bare-identifier target counts as "this var is reassigned";
849
+ // dotted/bracket targets (out.a, arr[i]) mutate contents, not the binding.
850
+ if (/^[A-Za-z_$][\w$]*$/.test(stmt.target)) {
851
+ globallyAssignedNames.add(stmt.target);
852
+ }
853
+ }
854
+ const nested = getNestedStatements(stmt);
855
+ if (nested)
856
+ collectAssignedNames(nested);
857
+ }
858
+ };
859
+ for (const body of allStatementBodies)
860
+ collectAssignedNames(body);
861
+ // Walk one lexical scope (a top-level or function body) in a single pass,
862
+ // registering declarations and detecting mutations against the SAME
863
+ // scope-local maps. This is critical: the previous implementation ran two
864
+ // separate passes over all functions with flat name-keyed maps, so a
865
+ // `const labels` in one function collided with a `let labels` in another
866
+ // (demo #16 gap #2 — a read-only `const` Map was wrongly demoted because a
867
+ // same-named binding in a sibling function was mutated). Scope-local maps
868
+ // make declaration and mutation resolve within their own function.
869
+ const analyzeScope = (stmts) => {
870
+ const letVars = new Map();
871
+ const constVars = new Map();
872
+ const walk = (statements) => {
873
+ for (const stmt of statements) {
874
+ if (stmt.kind === 'var_decl') {
875
+ if (stmt.storage === 'let') {
876
+ letVars.set(stmt.name, { name: stmt.name, everAssigned: false, stmt, span: stmt.sourceSpan });
877
+ }
878
+ else if (stmt.storage === 'const') {
879
+ // Track const decls whose contents could be mutated via a method
880
+ // or element/member assignment; the mutation checks below decide
881
+ // whether to demote.
882
+ constVars.set(stmt.name, { stmt, span: stmt.sourceSpan });
883
+ }
884
+ }
885
+ if (stmt.kind === 'assign') {
886
+ const entry = letVars.get(stmt.target);
887
+ if (entry)
888
+ entry.everAssigned = true;
889
+ // Element assignment on a `const` array/map (e.g. `arr[i] = x` where
890
+ // `arr` is `const`) compiles in TS but fails against the emitted
891
+ // `const std::vector<T>` / `const std::map<K,V>`. Demote the binding.
892
+ // The target string is the lowered C++ lvalue, e.g. `arr[0]`/`m[key]`.
893
+ const bracket = stmt.target.indexOf('[');
894
+ if (bracket > 0) {
895
+ const baseName = stmt.target.slice(0, bracket);
896
+ // A let array/map mutated via element assignment (arr[i] = x or
897
+ // m.set(k,v), which lowers to m[k]=v) is effectively reassigned —
898
+ // mark it so ownership-suggest-const does not wrongly recommend
899
+ // making it const (which would then fail g++). (demo #15 fix C)
900
+ const letEntry = letVars.get(baseName);
901
+ if (letEntry)
902
+ letEntry.everAssigned = true;
903
+ const constEntry = constVars.get(baseName);
904
+ if (constEntry && constEntry.stmt.storage === 'const') {
905
+ constEntry.stmt.storage = 'let';
906
+ diagnostics.push({
907
+ severity: 'info',
908
+ message: `'${baseName}' is declared 'const' but its contents are mutated via index assignment — demoted to non-const in C++ so the mutation compiles.`,
909
+ line: constEntry.span.startLine,
910
+ column: constEntry.span.startColumn,
911
+ code: 'ownership-const-content-mutated',
912
+ source: 'ownership-analysis',
913
+ });
914
+ }
915
+ }
916
+ // Member assignment on a `const` struct local (e.g. `out.a = 5` where
917
+ // `out` is `const Pair`) compiles in TS but fails against the emitted
918
+ // `const Pair out` (read-only aggregate). Demote the binding. The
919
+ // target is a dotted lvalue like `out.a` or `out->a`.
920
+ const dot = stmt.target.indexOf('.');
921
+ const arrow = stmt.target.indexOf('->');
922
+ const sepIdx = dot >= 0 ? dot : arrow;
923
+ if (sepIdx > 0) {
924
+ const baseName = stmt.target.slice(0, sepIdx);
925
+ const constEntry = constVars.get(baseName);
926
+ if (constEntry && constEntry.stmt.storage === 'const') {
927
+ constEntry.stmt.storage = 'let';
928
+ diagnostics.push({
929
+ severity: 'info',
930
+ message: `'${baseName}' is declared 'const' but a field is mutated via member assignment — demoted to non-const in C++ so the mutation compiles.`,
931
+ line: constEntry.span.startLine,
932
+ column: constEntry.span.startColumn,
933
+ code: 'ownership-const-content-mutated',
934
+ source: 'ownership-analysis',
935
+ });
936
+ }
937
+ }
938
+ }
939
+ if (stmt.kind === 'update') {
940
+ const entry = letVars.get(stmt.target);
941
+ if (entry)
942
+ entry.everAssigned = true;
943
+ // A `++`/`--` on a member of a `const` local (e.g. `t.hits++` where
944
+ // `t` is a const loop variable) is the UpdateExpression analogue of
945
+ // the member-assignment demotion above. The target string is the
946
+ // lowered lvalue, e.g. `t.hits` / `t->hits`. Demote the const base.
947
+ const dot = stmt.target.indexOf('.');
948
+ const arrow = stmt.target.indexOf('->');
949
+ const sepIdx = dot >= 0 ? dot : arrow;
950
+ if (sepIdx > 0) {
951
+ const baseName = stmt.target.slice(0, sepIdx);
952
+ const constEntry = constVars.get(baseName);
953
+ if (constEntry && constEntry.stmt.storage === 'const') {
954
+ constEntry.stmt.storage = 'let';
955
+ diagnostics.push({
956
+ severity: 'info',
957
+ message: `'${baseName}' is declared 'const' but a field is mutated via ++/-- — demoted to non-const in C++ so the mutation compiles.`,
958
+ line: constEntry.span.startLine,
959
+ column: constEntry.span.startColumn,
960
+ code: 'ownership-const-content-mutated',
961
+ source: 'ownership-analysis',
962
+ });
963
+ }
964
+ }
965
+ }
966
+ // A `let`/`const` collection mutated via a mutating method call
967
+ // (e.g. arr.push(x)) is effectively reassigned — the C++ emitter must
968
+ // keep the binding non-const so push_back/splice/etc. compile. The
969
+ // callee is the lowered C++ name, e.g. "arr.push_back".
970
+ if (stmt.kind === 'call' && typeof stmt.callee === 'string') {
971
+ const callee = stmt.callee;
972
+ const dot = callee.lastIndexOf('.');
973
+ if (dot > 0) {
974
+ const receiver = callee.slice(0, dot);
975
+ const method = callee.slice(dot + 1);
976
+ if (MUTATING_METHODS.has(method)) {
977
+ const entry = letVars.get(receiver);
978
+ if (entry)
979
+ entry.everAssigned = true;
980
+ // Demote a `const` binding whose contents are mutated via a
981
+ // method call: TS permits this, but the emitted
982
+ // `const std::vector<T>` (or std::map/std::string) rejects
983
+ // `.push_back`/etc. Flip the IR storage to `let` so the
984
+ // emitter drops the `const` qualifier, and surface an
985
+ // info-diagnostic so the rewrite isn't silent.
986
+ const constEntry = constVars.get(receiver);
987
+ if (constEntry && constEntry.stmt.storage === 'const') {
988
+ constEntry.stmt.storage = 'let';
989
+ diagnostics.push({
990
+ severity: 'info',
991
+ message: `'${receiver}' is declared 'const' but its contents are mutated via .${method}() — demoted to non-const in C++ so the mutation compiles.`,
992
+ line: constEntry.span.startLine,
993
+ column: constEntry.span.startColumn,
994
+ code: 'ownership-const-content-mutated',
995
+ source: 'ownership-analysis',
996
+ });
997
+ }
998
+ }
999
+ }
1000
+ }
1001
+ // Recurse into nested statements (shares this scope's maps — a nested
1002
+ // block can see and mutate the enclosing function's locals).
1003
+ const nested = getNestedStatements(stmt);
1004
+ if (nested) {
1005
+ walk(nested);
1006
+ }
1007
+ }
1008
+ };
1009
+ walk(stmts);
1010
+ // Promote `let` bindings that are never reassigned (in this scope or any
1011
+ // other) to `const` in the emitted C++. The transpiler's whole-program
1012
+ // reassignment analysis proves the binding is never written after init —
1013
+ // information avr-gcc cannot recover across translation units. Emitting
1014
+ // `const` lets the compiler place the value in flash/ROM and enables
1015
+ // constant folding. The inverse demotion (const→let when a member is
1016
+ // mutated) already mutates `.storage` the same way (see above), so the
1017
+ // emit path already handles both directions.
1018
+ for (const entry of letVars.values()) {
1019
+ if (!entry.everAssigned && !globallyAssignedNames.has(entry.name)) {
1020
+ entry.stmt.storage = 'const';
1021
+ diagnostics.push({
1022
+ severity: 'info',
1023
+ message: `'${entry.name}' is never reassigned — emitted as \`const\` so the C++ compiler can place it in ROM and fold it.`,
1024
+ line: entry.span.startLine,
1025
+ column: entry.span.startColumn,
1026
+ code: 'ownership-suggest-const',
1027
+ source: 'ownership-analysis',
1028
+ });
1029
+ }
1030
+ }
1031
+ };
1032
+ for (const body of allStatementBodies)
1033
+ analyzeScope(body);
1034
+ }
1035
+ /**
1036
+ * Check for borrow mismatch: a Shared<T>-annotated variable passed as a Mutable<T> parameter.
1037
+ * Runs as a separate top-level pass so it doesn't need to thread state through the main
1038
+ * recursive analysis.
1039
+ */
1040
+ function checkBorrowMismatch(program, diagnostics) {
1041
+ // Build function parameter ownership map
1042
+ const fnParamKinds = new Map();
1043
+ for (const fn of program.functions) {
1044
+ fnParamKinds.set(fn.originalName, fn.parameters.map(p => p.ownershipKind));
1045
+ }
1046
+ // Build a flat varName → ownershipKind map from a list of statements (shallow, no nested)
1047
+ const buildVarKinds = (stmts, seed) => {
1048
+ const map = seed ?? new Map();
1049
+ for (const stmt of stmts) {
1050
+ if (stmt.kind === 'var_decl') {
1051
+ if (stmt.ownershipKind)
1052
+ map.set(stmt.name, stmt.ownershipKind);
1053
+ }
1054
+ }
1055
+ return map;
1056
+ };
1057
+ // Scan statements for call sites and check ownership mismatch on arguments
1058
+ const scanCalls = (stmts, varKinds) => {
1059
+ for (const stmt of stmts) {
1060
+ if (stmt.kind === 'call') {
1061
+ const paramKinds = fnParamKinds.get(stmt.callee);
1062
+ if (paramKinds) {
1063
+ for (let i = 0; i < stmt.args.length; i++) {
1064
+ const paramKind = paramKinds[i];
1065
+ const arg = stmt.args[i];
1066
+ if (paramKind === 'mutable' && arg?.kind === 'identifier') {
1067
+ const argKind = varKinds.get(arg.value);
1068
+ if (argKind === 'shared') {
1069
+ diagnostics.push({
1070
+ severity: 'error',
1071
+ message: `Cannot pass '${arg.value}' (immutable Shared) to '${stmt.callee}' which expects a mutable borrow.`,
1072
+ hint: `change '${arg.value}: Shared = ...' → '${arg.value}: Mutable = ...'`,
1073
+ line: stmt.sourceSpan.startLine,
1074
+ column: stmt.sourceSpan.startColumn,
1075
+ code: 'ownership-borrow-mismatch',
1076
+ source: 'ownership-analysis',
1077
+ });
1078
+ }
1079
+ }
1080
+ }
1081
+ }
1082
+ }
1083
+ // Recurse into nested blocks
1084
+ const nested = getNestedStatements(stmt);
1085
+ if (nested)
1086
+ scanCalls(nested, varKinds);
1087
+ }
1088
+ };
1089
+ // Check top-level statements
1090
+ scanCalls(program.topLevelStatements, buildVarKinds(program.topLevelStatements));
1091
+ // Check each function (include parameters in the varKinds map)
1092
+ for (const fn of program.functions) {
1093
+ const varKinds = buildVarKinds(fn.statements);
1094
+ for (const param of fn.parameters) {
1095
+ const kind = param.ownershipKind;
1096
+ if (kind)
1097
+ varKinds.set(param.name, kind);
1098
+ }
1099
+ scanCalls(fn.statements, varKinds);
1100
+ }
1101
+ }
1102
+ /**
1103
+ * After a child scope exits, check whether any variable in an ancestor scope borrows
1104
+ * an owned variable that was declared only in the exiting child scope. Those borrows
1105
+ * are now dangling references in the generated C++.
1106
+ */
1107
+ function checkDanglingBorrowsOnScopeExit(exitingScope, diagnostics, span) {
1108
+ // Collect the names of owned variables that are being destroyed
1109
+ const exitingOwnedNames = new Set();
1110
+ for (const v of exitingScope.vars.values()) {
1111
+ if (v.ownershipKind === 'owned') {
1112
+ exitingOwnedNames.add(v.name);
1113
+ }
1114
+ }
1115
+ if (exitingOwnedNames.size === 0)
1116
+ return;
1117
+ // Walk up ancestor scopes looking for borrows whose source is one of those variables
1118
+ let ancestor = exitingScope.parent;
1119
+ while (ancestor) {
1120
+ for (const v of ancestor.vars.values()) {
1121
+ if ((v.ownershipKind === 'shared' || v.ownershipKind === 'mutable') &&
1122
+ v.borrowSource &&
1123
+ exitingOwnedNames.has(v.borrowSource)) {
1124
+ diagnostics.push({
1125
+ severity: 'error',
1126
+ message: `'${v.name}' borrows '${v.borrowSource}' which goes out of scope here — potential dangling reference.`,
1127
+ hint: `move '${v.borrowSource}' to the outer scope, or ensure '${v.name}' does not outlive it`,
1128
+ line: span.startLine,
1129
+ column: span.startColumn,
1130
+ code: 'ownership-dangling-borrow',
1131
+ source: 'ownership-analysis',
1132
+ });
1133
+ }
1134
+ }
1135
+ ancestor = ancestor.parent;
1136
+ }
1137
+ }
1138
+ /**
1139
+ * Get nested statements from a compound statement.
1140
+ */
1141
+ function getNestedStatements(stmt) {
1142
+ switch (stmt.kind) {
1143
+ case 'if': {
1144
+ return [...stmt.thenBranch, ...(stmt.elseBranch ?? [])];
1145
+ }
1146
+ case 'while':
1147
+ case 'do_while': {
1148
+ return stmt.body;
1149
+ }
1150
+ case 'for': {
1151
+ const result = [];
1152
+ if (stmt.initializer)
1153
+ result.push(stmt.initializer);
1154
+ if (stmt.increment)
1155
+ result.push(stmt.increment);
1156
+ result.push(...stmt.body);
1157
+ return result;
1158
+ }
1159
+ case 'for_of':
1160
+ case 'for_in': {
1161
+ const result = [];
1162
+ result.push(stmt.variable);
1163
+ result.push(...stmt.body);
1164
+ return result;
1165
+ }
1166
+ case 'switch': {
1167
+ const result = [];
1168
+ for (const c of stmt.cases) {
1169
+ result.push(...c.body);
1170
+ }
1171
+ return result;
1172
+ }
1173
+ case 'block':
1174
+ case 'labeled': {
1175
+ return stmt.body;
1176
+ }
1177
+ case 'try': {
1178
+ return [
1179
+ ...stmt.tryBlock,
1180
+ ...(stmt.catchBlock ?? []),
1181
+ ...(stmt.finallyBlock ?? []),
1182
+ ];
1183
+ }
1184
+ case 'call': {
1185
+ const result = [];
1186
+ for (const arg of stmt.args) {
1187
+ if (arg.kind === 'callback' && arg.statements) {
1188
+ result.push(...arg.statements);
1189
+ }
1190
+ }
1191
+ return result.length > 0 ? result : undefined;
1192
+ }
1193
+ case 'var_decl':
1194
+ case 'assign':
1195
+ case 'update':
1196
+ case 'return':
1197
+ case 'break':
1198
+ case 'continue':
1199
+ case 'throw':
1200
+ case 'hal-op':
1201
+ case 'yield':
1202
+ case 'super_call':
1203
+ return undefined;
1204
+ default:
1205
+ assertNever(stmt);
1206
+ }
1207
+ }