@greenpandastudios/aug-cli 0.19.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 (414) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +13 -0
  3. package/THIRD_PARTY_NOTICES.md +18 -0
  4. package/bin/aug.mjs +3 -0
  5. package/docs/api/crypto.md +351 -0
  6. package/docs/api/io.md +207 -0
  7. package/docs/api/json.md +21 -0
  8. package/docs/api/memory.md +109 -0
  9. package/docs/api/time.md +54 -0
  10. package/docs/api/web.md +184 -0
  11. package/docs/assets/benchmarks/execution.svg +2978 -0
  12. package/docs/assets/benchmarks/http.svg +1674 -0
  13. package/docs/assets/benchmarks/improvements.svg +2315 -0
  14. package/docs/assets/benchmarks/memory.svg +1891 -0
  15. package/docs/benchmark-baseline.json +1087 -0
  16. package/docs/benchmark-results.json +91123 -0
  17. package/docs/benchmarks.json +38 -0
  18. package/docs/compatibility.md +42 -0
  19. package/docs/concurrency-implementation.md +18 -0
  20. package/docs/diagnostics.md +97 -0
  21. package/docs/docker.md +35 -0
  22. package/docs/example-projects.json +26 -0
  23. package/docs/examples/approved-design/counters.md +253 -0
  24. package/docs/examples/approved-design/dependencies/august/0.19.0/io/contracts.md +395 -0
  25. package/docs/examples/approved-design/domain/app.md +165 -0
  26. package/docs/examples/approved-design/domain/export.md +83 -0
  27. package/docs/examples/approved-design/domain/models.md +80 -0
  28. package/docs/examples/approved-design/domain/numbers.md +248 -0
  29. package/docs/examples/approved-design/index.md +39 -0
  30. package/docs/examples/approved-design/main-yaml.md +24 -0
  31. package/docs/examples/approved-design/main.md +215 -0
  32. package/docs/examples/benchmark/index.md +33 -0
  33. package/docs/examples/benchmark/main-yaml.md +17 -0
  34. package/docs/examples/benchmark/main.md +117 -0
  35. package/docs/examples/cli-args/index.md +32 -0
  36. package/docs/examples/cli-args/main.md +107 -0
  37. package/docs/examples/collections/index.md +32 -0
  38. package/docs/examples/collections/main.md +118 -0
  39. package/docs/examples/collections-benchmark/index.md +34 -0
  40. package/docs/examples/collections-benchmark/main.md +115 -0
  41. package/docs/examples/cpu-benchmark/index.md +34 -0
  42. package/docs/examples/cpu-benchmark/main.md +90 -0
  43. package/docs/examples/developer-workflow/calculator.md +357 -0
  44. package/docs/examples/developer-workflow/dependencies/august/0.19.0/io/contracts.md +395 -0
  45. package/docs/examples/developer-workflow/index.md +37 -0
  46. package/docs/examples/developer-workflow/logging/console.md +124 -0
  47. package/docs/examples/developer-workflow/logging/export.md +70 -0
  48. package/docs/examples/developer-workflow/logging/logger.md +111 -0
  49. package/docs/examples/developer-workflow/main.md +172 -0
  50. package/docs/examples/drop/index.md +33 -0
  51. package/docs/examples/drop/main.md +85 -0
  52. package/docs/examples/drop/resource.md +95 -0
  53. package/docs/examples/errors/errors.md +85 -0
  54. package/docs/examples/errors/index.md +33 -0
  55. package/docs/examples/errors/main.md +92 -0
  56. package/docs/examples/ffi/index.md +33 -0
  57. package/docs/examples/ffi/main.md +75 -0
  58. package/docs/examples/ffi/native.md +93 -0
  59. package/docs/examples/generic-di/dependencies/august/0.19.0/io/contracts.md +395 -0
  60. package/docs/examples/generic-di/index.md +33 -0
  61. package/docs/examples/generic-di/main.md +111 -0
  62. package/docs/examples/generic-di/types.md +180 -0
  63. package/docs/examples/generics/index.md +33 -0
  64. package/docs/examples/generics/main.md +120 -0
  65. package/docs/examples/generics/types.md +189 -0
  66. package/docs/examples/hello/app/export.md +67 -0
  67. package/docs/examples/hello/app/greeter.md +190 -0
  68. package/docs/examples/hello/dependencies/august/0.19.0/io/contracts.md +395 -0
  69. package/docs/examples/hello/index.md +37 -0
  70. package/docs/examples/hello/logging/console.md +121 -0
  71. package/docs/examples/hello/logging/export.md +71 -0
  72. package/docs/examples/hello/logging/logger.md +120 -0
  73. package/docs/examples/hello/main.md +115 -0
  74. package/docs/examples/http-benchmark/index.md +35 -0
  75. package/docs/examples/http-benchmark/main.md +79 -0
  76. package/docs/examples/http-benchmark/routes.md +88 -0
  77. package/docs/examples/index.md +60 -0
  78. package/docs/examples/interceptors/app.md +250 -0
  79. package/docs/examples/interceptors/dependencies/august/0.19.0/io/contracts.md +395 -0
  80. package/docs/examples/interceptors/index.md +35 -0
  81. package/docs/examples/interceptors/interceptors.md +284 -0
  82. package/docs/examples/interceptors/logging.md +155 -0
  83. package/docs/examples/interceptors/main.md +162 -0
  84. package/docs/examples/json-benchmark/data.md +71 -0
  85. package/docs/examples/json-benchmark/dependencies/august/0.19.0/json/contracts.md +103 -0
  86. package/docs/examples/json-benchmark/index.md +35 -0
  87. package/docs/examples/json-benchmark/main.md +131 -0
  88. package/docs/examples/new-syntax/console.md +118 -0
  89. package/docs/examples/new-syntax/dependencies/august/0.19.0/io/contracts.md +395 -0
  90. package/docs/examples/new-syntax/greeter.md +145 -0
  91. package/docs/examples/new-syntax/index.md +36 -0
  92. package/docs/examples/new-syntax/logger.md +111 -0
  93. package/docs/examples/new-syntax/main.md +135 -0
  94. package/docs/examples/new-syntax/math.md +79 -0
  95. package/docs/examples/oidc-login/client/contracts.md +151 -0
  96. package/docs/examples/oidc-login/client/endpoints.md +263 -0
  97. package/docs/examples/oidc-login/client/export.md +107 -0
  98. package/docs/examples/oidc-login/client/login.md +515 -0
  99. package/docs/examples/oidc-login/client/logout.md +249 -0
  100. package/docs/examples/oidc-login/client/protocol.md +595 -0
  101. package/docs/examples/oidc-login/client/session.md +297 -0
  102. package/docs/examples/oidc-login/client/views.md +144 -0
  103. package/docs/examples/oidc-login/common/export.md +115 -0
  104. package/docs/examples/oidc-login/common/headers.md +162 -0
  105. package/docs/examples/oidc-login/common/keys.md +336 -0
  106. package/docs/examples/oidc-login/common/settings.md +116 -0
  107. package/docs/examples/oidc-login/common/views.md +103 -0
  108. package/docs/examples/oidc-login/dependencies/august/0.19.0/crypto/contracts.md +925 -0
  109. package/docs/examples/oidc-login/dependencies/august/0.19.0/crypto/jose.md +434 -0
  110. package/docs/examples/oidc-login/dependencies/august/0.19.0/json/contracts.md +103 -0
  111. package/docs/examples/oidc-login/dependencies/august/0.19.0/memory/store.md +374 -0
  112. package/docs/examples/oidc-login/dependencies/august/0.19.0/time/contracts.md +150 -0
  113. package/docs/examples/oidc-login/dependencies/august/0.19.0/web/contracts.md +532 -0
  114. package/docs/examples/oidc-login/index.md +57 -0
  115. package/docs/examples/oidc-login/main-yaml.md +28 -0
  116. package/docs/examples/oidc-login/main.md +283 -0
  117. package/docs/examples/oidc-login/provider/authorization.md +457 -0
  118. package/docs/examples/oidc-login/provider/contracts.md +267 -0
  119. package/docs/examples/oidc-login/provider/credentials.md +148 -0
  120. package/docs/examples/oidc-login/provider/discovery.md +215 -0
  121. package/docs/examples/oidc-login/provider/export.md +131 -0
  122. package/docs/examples/oidc-login/provider/token.md +364 -0
  123. package/docs/examples/oidc-login/provider/userinfo.md +228 -0
  124. package/docs/examples/oidc-login/provider/views.md +137 -0
  125. package/docs/examples/ownership/counter.md +140 -0
  126. package/docs/examples/ownership/index.md +33 -0
  127. package/docs/examples/ownership/main.md +90 -0
  128. package/docs/examples/ownership-transfer/dependencies/august/0.19.0/io/contracts.md +395 -0
  129. package/docs/examples/ownership-transfer/index.md +33 -0
  130. package/docs/examples/ownership-transfer/main.md +125 -0
  131. package/docs/examples/ownership-transfer/resource.md +150 -0
  132. package/docs/examples/packages-app/dependencies/packages/@example/aug-math/0.1.0/arithmetic.md +117 -0
  133. package/docs/examples/packages-app/index.md +34 -0
  134. package/docs/examples/packages-app/main-yaml.md +18 -0
  135. package/docs/examples/packages-app/main.md +80 -0
  136. package/docs/examples/packages-math/aug-package-json.md +24 -0
  137. package/docs/examples/packages-math/index.md +36 -0
  138. package/docs/examples/packages-math/package-json.md +25 -0
  139. package/docs/examples/packages-math/src/arithmetic.md +121 -0
  140. package/docs/examples/packages-math/src/export.md +63 -0
  141. package/docs/examples/startup-benchmark/index.md +34 -0
  142. package/docs/examples/startup-benchmark/main.md +68 -0
  143. package/docs/examples/visibility/counter.md +142 -0
  144. package/docs/examples/visibility/index.md +33 -0
  145. package/docs/examples/visibility/main.md +96 -0
  146. package/docs/examples.json +47 -0
  147. package/docs/getting-started.md +5 -0
  148. package/docs/grammar.md +136 -0
  149. package/docs/implementation-map.md +106 -0
  150. package/docs/index.md +46 -0
  151. package/docs/language-conformance.md +30 -0
  152. package/docs/language-constructs.md +1461 -0
  153. package/docs/language-design-audit.md +147 -0
  154. package/docs/maintaining-docs.md +43 -0
  155. package/docs/packages.md +184 -0
  156. package/docs/performance.md +535 -0
  157. package/docs/production-readiness.md +39 -0
  158. package/docs/reference.md +361 -0
  159. package/docs/release-review.md +23 -0
  160. package/docs/releasing.md +79 -0
  161. package/docs/roadmap.md +13 -0
  162. package/docs/specifications.md +86 -0
  163. package/docs/testing.md +120 -0
  164. package/docs/tooling.md +197 -0
  165. package/docs/web-implementation-plan.md +39 -0
  166. package/docs/web-library-gaps.md +27 -0
  167. package/docs/web.md +197 -0
  168. package/examples/approved-design/.aug-spec/august/0.19.0/io/contracts.aug +36 -0
  169. package/examples/approved-design/.aug-spec/august/0.19.0/io/contracts.aug.md +316 -0
  170. package/examples/approved-design/.aug-spec/manifest.json +14 -0
  171. package/examples/approved-design/counters.aug +22 -0
  172. package/examples/approved-design/counters.aug.md +177 -0
  173. package/examples/approved-design/domain/app.aug +12 -0
  174. package/examples/approved-design/domain/app.aug.md +97 -0
  175. package/examples/approved-design/domain/export.aug +5 -0
  176. package/examples/approved-design/domain/export.aug.md +25 -0
  177. package/examples/approved-design/domain/models.aug +2 -0
  178. package/examples/approved-design/domain/models.aug.md +30 -0
  179. package/examples/approved-design/domain/numbers.aug +29 -0
  180. package/examples/approved-design/domain/numbers.aug.md +141 -0
  181. package/examples/approved-design/main.aug +26 -0
  182. package/examples/approved-design/main.aug.md +108 -0
  183. package/examples/approved-design/main.yaml +8 -0
  184. package/examples/benchmark/.aug-spec/manifest.json +7 -0
  185. package/examples/benchmark/main.aug +17 -0
  186. package/examples/benchmark/main.aug.md +43 -0
  187. package/examples/benchmark/main.yaml +1 -0
  188. package/examples/cli-args/.aug-spec/manifest.json +7 -0
  189. package/examples/cli-args/main.aug +15 -0
  190. package/examples/cli-args/main.aug.md +38 -0
  191. package/examples/collections/.aug-spec/manifest.json +7 -0
  192. package/examples/collections/main.aug +18 -0
  193. package/examples/collections/main.aug.md +43 -0
  194. package/examples/developer-workflow/.aug-spec/august/0.19.0/io/contracts.aug +36 -0
  195. package/examples/developer-workflow/.aug-spec/august/0.19.0/io/contracts.aug.md +316 -0
  196. package/examples/developer-workflow/.aug-spec/manifest.json +13 -0
  197. package/examples/developer-workflow/calculator.aug +55 -0
  198. package/examples/developer-workflow/calculator.aug.md +228 -0
  199. package/examples/developer-workflow/logging/console.aug +8 -0
  200. package/examples/developer-workflow/logging/console.aug.md +67 -0
  201. package/examples/developer-workflow/logging/export.aug +2 -0
  202. package/examples/developer-workflow/logging/export.aug.md +19 -0
  203. package/examples/developer-workflow/logging/logger.aug +6 -0
  204. package/examples/developer-workflow/logging/logger.aug.md +57 -0
  205. package/examples/developer-workflow/main.aug +25 -0
  206. package/examples/developer-workflow/main.aug.md +79 -0
  207. package/examples/drop/.aug-spec/manifest.json +8 -0
  208. package/examples/drop/main.aug +3 -0
  209. package/examples/drop/main.aug.md +35 -0
  210. package/examples/drop/resource.aug +8 -0
  211. package/examples/drop/resource.aug.md +44 -0
  212. package/examples/errors/.aug-spec/manifest.json +8 -0
  213. package/examples/errors/errors.aug +6 -0
  214. package/examples/errors/errors.aug.md +33 -0
  215. package/examples/errors/main.aug +7 -0
  216. package/examples/errors/main.aug.md +36 -0
  217. package/examples/ffi/.aug-spec/manifest.json +8 -0
  218. package/examples/ffi/main.aug +2 -0
  219. package/examples/ffi/main.aug.md +27 -0
  220. package/examples/ffi/native.aug +6 -0
  221. package/examples/ffi/native.aug.md +43 -0
  222. package/examples/generic-di/.aug-spec/august/0.19.0/io/contracts.aug +36 -0
  223. package/examples/generic-di/.aug-spec/august/0.19.0/io/contracts.aug.md +316 -0
  224. package/examples/generic-di/.aug-spec/manifest.json +10 -0
  225. package/examples/generic-di/main.aug +9 -0
  226. package/examples/generic-di/main.aug.md +49 -0
  227. package/examples/generic-di/types.aug +17 -0
  228. package/examples/generic-di/types.aug.md +124 -0
  229. package/examples/generics/.aug-spec/manifest.json +8 -0
  230. package/examples/generics/main.aug +9 -0
  231. package/examples/generics/main.aug.md +58 -0
  232. package/examples/generics/types.aug +19 -0
  233. package/examples/generics/types.aug.md +132 -0
  234. package/examples/hello/.aug-spec/august/0.19.0/io/contracts.aug +36 -0
  235. package/examples/hello/.aug-spec/august/0.19.0/io/contracts.aug.md +316 -0
  236. package/examples/hello/.aug-spec/manifest.json +14 -0
  237. package/examples/hello/app/export.aug +1 -0
  238. package/examples/hello/app/export.aug.md +17 -0
  239. package/examples/hello/app/greeter.aug +22 -0
  240. package/examples/hello/app/greeter.aug.md +109 -0
  241. package/examples/hello/logging/console.aug +7 -0
  242. package/examples/hello/logging/console.aug.md +65 -0
  243. package/examples/hello/logging/export.aug +2 -0
  244. package/examples/hello/logging/export.aug.md +19 -0
  245. package/examples/hello/logging/logger.aug +9 -0
  246. package/examples/hello/logging/logger.aug.md +59 -0
  247. package/examples/hello/main.aug +9 -0
  248. package/examples/hello/main.aug.md +49 -0
  249. package/examples/interceptors/.aug-spec/august/0.19.0/io/contracts.aug +36 -0
  250. package/examples/interceptors/.aug-spec/august/0.19.0/io/contracts.aug.md +316 -0
  251. package/examples/interceptors/.aug-spec/manifest.json +12 -0
  252. package/examples/interceptors/app.aug +28 -0
  253. package/examples/interceptors/app.aug.md +162 -0
  254. package/examples/interceptors/interceptors.aug +40 -0
  255. package/examples/interceptors/interceptors.aug.md +180 -0
  256. package/examples/interceptors/logging.aug +12 -0
  257. package/examples/interceptors/logging.aug.md +96 -0
  258. package/examples/interceptors/main.aug +22 -0
  259. package/examples/interceptors/main.aug.md +76 -0
  260. package/examples/new-syntax/.aug-spec/august/0.19.0/io/contracts.aug +36 -0
  261. package/examples/new-syntax/.aug-spec/august/0.19.0/io/contracts.aug.md +316 -0
  262. package/examples/new-syntax/.aug-spec/manifest.json +13 -0
  263. package/examples/new-syntax/console.aug +7 -0
  264. package/examples/new-syntax/console.aug.md +63 -0
  265. package/examples/new-syntax/greeter.aug +10 -0
  266. package/examples/new-syntax/greeter.aug.md +89 -0
  267. package/examples/new-syntax/logger.aug +6 -0
  268. package/examples/new-syntax/logger.aug.md +57 -0
  269. package/examples/new-syntax/main.aug +12 -0
  270. package/examples/new-syntax/main.aug.md +64 -0
  271. package/examples/new-syntax/math.aug +3 -0
  272. package/examples/new-syntax/math.aug.md +29 -0
  273. package/examples/oidc-login/.aug-spec/august/0.19.0/crypto/contracts.aug +73 -0
  274. package/examples/oidc-login/.aug-spec/august/0.19.0/crypto/contracts.aug.md +791 -0
  275. package/examples/oidc-login/.aug-spec/august/0.19.0/crypto/jose.aug +68 -0
  276. package/examples/oidc-login/.aug-spec/august/0.19.0/crypto/jose.aug.md +266 -0
  277. package/examples/oidc-login/.aug-spec/august/0.19.0/json/contracts.aug +6 -0
  278. package/examples/oidc-login/.aug-spec/august/0.19.0/json/contracts.aug.md +55 -0
  279. package/examples/oidc-login/.aug-spec/august/0.19.0/memory/store.aug +45 -0
  280. package/examples/oidc-login/.aug-spec/august/0.19.0/memory/store.aug.md +250 -0
  281. package/examples/oidc-login/.aug-spec/august/0.19.0/time/contracts.aug +11 -0
  282. package/examples/oidc-login/.aug-spec/august/0.19.0/time/contracts.aug.md +96 -0
  283. package/examples/oidc-login/.aug-spec/august/0.19.0/web/contracts.aug +58 -0
  284. package/examples/oidc-login/.aug-spec/august/0.19.0/web/contracts.aug.md +420 -0
  285. package/examples/oidc-login/.aug-spec/manifest.json +40 -0
  286. package/examples/oidc-login/README.md +29 -0
  287. package/examples/oidc-login/client/contracts.aug +7 -0
  288. package/examples/oidc-login/client/contracts.aug.md +80 -0
  289. package/examples/oidc-login/client/endpoints.aug +21 -0
  290. package/examples/oidc-login/client/endpoints.aug.md +161 -0
  291. package/examples/oidc-login/client/export.aug +7 -0
  292. package/examples/oidc-login/client/export.aug.md +29 -0
  293. package/examples/oidc-login/client/login.aug +58 -0
  294. package/examples/oidc-login/client/login.aug.md +331 -0
  295. package/examples/oidc-login/client/logout.aug +18 -0
  296. package/examples/oidc-login/client/logout.aug.md +150 -0
  297. package/examples/oidc-login/client/protocol.aug +106 -0
  298. package/examples/oidc-login/client/protocol.aug.md +326 -0
  299. package/examples/oidc-login/client/session.aug +34 -0
  300. package/examples/oidc-login/client/session.aug.md +155 -0
  301. package/examples/oidc-login/client/views.aug +21 -0
  302. package/examples/oidc-login/client/views.aug.md +68 -0
  303. package/examples/oidc-login/common/export.aug +9 -0
  304. package/examples/oidc-login/common/export.aug.md +33 -0
  305. package/examples/oidc-login/common/headers.aug +11 -0
  306. package/examples/oidc-login/common/headers.aug.md +79 -0
  307. package/examples/oidc-login/common/keys.aug +37 -0
  308. package/examples/oidc-login/common/keys.aug.md +207 -0
  309. package/examples/oidc-login/common/settings.aug +4 -0
  310. package/examples/oidc-login/common/settings.aug.md +47 -0
  311. package/examples/oidc-login/common/views.aug +16 -0
  312. package/examples/oidc-login/common/views.aug.md +34 -0
  313. package/examples/oidc-login/main.aug +39 -0
  314. package/examples/oidc-login/main.aug.md +166 -0
  315. package/examples/oidc-login/main.yaml +12 -0
  316. package/examples/oidc-login/provider/authorization.aug +59 -0
  317. package/examples/oidc-login/provider/authorization.aug.md +264 -0
  318. package/examples/oidc-login/provider/contracts.aug +15 -0
  319. package/examples/oidc-login/provider/contracts.aug.md +193 -0
  320. package/examples/oidc-login/provider/credentials.aug +10 -0
  321. package/examples/oidc-login/provider/credentials.aug.md +64 -0
  322. package/examples/oidc-login/provider/discovery.aug +13 -0
  323. package/examples/oidc-login/provider/discovery.aug.md +133 -0
  324. package/examples/oidc-login/provider/export.aug +13 -0
  325. package/examples/oidc-login/provider/export.aug.md +41 -0
  326. package/examples/oidc-login/provider/token.aug +37 -0
  327. package/examples/oidc-login/provider/token.aug.md +223 -0
  328. package/examples/oidc-login/provider/userinfo.aug +26 -0
  329. package/examples/oidc-login/provider/userinfo.aug.md +104 -0
  330. package/examples/oidc-login/provider/views.aug +18 -0
  331. package/examples/oidc-login/provider/views.aug.md +63 -0
  332. package/examples/ownership/.aug-spec/manifest.json +8 -0
  333. package/examples/ownership/counter.aug +14 -0
  334. package/examples/ownership/counter.aug.md +85 -0
  335. package/examples/ownership/main.aug +4 -0
  336. package/examples/ownership/main.aug.md +38 -0
  337. package/examples/ownership-transfer/.aug-spec/august/0.19.0/io/contracts.aug +36 -0
  338. package/examples/ownership-transfer/.aug-spec/august/0.19.0/io/contracts.aug.md +316 -0
  339. package/examples/ownership-transfer/.aug-spec/manifest.json +10 -0
  340. package/examples/ownership-transfer/main.aug +9 -0
  341. package/examples/ownership-transfer/main.aug.md +63 -0
  342. package/examples/ownership-transfer/resource.aug +16 -0
  343. package/examples/ownership-transfer/resource.aug.md +89 -0
  344. package/examples/packages/README.md +15 -0
  345. package/examples/packages/app/.aug-spec/manifest.json +9 -0
  346. package/examples/packages/app/.aug-spec/packages/@example/aug-math/0.1.0/arithmetic.aug +9 -0
  347. package/examples/packages/app/.aug-spec/packages/@example/aug-math/0.1.0/arithmetic.aug.md +63 -0
  348. package/examples/packages/app/main.aug +2 -0
  349. package/examples/packages/app/main.aug.md +33 -0
  350. package/examples/packages/app/main.yaml +2 -0
  351. package/examples/packages/math/.aug-spec/manifest.json +8 -0
  352. package/examples/packages/math/README.md +3 -0
  353. package/examples/packages/math/aug-package.json +8 -0
  354. package/examples/packages/math/package.json +9 -0
  355. package/examples/packages/math/src/arithmetic.aug +8 -0
  356. package/examples/packages/math/src/arithmetic.aug.md +63 -0
  357. package/examples/packages/math/src/export.aug +1 -0
  358. package/examples/packages/math/src/export.aug.md +17 -0
  359. package/examples/visibility/.aug-spec/manifest.json +8 -0
  360. package/examples/visibility/counter.aug +14 -0
  361. package/examples/visibility/counter.aug.md +87 -0
  362. package/examples/visibility/main.aug +7 -0
  363. package/examples/visibility/main.aug.md +39 -0
  364. package/package.json +50 -0
  365. package/runtime/aug_crypto.c +121 -0
  366. package/runtime/aug_html.c +93 -0
  367. package/runtime/aug_http.c +829 -0
  368. package/runtime/aug_json.c +165 -0
  369. package/runtime/aug_runtime.c +792 -0
  370. package/runtime/aug_runtime.h +245 -0
  371. package/runtime/aug_tasks.c +172 -0
  372. package/runtime/aug_time.c +5 -0
  373. package/runtime/aug_values.c +80 -0
  374. package/scripts/bootstrap-native.mjs +121 -0
  375. package/scripts/native-dependencies.lock.json +65 -0
  376. package/scripts/native-home.mjs +11 -0
  377. package/src/actions.js +109 -0
  378. package/src/ast.js +19 -0
  379. package/src/builtins.js +93 -0
  380. package/src/checker.js +2896 -0
  381. package/src/cli.js +344 -0
  382. package/src/codegen.js +1159 -0
  383. package/src/config.js +149 -0
  384. package/src/continuation.js +110 -0
  385. package/src/di.js +24 -0
  386. package/src/documentation.js +27 -0
  387. package/src/editor.js +782 -0
  388. package/src/effects.js +29 -0
  389. package/src/fixes.js +222 -0
  390. package/src/formatter.js +296 -0
  391. package/src/freshness.js +154 -0
  392. package/src/help.js +212 -0
  393. package/src/html.js +27 -0
  394. package/src/http-policies.js +90 -0
  395. package/src/inference.js +16 -0
  396. package/src/interceptors.js +70 -0
  397. package/src/javadoc.js +67 -0
  398. package/src/lexer.js +199 -0
  399. package/src/libraries.js +31 -0
  400. package/src/lsp.js +180 -0
  401. package/src/native.js +144 -0
  402. package/src/navigation.js +162 -0
  403. package/src/openapi.js +220 -0
  404. package/src/ownership.js +232 -0
  405. package/src/package-manager.js +299 -0
  406. package/src/parser.js +1155 -0
  407. package/src/policies.js +130 -0
  408. package/src/project-init.js +19 -0
  409. package/src/project.js +292 -0
  410. package/src/schemas.js +62 -0
  411. package/src/semantic.js +255 -0
  412. package/src/spec.js +738 -0
  413. package/src/testing.js +186 -0
  414. package/src/types.js +10 -0
package/src/checker.js ADDED
@@ -0,0 +1,2896 @@
1
+ import { fieldsOf, initializationOf, syntheticType, typeName } from "./ast.js";
2
+ import { isPrivateName } from "./project.js";
3
+ import { canFallThrough, canRepeatNext } from "./continuation.js";
4
+ import { returnsFresh } from "./freshness.js";
5
+ import { inferType } from "./inference.js";
6
+ import { sameType, tyName } from "./types.js";
7
+ import { capabilityKey, coversChange, effectContract } from "./effects.js";
8
+ import { allocationOrigin, OwnershipFlow, sourceName, unionOrigins } from "./ownership.js";
9
+ import { builtinType as builtin, builtinTypes, builtinProperties, collectionOperations, errorNames, operationType } from "./builtins.js";
10
+ import { orderGraph } from "./di.js";
11
+ import { javadocBefore } from "./javadoc.js";
12
+ import { callableDocumentation } from "./documentation.js";
13
+ import { jsonDataType } from "./schemas.js";
14
+ import { htmlTags, htmlVoidTags, htmlAttribute, htmlUrlAttributes } from "./html.js";
15
+ import { generateOpenApi } from "./openapi.js";
16
+ import { httpPolicyNames, checkHttpPolicy } from "./http-policies.js";
17
+ export { tyName } from "./types.js";
18
+ const builtinNames = new Set(Object.keys(builtinTypes));
19
+ const errorTy = { id: 'error', name: '<error>', kind: 'error', args: [], nullable: false };
20
+ const nullTy = { id: 'null', name: 'null', kind: 'null', args: [], nullable: true };
21
+ export function checkProject(project) {
22
+ const checker = new Checker(project);
23
+ const checked = checker.check();
24
+ if (project.config.openapi.enabled && !checked.diagnostics.some(issue => issue.severity !== 'warning'))
25
+ checked.diagnostics.push(...generateOpenApi(checked).diagnostics);
26
+ return checked;
27
+ }
28
+ class Checker {
29
+ project;
30
+ diagnostics;
31
+ expressionTypes = new WeakMap();
32
+ defaults = new Map();
33
+ callPlans = new WeakMap();
34
+ bindings = [];
35
+ interceptorPlans = new Map();
36
+ effectContracts = new Map();
37
+ expressionOrigins = new WeakMap();
38
+ scopes = new Map();
39
+ markupCalls = new WeakMap();
40
+ actions = new Map();
41
+ httpPolicies = new Map();
42
+ bindingByKey = new Map();
43
+ constructorFreshness = new Map();
44
+ constraintStack = new Set();
45
+ inferredUses = new Map();
46
+ inferring = false;
47
+ inferenceChanged = false;
48
+ changingInference = new Set();
49
+ constructor(project) { this.project = project; this.diagnostics = [...project.diagnostics]; }
50
+ check() {
51
+ for (const def of this.project.definitions.values())
52
+ if (def.node.kind === 'interceptor')
53
+ this.checkInterceptorShape(def);
54
+ this.planInterceptors();
55
+ this.inferImplementationEffects();
56
+ for (const def of this.project.definitions.values()) {
57
+ if (def.node.kind === 'class')
58
+ this.checkClassShape(def);
59
+ else if (def.node.kind === 'interface')
60
+ this.checkInterfaceShape(def);
61
+ else if (def.node.kind === 'function')
62
+ this.checkFunctionSignature(def.node, def.file, new Map());
63
+ }
64
+ this.checkBindings();
65
+ this.checkInterceptorDependencies();
66
+ for (const def of this.project.definitions.values()) {
67
+ if (def.node.kind === 'function' && def.node.body)
68
+ this.checkFunctionBody(def.node, def.file);
69
+ if (def.node.kind === 'class') {
70
+ if (initializationOf(def.node).length)
71
+ this.checkFunctionBody({
72
+ kind: 'function', name: def.name, typeParams: [], params: [],
73
+ returns: syntheticType('void', def.node.span), returnOwnership: 'managed',
74
+ throws: def.node.validationErrors ?? [], body: initializationOf(def.node), externC: false, span: def.node.span,
75
+ }, def.file, def, true);
76
+ for (const method of def.node.methods)
77
+ if (method.body)
78
+ this.checkFunctionBody(method, def.file, def);
79
+ }
80
+ if (def.node.kind === 'interface') {
81
+ for (const method of def.node.methods)
82
+ if (method.body)
83
+ this.checkFunctionBody(method, def.file, def);
84
+ }
85
+ if (def.node.kind === 'interceptor') {
86
+ for (const method of def.node.methods)
87
+ if (method.body) {
88
+ const next = method.name === 'around' ? this.nextFor(method, def.file, this.paramsFor(def.node.typeParams, def.node, def.file)) : undefined;
89
+ this.checkFunctionBody(method, def.file, def, false, undefined, next);
90
+ }
91
+ }
92
+ }
93
+ for (const layers of this.interceptorPlans.values())
94
+ for (const layer of layers) {
95
+ this.checkFunctionBody(layer.around, layer.definition.file, layer.definition, false, layer.types, { params: layer.around.params, types: layer.inputTypes,
96
+ returns: layer.outputType, returnOwnership: layer.around.returnOwnership,
97
+ indices: layer.argumentIndices });
98
+ }
99
+ for (const [node] of this.interceptorPlans)
100
+ if (node.annotations?.length) {
101
+ const source = this.project.files.get(node.span.file)?.source ?? '';
102
+ const doc = javadocBefore(source, node.annotations[0].span.start);
103
+ const errors = this.effectiveErrors(node, node.span.file, this.paramsFor(node.typeParams, node, node.span.file));
104
+ for (const tag of doc?.tags ?? [])
105
+ if (['throws', 'exception'].includes(tag.name) && !errors.some(error => tyName(error) === tag.value.split(/\s/)[0]))
106
+ this.report(node.span, `@${tag.name} ${tag.value.split(/\s/)[0]} is absent from ${node.name}'s effective unless contract`, 'DOC');
107
+ }
108
+ if (this.project.main) {
109
+ const context = { file: this.project.main.path, locals: new Map(),
110
+ returns: builtin('void'), returnOwnership: 'managed', allowedErrors: this.project.testMode ? [builtin('Error')] : [],
111
+ unsafe: false, borrowed: new Set(), flow: new OwnershipFlow() };
112
+ for (const [index, item] of this.project.main.items.entries()) {
113
+ context.composition = !this.project.testMode || index < (this.project.testBodyStart ?? 0);
114
+ if (!['import', 'bind'].includes(item.kind)) {
115
+ if (this.isStatement(item)) {
116
+ this.captureScope(item.span, context);
117
+ this.checkStatement(item, context);
118
+ this.captureScope({ ...item.span, start: item.span.end, end: this.project.main.items[index + 1]?.span.start ?? this.project.main.source.length }, context);
119
+ }
120
+ }
121
+ }
122
+ }
123
+ const served = new Set(this.project.main?.items.flatMap(item => item.kind === 'serve' ? item.names.map(name => this.project.scopes.get(this.project.main.path)?.get(name)?.id) : []) ?? []);
124
+ for (const [expr, plan] of this.actions) {
125
+ if (!served.has(plan.endpoint.id) && !this.project.testMode && !this.project.library)
126
+ this.report(expr.span, `Serve endpoint ${plan.endpoint.name} in main.aug before referring to its HTTP action`, 'HTTP');
127
+ if (plan.endpoint.node.endpoint?.path === '/__aug/actions.js')
128
+ this.report(expr.span, '/__aug/actions.js is reserved for HTTP action transport', 'HTTP');
129
+ }
130
+ if (this.actions.size)
131
+ for (const def of this.project.definitions.values())
132
+ if (served.has(def.id) && def.node.kind === 'function' && def.node.endpoint?.path === '/__aug/actions.js')
133
+ this.report(def.node.span, '/__aug/actions.js is reserved for HTTP action transport', 'HTTP');
134
+ return { project: this.project, diagnostics: this.diagnostics, bindings: this.bindings,
135
+ expressionTypes: this.expressionTypes, defaults: this.defaults, callPlans: this.callPlans,
136
+ interceptorPlans: this.interceptorPlans, effectContracts: this.effectContracts,
137
+ expressionOrigins: this.expressionOrigins, scopes: this.scopes, markupCalls: this.markupCalls, actions: this.actions, httpPolicies: this.httpPolicies };
138
+ }
139
+ isStatement(item) {
140
+ return ['expr', 'assign', 'return', 'throw', 'if', 'while', 'for', 'destructure', 'match', 'try', 'unsafe', 'borrow', 'scope', 'freeze', 'serve', 'lock', 'yield'].includes(item.kind);
141
+ }
142
+ report(span, message, code = 'TYPE') {
143
+ if (this.inferring)
144
+ return;
145
+ this.diagnostics.push({ file: span.file, line: span.line, column: span.column, message, code });
146
+ }
147
+ /** Infer implementation details before comparing them with public contracts. */
148
+ inferImplementationEffects() {
149
+ const bodies = [];
150
+ for (const definition of this.project.definitions.values()) {
151
+ const node = definition.node;
152
+ const methods = node.kind === 'function' ? [node] : 'methods' in node ? node.methods : [];
153
+ for (const method of methods) {
154
+ if (!method.body || method.externC || method.uses?.length || method.name === 'drop')
155
+ continue;
156
+ if (node.kind !== 'class' && !isPrivateName(method.name))
157
+ continue;
158
+ if (node.kind === 'interface')
159
+ continue;
160
+ this.inferredUses.set(method, new Map());
161
+ bodies.push({ method, definition, owner: node.kind === 'function' ? undefined : definition });
162
+ }
163
+ }
164
+ this.inferring = true;
165
+ try {
166
+ for (const { method, owner } of bodies)
167
+ if (owner?.node.kind === 'class') {
168
+ const types = this.paramsFor(owner.node.typeParams, owner.node, owner.file);
169
+ for (const ref of owner.node.implements) {
170
+ const implemented = this.resolveType(ref, owner.file, types);
171
+ for (const entry of this.interfaceMethods(implemented, new Set()).get(method.name) ?? []) {
172
+ const contractOwner = this.project.definitions.get(entry.from);
173
+ if (contractOwner?.node.kind !== 'interface' || !contractOwner.node.capability)
174
+ continue;
175
+ // Invoking a capability is itself an effect, including synchronized stores and test adapters.
176
+ const contract = this.contractFor(entry.method, entry.file, contractOwner, entry.params);
177
+ for (const [key, effect] of contract.uses)
178
+ this.inferredUses.get(method).set(key, effect);
179
+ }
180
+ }
181
+ }
182
+ // A fixed point makes forward references and recursive helper calls order independent.
183
+ // Type-changing generic recursion must provide an explicit finite contract.
184
+ for (let pass = 0; pass <= bodies.length; pass++) {
185
+ this.inferenceChanged = false;
186
+ this.changingInference.clear();
187
+ for (const { method, definition, owner } of bodies)
188
+ this.checkFunctionBody(method, definition.file, owner);
189
+ if (!this.inferenceChanged)
190
+ return;
191
+ }
192
+ }
193
+ finally {
194
+ this.inferring = false;
195
+ }
196
+ for (const method of this.changingInference)
197
+ this.report(method.span, `Effect inference for ${method.name} did not reach a finite contract; declare uses on recursive generic helpers`, 'EFFECT');
198
+ }
199
+ checkMemberVisibility(ownerId, ownerName, name, span, context) {
200
+ if (isPrivateName(name) && context.self?.id !== ownerId)
201
+ this.report(span, `Member ${name} is private to ${ownerName}`, 'PRIVATE');
202
+ }
203
+ resolveType(ref, file, params = new Map(), validateConstraints = true) {
204
+ const param = params.get(ref.name);
205
+ if (param)
206
+ return { ...param, nullable: ref.nullable || param.nullable, optional: ref.optional || param.optional };
207
+ if (builtinNames.has(ref.name)) {
208
+ if (builtinTypes[ref.name].arity !== 0) {
209
+ const count = builtinTypes[ref.name].arity;
210
+ if (ref.name !== 'Tuple' && ref.args.length !== count)
211
+ this.report(ref.span, `${ref.name} expects ${count === 1 ? 'one type argument' : 'two type arguments'}`);
212
+ const args = ref.args.map(arg => this.resolveType(arg, file, params, validateConstraints));
213
+ if (ref.name !== 'Task' && args.some(arg => arg.id === 'builtin:void'))
214
+ this.report(ref.span, 'Collections cannot contain void', 'COLLECTION');
215
+ return { ...builtin(ref.name), args,
216
+ nullable: ref.nullable, optional: ref.optional };
217
+ }
218
+ if (ref.args.length)
219
+ this.report(ref.span, `${ref.name} does not take type arguments`);
220
+ return { ...builtin(ref.name), nullable: ref.nullable, optional: ref.optional };
221
+ }
222
+ const def = this.project.scopes.get(file)?.get(ref.name);
223
+ if (!def || def.node.kind === 'function' || def.node.kind === 'interceptor' || def.node.kind === 'composition') {
224
+ if (def?.node.kind === 'interceptor') {
225
+ this.report(ref.span, `${ref.name} is an interceptor; apply it with [${ref.name}]`, 'INTERCEPTOR');
226
+ return errorTy;
227
+ }
228
+ this.report(ref.span, `Unknown type ${ref.name}`);
229
+ return errorTy;
230
+ }
231
+ const expected = def.node.typeParams.length;
232
+ if (ref.args.length !== expected)
233
+ this.report(ref.span, `${ref.name} expects ${expected} type argument${expected === 1 ? '' : 's'}, got ${ref.args.length}`);
234
+ const args = ref.args.map(arg => this.resolveType(arg, file, params, validateConstraints));
235
+ if (validateConstraints)
236
+ this.checkConstraints(def.node, def.file, new Map(def.node.typeParams.map((name, index) => [name, args[index] ?? errorTy])), ref.span);
237
+ return { id: def.id, name: def.name, kind: def.node.kind, def, args, nullable: ref.nullable, optional: ref.optional };
238
+ }
239
+ paramsFor(names, header, file) {
240
+ const scope = header && 'span' in header ? `${file}:${header.span.start}` : 'inference';
241
+ const params = new Map(names.map(name => [name, { id: `param:${scope}:${name}`, name, kind: 'param',
242
+ args: [], nullable: false }]));
243
+ if (header && file)
244
+ this.addBounds(params, header, file);
245
+ return params;
246
+ }
247
+ addBounds(params, header, file) {
248
+ for (const name of header.typeParams) {
249
+ const type = params.get(name);
250
+ if (type?.kind === 'param')
251
+ params.set(name, { ...type,
252
+ bounds: (header.typeConstraints?.[name] ?? []).map(ref => this.resolveType(ref, file, params, false)) });
253
+ }
254
+ }
255
+ checkConstraints(header, file, types, span) {
256
+ const key = `${file}:${header.typeParams.map(name => tyName(types.get(name) ?? errorTy)).join(',')}:${JSON.stringify(header.typeConstraints ?? {})}`;
257
+ if (this.constraintStack.has(key))
258
+ return;
259
+ this.constraintStack.add(key);
260
+ for (const name of header.typeParams)
261
+ for (const ref of header.typeConstraints?.[name] ?? []) {
262
+ const constraint = this.resolveType(ref, file, types);
263
+ if (constraint.kind !== 'interface')
264
+ this.report(ref.span, `Constraint ${typeName(ref)} must be an interface`, 'GENERIC');
265
+ const actual = types.get(name);
266
+ if (actual && !this.assignable(actual, constraint))
267
+ this.report(span, `Type argument ${name}=${tyName(actual)} must implement ${tyName(constraint)}`, 'GENERIC');
268
+ }
269
+ this.constraintStack.delete(key);
270
+ }
271
+ checkVariance(header, file) {
272
+ const variance = header.typeVariance ?? {};
273
+ if (header.kind !== 'interface') {
274
+ if (Object.keys(variance).length)
275
+ this.report(header.span, 'Only interfaces may declare in/out variance', 'GENERIC');
276
+ return;
277
+ }
278
+ const visit = (ref, position) => {
279
+ const mode = variance[ref.name];
280
+ if (mode === 'out' && position <= 0 || mode === 'in' && position >= 0)
281
+ this.report(ref.span, `${mode} type parameter ${ref.name} occurs in an incompatible ${position === 0 ? 'invariant' : position < 0 ? 'input' : 'output'} position`, 'GENERIC');
282
+ const def = this.project.scopes.get(file)?.get(ref.name);
283
+ ref.args.forEach((arg, index) => {
284
+ const nested = ref.name === 'Tuple' ? 'out' : def && 'typeVariance' in def.node ? def.node.typeVariance?.[def.node.typeParams[index]] : undefined;
285
+ visit(arg, nested === 'out' ? position : nested === 'in' ? -position : 0);
286
+ });
287
+ };
288
+ const iface = header;
289
+ iface.extends.forEach(ref => visit(ref, 1));
290
+ iface.methods.forEach(method => {
291
+ method.params.forEach(param => visit(param.type, -1));
292
+ visit(method.returns, 1);
293
+ method.throws.forEach(ref => visit(ref, 1));
294
+ });
295
+ }
296
+ checkFunctionSignature(fn, file, inheritedParams) {
297
+ const params = new Map(inheritedParams);
298
+ for (const [key, value] of this.paramsFor(fn.typeParams, fn, file)) {
299
+ if (params.has(key))
300
+ this.report(fn.span, `Duplicate type parameter ${key}`);
301
+ params.set(key, value);
302
+ }
303
+ this.addBounds(params, fn, file);
304
+ this.checkVariance(fn, file);
305
+ const names = new Set();
306
+ for (const param of fn.params) {
307
+ if (names.has(param.name))
308
+ this.report(param.span, `Duplicate parameter ${param.name}`);
309
+ names.add(param.name);
310
+ if (param.name === 'next')
311
+ this.report(param.span, 'next is reserved for interceptor continuations', 'NEXT');
312
+ if (param.injected && param.ownership !== 'managed')
313
+ this.report(param.span, 'A resolve parameter cannot also be own or borrow', 'DI');
314
+ this.resolveType(param.type, file, params);
315
+ }
316
+ this.resolveType(fn.returns, file, params);
317
+ if (fn.endpoint) {
318
+ const endpoint = fn.endpoint;
319
+ if (!/^[A-Z]+$/.test(endpoint.method))
320
+ this.report(fn.span, 'Endpoint methods use uppercase HTTP method names', 'HTTP');
321
+ if (!endpoint.path.startsWith('/') || /[?#\r\n]/.test(endpoint.path))
322
+ this.report(fn.span, 'Endpoint paths start with / and contain no query, fragment, or control characters', 'HTTP');
323
+ if (!Number.isInteger(endpoint.status) || endpoint.status < 100 || endpoint.status > 599)
324
+ this.report(fn.span, 'HTTP statuses must be integers from 100 to 599', 'HTTP');
325
+ if (!fn.body || fn.typeParams.length)
326
+ this.report(fn.span, 'Endpoints need a concrete body and parameter types', 'HTTP');
327
+ const body = fn.params.filter(param => param.source?.kind === 'body' || param.source?.kind === 'form');
328
+ if (body.length > 1)
329
+ this.report(fn.span, 'An endpoint has one body consumer', 'HTTP');
330
+ for (const param of fn.params) {
331
+ if (param.injected && param.source || !param.injected && !param.source)
332
+ this.report(param.span, 'Endpoint inputs declare from path/query/header/body/cookie/form/request; dependencies use resolve', 'HTTP');
333
+ if (param.ownership !== 'managed')
334
+ this.report(param.span, 'HTTP-bound inputs provide read-only access', 'HTTP');
335
+ const type = this.resolveType(param.type, file, params);
336
+ if (param.source?.kind === 'path' && !endpoint.path.includes('{' + (param.source.name ?? param.name) + '}'))
337
+ this.report(param.span, 'Path input must match a placeholder in the endpoint path', 'HTTP');
338
+ if (param.source && ['path', 'query', 'header', 'cookie'].includes(param.source.kind) && !['int', 'float', 'bool', 'string'].includes(type.name))
339
+ this.report(param.span, 'Scalar HTTP inputs use int, float, bool, or string', 'HTTP');
340
+ if (param.source && ['body', 'form'].includes(param.source.kind) && !jsonDataType(this.project, type))
341
+ this.report(param.span, 'Decoded HTTP bodies use JSON data records or collections', 'HTTP');
342
+ if (param.source?.kind === 'request' && type.name !== 'HttpRequest')
343
+ this.report(param.span, 'from request provides HttpRequest', 'HTTP');
344
+ }
345
+ const result = this.resolveType(fn.returns, file, params);
346
+ if (endpoint.streams) {
347
+ if (!['ServerEvent', 'Bytes', 'Html'].includes(result.name) || result.optional || result.nullable)
348
+ this.report(fn.span, 'An endpoint streams ServerEvent<T>, Bytes, or Html', 'HTTP');
349
+ if (result.name === 'ServerEvent' && !jsonDataType(this.project, result.args[0]))
350
+ this.report(fn.span, 'ServerEvent data uses a concrete JSON data type', 'HTTP');
351
+ }
352
+ else if (!jsonDataType(this.project, result) && !['void', 'HttpResponse', 'Html', 'Bytes'].includes(result.name))
353
+ this.report(fn.span, `${tyName(result)} cannot be serialized as an HTTP response`, 'HTTP');
354
+ for (const error of endpoint.errors)
355
+ if (!Number.isInteger(error.status) || error.status < 400 || error.status > 599)
356
+ this.report(error.type.span, 'Mapped error statuses must be from 400 to 599', 'HTTP');
357
+ }
358
+ for (const changed of fn.changes ?? []) {
359
+ const root = changed.split('.')[0];
360
+ const param = fn.params.find(param => param.name === root);
361
+ if (root !== 'self' && !param)
362
+ this.report(fn.span, `changes ${changed} must name self or a parameter`, 'EFFECT');
363
+ if (param && param.ownership !== 'borrow' && param.ownership !== 'own')
364
+ this.report(param.span, `Mutable input ${root} must use borrow or own`, 'EFFECT');
365
+ }
366
+ for (const thrown of fn.throws) {
367
+ const type = this.resolveType(thrown, file, params);
368
+ if (!this.implementsError(type))
369
+ this.report(thrown.span, `Thrown type ${typeName(thrown)} must implement Error`);
370
+ }
371
+ if (fn.externC) {
372
+ if (fn.nativePure && (fn.uses?.length || fn.changes?.length))
373
+ this.report(fn.span, 'A pure native declaration cannot declare effects', 'FFI');
374
+ if (fn.params.some(param => param.injected))
375
+ this.report(fn.span, 'extern C declarations cannot use resolve parameters', 'FFI');
376
+ if (fn.typeParams.length || fn.throws.length && !fn.valueAbi)
377
+ this.report(fn.span, 'extern C functions cannot be generic or declare unless errors', 'FFI');
378
+ if (fn.returnOwnership === 'own' || fn.params.some(param => param.ownership !== 'managed'))
379
+ this.report(fn.span, 'extern C functions cannot transfer AugScript ownership', 'FFI');
380
+ for (const type of fn.valueAbi ? [] : [...fn.params.map(p => p.type), fn.returns]) {
381
+ if (!['int', 'c_int', 'float', 'bool', 'string', 'void'].includes(type.name) || type.args.length || type.nullable)
382
+ this.report(type.span, `Type ${typeName(type)} is not supported at the C FFI boundary`, 'FFI');
383
+ }
384
+ }
385
+ if (fn.name === 'drop' && (fn.params.length || fn.throws.length || fn.returns.name !== 'void' || fn.uses?.length))
386
+ this.report(fn.span, 'drop must take no inputs, return void, and perform only local cleanup; external effects require an explicit method', 'EFFECT');
387
+ }
388
+ checkInterceptorShape(def) {
389
+ const node = def.node;
390
+ const types = this.paramsFor(node.typeParams, node, def.file);
391
+ this.checkVariance(node, def.file);
392
+ if (new Set(node.typeParams).size !== node.typeParams.length)
393
+ this.report(node.span, 'Duplicate interceptor type parameter', 'INTERCEPTOR');
394
+ const names = new Set();
395
+ for (const field of node.fields) {
396
+ if (names.has(field.name))
397
+ this.report(field.span, `Duplicate field ${field.name}`);
398
+ names.add(field.name);
399
+ this.resolveType(field.type, def.file, types);
400
+ if (!field.injected)
401
+ this.report(field.span, `Interceptor constructor parameter ${field.name} must be marked resolve`, 'INTERCEPTOR');
402
+ if (field.ownership !== 'managed')
403
+ this.report(field.span, 'Interceptor dependencies use managed resolve parameters', 'DI');
404
+ }
405
+ for (const method of node.methods) {
406
+ if (names.has(method.name))
407
+ this.report(method.span, `Duplicate interceptor member ${method.name}`);
408
+ names.add(method.name);
409
+ this.checkFunctionSignature(method, def.file, types);
410
+ if (method.name === 'around') {
411
+ if (!method.body)
412
+ this.report(method.span, 'around requires an implementation', 'INTERCEPTOR');
413
+ if (method.typeParams.length)
414
+ this.report(method.span, 'Declare generic parameters on the interceptor, rather than around', 'INTERCEPTOR');
415
+ if (method.annotations?.length)
416
+ this.report(method.span, 'Apply interceptor layers to the target declaration; around cannot be annotated', 'INTERCEPTOR');
417
+ if (method.returns.name !== 'void' && canFallThrough(method.body ?? []))
418
+ this.report(method.span, 'around must return its result or throw on every execution path', 'INTERCEPTOR');
419
+ if (canRepeatNext(method.body ?? []))
420
+ this.report(method.span, 'next may be called at most once on each execution path; a loop or catch path can call it again', 'NEXT');
421
+ }
422
+ }
423
+ if (!node.methods.some(method => method.name === 'around'))
424
+ this.report(node.span, `${node.name} must define around(...)`, 'INTERCEPTOR');
425
+ }
426
+ nextFor(method, file, types) {
427
+ return { params: method.params, types: method.params.map(param => this.resolveType(param.type, file, types)),
428
+ returns: this.resolveType(method.returns, file, types), returnOwnership: method.returnOwnership };
429
+ }
430
+ inferInterceptorType(ref, actual, names, types, file) {
431
+ inferType(ref, actual, names, types, message => this.report(ref.span, message, 'GENERIC'), (name, value) => {
432
+ const expected = this.project.scopes.get(file)?.get(name);
433
+ return expected?.node.kind === 'interface' ? this.interfaceView(value, expected.id) : value;
434
+ });
435
+ }
436
+ interfaceView(type, id, seen = new Set()) {
437
+ if (type.id === id)
438
+ return type;
439
+ if (!type.def || seen.has(type.id))
440
+ return undefined;
441
+ seen.add(type.id);
442
+ const node = type.def.node;
443
+ if (node.kind !== 'class' && node.kind !== 'interface')
444
+ return undefined;
445
+ const types = new Map(node.typeParams.map((name, index) => [name, type.args[index] ?? errorTy]));
446
+ for (const ref of node.kind === 'class' ? node.implements : node.extends) {
447
+ const found = this.interfaceView(this.resolveType(ref, type.def.file, types), id, seen);
448
+ if (found)
449
+ return found;
450
+ }
451
+ return undefined;
452
+ }
453
+ planInterceptors() {
454
+ const plan = (node, file, ownerTypes, inputs, output, ownership) => {
455
+ const layers = [];
456
+ this.interceptorPlans.set(node, layers);
457
+ let seenCustom = false;
458
+ for (const annotation of node.annotations ?? []) {
459
+ if (httpPolicyNames.includes(annotation.name)) {
460
+ if (node.kind !== 'function' || !node.endpoint)
461
+ this.report(annotation.span, `${annotation.name} is an HTTP endpoint policy`, 'HTTP');
462
+ else {
463
+ if (seenCustom)
464
+ this.report(annotation.span, 'Put HTTP policies before custom parameter interceptors so guards run before decoding', 'HTTP');
465
+ const policies = this.httpPolicies.get(node) ?? [];
466
+ if (['Cors', 'Compress'].includes(annotation.name) && policies.some(policy => policy.name === annotation.name))
467
+ this.report(annotation.span, `${annotation.name} may appear only once on an endpoint`, 'HTTP');
468
+ policies.push(checkHttpPolicy(annotation, node, this.diagnostics, (param, name) => this.resolveType(param.type, file, ownerTypes).id === `august/web/contracts.aug:${name}`));
469
+ this.httpPolicies.set(node, policies);
470
+ }
471
+ continue;
472
+ }
473
+ seenCustom = true;
474
+ const definition = this.project.scopes.get(file)?.get(annotation.name);
475
+ if (!definition) {
476
+ this.report(annotation.nameSpan, `Unknown interceptor ${annotation.name}`, 'NAME');
477
+ continue;
478
+ }
479
+ if (definition.node.kind !== 'interceptor') {
480
+ this.report(annotation.nameSpan, `${annotation.name} must be declared with interceptor`, 'INTERCEPTOR');
481
+ continue;
482
+ }
483
+ const interceptor = definition.node;
484
+ const around = interceptor.methods.find(method => method.name === 'around');
485
+ if (!around)
486
+ continue;
487
+ if (node.kind === 'function' && !node.body && !node.externC) {
488
+ this.report(annotation.span, 'An interceptor requires an executable function or method body', 'INTERCEPTOR');
489
+ continue;
490
+ }
491
+ const mapping = new Map();
492
+ for (const entry of annotation.mappings) {
493
+ if (entry.value)
494
+ this.report(entry.span, 'Custom interceptor mappings name a target parameter; literal options belong to built-in HTTP policies', 'INTERCEPTOR');
495
+ if (mapping.has(entry.name))
496
+ this.report(entry.span, `Duplicate mapping ${entry.name}`, 'INTERCEPTOR');
497
+ const param = around.params.find(param => param.name === entry.name);
498
+ if (!param)
499
+ this.report(entry.span, `${interceptor.name}.around has no parameter ${entry.name}`, 'INTERCEPTOR');
500
+ else if (param.injected)
501
+ this.report(entry.span, `${entry.name} is resolved from DI and cannot be mapped`, 'INTERCEPTOR');
502
+ if (!inputs.some(param => param.name === entry.source))
503
+ this.report(entry.sourceSpan, `${node.name} has no parameter ${entry.source}`, 'INTERCEPTOR');
504
+ mapping.set(entry.name, entry.source);
505
+ }
506
+ const types = new Map();
507
+ if (annotation.typeArgs.length && annotation.typeArgs.length !== interceptor.typeParams.length)
508
+ this.report(annotation.span, `${interceptor.name} expects ${interceptor.typeParams.length} type arguments`, 'INTERCEPTOR');
509
+ interceptor.typeParams.forEach((name, index) => {
510
+ if (annotation.typeArgs[index])
511
+ types.set(name, this.resolveType(annotation.typeArgs[index], file, ownerTypes));
512
+ });
513
+ const inferredNames = interceptor.typeParams.filter((_, index) => !annotation.typeArgs[index]);
514
+ this.inferInterceptorType(around.returns, output, inferredNames, types, definition.file);
515
+ const argumentIndices = around.params.map(param => param.injected ? undefined :
516
+ inputs.findIndex(input => input.name === (mapping.get(param.name) ?? param.name)));
517
+ const inputTypes = argumentIndices.map((index, i) => index !== undefined && index >= 0 ?
518
+ this.resolveType(inputs[index].type, file, ownerTypes) : errorTy);
519
+ around.params.forEach((param, index) => {
520
+ if (!param.injected)
521
+ this.inferInterceptorType(param.type, inputTypes[index], inferredNames, types, definition.file);
522
+ });
523
+ for (const name of interceptor.typeParams)
524
+ if (!types.has(name)) {
525
+ this.report(annotation.span, `Cannot infer interceptor type ${name}; supply explicit type arguments on ${interceptor.name}`, 'INTERCEPTOR');
526
+ types.set(name, errorTy);
527
+ }
528
+ this.checkConstraints(interceptor, definition.file, types, annotation.span);
529
+ const selected = new Map();
530
+ const argumentBindings = around.params.map((param, i) => {
531
+ const expected = this.resolveType(param.type, definition.file, types);
532
+ if (param.injected) {
533
+ inputTypes[i] = expected;
534
+ return this.bindingKeyForType(expected);
535
+ }
536
+ const index = argumentIndices[i];
537
+ if (index < 0)
538
+ this.report(annotation.span, `${interceptor.name}.around needs parameter ${param.name}; map it to a parameter of ${node.name}`, 'INTERCEPTOR');
539
+ else {
540
+ if (!this.assignable(inputTypes[i], expected))
541
+ this.report(annotation.span, `Mapping ${inputs[index].name} to ${param.name} expects ${tyName(expected)}, got ${tyName(inputTypes[i])}`, 'INTERCEPTOR');
542
+ if (param.ownership !== inputs[index].ownership)
543
+ this.report(annotation.span, `Parameter ${param.name} must use ${inputs[index].ownership} ownership to match ${inputs[index].name}`, 'OWN');
544
+ const previous = selected.get(index);
545
+ if (previous && (param.ownership !== 'managed' || previous.ownership !== 'managed'))
546
+ this.report(annotation.span, `Cannot map owned or borrowed parameter ${inputs[index].name} more than once`, 'OWN');
547
+ selected.set(index, param);
548
+ }
549
+ return undefined;
550
+ });
551
+ const result = this.resolveType(around.returns, definition.file, types);
552
+ if (!this.assignable(result, output))
553
+ this.report(annotation.span, `${interceptor.name}.around returns ${tyName(result)}; ${node.name} requires ${tyName(output)}`, 'INTERCEPTOR');
554
+ if (around.returnOwnership !== ownership)
555
+ this.report(annotation.span, `Interceptor ${interceptor.name} must preserve ${ownership} return ownership`, 'OWN');
556
+ const constructorKeys = interceptor.fields.map(field => this.bindingKeyForType(this.resolveType(field.type, definition.file, types)));
557
+ const injectedIndex = (ref) => {
558
+ const required = this.resolveType(ref, definition.file, types);
559
+ const index = inputs.findIndex(input => input.injected &&
560
+ this.assignable(this.resolveType(input.type, file, ownerTypes), required));
561
+ if (index < 0)
562
+ this.report(annotation.span, `${node.name} must declare a resolve ${tyName(required)} parameter required by interceptor ${interceptor.name}`, 'DI');
563
+ return index;
564
+ };
565
+ const constructorInputIndices = interceptor.fields.map(field => injectedIndex(field.type));
566
+ const argumentInjectionIndices = around.params.map(param => param.injected ? injectedIndex(param.type) : undefined);
567
+ layers.push({ annotation, definition, around, argumentIndices, argumentBindings, constructorKeys,
568
+ types, inputTypes, outputType: output, constructorInputIndices, argumentInjectionIndices,
569
+ errors: around.throws.map(ref => this.resolveType(ref, definition.file, types)) });
570
+ }
571
+ };
572
+ for (const def of this.project.definitions.values()) {
573
+ const node = def.node;
574
+ const types = this.paramsFor(node.typeParams, node, def.file);
575
+ if (node.kind === 'class')
576
+ plan(node, def.file, types, node.fields, { id: def.id, name: def.name, kind: 'class', def, args: [...types.values()], nullable: false }, 'managed');
577
+ if (node.kind === 'function')
578
+ plan(node, def.file, types, node.params, this.resolveType(node.returns, def.file, types), node.returnOwnership);
579
+ if ('methods' in node)
580
+ for (const method of node.methods) {
581
+ if (node.kind === 'interceptor' && method.name === 'around')
582
+ continue;
583
+ const methodTypes = new Map([...types, ...this.paramsFor(method.typeParams, method, def.file)]);
584
+ plan(method, def.file, methodTypes, method.params, this.resolveType(method.returns, def.file, methodTypes), method.returnOwnership);
585
+ }
586
+ }
587
+ }
588
+ substitute(type, types) {
589
+ if (type.kind === 'param' && types.has(type.name)) {
590
+ const value = types.get(type.name);
591
+ return { ...value, nullable: type.nullable || value.nullable };
592
+ }
593
+ return { ...type, args: type.args.map(arg => this.substitute(arg, types)) };
594
+ }
595
+ effectiveErrors(node, file, types) {
596
+ return [
597
+ ...(node.kind === 'function' ? node.throws : node.validationErrors ?? []).map(ref => this.resolveType(ref, file, types)),
598
+ ...(this.interceptorPlans.get(node) ?? []).flatMap(layer => layer.errors.map(type => this.substitute(type, types))),
599
+ ];
600
+ }
601
+ checkInterceptorDependencies() {
602
+ for (const [node, layers] of this.interceptorPlans) {
603
+ if (node.kind === 'class')
604
+ for (const layer of layers) {
605
+ const effects = this.contractFor(layer.around, layer.definition.file, layer.definition, layer.types);
606
+ if (effects.uses.size || effects.changes.some(path => path !== 'self' && !path.startsWith('self.')))
607
+ this.report(layer.annotation.span, 'Constructor interceptors must be pure; move external work to an explicit startup method', 'EFFECT');
608
+ }
609
+ if (node.kind === 'function' && node.name === 'drop' && layers.length) {
610
+ const owner = [...this.project.definitions.values()].find(def => 'methods' in def.node && def.node.methods.includes(node));
611
+ const types = new Map([...(owner && 'typeParams' in owner.node ? this.paramsFor(owner.node.typeParams, owner.node, owner.file) : []),
612
+ ...this.paramsFor(node.typeParams, node, node.span.file)]);
613
+ if (this.effectiveContract(node, node.span.file, owner, types).uses.size || this.effectiveErrors(node, node.span.file, types).length)
614
+ this.report(node.span, 'drop interceptor layers must perform only local cleanup and cannot add effects or errors', 'EFFECT');
615
+ }
616
+ }
617
+ }
618
+ checkClassShape(def) {
619
+ const cls = def.node;
620
+ if (!cls.record && cls.implements.length === 0)
621
+ this.report(cls.span, `${cls.name} must implement at least one interface`, 'INTERFACE');
622
+ const params = this.paramsFor(cls.typeParams, cls, def.file);
623
+ this.checkVariance(cls, def.file);
624
+ const names = new Set();
625
+ for (const field of fieldsOf(cls)) {
626
+ if (names.has(field.name))
627
+ this.report(field.span, `Duplicate field ${field.name}`);
628
+ names.add(field.name);
629
+ this.resolveType(field.type, def.file, params);
630
+ if (cls.record && (field.mutable || field.injected || field.ownership !== 'managed'))
631
+ this.report(field.span, 'Record fields are immutable data; dependencies and ownership belong in behavioral classes', 'RECORD');
632
+ if (cls.record && !this.immutableData(this.resolveType(field.type, def.file, params)))
633
+ this.report(field.span, 'Record fields must contain primitives, tuples, or other immutable records', 'RECORD');
634
+ if (field.injected && field.ownership !== 'managed')
635
+ this.report(field.span, 'A resolve parameter cannot also be own or borrow', 'DI');
636
+ if (field.ownership === 'borrow')
637
+ this.report(field.span, 'A mutable borrow cannot be stored in a process-managed class field', 'BORROW');
638
+ }
639
+ for (const method of cls.methods) {
640
+ if (names.has(method.name))
641
+ this.report(method.span, `Method ${method.name} conflicts with a field`);
642
+ names.add(method.name);
643
+ this.checkFunctionSignature(method, def.file, params);
644
+ }
645
+ const inherited = new Map();
646
+ for (const ifaceRef of cls.implements) {
647
+ const iface = this.resolveType(ifaceRef, def.file, params);
648
+ if (iface.kind !== 'interface') {
649
+ this.report(ifaceRef.span, `${iface.name} is not an interface`);
650
+ continue;
651
+ }
652
+ for (const [name, methods] of this.interfaceMethods(iface, new Set())) {
653
+ inherited.set(name, [...(inherited.get(name) ?? []), ...methods]);
654
+ }
655
+ }
656
+ const defaults = new Map();
657
+ for (const [name, methods] of inherited) {
658
+ const own = cls.methods.find(method => method.name === name);
659
+ const reference = methods[0];
660
+ if (own) {
661
+ if (!this.sameSignature(own, def.file, params, reference))
662
+ this.report(own.span, `Method ${name} does not match the interface signature`);
663
+ }
664
+ else {
665
+ const concrete = methods.filter(entry => !!entry.method.body);
666
+ const unique = new Map(concrete.map(entry => [entry.from, entry.method]));
667
+ if (unique.size > 1)
668
+ this.report(cls.span, `Conflicting default implementations of ${name}; ${cls.name} must override it`, 'INTERFACE');
669
+ else if (concrete[0])
670
+ defaults.set(name, concrete[0]);
671
+ else
672
+ this.report(cls.span, `${cls.name} must implement ${name}`, 'INTERFACE');
673
+ }
674
+ }
675
+ this.defaults.set(def.id, defaults);
676
+ }
677
+ checkInterfaceShape(def) {
678
+ const iface = def.node;
679
+ const params = this.paramsFor(iface.typeParams, iface, def.file);
680
+ this.checkVariance(iface, def.file);
681
+ const names = new Set();
682
+ for (const method of iface.methods) {
683
+ if (names.has(method.name))
684
+ this.report(method.span, `Duplicate interface method ${method.name}`);
685
+ names.add(method.name);
686
+ this.checkFunctionSignature(method, def.file, params);
687
+ }
688
+ for (const parent of iface.extends) {
689
+ const type = this.resolveType(parent, def.file, params);
690
+ if (type.kind !== 'interface')
691
+ this.report(parent.span, `${parent.name} is not an interface`);
692
+ }
693
+ this.interfaceMethods({ id: def.id, name: def.name, kind: 'interface', def,
694
+ args: [...params.values()], nullable: false }, new Set());
695
+ }
696
+ interfaceMethods(type, visiting) {
697
+ const result = new Map();
698
+ if (type.id === 'builtin:Error')
699
+ return result;
700
+ if (!type.def || type.def.node.kind !== 'interface')
701
+ return result;
702
+ if (visiting.has(type.id)) {
703
+ this.report(type.def.node.span, `Interface inheritance cycle involving ${type.name}`, 'INTERFACE');
704
+ return result;
705
+ }
706
+ visiting.add(type.id);
707
+ const iface = type.def.node;
708
+ const mapping = new Map(iface.typeParams.map((name, index) => [name, type.args[index] ?? errorTy]));
709
+ for (const parent of iface.extends) {
710
+ const parentType = this.resolveType(parent, type.def.file, mapping);
711
+ for (const [name, entries] of this.interfaceMethods(parentType, visiting))
712
+ result.set(name, [...(result.get(name) ?? []), ...entries]);
713
+ }
714
+ for (const method of iface.methods)
715
+ result.set(method.name, [{ method, from: type.def.id, file: type.def.file, params: mapping }]);
716
+ visiting.delete(type.id);
717
+ return result;
718
+ }
719
+ sameSignature(left, leftFile, leftOwnerParams, right) {
720
+ const method = right.method;
721
+ if (left.params.length !== method.params.length ||
722
+ left.typeParams.length !== method.typeParams.length)
723
+ return false;
724
+ const leftParams = new Map(leftOwnerParams);
725
+ const rightParams = new Map(right.params);
726
+ for (let i = 0; i < left.typeParams.length; i++) {
727
+ const placeholder = { id: `method-param:${i}`, name: `$${i}`, kind: 'param',
728
+ args: [], nullable: false };
729
+ leftParams.set(left.typeParams[i], placeholder);
730
+ rightParams.set(method.typeParams[i], placeholder);
731
+ }
732
+ const equalRef = (a, b) => this.sameType(this.resolveType(a, leftFile, leftParams), this.resolveType(b, right.file, rightParams));
733
+ const constraintsEqual = left.typeParams.every((name, index) => {
734
+ const a = left.typeConstraints?.[name] ?? [];
735
+ const b = method.typeConstraints?.[method.typeParams[index]] ?? [];
736
+ return a.length === b.length && a.every((ref, i) => equalRef(ref, b[i]));
737
+ });
738
+ const leftOwner = [...this.project.definitions.values()].find(def => 'methods' in def.node && def.node.methods.includes(left));
739
+ const rightOwner = this.project.definitions.get(right.from);
740
+ const leftEffects = this.effectiveContract(left, leftFile, leftOwner, leftParams);
741
+ const rightEffects = this.effectiveContract(method, right.file, rightOwner, rightParams);
742
+ const effectsFit = leftEffects.changes.every(change => coversChange(rightEffects.changes, change)) &&
743
+ [...leftEffects.uses.keys()].every(key => rightEffects.uses.has(key));
744
+ return constraintsEqual && effectsFit && left.params.every((param, i) => param.name === method.params[i].name &&
745
+ param.injected === method.params[i].injected &&
746
+ param.ownership === method.params[i].ownership &&
747
+ equalRef(param.type, method.params[i].type)) &&
748
+ equalRef(left.returns, method.returns) &&
749
+ left.returnOwnership === method.returnOwnership &&
750
+ this.effectiveErrors(left, leftFile, leftParams).every(thrown => this.effectiveErrors(method, right.file, rightParams).some(allowed => this.assignable(thrown, allowed)));
751
+ }
752
+ sameType(left, right) {
753
+ return sameType(left, right);
754
+ }
755
+ implementsError(type) {
756
+ if (type.id === 'builtin:Error')
757
+ return true;
758
+ if (errorNames.some(name => type.id === `builtin:${name}`))
759
+ return true;
760
+ if (type.kind === 'error')
761
+ return true;
762
+ if (type.kind === 'param')
763
+ return !!type.bounds?.some(bound => this.implementsError(bound));
764
+ return (type.kind === 'class' || type.kind === 'interface') &&
765
+ this.implementsInterface(type, builtin('Error'));
766
+ }
767
+ implementsInterface(classType, iface, seen = new Set()) {
768
+ if (classType.id === iface.id)
769
+ return this.compatibleArguments(classType, iface);
770
+ if (seen.has(classType.id))
771
+ return false;
772
+ seen.add(classType.id);
773
+ const def = classType.def;
774
+ if (!def)
775
+ return false;
776
+ const refs = def.node.kind === 'class' ? def.node.implements :
777
+ def.node.kind === 'interface' ? def.node.extends : [];
778
+ const names = def.node.kind === 'class' || def.node.kind === 'interface' ? def.node.typeParams : [];
779
+ const params = new Map(names.map((name, index) => [name, classType.args[index] ?? errorTy]));
780
+ return refs.some(ref => {
781
+ const candidate = this.resolveType(ref, def.file, params);
782
+ return this.implementsInterface(candidate, iface, seen);
783
+ });
784
+ }
785
+ assignable(source, target) {
786
+ if (source.kind === 'error' || target.kind === 'error')
787
+ return true;
788
+ if (source.kind === 'null')
789
+ return target.nullable || !!target.optional;
790
+ if ((source.nullable || source.optional) && !target.nullable && !target.optional)
791
+ return false;
792
+ if (target.id === 'builtin:Data')
793
+ return this.immutableData({ ...source, nullable: false, optional: false });
794
+ if (source.id === target.id)
795
+ return this.compatibleArguments(source, target);
796
+ if (source.kind === 'param' && source.bounds?.some(bound => this.assignable(bound, target)))
797
+ return true;
798
+ if (source.id === 'builtin:c_int' && ['builtin:int', 'builtin:float'].includes(target.id))
799
+ return true;
800
+ if (source.id === 'builtin:int' && target.id === 'builtin:float')
801
+ return true;
802
+ if (target.id === 'builtin:Error' && this.implementsError(source))
803
+ return true;
804
+ return target.kind === 'interface' && this.implementsInterface(source, target);
805
+ }
806
+ compatibleArguments(source, target) {
807
+ return source.args.length === target.args.length && source.args.every((arg, index) => {
808
+ const mode = source.id === 'builtin:Tuple' ? 'out' : source.def?.node.kind === 'interface' ?
809
+ source.def.node.typeVariance?.[source.def.node.typeParams[index]] : undefined;
810
+ return mode === 'out' ? this.assignable(arg, target.args[index]) :
811
+ mode === 'in' ? this.assignable(target.args[index], arg) : this.sameType(arg, target.args[index]);
812
+ });
813
+ }
814
+ checkBindings() {
815
+ const main = this.project.main;
816
+ if (!main)
817
+ return;
818
+ let seenStatement = false;
819
+ const declarations = [];
820
+ for (const item of main.items) {
821
+ if (item.kind === 'import')
822
+ continue;
823
+ if (item.kind === 'include') {
824
+ if (seenStatement)
825
+ this.report(item.span, 'Composition includes must precede executable statements', 'DI');
826
+ const def = this.project.scopes.get(main.path)?.get(item.name);
827
+ if (def?.node.kind !== 'composition')
828
+ this.report(item.span, `${item.name} must be an explicitly imported composition`, 'DI');
829
+ else
830
+ declarations.push(...def.node.bindings);
831
+ continue;
832
+ }
833
+ if (item.kind !== 'bind') {
834
+ seenStatement = true;
835
+ continue;
836
+ }
837
+ if (seenStatement)
838
+ this.report(item.span, 'Bindings must precede executable statements', 'DI');
839
+ declarations.push(item);
840
+ }
841
+ for (const item of declarations) {
842
+ const key = this.bindingKey(item.key, item.keyTypeArgs);
843
+ if (this.bindingByKey.has(key)) {
844
+ this.report(item.span, `Duplicate binding ${key}`, 'DI');
845
+ continue;
846
+ }
847
+ const targetType = this.resolveType(item.target, item.span.file);
848
+ const target = targetType.def;
849
+ if (!target || target.node.kind !== 'class') {
850
+ this.report(item.span, `Binding target ${item.target.name} must be a class`, 'DI');
851
+ continue;
852
+ }
853
+ const interfaceDef = this.project.scopes.get(item.span.file)?.get(item.key);
854
+ let exposedType = targetType;
855
+ if (interfaceDef?.node.kind === 'interface') {
856
+ exposedType = this.resolveType({ name: item.key, args: item.keyTypeArgs,
857
+ nullable: false, span: item.span }, item.span.file);
858
+ if (!this.assignable(targetType, exposedType))
859
+ this.report(item.span, `${target.name} does not implement ${key}`, 'DI');
860
+ }
861
+ else if (interfaceDef?.node.kind === 'class')
862
+ this.report(item.span, `Binding key ${item.key} is a class; use an interface name or a new lowercase key`, 'DI');
863
+ else if (item.keyTypeArgs.length)
864
+ this.report(item.span, `Named binding ${item.key} cannot take type arguments`, 'DI');
865
+ const stateful = fieldsOf(target.node).some(field => field.mutable || field.ownership === 'own' || field.type.name === 'Shared') ||
866
+ target.node.methods.some(method => method.changes?.some(path => path === 'self' || path.startsWith('self.')));
867
+ const info = { key, target, exposedType, stateful, lifetime: item.lifetime ?? (stateful ? 'fresh' : 'shared'),
868
+ dependencies: [], constructorKeys: [], declaration: item, requiresScope: item.lifetime === 'scoped' };
869
+ this.bindings.push(info);
870
+ this.bindingByKey.set(key, info);
871
+ }
872
+ for (const info of this.bindings) {
873
+ const cls = info.target.node;
874
+ const target = this.resolveType(info.declaration.target, info.declaration.span.file);
875
+ const types = new Map(cls.typeParams.map((name, i) => [name, target.args[i] ?? errorTy]));
876
+ for (const field of cls.fields) {
877
+ if (!field.injected) {
878
+ this.report(info.declaration.span, `${cls.name} cannot be bound because ${field.name} needs a constructor argument`, 'DI');
879
+ continue;
880
+ }
881
+ const fieldType = this.resolveType(field.type, info.target.file, types);
882
+ const key = this.bindingKeyForType(fieldType);
883
+ info.constructorKeys.push(key);
884
+ const dep = this.bindingByKey.get(key);
885
+ if (!dep)
886
+ this.report(field.span, `No binding for ${field.type.name}, required by ${cls.name}`, 'DI');
887
+ else {
888
+ if (!this.assignable(dep.exposedType, fieldType))
889
+ this.report(field.span, `Binding ${key} has incompatible provenance for ${cls.name}.${field.name}`, 'DI');
890
+ info.dependencies.push(dep.key);
891
+ }
892
+ }
893
+ // Layer dependencies are checked against, and forwarded from, these same
894
+ // constructor inputs. There are no additional hidden global lookups.
895
+ for (const error of this.effectiveErrors(cls, info.target.file, types))
896
+ this.report(info.declaration.span, `DI construction of ${cls.name} can throw ${tyName(error)}; catch that error inside its interceptor`, 'THROWS');
897
+ }
898
+ for (let pass = 0; pass < this.bindings.length; pass++)
899
+ for (const info of this.bindings) {
900
+ info.stateful ||= info.dependencies.some(key => this.bindingByKey.get(key)?.stateful);
901
+ info.requiresScope ||= info.dependencies.some(key => this.bindingByKey.get(key)?.requiresScope);
902
+ if (!info.declaration.lifetime && info.stateful)
903
+ info.lifetime = 'fresh';
904
+ }
905
+ for (const info of this.bindings) {
906
+ if (info.stateful && info.lifetime !== 'fresh' && !info.declaration.sharedMutation)
907
+ this.report(info.declaration.span, `${info.key} retains mutable state; write ${info.lifetime} mutable to choose shared state explicitly`, 'DI');
908
+ if (info.lifetime === 'shared' && info.requiresScope)
909
+ this.report(info.declaration.span, `Shared ${info.key} cannot retain a scoped dependency`, 'DI');
910
+ if (info.stateful && info.lifetime === 'shared' && info.declaration.sharedMutation && this.project.config.lint.includes('architecture'))
911
+ this.diagnostics.push({ ...info.declaration.span, code: 'LINT', severity: 'warning',
912
+ message: `${info.key} shares mutable state across the process; review this module boundary when its contract grows` });
913
+ }
914
+ const ordered = [];
915
+ ordered.push(...orderGraph(this.bindings, (info, chain) => this.report(info.declaration.span, `Dependency cycle: ${chain.join(' -> ')}`, 'DI')));
916
+ this.bindings.splice(0, this.bindings.length, ...ordered);
917
+ }
918
+ checkFunctionBody(fn, file, owner, initializing = false, suppliedTypes, next) {
919
+ const params = suppliedTypes ? new Map(suppliedTypes) :
920
+ this.paramsFor(owner && 'typeParams' in owner.node ? owner.node.typeParams : [], owner?.node, file);
921
+ for (const [name, ty] of this.paramsFor(fn.typeParams, fn, file))
922
+ params.set(name, ty);
923
+ this.addBounds(params, fn, file);
924
+ const context = { file, locals: new Map(), returns: this.resolveType(fn.returns, file, params),
925
+ returnOwnership: fn.returnOwnership,
926
+ allowedErrors: fn.throws.map(type => this.resolveType(type, file, params)),
927
+ unsafe: false, borrowed: new Set(), initializing, types: params, next, callable: fn, owner,
928
+ flow: new OwnershipFlow() };
929
+ if (fn.endpoint?.streams) {
930
+ context.stream = context.returns;
931
+ context.returns = builtin('void');
932
+ }
933
+ if (owner) {
934
+ context.self = { id: owner.id, name: owner.name,
935
+ kind: owner.node.kind === 'class' ? 'class' : owner.node.kind === 'interceptor' ? 'interceptor' : 'interface', def: owner,
936
+ args: [...params.values()].slice(0, owner.node.typeParams.length), nullable: false };
937
+ context.locals.set('self', { type: context.self, ownership: (fn.changes ?? []).some(path => path === 'self' || path.startsWith('self.')) || initializing ? 'borrow' : 'managed', moved: false });
938
+ if (owner.node.kind === 'class' || owner.node.kind === 'interceptor') {
939
+ for (const field of fieldsOf(owner.node))
940
+ context.locals.set(field.name, { type: { ...this.resolveType(field.type, file, params), readonly: !field.mutable }, ownership: field.ownership,
941
+ moved: false, origin: 'field', definition: field.span });
942
+ }
943
+ }
944
+ for (const param of fn.params)
945
+ context.locals.set(param.name, { type: { ...this.resolveType(param.type, file, params), readonly: param.ownership === 'managed' }, ownership: param.ownership, moved: false, definition: param.span });
946
+ context.locals.forEach(local => { local.declaredType ??= local.type; });
947
+ for (const [name, local] of context.locals) {
948
+ const field = owner && 'fields' in owner.node ? fieldsOf(owner.node).find(field => field.name === name) : undefined;
949
+ const origins = field && this.isReference(local.type) ? new Set([`inside:${owner.id}`]) : field ? new Set() : name === 'self' ? new Set([`self:${owner?.id}`]) :
950
+ this.isReference(local.type) ? new Set([`input:${file}:${fn.span.start}:${name}`]) : new Set();
951
+ context.flow.declare(name, origins, !!local.type.readonly, {
952
+ parent: field ? 'self' : undefined,
953
+ external: field ? `self.${name}` : name === 'self' ? 'self' : local.ownership === 'own' ? undefined : name,
954
+ borrowedInput: local.ownership === 'borrow' && name !== 'self',
955
+ });
956
+ }
957
+ if (owner && 'fields' in owner.node)
958
+ context.flow.object(context.flow.origins('self'), fieldsOf(owner.node).map(field => ({
959
+ name: field.name, origins: context.flow.origins(field.name), mutable: !!field.mutable || field.ownership === 'own',
960
+ })));
961
+ for (const [name, local] of context.locals)
962
+ if (local.ownership === 'borrow' && !initializing) {
963
+ context.flow.borrow(name, fn.span, (span, message) => this.report(span, message, 'BORROW'), true);
964
+ context.borrowed.add(name);
965
+ }
966
+ context.effects = this.contractFor(fn, file, owner, params);
967
+ if ((fn.changes ?? []).some(path => path.startsWith('self')) && !owner)
968
+ this.report(fn.span, 'A free function has no self to change', 'EFFECT');
969
+ this.checkStatements(fn.body ?? [], context, fn.span);
970
+ if (fn.body && context.returns.id !== 'builtin:void' &&
971
+ !(owner?.node.kind === 'interceptor' && fn.name === 'around') && canFallThrough(fn.body))
972
+ this.report(fn.span, `${fn.name} must return a value or throw on every path`, 'TYPE');
973
+ }
974
+ cloneContext(context) {
975
+ return { ...context, locals: new Map([...context.locals].map(([key, value]) => [key, { ...value }])),
976
+ borrowed: new Set(context.borrowed), flow: context.flow.clone() };
977
+ }
978
+ mergeMoved(target, branches) {
979
+ target.flow.join(branches.map(branch => branch.flow));
980
+ for (const [name, local] of target.locals) {
981
+ if (branches.some(branch => branch.locals.get(name)?.moved))
982
+ local.moved = true;
983
+ if (branches.some(branch => branch.locals.get(name)?.type.nullable))
984
+ local.type = local.declaredType ?? { ...local.type, nullable: true };
985
+ }
986
+ }
987
+ contractFor(fn, file, owner, supplied = new Map()) {
988
+ const types = new Map(supplied);
989
+ for (const [name, value] of this.paramsFor(fn.typeParams, fn, file))
990
+ if (!types.has(name))
991
+ types.set(name, value);
992
+ const sources = new Map(fn.params.map(param => [param.name, this.resolveType(param.type, file, types)]));
993
+ if (owner && 'typeParams' in owner.node) {
994
+ const self = { id: owner.id, name: owner.name, def: owner,
995
+ kind: owner.node.kind === 'class' ? 'class' : owner.node.kind === 'interceptor' ? 'interceptor' : 'interface',
996
+ args: owner.node.typeParams.map(name => types.get(name) ?? this.paramsFor([name]).get(name)), nullable: false };
997
+ sources.set('self', self);
998
+ if ('fields' in owner.node)
999
+ for (const field of fieldsOf(owner.node))
1000
+ if (!sources.has(field.name))
1001
+ sources.set(field.name, this.resolveType(field.type, owner.file, types));
1002
+ }
1003
+ const result = effectContract(fn, {
1004
+ source: path => {
1005
+ const parts = path.split('.');
1006
+ let source = sources.get(parts[0]);
1007
+ const def = this.project.scopes.get(file)?.get(parts[0]);
1008
+ if (!source && def && def.node.kind !== 'function' && def.node.kind !== 'interceptor' && def.node.kind !== 'composition')
1009
+ source = { id: def.id, name: def.name, def, kind: def.node.kind, args: def.node.typeParams.map(name => types.get(name) ?? errorTy), nullable: false };
1010
+ for (const name of parts.slice(1)) {
1011
+ const node = source?.def?.node;
1012
+ const field = node && 'fields' in node ? fieldsOf(node).find(field => field.name === name) : undefined;
1013
+ source = field && source?.def ? this.resolveType(field.type, source.def.file, new Map(source.def.node.typeParams.map((param, index) => [param, source.args[index] ?? errorTy]))) : undefined;
1014
+ }
1015
+ return source;
1016
+ },
1017
+ interfaces: type => {
1018
+ const output = [];
1019
+ const seen = new Set();
1020
+ const visit = (value) => {
1021
+ const key = `${value.id}:${tyName(value)}`;
1022
+ if (seen.has(key))
1023
+ return;
1024
+ seen.add(key);
1025
+ for (const bound of value.bounds ?? []) {
1026
+ output.push(bound);
1027
+ visit(bound);
1028
+ }
1029
+ const node = value.def?.node;
1030
+ const refs = node?.kind === 'class' ? node.implements : node?.kind === 'interface' ? node.extends : [];
1031
+ const substitutions = new Map((node?.typeParams ?? []).map((name, index) => [name, value.args[index] ?? errorTy]));
1032
+ for (const ref of refs) {
1033
+ const resolved = this.resolveType(ref, value.def.file, substitutions);
1034
+ output.push(resolved);
1035
+ visit(resolved);
1036
+ }
1037
+ };
1038
+ visit(type);
1039
+ return output;
1040
+ },
1041
+ operation: (type, name) => this.interfaceMethods(type, new Set()).has(name),
1042
+ native: name => {
1043
+ const def = this.project.scopes.get(file)?.get(name);
1044
+ return def?.node.kind === 'function' && def.node.externC ? def.id : undefined;
1045
+ },
1046
+ report: (span, message) => this.report(span, message, 'EFFECT'),
1047
+ });
1048
+ const inferred = this.inferredUses.get(fn);
1049
+ if (!inferred) {
1050
+ this.effectContracts.set(fn, result);
1051
+ return result;
1052
+ }
1053
+ const uses = new Map(result.uses);
1054
+ for (const [key, effect] of inferred) {
1055
+ const capability = effect.capability && this.substitute(effect.capability, types);
1056
+ uses.set(capability ? capabilityKey(capability, effect.operation) : key, { ...effect, capability, source: capability ? tyName(capability) : effect.source });
1057
+ }
1058
+ const contract = { ...result, uses, inferred: true };
1059
+ this.effectContracts.set(fn, contract);
1060
+ return contract;
1061
+ }
1062
+ requireUse(key, display, span, context, effect) {
1063
+ if (context.locked)
1064
+ this.report(span, `Release the lock before ${display}; I/O and capability calls cannot hold a lock`, 'CONCURRENCY');
1065
+ if (!context.callable)
1066
+ return;
1067
+ const inferred = this.inferring && this.inferredUses.get(context.callable);
1068
+ if (inferred) {
1069
+ if (!inferred.has(key)) {
1070
+ const separator = display.lastIndexOf('.');
1071
+ inferred.set(key, effect ?? { source: display.slice(0, separator), operation: display.slice(separator + 1), span });
1072
+ this.inferenceChanged = true;
1073
+ this.changingInference.add(context.callable);
1074
+ }
1075
+ return;
1076
+ }
1077
+ if (!context.effects?.uses.has(key))
1078
+ this.report(span, `${context.callable.name} is pure for ${display}; declare uses ${display}`, 'EFFECT');
1079
+ }
1080
+ effectiveContract(fn, file, owner, types = new Map()) {
1081
+ const base = this.contractFor(fn, file, owner, types);
1082
+ const uses = new Map(base.uses);
1083
+ const changes = [...base.changes];
1084
+ for (const layer of this.interceptorPlans.get(fn) ?? []) {
1085
+ const layerTypes = new Map([...layer.types].map(([name, type]) => [name, this.substitute(type, types)]));
1086
+ const contract = this.contractFor(layer.around, layer.definition.file, layer.definition, layerTypes);
1087
+ for (const [key, effect] of contract.uses)
1088
+ uses.set(key, effect);
1089
+ for (const changed of contract.changes) {
1090
+ const [root, ...path] = changed.split('.');
1091
+ if (root === 'self')
1092
+ continue;
1093
+ const index = layer.around.params.findIndex(param => param.name === root);
1094
+ const target = layer.argumentIndices[index];
1095
+ if (target !== undefined && fn.params[target])
1096
+ changes.push([fn.params[target].name, ...path].join('.'));
1097
+ }
1098
+ }
1099
+ const result = { changes: [...new Set(changes)], uses, inferred: base.inferred };
1100
+ this.effectContracts.set(fn, result);
1101
+ return result;
1102
+ }
1103
+ intrinsicEffect(operation, span, context) {
1104
+ if (context.locked)
1105
+ this.report(span, `Release the lock before ${operation}`, 'CONCURRENCY');
1106
+ if (!context.callable)
1107
+ return;
1108
+ const capability = operation === 'print' ? 'Console' : operation === 'read_file' ? 'FileReader' :
1109
+ operation === 'write_file' ? 'FileWriter' : 'Arguments';
1110
+ const method = operation === 'print' || operation === 'write_file' ? 'write' : 'read';
1111
+ if (!this.project.files.get(context.file)?.builtin)
1112
+ this.report(span, `Use a resolve ${capability} dependency and its ${method} operation outside main or test code`, 'EFFECT');
1113
+ this.requireUse(`august/io/contracts.aug:${capability}<>.${method}`, `${capability}.${method}`, span, context);
1114
+ }
1115
+ requireChange(object, span, context) {
1116
+ if (!context.callable)
1117
+ return;
1118
+ const root = this.rootName(object);
1119
+ if (!root)
1120
+ return;
1121
+ const tracked = context.flow.external(root);
1122
+ const origin = tracked?.split('.')[0] ?? this.aliasRoot(root, context.locals);
1123
+ const local = context.locals.get(origin);
1124
+ const path = tracked ?? (origin === 'self' ? 'self' : local?.origin === 'field' ? `self.${origin}` : origin);
1125
+ if (context.initializing && path.startsWith('self'))
1126
+ return;
1127
+ const input = context.callable.params.find(param => param.name === origin);
1128
+ if (path.startsWith('self') || input && input.ownership !== 'own') {
1129
+ if (!coversChange(context.effects?.changes ?? [], path))
1130
+ this.report(span, `${context.callable.name} must declare changes ${path.startsWith('self') ? 'self' : origin}`, 'EFFECT');
1131
+ }
1132
+ }
1133
+ immutableData(type, seen = new Set()) {
1134
+ if (['builtin:int', 'builtin:c_int', 'builtin:float', 'builtin:string', 'builtin:bool', 'null', 'builtin:Data'].includes(type.id))
1135
+ return true;
1136
+ if (type.kind === 'param')
1137
+ return !!type.bounds?.some(bound => bound.id === 'builtin:Data');
1138
+ if (['builtin:Tuple', 'builtin:List', 'builtin:Set', 'builtin:Map'].includes(type.id))
1139
+ return type.args.every(arg => this.immutableData(arg, seen));
1140
+ if (['builtin:Bytes', 'builtin:Json', 'builtin:RsaPrivateKey', 'builtin:RsaPublicKey'].includes(type.id))
1141
+ return true;
1142
+ const node = type.def?.node;
1143
+ if (node?.kind !== 'class' || !node.record)
1144
+ return false;
1145
+ if (seen.has(type.id))
1146
+ return true;
1147
+ seen.add(type.id);
1148
+ const types = new Map(node.typeParams.map((name, index) => [name, type.args[index] ?? errorTy]));
1149
+ return node.fields.every(field => this.immutableData(this.resolveType(field.type, type.def.file, types), seen));
1150
+ }
1151
+ /** Literal containers may be frozen in place only when none of their children retain mutable aliases. */
1152
+ immutableInput(expr, type = this.expressionTypes.get(expr) ?? errorTy) {
1153
+ if (type.frozen || !['List', 'Set', 'Map', 'Tuple'].includes(type.name))
1154
+ return true;
1155
+ if (expr.kind === 'collection')
1156
+ return expr.items.every(item => this.immutableInput(item));
1157
+ if (expr.kind === 'call' && expr.callee.kind === 'name' && ['List', 'Set', 'Map', 'Tuple'].includes(expr.callee.name))
1158
+ return expr.args.every(item => this.immutableInput(item));
1159
+ const mutableChildren = (type) => !type.frozen &&
1160
+ (['List', 'Set', 'Map'].includes(type.name) || type.name === 'Tuple' && type.args.some(mutableChildren));
1161
+ return !mutableChildren(type);
1162
+ }
1163
+ patternLocals(names, type, origins, context, span, source) {
1164
+ const types = names.length === 1 ? [type] : type.id === 'builtin:Tuple' ? type.args : [];
1165
+ if (types.length !== names.length)
1166
+ this.report(span, `Pattern needs a Tuple with ${names.length} positions`, 'PATTERN');
1167
+ for (const [index, name] of names.entries()) {
1168
+ if (context.locals.has(name))
1169
+ this.report(span, `Pattern variable ${name} already exists`, 'PATTERN');
1170
+ const item = { ...(types[index] ?? errorTy), readonly: true };
1171
+ context.locals.set(name, { type: item, declaredType: item, ownership: 'managed', moved: false, definition: span });
1172
+ context.flow.declare(name, names.length === 1 ? origins : context.flow.field(origins, String(index)), true, { source });
1173
+ }
1174
+ }
1175
+ captureScope(span, context) {
1176
+ const source = this.project.files.get(context.file)?.source ?? '';
1177
+ const parameters = context.callable?.params ?? [];
1178
+ const fields = context.owner && 'fields' in context.owner.node ? fieldsOf(context.owner.node) : [];
1179
+ const doc = context.callable ? callableDocumentation(this.project, context.callable, context.owner) : undefined;
1180
+ const fieldDoc = context.owner ? javadocBefore(source, context.owner.node.span.start) : undefined;
1181
+ this.scopes.set(`${span.file}:${span.start}:${span.end}`, { span, locals: [...context.locals].map(([name, local]) => {
1182
+ const param = parameters.find(param => param.name === name);
1183
+ const field = local.origin === 'field' ? fields.find(field => field.name === name) : undefined;
1184
+ return { name, type: local.type, moved: local.moved, ownership: local.ownership, definition: local.definition,
1185
+ kind: field ? 'property' : param || name === 'self' ? 'parameter' : 'variable',
1186
+ documentation: doc?.parameters.get(param?.label ?? name) ?? fieldDoc?.parameters.get(field?.label ?? name) };
1187
+ }) });
1188
+ }
1189
+ checkStatements(body, context, fallback, joinsTasks = false) {
1190
+ const names = new Set(context.locals.keys());
1191
+ const previous = context.region;
1192
+ const region = body.span ?? fallback ?? previous;
1193
+ if (region) {
1194
+ context.region = region;
1195
+ this.captureScope({ ...region, end: body[0]?.span.start ?? region.end }, context);
1196
+ }
1197
+ body.forEach((stmt, index) => {
1198
+ this.captureScope(stmt.span, context);
1199
+ this.checkStatement(stmt, context);
1200
+ if (region)
1201
+ this.captureScope({ ...stmt.span, start: stmt.span.end, end: body[index + 1]?.span.start ?? region.end }, context);
1202
+ });
1203
+ if (!joinsTasks)
1204
+ for (const [name, local] of context.locals)
1205
+ if (!names.has(name) && local.ownership === 'own' && !local.moved &&
1206
+ context.flow.hasTaskCapture(context.flow.origins(name)))
1207
+ this.report(local.definition ?? fallback, `Owned ${name} is still borrowed by a task; wait before leaving this block or declare it directly in the task's scope block`, 'CONCURRENCY');
1208
+ context.region = previous;
1209
+ }
1210
+ checkStatement(stmt, context) {
1211
+ if (stmt.kind === 'lock') {
1212
+ if (context.locked)
1213
+ this.report(stmt.span, 'Nested locks require a single combined shared value to avoid lock-order deadlocks', 'CONCURRENCY');
1214
+ const type = this.checkExpression(stmt.value, context);
1215
+ if (type.id !== 'builtin:Shared')
1216
+ this.report(stmt.value.span, 'lock requires Shared<T>', 'CONCURRENCY');
1217
+ if (context.callable && !context.effects?.uses.size && !context.effects?.changes.length)
1218
+ this.report(stmt.span, 'A callable that locks shared state declares its capability operation or changes contract', 'EFFECT');
1219
+ const inside = this.cloneContext(context);
1220
+ inside.locked = true;
1221
+ const origins = allocationOrigin(stmt.span);
1222
+ inside.flow.region(origins);
1223
+ inside.locals.set(stmt.name, { type: { ...(type.args[0] ?? errorTy), readonly: false }, ownership: 'borrow', moved: false, definition: stmt.span });
1224
+ inside.flow.declare(stmt.name, origins, false);
1225
+ inside.flow.borrow(stmt.name, stmt.span, (span, message) => this.report(span, message, 'BORROW'), true);
1226
+ inside.borrowed.add(stmt.name);
1227
+ this.checkStatements(stmt.body, inside, stmt.span);
1228
+ this.mergeMoved(context, [inside]);
1229
+ return;
1230
+ }
1231
+ if (stmt.kind === 'serve') {
1232
+ if (context.callable || context.file !== this.project.main?.path)
1233
+ this.report(stmt.span, 'Select served endpoints in main.aug', 'HTTP');
1234
+ if (this.checkExpression(stmt.port, context).id !== 'builtin:int')
1235
+ this.report(stmt.port.span, 'The listening port is an int', 'HTTP');
1236
+ if (stmt.port.kind === 'literal' && typeof stmt.port.value === 'number' && (stmt.port.value < 0 || stmt.port.value > 65535))
1237
+ this.report(stmt.port.span, 'Ports range from 0 to 65535; 0 selects an available port', 'HTTP');
1238
+ const routes = new Set();
1239
+ for (const name of stmt.names) {
1240
+ const def = this.project.scopes.get(context.file)?.get(name);
1241
+ if (def?.node.kind !== 'function' || !def.node.endpoint) {
1242
+ this.report(stmt.span, `${name} must be an explicitly imported endpoint`, 'HTTP');
1243
+ continue;
1244
+ }
1245
+ const route = def.node.endpoint.method + ' ' + def.node.endpoint.path.replace(/\{[^}]+\}/g, '{}');
1246
+ if (routes.has(route))
1247
+ this.report(stmt.span, `Conflicting HTTP route ${route}`, 'HTTP');
1248
+ routes.add(route);
1249
+ for (const param of def.node.params.filter(param => param.injected)) {
1250
+ const type = this.resolveType(param.type, def.file);
1251
+ const key = this.bindingKeyForType(type);
1252
+ const binding = this.bindingByKey.get(key);
1253
+ if (!binding || !this.assignable(binding.exposedType, type))
1254
+ this.report(param.span, `Served endpoint ${name} requires a binding for ${key}`, 'DI');
1255
+ }
1256
+ }
1257
+ return;
1258
+ }
1259
+ if (stmt.kind === 'freeze') {
1260
+ const type = this.checkExpression(stmt.value, context);
1261
+ if (!this.immutableData(type))
1262
+ this.report(stmt.span, 'Freeze immutable data records and collections; behavioral objects retain explicit mutation contracts', 'FREEZE');
1263
+ const origins = this.placesOf(stmt.value, context);
1264
+ if (context.locals.has(stmt.name))
1265
+ this.report(stmt.span, `Variable ${stmt.name} already exists`, 'NAME');
1266
+ const root = sourceName(stmt.value);
1267
+ if (root && context.flow.external(root) && context.locals.get(root)?.ownership !== 'own')
1268
+ this.report(stmt.span, 'Freezing an external input requires own ownership', 'OWN');
1269
+ context.flow.freeze(origins, stmt.span, (span, message) => this.report(span, message, 'BORROW'));
1270
+ for (const [name, local] of context.locals)
1271
+ if (context.flow.frozen(context.flow.origins(name))) {
1272
+ local.type = { ...local.type, readonly: true, frozen: true };
1273
+ local.ownership = 'managed';
1274
+ }
1275
+ context.locals.set(stmt.name, { type: { ...type, readonly: true, frozen: true }, ownership: 'managed', moved: false, definition: stmt.span });
1276
+ context.flow.declare(stmt.name, origins, true);
1277
+ return;
1278
+ }
1279
+ if (stmt.kind === 'expr') {
1280
+ this.checkExpression(stmt.expr, context);
1281
+ return;
1282
+ }
1283
+ if (stmt.kind === 'scope') {
1284
+ const inside = this.cloneContext(context);
1285
+ inside.scope = stmt.span;
1286
+ inside.loopDepth = 0;
1287
+ const exceptionStart = context.exceptionalFlows?.length ?? 0;
1288
+ const scope = `tasks:${stmt.span.file}:${stmt.span.start}`;
1289
+ inside.flow.region(new Set([scope, ...this.bindings.filter(binding => binding.lifetime === 'scoped').map(binding => `scope:${stmt.span.file}:${stmt.span.start}:${binding.key}`)]));
1290
+ this.checkStatements(stmt.body, inside, stmt.span, true);
1291
+ const errors = inside.flow.pendingTaskErrors(scope);
1292
+ inside.flow.joinTasks(scope);
1293
+ for (const error of errors)
1294
+ this.checkAllowedError(error, stmt.span, { ...context, flow: inside.flow });
1295
+ for (const failure of context.exceptionalFlows?.slice(exceptionStart) ?? [])
1296
+ failure.flow.joinTasks(scope);
1297
+ this.mergeMoved(context, [inside]);
1298
+ return;
1299
+ }
1300
+ if (stmt.kind === 'destructure') {
1301
+ const type = this.checkExpression(stmt.value, context);
1302
+ if (type.id !== 'builtin:Tuple' || type.args.length !== stmt.names.length)
1303
+ this.report(stmt.span, `Destructuring needs a Tuple with ${stmt.names.length} positions`, 'PATTERN');
1304
+ this.patternLocals(stmt.names, type, this.placesOf(stmt.value, context), context, stmt.span);
1305
+ return;
1306
+ }
1307
+ if (stmt.kind === 'for') {
1308
+ const iterable = this.checkExpression(stmt.iterable, context);
1309
+ if (!['builtin:List', 'builtin:Set', 'builtin:Map', 'builtin:Tuple'].includes(iterable.id))
1310
+ this.report(stmt.iterable.span, 'for needs a List, Set, Map, or Tuple', 'ITERATION');
1311
+ const type = iterable.id === 'builtin:Map' ? { ...builtin('Tuple'), args: iterable.args } :
1312
+ iterable.id === 'builtin:Tuple' ? iterable.args[0] ?? errorTy : iterable.args[0] ?? errorTy;
1313
+ if (iterable.id === 'builtin:Tuple' && iterable.args.some(arg => !this.sameType(arg, type)))
1314
+ this.report(stmt.iterable.span, 'Iterated tuple positions must have the same type; destructure heterogeneous tuples', 'ITERATION');
1315
+ const inside = this.cloneContext(context);
1316
+ inside.loopDepth = (context.loopDepth ?? 0) + 1;
1317
+ this.patternLocals(stmt.names, type, context.flow.field(this.placesOf(stmt.iterable, context)), inside, stmt.span, sourceName(stmt.iterable));
1318
+ let previous = '';
1319
+ for (let count = 0; count <= context.locals.size * 3 + 3; count++) {
1320
+ this.checkStatements(stmt.body, inside, stmt.span);
1321
+ const signature = JSON.stringify([...inside.locals].map(([name, local]) => [name, local.moved])) + inside.flow.signature();
1322
+ if (signature === previous || !canFallThrough(stmt.body))
1323
+ break;
1324
+ previous = signature;
1325
+ }
1326
+ this.mergeMoved(context, [inside]);
1327
+ return;
1328
+ }
1329
+ if (stmt.kind === 'match') {
1330
+ const value = this.checkExpression(stmt.value, context);
1331
+ const seen = new Set();
1332
+ const branches = [];
1333
+ for (const clause of stmt.cases) {
1334
+ const inside = this.cloneContext(context);
1335
+ const key = clause.pattern === 'literal' ? JSON.stringify(clause.literal?.kind === 'literal' ? clause.literal.value : '?') :
1336
+ clause.pattern === 'type' ? typeName(clause.type) : clause.pattern;
1337
+ if (seen.has(key) || seen.has('else') || seen.has('some') && clause.pattern !== 'null')
1338
+ this.report(clause.span, 'Unreachable or repeated match case', 'MATCH');
1339
+ seen.add(key);
1340
+ let narrowed = value;
1341
+ if (clause.pattern === 'null' || clause.pattern === 'some') {
1342
+ if (clause.pattern === 'null' ? !value.nullable : !value.nullable && !value.optional)
1343
+ this.report(clause.span, 'null/some patterns require a nullable or optional value', 'MATCH');
1344
+ narrowed = clause.pattern === 'some' ? { ...value, nullable: false, optional: false } : nullTy;
1345
+ }
1346
+ else if (clause.pattern === 'literal') {
1347
+ const actual = this.checkExpression(clause.literal, context);
1348
+ if (!this.assignable(actual, value))
1349
+ this.report(clause.span, 'Match literal has an incompatible type', 'MATCH');
1350
+ }
1351
+ else if (clause.pattern === 'type') {
1352
+ narrowed = this.resolveType(clause.type, context.file, context.types);
1353
+ if (narrowed.kind !== 'class' || narrowed.args.length || !this.assignable(narrowed, { ...value, nullable: true }))
1354
+ this.report(clause.span, 'Type patterns require a concrete non-generic class compatible with the matched value', 'MATCH');
1355
+ }
1356
+ if (stmt.value.kind === 'name' && clause.pattern === 'some') {
1357
+ const local = inside.locals.get(stmt.value.name);
1358
+ if (local)
1359
+ local.type = narrowed;
1360
+ }
1361
+ if (clause.name)
1362
+ this.patternLocals([clause.name], narrowed, this.placesOf(stmt.value, context), inside, clause.span, sourceName(stmt.value));
1363
+ this.checkStatements(clause.body, inside, clause.span);
1364
+ if (canFallThrough(clause.body))
1365
+ branches.push(inside);
1366
+ }
1367
+ const exhaustive = seen.has('else') || (value.nullable || value.optional) && seen.has('null') && seen.has('some') ||
1368
+ !value.nullable && !value.optional && value.id === 'builtin:bool' && seen.has('true') && seen.has('false');
1369
+ if (!exhaustive)
1370
+ this.report(stmt.span, 'Match is incomplete; cover both booleans, null and some, or add else', 'MATCH');
1371
+ this.mergeMoved(context, branches);
1372
+ return;
1373
+ }
1374
+ if (stmt.kind === 'assign') {
1375
+ if (stmt.target.kind === 'name' && stmt.target.name === 'next')
1376
+ this.report(stmt.target.span, 'next is reserved for interceptor continuations', 'NEXT');
1377
+ const expected = stmt.declaredType ? this.resolveType(stmt.declaredType, context.file, context.types) :
1378
+ stmt.target.kind === 'name' ? context.locals.get(stmt.target.name)?.declaredType ?? context.locals.get(stmt.target.name)?.type : undefined;
1379
+ const value = this.checkExpression(stmt.value, context, expected);
1380
+ const source = stmt.value.kind === 'name' ? context.locals.get(stmt.value.name) : undefined;
1381
+ const origins = this.placesOf(stmt.value, context);
1382
+ const ownedSource = this.ownershipOf(stmt.value, context) === 'own';
1383
+ if (ownedSource && stmt.ownership !== 'own' && stmt.target.kind === 'name' && !context.locals.has(stmt.target.name))
1384
+ this.report(stmt.value.span, 'An owned object cannot be copied into a managed variable', 'OWN');
1385
+ if (stmt.target.kind === 'name') {
1386
+ const existing = context.locals.get(stmt.target.name);
1387
+ if (stmt.declaredType) {
1388
+ if (existing)
1389
+ this.report(stmt.target.span, `Variable ${stmt.target.name} already exists`);
1390
+ const target = expected;
1391
+ if (!this.assignable(value, target))
1392
+ this.report(stmt.value.span, `Cannot assign ${tyName(value)} to ${tyName(target)}`);
1393
+ if (stmt.ownership === 'own') {
1394
+ if (stmt.value.kind === 'member')
1395
+ this.report(stmt.value.span, 'Moving an owned field requires an explicit take operation', 'OWN');
1396
+ if (!['own', 'fresh'].includes(this.ownershipOf(stmt.value, context)))
1397
+ this.report(stmt.value.span, 'own value must come from a new object or another owned value', 'OWN');
1398
+ if (source?.origin === 'field')
1399
+ this.report(stmt.value.span, 'Moving an owned field requires an explicit take operation', 'OWN');
1400
+ if (source?.ownership === 'own' && stmt.value.kind === 'name') {
1401
+ this.moveOwnedLocal(stmt.value.name, stmt.value.span, context);
1402
+ }
1403
+ }
1404
+ else if (source?.ownership === 'own')
1405
+ this.report(stmt.value.span, `Copying owned value ${stmt.value.kind === 'name' ? stmt.value.name : ''} is forbidden`, 'OWN');
1406
+ context.locals.set(stmt.target.name, { type: { ...target, readonly: value.readonly, frozen: value.frozen }, declaredType: target, ownership: stmt.ownership,
1407
+ moved: false, origin: stmt.value.kind === 'name' ? stmt.value.name : undefined, definition: stmt.target.span });
1408
+ context.flow.declare(stmt.target.name, origins, !!value.readonly, { source: sourceName(stmt.value) });
1409
+ }
1410
+ else if (existing) {
1411
+ if (existing.ownership === 'own')
1412
+ this.report(stmt.target.span, `Reassigning owned value ${stmt.target.name} is not supported; use a new scope`, 'OWN');
1413
+ if (source?.ownership === 'own')
1414
+ this.report(stmt.value.span, 'Cannot copy an owned value into a managed variable', 'OWN');
1415
+ if (existing.origin === 'field' && existing.ownership !== 'own' &&
1416
+ !context.initializing && !context.borrowed.has('self') && context.locals.get('self')?.ownership !== 'borrow')
1417
+ this.report(stmt.target.span, `Mutating field ${stmt.target.name} requires borrow self`, 'BORROW');
1418
+ if (existing.origin === 'field' && context.flow.hasTaskCapture(unionOrigins(context.flow.origins('self'), context.flow.origins(stmt.target.name))))
1419
+ this.report(stmt.target.span, `Cannot mutate ${stmt.target.name} while a task uses it; wait for the task first`, 'CONCURRENCY');
1420
+ if (existing.origin === 'field' && !context.initializing) {
1421
+ const name = stmt.target.name;
1422
+ const storage = context.owner && 'fields' in context.owner.node ? fieldsOf(context.owner.node).find(field => field.name === name) : undefined;
1423
+ if (!storage?.mutable)
1424
+ this.report(stmt.target.span, `Field ${name} is read-only; declare mutable storage`, 'MUTABILITY');
1425
+ if (storage?.mutable && this.isReference(value) && value.readonly)
1426
+ this.report(stmt.value.span, 'Read-only references cannot become mutable storage', 'MUTABILITY');
1427
+ context.flow.escape(origins, stmt.value.span, (span, message) => this.report(span, message, 'BORROW'));
1428
+ this.requireChange(stmt.target, stmt.span, context);
1429
+ }
1430
+ if (existing.origin === 'field' && context.initializing && context.owner?.node.kind === 'class' && context.owner.node.record)
1431
+ this.report(stmt.span, 'Record validation cannot replace fields; transform inputs in a separate factory', 'RECORD');
1432
+ const declared = existing.declaredType ?? existing.type;
1433
+ if (!this.assignable(value, declared))
1434
+ this.report(stmt.value.span, `Cannot assign ${tyName(value)} to ${tyName(declared)}`);
1435
+ if (existing.origin !== 'field')
1436
+ existing.type = { ...declared, nullable: value.nullable, optional: value.optional, readonly: value.readonly, frozen: value.frozen };
1437
+ context.flow.rebind(stmt.target.name, origins, !!value.readonly, stmt.span, (span, message) => this.report(span, message, 'BORROW'), sourceName(stmt.value));
1438
+ existing.moved = false;
1439
+ }
1440
+ else {
1441
+ if (source?.ownership === 'own')
1442
+ this.report(stmt.value.span, 'Cannot copy an owned value into a managed variable', 'OWN');
1443
+ context.locals.set(stmt.target.name, { type: value, declaredType: value, ownership: 'managed', moved: false,
1444
+ origin: stmt.value.kind === 'name' ? stmt.value.name : undefined, definition: stmt.target.span });
1445
+ context.flow.declare(stmt.target.name, origins, !!value.readonly, { source: sourceName(stmt.value) });
1446
+ }
1447
+ }
1448
+ else if (stmt.target.kind === 'member') {
1449
+ const object = this.checkExpression(stmt.target.object, context);
1450
+ if (context.flow.hasTaskCapture(this.placesOf(stmt.target.object, context)))
1451
+ this.report(stmt.target.span, 'Cannot mutate a value while a task uses it; wait for the task first', 'CONCURRENCY');
1452
+ const target = this.memberType(object, stmt.target.name, stmt.target.span, context);
1453
+ if (!this.assignable(value, target))
1454
+ this.report(stmt.value.span, `Cannot assign ${tyName(value)} to ${tyName(target)}`);
1455
+ const root = this.rootName(stmt.target.object);
1456
+ if (context.initializing && context.owner?.node.kind === 'class' && context.owner.node.record)
1457
+ this.report(stmt.span, 'Record validation cannot replace fields; transform inputs in a separate factory', 'RECORD');
1458
+ if (root && !(context.initializing && root === 'self') &&
1459
+ !context.borrowed.has(root) && !['own', 'borrow'].includes(context.locals.get(root)?.ownership ?? 'managed'))
1460
+ this.report(stmt.target.span, `Mutating ${root} requires borrow ${root}`, 'BORROW');
1461
+ const node = object.def?.node;
1462
+ const fieldName = stmt.target.name;
1463
+ const field = node?.kind === 'class' || node?.kind === 'interceptor' ? fieldsOf(node).find(item => item.name === fieldName) : undefined;
1464
+ if (!(context.initializing && root === 'self') && !field?.mutable)
1465
+ this.report(stmt.target.span, `Field ${fieldName} is read-only; declare mutable storage`, 'MUTABILITY');
1466
+ if (object.readonly && !(context.initializing && root === 'self'))
1467
+ this.report(stmt.target.span, 'A read-only input cannot provide mutable access', 'MUTABILITY');
1468
+ if (field?.mutable && this.isReference(value) && value.readonly)
1469
+ this.report(stmt.value.span, 'Read-only references cannot become mutable storage', 'MUTABILITY');
1470
+ context.flow.escape(origins, stmt.value.span, (span, message) => this.report(span, message, 'BORROW'));
1471
+ this.requireChange(stmt.target.object, stmt.span, context);
1472
+ if (field?.ownership === 'own') {
1473
+ if (source?.ownership === 'own' && stmt.value.kind === 'name')
1474
+ this.moveOwnedLocal(stmt.value.name, stmt.value.span, context);
1475
+ else if (!['own', 'fresh'].includes(this.ownershipOf(stmt.value, context)))
1476
+ this.report(stmt.value.span, 'An owned field requires a new object or another owned value', 'OWN');
1477
+ }
1478
+ else if (source?.ownership === 'own')
1479
+ this.report(stmt.value.span, 'Cannot copy an owned value into a managed field', 'OWN');
1480
+ }
1481
+ else
1482
+ this.report(stmt.target.span, 'Assignment target must be a variable or field');
1483
+ return;
1484
+ }
1485
+ if (stmt.kind === 'yield') {
1486
+ if (!context.stream)
1487
+ this.report(stmt.span, 'yield belongs in a streaming endpoint', 'HTTP');
1488
+ if (context.locked)
1489
+ this.report(stmt.span, 'Release the lock before yielding a stream item', 'CONCURRENCY');
1490
+ const type = this.checkExpression(stmt.value, context, context.stream);
1491
+ if (context.stream && !this.assignable(type, context.stream))
1492
+ this.report(stmt.span, `Expected stream item ${tyName(context.stream)}, got ${tyName(type)}`, 'TYPE');
1493
+ this.checkAllowedError(builtin('HttpError'), stmt.span, context);
1494
+ return;
1495
+ }
1496
+ if (stmt.kind === 'return') {
1497
+ const type = stmt.value ? this.checkExpression(stmt.value, context, context.returns) : builtin('void');
1498
+ const declaredErrors = context.callable?.throws.map(type => this.resolveType(type, context.file, context.types)) ?? [];
1499
+ for (const error of context.flow.pendingTaskErrors())
1500
+ this.checkAllowedError(error, stmt.span, { ...context, allowedErrors: declaredErrors, exceptionalFlows: undefined });
1501
+ if (!this.assignable(type, context.returns))
1502
+ this.report(stmt.span, `Expected return ${tyName(context.returns)}, got ${tyName(type)}`);
1503
+ const ownership = stmt.value ? this.ownershipOf(stmt.value, context) : 'managed';
1504
+ if (stmt.value && this.isReference(type))
1505
+ context.flow.escape(this.placesOf(stmt.value, context), stmt.span, (span, message) => this.report(span, message, 'BORROW'), true, this.immutableData(type) && (!!type.frozen || type.def?.node.kind === 'class' && !!type.def.node.record || ['Bytes', 'Json', 'RsaPrivateKey', 'RsaPublicKey'].includes(type.name)));
1506
+ if (stmt.value?.kind === 'member' && ownership === 'own')
1507
+ this.report(stmt.span, 'Moving an owned field requires an explicit take operation', 'OWN');
1508
+ if (context.returnOwnership === 'own' && !['own', 'fresh'].includes(ownership))
1509
+ this.report(stmt.span, 'Owned return requires a new or owned value', 'OWN');
1510
+ if (context.returnOwnership === 'managed' && ownership === 'own')
1511
+ this.report(stmt.span, 'Cannot return an owned value as managed', 'OWN');
1512
+ if (stmt.value?.kind === 'name') {
1513
+ const local = context.locals.get(stmt.value.name);
1514
+ if (local?.origin === 'field' && local.ownership === 'own')
1515
+ this.report(stmt.span, 'Moving an owned field requires an explicit take operation', 'OWN');
1516
+ if (context.returnOwnership === 'own' && local?.ownership === 'own') {
1517
+ this.moveOwnedLocal(stmt.value.name, stmt.span, context);
1518
+ }
1519
+ }
1520
+ return;
1521
+ }
1522
+ if (stmt.kind === 'throw') {
1523
+ const type = this.checkExpression(stmt.value, context);
1524
+ if (!this.implementsError(type))
1525
+ this.report(stmt.value.span, `Cannot throw ${tyName(type)} because it does not implement Error`);
1526
+ this.checkAllowedError(type, stmt.span, context);
1527
+ if (stmt.value.kind === 'name')
1528
+ this.moveOwnedLocal(stmt.value.name, stmt.value.span, context);
1529
+ return;
1530
+ }
1531
+ if (stmt.kind === 'if' || stmt.kind === 'while') {
1532
+ const test = this.checkExpression(stmt.test, context);
1533
+ if (!this.assignable(test, builtin('bool')))
1534
+ this.report(stmt.test.span, `Condition must be bool, got ${tyName(test)}`);
1535
+ const bodyContext = this.cloneContext(context);
1536
+ if (stmt.kind === 'while')
1537
+ bodyContext.loopDepth = (context.loopDepth ?? 0) + 1;
1538
+ this.narrow(stmt.test, true, bodyContext);
1539
+ this.checkStatements(stmt.kind === 'if' ? stmt.then : stmt.body, bodyContext, stmt.span);
1540
+ if (stmt.kind === 'if') {
1541
+ const elseContext = this.cloneContext(context);
1542
+ this.narrow(stmt.test, false, elseContext);
1543
+ this.checkStatements(stmt.otherwise, elseContext, stmt.span);
1544
+ this.mergeMoved(context, [
1545
+ ...(canFallThrough(stmt.then) ? [bodyContext] : []),
1546
+ ...(canFallThrough(stmt.otherwise) ? [elseContext] : []),
1547
+ ]);
1548
+ if (!canFallThrough(stmt.then))
1549
+ this.narrow(stmt.test, false, context);
1550
+ if (stmt.otherwise.length && !canFallThrough(stmt.otherwise))
1551
+ this.narrow(stmt.test, true, context);
1552
+ }
1553
+ else {
1554
+ if (canFallThrough(stmt.body)) {
1555
+ let previous = '';
1556
+ let iterations = 0;
1557
+ const limit = context.locals.size * 3 + 3;
1558
+ while (iterations++ < limit) {
1559
+ this.mergeMoved(context, [bodyContext]);
1560
+ const state = context.flow.signature() + JSON.stringify([...context.locals].map(([name, local]) => [name, local.moved, local.type.nullable]));
1561
+ if (state === previous)
1562
+ break;
1563
+ previous = state;
1564
+ const iteration = this.cloneContext(context);
1565
+ iteration.loopDepth = (context.loopDepth ?? 0) + 1;
1566
+ this.narrow(stmt.test, true, iteration);
1567
+ this.checkExpression(stmt.test, iteration);
1568
+ this.checkStatements(stmt.body, iteration, stmt.span);
1569
+ this.mergeMoved(bodyContext, [iteration]);
1570
+ }
1571
+ }
1572
+ this.mergeMoved(context, [bodyContext]);
1573
+ }
1574
+ return;
1575
+ }
1576
+ if (stmt.kind === 'try') {
1577
+ const catches = stmt.catches.map(entry => this.resolveType(entry.type, context.file, context.types));
1578
+ const inside = this.cloneContext(context);
1579
+ inside.allowedErrors = [...inside.allowedErrors, ...catches];
1580
+ inside.exceptionalFlows = [];
1581
+ this.checkStatements(stmt.body, inside, stmt.span);
1582
+ const branches = [inside];
1583
+ for (let i = 0; i < stmt.catches.length; i++) {
1584
+ const clause = stmt.catches[i];
1585
+ if (!this.implementsError(catches[i]))
1586
+ this.report(clause.type.span, `Catch type ${clause.type.name} must implement Error`);
1587
+ const catchContext = this.cloneContext(context);
1588
+ const failures = inside.exceptionalFlows.filter(failure => this.assignable(failure.type, catches[i]));
1589
+ if (failures.length)
1590
+ catchContext.flow.join(failures.map(failure => failure.flow));
1591
+ catchContext.locals.set(clause.name, { type: catches[i], ownership: 'managed', moved: false });
1592
+ catchContext.flow.declare(clause.name, allocationOrigin(clause.span), false);
1593
+ this.checkStatements(clause.body, catchContext, clause.span);
1594
+ branches.push(catchContext);
1595
+ }
1596
+ context.exceptionalFlows?.push(...inside.exceptionalFlows.filter(failure => !catches.some(type => this.assignable(failure.type, type))));
1597
+ this.mergeMoved(context, branches);
1598
+ if (stmt.always) {
1599
+ const cleanup = this.cloneContext(context);
1600
+ this.checkStatements(stmt.always, cleanup, stmt.span);
1601
+ const exits = (body) => body.some(statement => statement.kind === 'return' || statement.kind === 'serve' ||
1602
+ ('body' in statement && exits(statement.body)) || statement.kind === 'if' && (exits(statement.then) || exits(statement.otherwise)));
1603
+ const starts = (value) => !!value && typeof value === 'object' &&
1604
+ (Array.isArray(value) ? value.some(starts) : value.kind === 'start' || Object.entries(value).some(([key, child]) => key !== 'span' && starts(child)));
1605
+ if (exits(stmt.always) || starts(stmt.always))
1606
+ this.report(stmt.span, 'always cleanup cannot return, start tasks, or start a server', 'CONCURRENCY');
1607
+ this.mergeMoved(context, [cleanup]);
1608
+ }
1609
+ return;
1610
+ }
1611
+ if (stmt.kind === 'unsafe') {
1612
+ const inside = this.cloneContext(context);
1613
+ inside.unsafe = true;
1614
+ this.checkStatements(stmt.body, inside, stmt.span);
1615
+ this.mergeMoved(context, [inside]);
1616
+ return;
1617
+ }
1618
+ if (stmt.kind === 'borrow') {
1619
+ const local = context.locals.get(stmt.name);
1620
+ if (!local)
1621
+ this.report(stmt.span, `Cannot borrow unknown variable ${stmt.name}`, 'BORROW');
1622
+ if (local?.type.readonly)
1623
+ this.report(stmt.span, `Cannot borrow read-only ${stmt.name} for mutation`, 'MUTABILITY');
1624
+ const inside = this.cloneContext(context);
1625
+ inside.flow.borrow(stmt.name, stmt.span, (span, message) => this.report(span, message, 'BORROW'));
1626
+ inside.borrowed.add(stmt.name);
1627
+ this.checkStatements(stmt.body, inside, stmt.span);
1628
+ this.mergeMoved(context, [inside]);
1629
+ }
1630
+ }
1631
+ rootName(expr) {
1632
+ return expr.kind === 'name' ? expr.name : expr.kind === 'member' ? this.rootName(expr.object) : undefined;
1633
+ }
1634
+ narrow(expr, truth, context) {
1635
+ if (expr.kind === 'unary' && expr.op === '!') {
1636
+ this.narrow(expr.value, !truth, context);
1637
+ return;
1638
+ }
1639
+ if (expr.kind !== 'binary')
1640
+ return;
1641
+ if (expr.op === '&&' && truth || expr.op === '||' && !truth) {
1642
+ this.narrow(expr.left, truth, context);
1643
+ this.narrow(expr.right, truth, context);
1644
+ return;
1645
+ }
1646
+ if (expr.op !== '==' && expr.op !== '!=')
1647
+ return;
1648
+ const name = expr.left.kind === 'name' && expr.right.kind === 'literal' && expr.right.value === null ? expr.left.name :
1649
+ expr.right.kind === 'name' && expr.left.kind === 'literal' && expr.left.value === null ? expr.right.name : undefined;
1650
+ if (name && (expr.op === '!=' ? truth : !truth)) {
1651
+ const local = context.locals.get(name);
1652
+ if (local && local.origin !== 'field')
1653
+ local.type = { ...local.type, nullable: false, optional: false };
1654
+ }
1655
+ }
1656
+ aliasRoot(name, locals) {
1657
+ const seen = new Set();
1658
+ while (locals.get(name)?.origin && !seen.has(name)) {
1659
+ seen.add(name);
1660
+ name = locals.get(name).origin;
1661
+ }
1662
+ return name;
1663
+ }
1664
+ ownershipOf(expr, context) {
1665
+ if (expr.kind === 'name')
1666
+ return context.locals.get(expr.name)?.ownership === 'own' ? 'own' : 'managed';
1667
+ if (expr.kind === 'collection')
1668
+ return 'fresh';
1669
+ if (expr.kind === 'member') {
1670
+ const type = this.expressionTypes.get(expr.object);
1671
+ const node = type?.def?.node;
1672
+ if (node && 'fields' in node && fieldsOf(node).find(field => field.name === expr.name)?.ownership === 'own')
1673
+ return 'own';
1674
+ }
1675
+ if (expr.kind !== 'call')
1676
+ return 'managed';
1677
+ if (expr.callee.kind === 'name') {
1678
+ if (['List', 'Map', 'Set', 'Tuple', 'Shared'].includes(expr.callee.name))
1679
+ return 'fresh';
1680
+ if (expr.callee.name === 'next' && context.next)
1681
+ return context.next.returnOwnership === 'own' ? 'own' : 'managed';
1682
+ const def = this.project.scopes.get(context.file)?.get(expr.callee.name);
1683
+ if (def?.node.kind === 'class')
1684
+ return this.constructorIsFresh(def.node) ? 'fresh' : 'managed';
1685
+ if (def?.node.kind === 'function' && def.node.returnOwnership === 'own')
1686
+ return 'own';
1687
+ if (def?.node.kind === 'function' && this.functionIsFresh(def.node, def.file))
1688
+ return 'fresh';
1689
+ }
1690
+ if (expr.callee.kind === 'member') {
1691
+ const receiver = this.expressionTypes.get(expr.callee.object);
1692
+ const methodName = expr.callee.name;
1693
+ const node = receiver?.def?.node;
1694
+ const method = node?.kind === 'class' || node?.kind === 'interceptor' ?
1695
+ node.methods.find(item => item.name === methodName) ??
1696
+ this.defaults.get(receiver.def.id)?.get(methodName)?.method :
1697
+ node?.kind === 'interface' ?
1698
+ this.interfaceMethods(receiver, new Set()).get(methodName)?.[0]?.method : undefined;
1699
+ if (method?.returnOwnership === 'own')
1700
+ return 'own';
1701
+ if (method && this.functionIsFresh(method, method.span.file, node && 'fields' in node ? new Set(fieldsOf(node).map(field => field.name)) : new Set()))
1702
+ return 'fresh';
1703
+ }
1704
+ return 'managed';
1705
+ }
1706
+ functionIsFresh(fn, file, fields = new Set(), seen = new Set()) {
1707
+ if (!fn.body || seen.has(fn))
1708
+ return false;
1709
+ seen.add(fn);
1710
+ const bodyFresh = returnsFresh(fn.body, fields, expr => {
1711
+ if (expr.kind === 'collection')
1712
+ return true;
1713
+ if (expr.kind !== 'call' || expr.callee.kind !== 'name')
1714
+ return false;
1715
+ if (['List', 'Set', 'Map', 'Tuple', 'arguments'].includes(expr.callee.name))
1716
+ return true;
1717
+ const def = this.project.scopes.get(file)?.get(expr.callee.name);
1718
+ return def?.node.kind === 'class' ? this.constructorIsFresh(def.node) :
1719
+ def?.node.kind === 'function' ? def.node.returnOwnership === 'own' || this.functionIsFresh(def.node, def.file, new Set(), seen) : false;
1720
+ });
1721
+ return bodyFresh && (this.interceptorPlans.get(fn) ?? []).every(layer => returnsFresh(layer.around.body ?? [], new Set(layer.definition.node.kind === 'interceptor' ? layer.definition.node.fields.map(field => field.name) : []), expr => expr.kind === 'call' && expr.callee.kind === 'name' && expr.callee.name === 'next'));
1722
+ }
1723
+ isReference(type) {
1724
+ return !['builtin:int', 'builtin:c_int', 'builtin:float', 'builtin:bool', 'builtin:string', 'builtin:void', 'null', 'error'].includes(type.id);
1725
+ }
1726
+ placesOf(expr, context) {
1727
+ if (!this.isReference(this.expressionTypes.get(expr) ?? errorTy))
1728
+ return new Set();
1729
+ if (expr.kind === 'name')
1730
+ return context.flow.origins(expr.name);
1731
+ if (expr.kind === 'start') {
1732
+ const origins = allocationOrigin(expr.span);
1733
+ context.flow.object(origins, [{ name: 'scope', origins: new Set([`tasks:${context.scope?.file}:${context.scope?.start}`]), mutable: false },
1734
+ { name: 'captures', origins: this.placesOf(expr.call, context), mutable: false }]);
1735
+ return origins;
1736
+ }
1737
+ if (expr.kind === 'resolve')
1738
+ return this.bindingOrigins(this.bindingKey(expr.name, expr.typeArgs), context, expr.span);
1739
+ if (expr.kind === 'member')
1740
+ return context.flow.field(this.placesOf(expr.object, context), expr.name);
1741
+ if (expr.kind === 'collection') {
1742
+ const origins = allocationOrigin(expr.span);
1743
+ context.flow.object(origins, expr.items.map((item, index) => ({ name: String(index), origins: this.placesOf(item, context), mutable: expr.collection !== 'Tuple' })));
1744
+ return origins;
1745
+ }
1746
+ if (expr.kind === 'call') {
1747
+ const def = expr.callee.kind === 'name' ? this.project.scopes.get(context.file)?.get(expr.callee.name) : undefined;
1748
+ if (def?.node.kind === 'class') {
1749
+ const plan = this.callPlans.get(expr);
1750
+ const fields = def.node.fields.map((field, index) => {
1751
+ const source = plan?.sourceIndices[index];
1752
+ let origins = new Set();
1753
+ if (source !== undefined)
1754
+ origins = this.placesOf(expr.args[source], context);
1755
+ const local = plan?.injectionSources?.[index];
1756
+ const key = plan?.bindingKeys[index];
1757
+ if (source === undefined)
1758
+ origins = local ? context.flow.origins(local.startsWith('self.') ? local.slice(5) : local) :
1759
+ key ? this.bindingOrigins(key, context, expr.span) : new Set();
1760
+ return { name: field.name, origins, mutable: !!field.mutable || field.ownership === 'own' };
1761
+ });
1762
+ const origins = allocationOrigin(expr.span);
1763
+ context.flow.object(origins, fields);
1764
+ return origins;
1765
+ }
1766
+ if (expr.callee.kind === 'member') {
1767
+ const receiver = this.expressionTypes.get(expr.callee.object);
1768
+ const method = expr.callee.name;
1769
+ if (['builtin:List', 'builtin:Tuple', 'builtin:Map'].includes(receiver?.id ?? '') &&
1770
+ (method === 'get' || receiver?.id === 'builtin:List' && method === 'at')) {
1771
+ const index = expr.args[0]?.kind === 'literal' && typeof expr.args[0].value === 'number' ? String(expr.args[0].value) : undefined;
1772
+ return context.flow.field(this.placesOf(expr.callee.object, context), receiver?.id === 'builtin:Tuple' ? index : undefined);
1773
+ }
1774
+ }
1775
+ const plan = this.callPlans.get(expr);
1776
+ const injected = (plan?.bindingKeys ?? []).map((key, index) => {
1777
+ const local = plan?.injectionSources?.[index];
1778
+ return local ? context.flow.origins(local.startsWith('self.') ? local.slice(5) : local) :
1779
+ key ? this.bindingOrigins(key, context, expr.span) : new Set();
1780
+ });
1781
+ const related = [...expr.args.map(arg => this.placesOf(arg, context)), ...injected,
1782
+ ...(expr.callee.kind === 'member' ? [this.placesOf(expr.callee.object, context)] : [])];
1783
+ const fresh = ['own', 'fresh'].includes(this.ownershipOf(expr, context));
1784
+ if (fresh) {
1785
+ const origins = allocationOrigin(expr.span);
1786
+ context.flow.object(origins, related.map((origins, index) => ({ name: `capture:${index}`, origins, mutable: true })));
1787
+ return origins;
1788
+ }
1789
+ return unionOrigins(...related);
1790
+ }
1791
+ return new Set();
1792
+ }
1793
+ bindingOrigins(key, context, span, seen = new Set()) {
1794
+ const binding = this.bindingByKey.get(key);
1795
+ const origins = new Set([binding?.lifetime === 'fresh' ? `fresh:${span.file}:${span.start}:${key}` :
1796
+ binding?.lifetime === 'scoped' ? `scope:${context.scope?.file}:${context.scope?.start}:${key}` : `binding:${key}`]);
1797
+ if (binding && !seen.has(key)) {
1798
+ seen.add(key);
1799
+ context.flow.object(origins, binding.constructorKeys.map((dependency, index) => ({ name: binding.target.node.fields[index].name,
1800
+ origins: this.bindingOrigins(dependency, context, span, seen), mutable: binding.stateful })));
1801
+ }
1802
+ return origins;
1803
+ }
1804
+ constructorIsFresh(node, visiting = new Set()) {
1805
+ const cached = this.constructorFreshness.get(node);
1806
+ if (cached !== undefined)
1807
+ return cached;
1808
+ if (visiting.has(node))
1809
+ return false;
1810
+ visiting.add(node);
1811
+ const result = (this.interceptorPlans.get(node) ?? []).every(layer => {
1812
+ const interceptor = layer.definition.node;
1813
+ return returnsFresh(layer.around.body ?? [], new Set(interceptor.fields.map(field => field.name)), expr => {
1814
+ if (expr.kind !== 'call' || expr.callee.kind !== 'name')
1815
+ return false;
1816
+ if (expr.callee.name === 'next')
1817
+ return true;
1818
+ const def = this.project.scopes.get(layer.definition.file)?.get(expr.callee.name);
1819
+ return def?.node.kind === 'class' && this.constructorIsFresh(def.node, visiting);
1820
+ });
1821
+ });
1822
+ visiting.delete(node);
1823
+ this.constructorFreshness.set(node, result);
1824
+ return result;
1825
+ }
1826
+ checkAllowedError(type, span, context) {
1827
+ if (context.deferredErrors) {
1828
+ context.deferredErrors.push(type);
1829
+ return;
1830
+ }
1831
+ context.exceptionalFlows?.push({ type, flow: context.flow.clone() });
1832
+ if (!context.allowedErrors.some(allowed => this.assignable(type, allowed)))
1833
+ this.report(span, `Unhandled ${tyName(type)}; catch it or declare unless ${tyName(type)}`, 'THROWS');
1834
+ }
1835
+ checkCollection(expr, context, expected) {
1836
+ const name = expr.collection === 'empty' ? expected?.name === 'Set' ? 'Set' : 'Map' : expr.collection;
1837
+ const target = expected?.id === `builtin:${name}` ? expected : undefined;
1838
+ const types = expr.items.map((item, index) => {
1839
+ const wanted = target?.args[name === 'Tuple' ? index : name === 'Map' ? index % 2 : 0];
1840
+ const actual = this.checkExpression(item, context, wanted);
1841
+ if (this.ownershipOf(item, context) === 'own')
1842
+ this.report(item.span, `${name} cannot copy an owned value`, 'OWN');
1843
+ if (this.isReference(actual))
1844
+ this.checkBorrowEscape(item, context, false);
1845
+ if (actual.id === 'builtin:void')
1846
+ this.report(item.span, 'Collections cannot contain void', 'COLLECTION');
1847
+ return actual;
1848
+ });
1849
+ if (name === 'Tuple') {
1850
+ if (target && target.args.length !== types.length)
1851
+ this.report(expr.span, `Tuple needs ${target.args.length} items, got ${types.length}`, 'COLLECTION');
1852
+ types.forEach((actual, index) => {
1853
+ if (target?.args[index] && !this.assignable(actual, target.args[index]))
1854
+ this.report(expr.items[index].span, `Tuple item ${index} expects ${tyName(target.args[index])}, got ${tyName(actual)}`, 'COLLECTION');
1855
+ });
1856
+ return { ...builtin(name), args: target?.args ?? types };
1857
+ }
1858
+ const infer = (indices, part, wanted) => {
1859
+ let element = wanted;
1860
+ for (const index of indices) {
1861
+ const actual = types[index];
1862
+ if (!element)
1863
+ element = actual;
1864
+ else if (!wanted) {
1865
+ if (actual.kind === 'null')
1866
+ element = { ...element, nullable: true };
1867
+ else if (element.kind === 'null')
1868
+ element = { ...actual, nullable: true };
1869
+ else if (['builtin:int', 'builtin:float'].includes(element.id) &&
1870
+ ['builtin:int', 'builtin:float'].includes(actual.id))
1871
+ element = {
1872
+ ...builtin(element.id === 'builtin:float' || actual.id === 'builtin:float' ? 'float' : 'int'),
1873
+ nullable: element.nullable || actual.nullable
1874
+ };
1875
+ else if (this.assignable(element, actual))
1876
+ element = actual;
1877
+ }
1878
+ }
1879
+ if (!element || element.kind === 'null') {
1880
+ this.report(expr.span, `${name} ${part} need a type; use a typed declaration such as ${name === 'Map' ? 'Map<int, string>' : `${name}<int>`}`, 'COLLECTION');
1881
+ return errorTy;
1882
+ }
1883
+ for (const index of indices)
1884
+ if (!this.assignable(types[index], element))
1885
+ this.report(expr.items[index].span, `${name} ${part} expect ${tyName(element)}, got ${tyName(types[index])}`, 'COLLECTION');
1886
+ return element;
1887
+ };
1888
+ const indices = types.map((_, index) => index);
1889
+ const args = name === 'Map' ? [infer(indices.filter(index => index % 2 === 0), 'keys', target?.args[0]),
1890
+ infer(indices.filter(index => index % 2 === 1), 'values', target?.args[1])] :
1891
+ [infer(indices, 'items', target?.args[0])];
1892
+ return { ...builtin(name), args };
1893
+ }
1894
+ checkExpression(expr, context, expected) {
1895
+ let type = errorTy;
1896
+ if (expr.kind === 'markupText')
1897
+ type = builtin('Html');
1898
+ else if (expr.kind === 'markup') {
1899
+ if (expr.tag && /^[A-Z]/.test(expr.tag)) {
1900
+ const args = expr.attributes.map(attribute => attribute.value), labels = expr.attributes.map(attribute => attribute.name);
1901
+ const definition = this.project.scopes.get(context.file)?.get(expr.tag);
1902
+ if (definition?.node.kind === 'function' && definition.node.params.some(param => param.name === 'children')) {
1903
+ labels.push('children');
1904
+ args.push({ kind: 'collection', collection: 'List', items: expr.children.map(child => ({ kind: 'markup', tag: '', attributes: [], children: [child], span: child.span })), span: expr.span });
1905
+ }
1906
+ else if (expr.children.length)
1907
+ this.report(expr.span, `${expr.tag} needs a List<Html> children parameter to accept children`, 'HTML');
1908
+ const call = { kind: 'call', callee: { kind: 'name', name: expr.tag, span: expr.span }, args, argLabels: labels, typeArgs: [], span: expr.span };
1909
+ this.markupCalls.set(expr, call);
1910
+ type = this.checkExpression(call, context);
1911
+ if (type.id !== 'builtin:Html')
1912
+ this.report(expr.span, `Server component ${expr.tag} returns Html`, 'HTML');
1913
+ }
1914
+ else {
1915
+ if (expr.tag && !htmlTags.has(expr.tag))
1916
+ this.report(expr.span, `Unknown or executable HTML element ${expr.tag}; use a server component or an endpoint action`, 'HTML');
1917
+ if (htmlVoidTags.has(expr.tag) && expr.children.length)
1918
+ this.report(expr.span, `<${expr.tag}> cannot have children`, 'HTML');
1919
+ const names = new Set();
1920
+ for (const attribute of expr.attributes) {
1921
+ if (names.has(attribute.name))
1922
+ this.report(attribute.span, `Duplicate HTML attribute ${attribute.name}`, 'HTML');
1923
+ names.add(attribute.name);
1924
+ const wanted = htmlAttribute(expr.tag, attribute.name), actual = this.checkExpression(attribute.value, context);
1925
+ if (!wanted)
1926
+ this.report(attribute.span, `<${expr.tag}> has no supported attribute ${attribute.name}`, 'HTML');
1927
+ else if (actual.name !== wanted && !(wanted === 'string' && actual.name === 'int'))
1928
+ this.report(attribute.value.span, `${attribute.name} expects ${wanted}`, 'HTML');
1929
+ if (htmlUrlAttributes.has(attribute.name)) {
1930
+ if (attribute.value.kind !== 'literal')
1931
+ this.checkAllowedError(builtin('HttpError'), attribute.span, context);
1932
+ else if (typeof attribute.value.value === 'string' && /^\s*(javascript|data|vbscript):/i.test(attribute.value.value))
1933
+ this.report(attribute.value.span, 'Executable URL schemes are not HTML links', 'HTML');
1934
+ }
1935
+ }
1936
+ for (const child of expr.children) {
1937
+ const actual = this.checkExpression(child, context);
1938
+ if (!['Html', 'string', 'int', 'float', 'bool', 'null'].includes(actual.name) && !(actual.name === 'List' && actual.args[0]?.name === 'Html'))
1939
+ this.report(child.span, 'HTML children are Html, escaped text, numbers, or List<Html>', 'HTML');
1940
+ }
1941
+ type = builtin('Html');
1942
+ }
1943
+ }
1944
+ else if (expr.kind === 'handle') {
1945
+ type = this.checkHandle(expr, context);
1946
+ }
1947
+ else if (expr.kind === 'formInput') {
1948
+ this.report(expr.span, 'input from form belongs inside a handle endpoint(...) action', 'HTTP');
1949
+ }
1950
+ else if (expr.kind === 'start') {
1951
+ if (context.locked)
1952
+ this.report(expr.span, 'Release the lock before starting a task', 'CONCURRENCY');
1953
+ if (!context.scope)
1954
+ this.report(expr.span, 'Start a task inside a scope block, which joins every child before leaving', 'CONCURRENCY');
1955
+ if (expr.call.kind !== 'call')
1956
+ this.report(expr.call.span, 'start requires a labeled function or method call', 'CONCURRENCY');
1957
+ const errors = [];
1958
+ const result = this.checkExpression(expr.call, { ...context, deferredErrors: errors }, expected?.id === 'builtin:Task' ? expected.args[0] : undefined);
1959
+ type = { ...builtin('Task'), args: [result] };
1960
+ if (expr.call.kind === 'call' && context.scope) {
1961
+ const plan = this.callPlans.get(expr.call), task = [...allocationOrigin(expr.span)][0], scope = `tasks:${context.scope.file}:${context.scope.start}`;
1962
+ context.flow.registerTask(task, scope, errors);
1963
+ const captureOrigins = (origins, actual, exclusive, span) => {
1964
+ if (!this.isReference(actual))
1965
+ return;
1966
+ const shareable = actual.frozen || actual.name === 'Shared' ||
1967
+ actual.def?.node.kind === 'class' && actual.def.node.record ||
1968
+ ['Bytes', 'Json', 'Html', 'Headers', 'RsaPublicKey', 'RsaPrivateKey'].includes(actual.name);
1969
+ context.flow.captureTask(task, scope, origins, shareable ? false : exclusive, !!context.loopDepth, span, (at, message) => this.report(at, message, 'CONCURRENCY'));
1970
+ };
1971
+ const capture = (argument, exclusive) => captureOrigins(this.placesOf(argument, context), this.expressionTypes.get(argument) ?? errorTy, exclusive, argument.span);
1972
+ expr.call.args.forEach((argument, index) => capture(argument, ['own', 'borrow'].includes(plan?.ownerships?.[plan.sourceIndices.indexOf(index)] ?? '')));
1973
+ if (expr.call.callee.kind === 'member')
1974
+ capture(expr.call.callee.object, !!plan?.mutatesReceiver);
1975
+ plan?.bindingKeys.forEach((key, index) => {
1976
+ const source = plan.injectionSources?.[index];
1977
+ if (!key && !source)
1978
+ return;
1979
+ const name = source?.startsWith('self.') ? source.slice(5) : source;
1980
+ const actual = name ? context.locals.get(name)?.type : key ? this.bindingByKey.get(key)?.exposedType : undefined;
1981
+ const origins = name ? context.flow.origins(name) : key ? this.bindingOrigins(key, context, expr.call.span) : new Set();
1982
+ captureOrigins(origins, actual ?? errorTy, false, expr.call.span);
1983
+ });
1984
+ }
1985
+ }
1986
+ else if (expr.kind === 'wait') {
1987
+ if (context.locked)
1988
+ this.report(expr.span, 'Release the lock before waiting for a task', 'CONCURRENCY');
1989
+ const values = expr.tasks.map(task => this.checkExpression(task, context));
1990
+ const errors = expr.tasks.flatMap((task, index) => {
1991
+ const value = values[index], tracked = context.flow.taskErrors(this.placesOf(task, context));
1992
+ return tracked ?? (value.id === 'builtin:Task' || value.id === 'builtin:List' && value.args[0]?.id === 'builtin:Task' ? [builtin('Error')] : []);
1993
+ });
1994
+ expr.tasks.forEach((task, index) => context.flow.waitTasks(this.placesOf(task, context), values[index].id === 'builtin:List'));
1995
+ for (const error of errors)
1996
+ this.checkAllowedError(error, expr.span, context);
1997
+ const result = (value, index) => {
1998
+ if (value.id === 'builtin:Task')
1999
+ return value.args[0];
2000
+ this.report(expr.tasks[index].span, 'wait for requires a Task or a List<Task<T>>', 'CONCURRENCY');
2001
+ return errorTy;
2002
+ };
2003
+ type = values.length === 1 && values[0].id === 'builtin:List' ? { ...builtin('List'), args: [result(values[0].args[0], 0)] } :
2004
+ values.length === 1 ? result(values[0], 0) : { ...builtin('Tuple'), args: values.map(result) };
2005
+ }
2006
+ else if (expr.kind === 'literal') {
2007
+ if (expr.numericType === 'int' && expr.numericText && (BigInt(expr.numericText) < -(1n << 63n) || BigInt(expr.numericText) >= 1n << 63n))
2008
+ this.report(expr.span, 'int literal is outside the signed 64-bit range', 'NUMBER');
2009
+ if (typeof expr.value === 'number' && !Number.isFinite(expr.value))
2010
+ this.report(expr.span, 'Numeric literals must be finite', 'NUMBER');
2011
+ if (typeof expr.value === 'string' && (expr.value.includes('\0') || !expr.value.isWellFormed()))
2012
+ this.report(expr.span, 'Strings are valid Unicode without NUL; use an unsafe byte adapter for binary data', 'TEXT');
2013
+ type = expr.value === null ? nullTy : typeof expr.value === 'string' ? builtin('string') :
2014
+ typeof expr.value === 'boolean' ? builtin('bool') :
2015
+ builtin(expr.numericType ?? (Number.isInteger(expr.value) ? 'int' : 'float'));
2016
+ }
2017
+ else if (expr.kind === 'collection') {
2018
+ type = this.checkCollection(expr, context, expected);
2019
+ }
2020
+ else if (expr.kind === 'name') {
2021
+ const local = context.locals.get(expr.name);
2022
+ if (local) {
2023
+ if (local.moved)
2024
+ this.report(expr.span, `Cannot use moved value ${expr.name}`, 'OWN');
2025
+ context.flow.read(expr.name, expr.span, (span, message) => this.report(span, message, 'BORROW'));
2026
+ type = local.type;
2027
+ }
2028
+ else {
2029
+ const def = this.project.scopes.get(context.file)?.get(expr.name);
2030
+ if (def?.node.kind === 'class' || def?.node.kind === 'interface')
2031
+ type = { id: def.id, name: def.name, kind: def.node.kind, def, args: [], nullable: false };
2032
+ else if (def?.node.kind === 'function')
2033
+ type = builtin('void');
2034
+ else if (errorNames.includes(expr.name))
2035
+ type = builtin(expr.name);
2036
+ else if (['print', 'arguments', 'List', 'Map', 'Set', 'Tuple', 'assert', 'read_file', 'write_file', 'c_int', 'int'].includes(expr.name))
2037
+ type = builtin('void');
2038
+ else if (expr.name === 'next')
2039
+ this.report(expr.span, 'next is only callable inside an interceptor around body', 'NEXT');
2040
+ else if (def?.node.kind === 'interceptor')
2041
+ this.report(expr.span, `${expr.name} is an interceptor; apply it with [${expr.name}]`, 'INTERCEPTOR');
2042
+ else
2043
+ this.report(expr.span, `Unknown name ${expr.name}`, 'NAME');
2044
+ }
2045
+ }
2046
+ else if (expr.kind === 'resolve') {
2047
+ if (context.callable || context.composition === false)
2048
+ this.report(expr.span, 'Declare resolve dependencies in the callable header; body lookups belong only in main or test setup', 'DI');
2049
+ const key = this.bindingKey(expr.name, expr.typeArgs);
2050
+ const binding = this.bindingByKey.get(key);
2051
+ if (isPrivateName(expr.name) && context.file !== this.project.main?.path)
2052
+ this.report(expr.span, `Binding ${key} is private to main.aug`, 'PRIVATE');
2053
+ const definition = this.project.scopes.get(context.file)?.get(expr.name);
2054
+ const declared = definition?.node.kind === 'interface' ? this.resolveType({ name: expr.name, args: expr.typeArgs, nullable: false, span: expr.span }, context.file, context.types) : undefined;
2055
+ if (!binding) {
2056
+ if (declared && context.callable)
2057
+ type = declared;
2058
+ else
2059
+ this.report(expr.span, `No binding named ${key}`, 'DI');
2060
+ }
2061
+ else {
2062
+ if (binding.requiresScope && !context.scope)
2063
+ this.report(expr.span, `Resolve ${key} inside a scope block`, 'DI');
2064
+ if (declared && !this.assignable(binding.exposedType, declared))
2065
+ this.report(expr.span, `Binding ${key} has incompatible provenance for the imported ${expr.name} contract`, 'DI');
2066
+ type = binding.exposedType;
2067
+ }
2068
+ }
2069
+ else if (expr.kind === 'member') {
2070
+ const object = this.checkExpression(expr.object, context);
2071
+ type = this.memberType(object, expr.name, expr.span, context);
2072
+ }
2073
+ else if (expr.kind === 'unary') {
2074
+ const value = this.checkExpression(expr.value, context);
2075
+ if (expr.op === '!' && value.id !== 'builtin:bool')
2076
+ this.report(expr.span, '! requires bool');
2077
+ if (expr.op === '-' && !['builtin:int', 'builtin:c_int', 'builtin:float'].includes(value.id))
2078
+ this.report(expr.span, '- requires a number');
2079
+ type = expr.op === '!' ? builtin('bool') : value;
2080
+ }
2081
+ else if (expr.kind === 'binary') {
2082
+ const left = this.checkExpression(expr.left, context);
2083
+ const rightContext = expr.op === '&&' || expr.op === '||' ? this.cloneContext(context) : context;
2084
+ if (rightContext !== context)
2085
+ this.narrow(expr.left, expr.op === '&&', rightContext);
2086
+ const right = this.checkExpression(expr.right, rightContext);
2087
+ const numeric = ['builtin:int', 'builtin:c_int', 'builtin:float'];
2088
+ const bothNumeric = numeric.includes(left.id) && numeric.includes(right.id);
2089
+ if (expr.op === '/' && !(expr.right.kind === 'literal' && typeof expr.right.value === 'number' && expr.right.value !== 0))
2090
+ this.checkAllowedError(builtin('ArithmeticError'), expr.span, context);
2091
+ if (expr.op === '&&' || expr.op === '||') {
2092
+ if (left.id !== 'builtin:bool' || right.id !== 'builtin:bool')
2093
+ this.report(expr.span, `${expr.op} requires bool operands`);
2094
+ type = builtin('bool');
2095
+ }
2096
+ else if (expr.op === '==' || expr.op === '!=') {
2097
+ if (!bothNumeric && !this.assignable(left, right) && !this.assignable(right, left))
2098
+ this.report(expr.span, `Cannot compare ${tyName(left)} and ${tyName(right)}`);
2099
+ type = builtin('bool');
2100
+ }
2101
+ else if (['<', '>', '<=', '>='].includes(expr.op)) {
2102
+ if (!bothNumeric)
2103
+ this.report(expr.span, `${expr.op} requires numeric operands`);
2104
+ type = builtin('bool');
2105
+ }
2106
+ else if (expr.op === '+' && left.id === 'builtin:string' && right.id === 'builtin:string')
2107
+ type = builtin('string');
2108
+ else if (bothNumeric)
2109
+ type = left.id === 'builtin:float' || right.id === 'builtin:float' ? builtin('float') : builtin('int');
2110
+ else
2111
+ this.report(expr.span, `Operator ${expr.op} does not accept ${tyName(left)} and ${tyName(right)}`);
2112
+ }
2113
+ else if (expr.kind === 'call') {
2114
+ type = this.checkCall(expr, context);
2115
+ }
2116
+ this.expressionTypes.set(expr, type);
2117
+ this.expressionOrigins.set(expr, this.placesOf(expr, context));
2118
+ return type;
2119
+ }
2120
+ memberType(receiver, name, span, context) {
2121
+ if (receiver.kind === 'param') {
2122
+ const bound = receiver.bounds?.find(type => this.interfaceMethods(type, new Set()).has(name));
2123
+ if (bound)
2124
+ return this.memberType(bound, name, span, context);
2125
+ }
2126
+ if (receiver.nullable)
2127
+ this.report(span, `Cannot access ${name} on nullable ${tyName(receiver)}`);
2128
+ if (receiver.optional && !receiver.nullable)
2129
+ this.report(span, `Match null and some before accessing ${name} on ${tyName(receiver)}`);
2130
+ const property = receiver.kind === 'builtin' && builtinProperties[receiver.name]?.find(property => property.name === name);
2131
+ if (property)
2132
+ return { ...operationType(property.type, receiver), readonly: true };
2133
+ const def = receiver.def;
2134
+ if (def?.node.kind === 'class' || def?.node.kind === 'interceptor') {
2135
+ const field = fieldsOf(def.node).find(param => param.name === name);
2136
+ if (field) {
2137
+ this.checkMemberVisibility(def.id, def.name, name, span, context);
2138
+ return { ...this.resolveType(field.type, def.file, new Map(def.node.typeParams.map((param, i) => [param, receiver.args[i] ?? errorTy]))), readonly: receiver.readonly || !field.mutable,
2139
+ frozen: receiver.frozen || def.node.kind === 'class' && !!def.node.record };
2140
+ }
2141
+ const method = def.node.methods.find(item => item.name === name) ??
2142
+ this.defaults.get(def.id)?.get(name)?.method;
2143
+ if (method) {
2144
+ this.checkMemberVisibility(def.id, def.name, name, span, context);
2145
+ return this.resolveType(method.returns, def.file, new Map(def.node.typeParams.map((param, i) => [param, receiver.args[i] ?? errorTy])));
2146
+ }
2147
+ }
2148
+ if (def?.node.kind === 'interface') {
2149
+ const entry = this.interfaceMethods(receiver, new Set()).get(name)?.[0];
2150
+ if (entry) {
2151
+ this.checkMemberVisibility(entry.from, def.name, name, span, context);
2152
+ return this.resolveType(entry.method.returns, def.file);
2153
+ }
2154
+ }
2155
+ this.report(span, `${tyName(receiver)} has no member ${name}`);
2156
+ return errorTy;
2157
+ }
2158
+ planCall(expr, params, display) {
2159
+ const names = params.map(param => typeof param === 'string' ? param : param.label ?? param.name);
2160
+ const injected = params.map(param => typeof param !== 'string' && param.injected);
2161
+ const plan = { sourceIndices: names.map(() => undefined),
2162
+ bindingKeys: names.map(() => undefined), ownerships: params.map(param => typeof param === 'string' ? undefined : param.ownership) };
2163
+ for (let source = 0; source < expr.args.length; source++) {
2164
+ const argument = expr.args[source];
2165
+ const label = expr.argLabels[source] ?? (argument.kind === 'name' ? argument.name : undefined);
2166
+ if (!label) {
2167
+ this.report(expr.args[source].span, `${display} arguments require labels`, 'CALL');
2168
+ continue;
2169
+ }
2170
+ const index = names.indexOf(label);
2171
+ if (index < 0)
2172
+ this.report(expr.args[source].span, `${display} has no parameter ${label}`, 'CALL');
2173
+ else if (injected[index])
2174
+ this.report(expr.args[source].span, `${label} is resolved from DI and cannot be passed`, 'CALL');
2175
+ else if (plan.sourceIndices[index] !== undefined)
2176
+ this.report(expr.args[source].span, `Duplicate argument ${label}`, 'CALL');
2177
+ else
2178
+ plan.sourceIndices[index] = source;
2179
+ }
2180
+ for (let index = 0; index < names.length; index++)
2181
+ if (!injected[index] &&
2182
+ !(typeof params[index] !== 'string' && params[index].type.optional) && plan.sourceIndices[index] === undefined)
2183
+ this.report(expr.span, `Missing argument ${names[index]} for ${display}`, 'CALL');
2184
+ this.callPlans.set(expr, plan);
2185
+ return plan;
2186
+ }
2187
+ planInjections(expr, params, file, types, plan, context) {
2188
+ params.forEach((param, index) => {
2189
+ if (!param.injected)
2190
+ return;
2191
+ const type = this.resolveType(param.type, file, types);
2192
+ const key = this.bindingKeyForType(type);
2193
+ if (context.callable) {
2194
+ const candidates = [];
2195
+ for (const input of context.callable.params.filter(param => param.injected))
2196
+ candidates.push({ name: input.name, type: this.resolveType(input.type, context.file, context.types) });
2197
+ if (context.owner && 'fields' in context.owner.node)
2198
+ for (const field of context.owner.node.fields.filter(field => field.injected))
2199
+ candidates.push({ name: `self.${field.name}`, type: this.resolveType(field.type, context.owner.file, context.types) });
2200
+ const matches = candidates.filter(candidate => this.assignable(candidate.type, type));
2201
+ if (matches.length !== 1)
2202
+ this.report(expr.span, matches.length ?
2203
+ `Ambiguous header dependencies for ${tyName(type)}; pass an ordinary labeled input` :
2204
+ `Declare a resolve ${tyName(type)} dependency in ${context.callable.name}'s header`, 'DI');
2205
+ plan.injectionSources ??= params.map(() => undefined);
2206
+ plan.injectionSources[index] = matches[0]?.name;
2207
+ return;
2208
+ }
2209
+ const binding = this.bindingByKey.get(key);
2210
+ if (!binding)
2211
+ this.report(expr.span, `No binding for ${key}, required by ${param.name}`, 'DI');
2212
+ else if (binding.requiresScope && !context.scope)
2213
+ this.report(expr.span, `Resolve ${key} inside a scope block`, 'DI');
2214
+ else if (!this.assignable(binding.exposedType, type))
2215
+ this.report(expr.span, `Binding ${key} cannot provide ${tyName(type)}`, 'DI');
2216
+ plan.bindingKeys[index] = key;
2217
+ });
2218
+ }
2219
+ bindingKeyForType(type) {
2220
+ return type.name + (type.args.length ? `<${type.args.map(arg => this.bindingKeyForType(arg)).join(',')}>` : '');
2221
+ }
2222
+ argumentExpectations(expr, context, receiver) {
2223
+ let params;
2224
+ let file = context.file;
2225
+ let types = new Map(context.types);
2226
+ const uninferred = new Set();
2227
+ if (expr.callee.kind === 'name') {
2228
+ const name = expr.callee.name;
2229
+ if (['List', 'Set', 'Tuple'].includes(name))
2230
+ return expr.args.map((_, index) => {
2231
+ const ref = expr.typeArgs[name === 'Tuple' ? index : 0];
2232
+ return ref ? this.resolveType(ref, context.file, context.types) : undefined;
2233
+ });
2234
+ const def = this.project.scopes.get(file)?.get(name);
2235
+ if (def?.node.kind === 'class' || def?.node.kind === 'function') {
2236
+ params = def.node.kind === 'class' ? def.node.fields : def.node.params;
2237
+ types = this.paramsFor(def.node.typeParams);
2238
+ def.node.typeParams.forEach((name, index) => {
2239
+ if (expr.typeArgs[index])
2240
+ types.set(name, this.resolveType(expr.typeArgs[index], context.file, context.types));
2241
+ else {
2242
+ uninferred.add(name);
2243
+ types.delete(name);
2244
+ }
2245
+ });
2246
+ file = def.file;
2247
+ }
2248
+ else if (name === 'next' && context.next)
2249
+ return expr.argLabels.map(label => context.next.types[context.next.params.findIndex(param => param.name === label)]);
2250
+ }
2251
+ else if (expr.callee.kind === 'member' && receiver) {
2252
+ const methodName = expr.callee.name;
2253
+ if (['builtin:List', 'builtin:Set', 'builtin:Map'].includes(receiver.id))
2254
+ return expr.argLabels.map(label => receiver.name === 'List' && label === 'value' || receiver.name === 'Set' && label === 'value' ? receiver.args[0] :
2255
+ receiver.name === 'Map' ? receiver.args[label === 'key' ? 0 : 1] : undefined);
2256
+ const def = receiver.def;
2257
+ if (def && 'methods' in def.node) {
2258
+ const entry = def.node.kind === 'interface' ? this.interfaceMethods(receiver, new Set()).get(methodName)?.[0] :
2259
+ this.defaults.get(def.id)?.get(methodName);
2260
+ const method = def.node.methods.find(method => method.name === methodName) ?? entry?.method;
2261
+ if (method) {
2262
+ params = method.params;
2263
+ file = method.span.file;
2264
+ types = entry ? new Map(entry.params) : new Map(def.node.typeParams.map((name, index) => [name, receiver.args[index] ?? errorTy]));
2265
+ method.typeParams.forEach((name, index) => {
2266
+ if (expr.typeArgs[index])
2267
+ types.set(name, this.resolveType(expr.typeArgs[index], context.file, context.types));
2268
+ else {
2269
+ uninferred.add(name);
2270
+ types.delete(name);
2271
+ }
2272
+ });
2273
+ }
2274
+ }
2275
+ }
2276
+ // Infer from known siblings before supplying context to empty literals. This
2277
+ // does not evaluate an expression, move a value, or apply its effects.
2278
+ for (let index = 0; index < expr.args.length; index++) {
2279
+ const label = expr.argLabels[index] ?? (expr.args[index].kind === 'name' ? expr.args[index].name : undefined);
2280
+ const param = params?.find(param => (param.label ?? param.name) === label);
2281
+ const actual = this.previewType(expr.args[index], context);
2282
+ if (param && actual)
2283
+ this.inferCallType(param.type, actual, [...uninferred], types);
2284
+ }
2285
+ for (const name of [...uninferred])
2286
+ if (types.has(name))
2287
+ uninferred.delete(name);
2288
+ return expr.argLabels.map((given, index) => {
2289
+ const label = given ?? (expr.args[index].kind === 'name' ? expr.args[index].name : undefined);
2290
+ const param = params?.find(param => (param.label ?? param.name) === label);
2291
+ const needsInference = (ref) => uninferred.has(ref.name) || ref.args.some(needsInference);
2292
+ if (param && needsInference(param.type))
2293
+ return undefined;
2294
+ return param ? this.resolveType(param.type, file, types) : undefined;
2295
+ });
2296
+ }
2297
+ previewType(expr, context) {
2298
+ if (expr.kind === 'name')
2299
+ return context.locals.get(expr.name)?.type;
2300
+ if (expr.kind === 'literal')
2301
+ return expr.value === null ? undefined :
2302
+ builtin(typeof expr.value === 'string' ? 'string' : typeof expr.value === 'boolean' ? 'bool' : expr.numericType ?? 'int');
2303
+ if (expr.kind === 'collection' && expr.items.length) {
2304
+ const items = expr.items.map(item => this.previewType(item, context));
2305
+ if (items.some(item => !item))
2306
+ return undefined;
2307
+ const name = expr.collection === 'empty' ? 'Map' : expr.collection;
2308
+ const args = name === 'Tuple' ? items : name === 'Map' ? [items[0], items[1]] : [items[0]];
2309
+ return { ...builtin(name), args };
2310
+ }
2311
+ if (expr.kind === 'call' && expr.callee.kind === 'name') {
2312
+ const def = this.project.scopes.get(context.file)?.get(expr.callee.name);
2313
+ if (def?.node.kind === 'class' && expr.typeArgs.length === def.node.typeParams.length)
2314
+ return { id: def.id, name: def.name, kind: 'class', def, nullable: false,
2315
+ args: expr.typeArgs.map(ref => this.resolveType(ref, context.file, context.types)) };
2316
+ }
2317
+ return this.expressionTypes.get(expr);
2318
+ }
2319
+ inferCallType(ref, actual, names, types) {
2320
+ inferType(ref, actual, names, types, message => this.report(ref.span, message, 'GENERIC'), (name, value) => {
2321
+ const expected = this.project.scopes.get(ref.span.file)?.get(name);
2322
+ return expected?.node.kind === 'interface' ? this.interfaceView(value, expected.id) : value;
2323
+ });
2324
+ }
2325
+ requireMutation(expr, object, context, display) {
2326
+ if (context.flow.hasTaskCapture(this.placesOf(object, context)))
2327
+ this.report(expr.span, `Cannot mutate ${display} while a task uses it; wait for the task first`, 'CONCURRENCY');
2328
+ if (this.expressionTypes.get(object)?.readonly)
2329
+ this.report(expr.span, `${display} cannot mutate a read-only input or field`, 'MUTABILITY');
2330
+ this.requireChange(object, expr.span, context);
2331
+ const root = this.rootName(object);
2332
+ if ((!root && !['own', 'fresh'].includes(this.ownershipOf(object, context))) || (root && !context.borrowed.has(root) && !context.flow.activeGrant(root) &&
2333
+ !['own', 'borrow'].includes(context.locals.get(root)?.ownership ?? 'managed')))
2334
+ this.report(expr.span, `${display} requires borrow${root ? ` ${root}` : ' of its receiver'}`, 'BORROW');
2335
+ }
2336
+ checkHandle(expr, context) {
2337
+ const call = expr.call;
2338
+ const endpoint = call.kind === 'call' && call.callee.kind === 'name' ? this.project.scopes.get(context.file)?.get(call.callee.name) : undefined;
2339
+ if (!endpoint || endpoint.node.kind !== 'function' || !endpoint.node.endpoint || call.kind !== 'call') {
2340
+ this.report(expr.span, 'handle requires an explicitly imported, named endpoint call', 'HTTP');
2341
+ return builtin('HttpAction');
2342
+ }
2343
+ if (call.typeArgs.length)
2344
+ this.report(call.span, 'HTTP actions use concrete endpoint types', 'HTTP');
2345
+ const params = endpoint.node.params.filter(param => !param.injected && !['request', 'cookie'].includes(param.source?.kind ?? ''));
2346
+ const sources = new Map();
2347
+ call.args.forEach((argument, index) => {
2348
+ const label = call.argLabels[index] ?? (argument.kind === 'name' ? argument.name : argument.kind === 'formInput' ? params.find(param => ['body', 'form'].includes(param.source?.kind ?? ''))?.name : undefined);
2349
+ if (!label || !params.some(param => param.name === label))
2350
+ this.report(argument.span, `HTTP action ${endpoint.name} has no input ${label ?? '(unlabeled)'}`, 'HTTP');
2351
+ else if (sources.has(label))
2352
+ this.report(argument.span, `Duplicate HTTP action input ${label}`, 'HTTP');
2353
+ else
2354
+ sources.set(label, index);
2355
+ });
2356
+ const parameters = params.map(param => {
2357
+ const type = this.resolveType(param.type, endpoint.file), source = sources.get(param.name), argument = source === undefined ? undefined : call.args[source];
2358
+ const form = argument?.kind === 'formInput';
2359
+ if (!argument && !type.optional)
2360
+ this.report(call.span, `Missing HTTP action input ${param.name}`, 'HTTP');
2361
+ if (form) {
2362
+ if (!['body', 'form'].includes(param.source?.kind ?? '') || type.def?.node.kind !== 'class' || !type.def.node.record)
2363
+ this.report(argument.span, 'input from form supplies one typed record body or form input', 'HTTP');
2364
+ if (!['POST', 'PATCH', 'PUT', 'DELETE'].includes(endpoint.node.kind === 'function' ? endpoint.node.endpoint.method : ''))
2365
+ this.report(argument.span, 'Form input requires a POST, PATCH, PUT, or DELETE endpoint', 'HTTP');
2366
+ }
2367
+ else if (argument) {
2368
+ const actual = this.checkExpression(argument, context, type);
2369
+ if (!this.assignable(actual, type))
2370
+ this.report(argument.span, `HTTP action ${param.name} expects ${tyName(type)}, got ${tyName(actual)}`, 'TYPE');
2371
+ if (!this.immutableInput(argument, actual))
2372
+ this.report(argument.span, 'Freeze mutable collection inputs before capturing an HTTP action', 'HTTP');
2373
+ }
2374
+ return { param, type, source, form };
2375
+ });
2376
+ this.actions.set(expr, { endpoint, parameters });
2377
+ this.checkAllowedError(builtin('HttpError'), expr.span, context);
2378
+ return builtin('HttpAction');
2379
+ }
2380
+ checkCall(expr, context) {
2381
+ const immediate = context.deferredErrors ? { ...context, deferredErrors: undefined } : context;
2382
+ let receiverType = expr.callee.kind === 'member' ? this.checkExpression(expr.callee.object, immediate) : undefined;
2383
+ if (receiverType?.kind === 'param' && expr.callee.kind === 'member') {
2384
+ const name = expr.callee.name;
2385
+ receiverType = receiverType.bounds?.find(type => this.interfaceMethods(type, new Set()).has(name)) ?? receiverType;
2386
+ }
2387
+ if (receiverType?.nullable || receiverType?.optional)
2388
+ this.report(expr.callee.span, `Narrow ${tyName(receiverType)} before calling a method`);
2389
+ const expectations = this.argumentExpectations(expr, context, receiverType);
2390
+ const requiresContext = (arg) => arg.kind === 'collection' && (!arg.items.length || arg.items.some(requiresContext));
2391
+ const deferred = new Set(expr.args.flatMap((arg, index) => !expectations[index] && requiresContext(arg) ? [index] : []));
2392
+ const argTypes = expr.args.map((arg, index) => deferred.has(index) ? errorTy : this.checkExpression(arg, immediate, expectations[index]));
2393
+ const inferredExpectations = this.argumentExpectations(expr, context, receiverType);
2394
+ for (const index of deferred)
2395
+ argTypes[index] = this.checkExpression(expr.args[index], immediate, inferredExpectations[index]);
2396
+ if (expr.callee.kind === 'name' && expr.callee.name === 'next')
2397
+ return this.checkNext(expr, argTypes, context);
2398
+ if (expr.callee.kind === 'name' && expr.callee.name === 'exit') {
2399
+ if (context.callable || context.file !== this.project.main?.path || context.locked)
2400
+ this.report(expr.span, 'exit belongs in main outside a lock', 'EFFECT');
2401
+ const plan = this.planCall(expr, ['status'], 'exit'), index = plan.sourceIndices[0];
2402
+ if (index !== undefined && argTypes[index].name !== 'int')
2403
+ this.report(expr.span, 'exit status is an int', 'TYPE');
2404
+ return builtin('void');
2405
+ }
2406
+ if (expr.callee.kind === 'name' && expr.callee.name === 'Json') {
2407
+ const plan = this.planCall(expr, ['value'], 'Json'), index = plan.sourceIndices[0];
2408
+ if (expr.typeArgs.length)
2409
+ this.report(expr.span, 'Json infers its data type', 'JSON');
2410
+ if (index !== undefined) {
2411
+ const value = argTypes[index];
2412
+ if (!jsonDataType(this.project, value))
2413
+ this.report(expr.args[index].span, `${tyName(value)} is not JSON data`, 'JSON');
2414
+ if (!this.immutableInput(expr.args[index], value))
2415
+ this.report(expr.args[index].span, 'Freeze collection values before wrapping them as immutable JSON', 'JSON');
2416
+ }
2417
+ return { ...builtin('Json'), readonly: true, frozen: true };
2418
+ }
2419
+ if (expr.callee.kind === 'name' && expr.callee.name === 'HttpTestClient') {
2420
+ this.planCall(expr, [], 'HttpTestClient');
2421
+ if (!this.project.testEndpoint || expr.args.length || expr.typeArgs.length)
2422
+ this.report(expr.span, 'HttpTestClient is supplied by test endpoint declarations', 'TEST');
2423
+ return { ...builtin('HttpTestClient'), readonly: true, frozen: true };
2424
+ }
2425
+ if (expr.callee.kind === 'name' && expr.callee.name === 'Shared') {
2426
+ const plan = this.planCall(expr, ['value'], 'Shared'), index = plan.sourceIndices[0];
2427
+ const result = index === undefined ? errorTy : argTypes[index];
2428
+ if (index !== undefined && !['fresh', 'own'].includes(this.ownershipOf(expr.args[index], context)))
2429
+ this.report(expr.args[index].span, 'Shared takes a fresh value or an owned value; existing mutable aliases cannot survive the transfer', 'OWN');
2430
+ if (expr.typeArgs.length > 1 || expr.typeArgs[0] && !this.assignable(result, this.resolveType(expr.typeArgs[0], context.file, context.types)))
2431
+ this.report(expr.span, 'Shared type argument must match its value', 'TYPE');
2432
+ if (index !== undefined && expr.args[index].kind === 'name') {
2433
+ this.moveOwnedLocal(expr.args[index].name, expr.args[index].span, context);
2434
+ }
2435
+ return { ...builtin('Shared'), args: [result] };
2436
+ }
2437
+ if (expr.callee.kind === 'name' && expr.callee.name === 'ServerEvent') {
2438
+ const params = ['data', 'id', 'event', 'retry'].map((name, index) => ({ name, type: { name: index === 0 ? 'T' : index === 3 ? 'int' : 'string', args: [], nullable: false, optional: index > 0, span: expr.span }, injected: false, ownership: 'managed', span: expr.span }));
2439
+ const plan = this.planCall(expr, params, 'ServerEvent'), data = plan.sourceIndices[0], result = expr.typeArgs[0] ? this.resolveType(expr.typeArgs[0], context.file, context.types) : data === undefined ? errorTy : argTypes[data];
2440
+ if (expr.typeArgs.length > 1 || !jsonDataType(this.project, result))
2441
+ this.report(expr.span, 'ServerEvent<T> needs JSON data', 'HTTP');
2442
+ if (data !== undefined && !this.immutableInput(expr.args[data], argTypes[data]))
2443
+ this.report(expr.args[data].span, 'Freeze collection data before creating a ServerEvent', 'HTTP');
2444
+ params.forEach((param, index) => { const source = plan.sourceIndices[index]; if (source !== undefined && !this.assignable(argTypes[source], index === 0 ? result : { ...builtin(param.type.name), optional: true }))
2445
+ this.report(expr.args[source].span, `Invalid ${param.name} for ServerEvent`, 'HTTP'); });
2446
+ this.checkAllowedError(builtin('HttpError'), expr.span, context);
2447
+ return { ...builtin('ServerEvent'), args: [result] };
2448
+ }
2449
+ if (expr.callee.kind === 'name' && ['HttpResponse', 'Headers'].includes(expr.callee.name)) {
2450
+ const name = expr.callee.name;
2451
+ const params = (name === 'Headers' ? [] : ['body', 'status', 'headers']).map((label, index) => ({ name: label,
2452
+ type: { name: index === 0 ? 'T' : index === 1 ? 'int' : 'Headers', args: [], nullable: false, optional: index > 0, span: expr.span },
2453
+ injected: false, ownership: 'managed', span: expr.span }));
2454
+ const plan = this.planCall(expr, params, name);
2455
+ const body = plan.sourceIndices[0];
2456
+ const result = name === 'Headers' ? builtin(name) : { ...builtin(name), args: [expr.typeArgs[0] ? this.resolveType(expr.typeArgs[0], context.file, context.types) : body === undefined ? errorTy : argTypes[body]] };
2457
+ if (name === 'Headers' && expr.typeArgs.length || name === 'HttpResponse' && expr.typeArgs.length > 1)
2458
+ this.report(expr.span, `${name} has invalid type arguments`, 'HTTP');
2459
+ params.forEach((param, index) => {
2460
+ const source = plan.sourceIndices[index];
2461
+ if (source !== undefined && !this.assignable(argTypes[source], index === 0 ? result.args[0] : builtin(param.type.name)))
2462
+ this.report(expr.args[source].span, `Invalid ${param.name} for ${name}`, 'HTTP');
2463
+ });
2464
+ if (name === 'HttpResponse' && plan.sourceIndices[1] !== undefined) {
2465
+ const status = expr.args[plan.sourceIndices[1]];
2466
+ if (status.kind === 'literal' && typeof status.value === 'number') {
2467
+ if (!Number.isInteger(status.value) || status.value < 200 || status.value > 599)
2468
+ this.report(status.span, 'HTTP response status must range from 200 to 599', 'HTTP');
2469
+ }
2470
+ else
2471
+ this.checkAllowedError(builtin('HttpError'), status.span, context);
2472
+ }
2473
+ return result;
2474
+ }
2475
+ if (expr.callee.kind === 'name' && ['c_int', 'int'].includes(expr.callee.name)) {
2476
+ const name = expr.callee.name;
2477
+ const plan = this.planCall(expr, ['value'], name);
2478
+ const input = plan.sourceIndices[0];
2479
+ const expected = builtin(name === 'c_int' ? 'int' : 'c_int');
2480
+ if (expr.typeArgs.length || input !== undefined && !this.assignable(argTypes[input], expected))
2481
+ this.report(expr.span, `${name} requires a labeled ${tyName(expected)} value`, 'FFI');
2482
+ if (name === 'c_int')
2483
+ this.checkAllowedError(builtin('ConversionError'), expr.span, context);
2484
+ return builtin(name);
2485
+ }
2486
+ if (expr.callee.kind === 'name' && expr.callee.name === 'print') {
2487
+ this.intrinsicEffect('print', expr.span, context);
2488
+ this.planCall(expr, ['value'], 'print');
2489
+ return builtin('void');
2490
+ }
2491
+ if (expr.callee.kind === 'name' && expr.callee.name === 'assert') {
2492
+ if (!this.project.testMode)
2493
+ this.report(expr.span, 'assert belongs inside a test case or its setup', 'TEST');
2494
+ if (expr.typeArgs.length || expr.args.length !== 1 ||
2495
+ expr.argLabels.some(label => label !== undefined && label !== 'condition'))
2496
+ this.report(expr.span, 'Use assert(condition) or assert(condition=condition)', 'TEST');
2497
+ if (argTypes[0]?.id !== 'builtin:bool')
2498
+ this.report(expr.span, 'assert requires a bool condition', 'TEST');
2499
+ return builtin('void');
2500
+ }
2501
+ if (expr.callee.kind === 'name' && expr.callee.name === 'arguments') {
2502
+ this.intrinsicEffect('arguments', expr.span, context);
2503
+ this.planCall(expr, [], 'arguments');
2504
+ return { ...builtin('List'), args: [builtin('string')] };
2505
+ }
2506
+ if (expr.callee.kind === 'name' && ['read_file', 'write_file'].includes(expr.callee.name)) {
2507
+ this.intrinsicEffect(expr.callee.name, expr.span, context);
2508
+ const names = expr.callee.name === 'read_file' ? ['path'] : ['path', 'content'];
2509
+ const plan = this.planCall(expr, names, expr.callee.name);
2510
+ for (const index of plan.sourceIndices)
2511
+ if (index !== undefined &&
2512
+ argTypes[index].id !== 'builtin:string')
2513
+ this.report(expr.args[index].span, `${expr.callee.name} requires string arguments`);
2514
+ this.checkAllowedError(builtin('FileError'), expr.span, context);
2515
+ return builtin(names.length === 1 ? 'string' : 'void');
2516
+ }
2517
+ if (expr.callee.kind === 'name' && ['List', 'Set', 'Tuple'].includes(expr.callee.name)) {
2518
+ const name = expr.callee.name;
2519
+ if (expr.argLabels.some(label => label !== undefined))
2520
+ this.report(expr.span, `${name} elements do not use argument labels`);
2521
+ if (name === 'Tuple') {
2522
+ if (expr.typeArgs.length !== argTypes.length)
2523
+ this.report(expr.span, 'Tuple constructor type arguments must match its items; prefer a tuple literal', 'COLLECTION');
2524
+ argTypes.forEach((actual, index) => {
2525
+ const wanted = expectations[index];
2526
+ if (wanted && !this.assignable(actual, wanted))
2527
+ this.report(expr.args[index].span, `Expected ${tyName(wanted)}, got ${tyName(actual)}`);
2528
+ if (this.ownershipOf(expr.args[index], context) === 'own')
2529
+ this.report(expr.args[index].span, 'Tuple cannot copy an owned value', 'OWN');
2530
+ });
2531
+ return { ...builtin(name), args: expectations.map(type => type ?? errorTy) };
2532
+ }
2533
+ if (expr.typeArgs.length !== 1)
2534
+ this.report(expr.span, `${name} constructor requires one type argument, such as ${name}<int>()`);
2535
+ const element = expr.typeArgs[0] ? this.resolveType(expr.typeArgs[0], context.file, context.types) : errorTy;
2536
+ for (let i = 0; i < argTypes.length; i++) {
2537
+ const argument = expr.args[i];
2538
+ if (!this.assignable(argTypes[i], element))
2539
+ this.report(expr.args[i].span, `Expected ${tyName(element)}, got ${tyName(argTypes[i])}`);
2540
+ if (this.ownershipOf(argument, context) === 'own')
2541
+ this.report(argument.span, `${name} cannot copy an owned value`, 'OWN');
2542
+ }
2543
+ return { ...builtin(name), args: [element] };
2544
+ }
2545
+ if (expr.callee.kind === 'name' && expr.callee.name === 'Map') {
2546
+ this.planCall(expr, [], 'Map');
2547
+ if (expr.typeArgs.length !== 2)
2548
+ this.report(expr.span, 'Map constructor requires two type arguments, such as Map<string,int>()');
2549
+ if (argTypes.length)
2550
+ this.report(expr.span, 'Map constructor takes no values');
2551
+ return { ...builtin('Map'), args: expr.typeArgs.map(arg => this.resolveType(arg, context.file, context.types)) };
2552
+ }
2553
+ if (expr.callee.kind === 'name' && errorNames.includes(expr.callee.name)) {
2554
+ this.planCall(expr, [], 'FileError');
2555
+ if (argTypes.length || expr.typeArgs.length)
2556
+ this.report(expr.span, 'FileError constructor takes no arguments');
2557
+ return builtin(expr.callee.name);
2558
+ }
2559
+ if (expr.callee.kind === 'member' && receiverType?.id.startsWith('builtin:') && collectionOperations[receiverType.name]) {
2560
+ const receiver = receiverType;
2561
+ const operation = collectionOperations[receiver.name].find(operation => operation.name === expr.callee.name);
2562
+ if (!operation) {
2563
+ this.report(expr.callee.span, `${receiver.name} has no method ${expr.callee.name}`);
2564
+ return errorTy;
2565
+ }
2566
+ const decoding = receiver.name === 'Json' && operation.name === 'decode' || receiver.name === 'HttpRequest' && operation.name === 'form';
2567
+ if (expr.typeArgs.length && !decoding)
2568
+ this.report(expr.span, 'Collection methods inherit their receiver type arguments', 'GENERIC');
2569
+ const plan = this.planCall(expr, operation.parameters.map(param => ({ name: param.label, type: { name: param.type.replace(/^optional /, ''), args: [], nullable: false, optional: param.type.startsWith('optional '), span: expr.span }, injected: false, ownership: 'managed', span: expr.span })), `${receiver.name}.${operation.name}`);
2570
+ operation.parameters.forEach((param, index) => {
2571
+ const source = plan.sourceIndices[index];
2572
+ const expected = operationType(param.type, receiver);
2573
+ if (source !== undefined && !this.assignable(argTypes[source], expected))
2574
+ this.report(expr.args[source].span, `Expected ${tyName(expected)}, got ${tyName(argTypes[source])}`);
2575
+ });
2576
+ if (operation.changes) {
2577
+ this.requireMutation(expr, expr.callee.object, context, `${receiver.name}.${operation.name}`);
2578
+ for (const arg of expr.args) {
2579
+ if (this.ownershipOf(arg, context) === 'own')
2580
+ this.report(arg.span, `${receiver.name} cannot copy an owned value`, 'OWN');
2581
+ this.checkBorrowEscape(arg, context);
2582
+ }
2583
+ }
2584
+ for (const error of operation.errors ?? [])
2585
+ this.checkAllowedError(builtin(error), expr.span, context);
2586
+ let position;
2587
+ if (operation.returns === 'position') {
2588
+ const index = plan.sourceIndices[0];
2589
+ const argument = index === undefined ? undefined : expr.args[index];
2590
+ if (argument?.kind !== 'literal' || argument.numericType === 'float' || typeof argument.value !== 'number' || !Number.isInteger(argument.value)) {
2591
+ this.report(expr.span, 'Tuple.get requires a constant int index so its result type is known', 'COLLECTION');
2592
+ return errorTy;
2593
+ }
2594
+ position = argument.value;
2595
+ if (position < 0 || position >= receiver.args.length) {
2596
+ this.report(argument.span, 'Tuple index out of range', 'COLLECTION');
2597
+ return errorTy;
2598
+ }
2599
+ }
2600
+ const result = decoding ? expr.typeArgs[0] ? this.resolveType(expr.typeArgs[0], context.file, context.types) : errorTy : operationType(operation.returns, receiver, position);
2601
+ if (decoding) {
2602
+ if (expr.typeArgs.length !== 1)
2603
+ this.report(expr.span, `${receiver.name}.${operation.name} needs one concrete data type`, 'JSON');
2604
+ else if (!jsonDataType(this.project, result))
2605
+ this.report(expr.span, `${tyName(result)} is not JSON data; use a record or data collection`, 'JSON');
2606
+ if (result.def?.node.kind === 'class')
2607
+ for (const error of result.def.node.validationErrors ?? [])
2608
+ this.checkAllowedError(this.resolveType(error, result.def.file), expr.span, context);
2609
+ }
2610
+ return this.isReference(result) ? { ...result, readonly: true, frozen: decoding || receiver.name === 'Json' } : result;
2611
+ }
2612
+ let fn;
2613
+ let fnFile = context.file;
2614
+ let ownerParams = new Map();
2615
+ if (expr.callee.kind === 'name') {
2616
+ const def = this.project.scopes.get(context.file)?.get(expr.callee.name);
2617
+ if (def?.node.kind === 'class') {
2618
+ const cls = def.node;
2619
+ const plan = this.planCall(expr, cls.fields, cls.name);
2620
+ const inferred = new Map();
2621
+ for (let i = 0; i < cls.typeParams.length; i++) {
2622
+ if (expr.typeArgs[i])
2623
+ inferred.set(cls.typeParams[i], this.resolveType(expr.typeArgs[i], context.file, context.types));
2624
+ }
2625
+ if (expr.typeArgs.length && expr.typeArgs.length !== cls.typeParams.length)
2626
+ this.report(expr.span, `${cls.name} expects ${cls.typeParams.length} type arguments`);
2627
+ for (let i = 0; i < cls.fields.length; i++) {
2628
+ const field = cls.fields[i];
2629
+ const source = plan.sourceIndices[i];
2630
+ if (source !== undefined)
2631
+ this.inferCallType(field.type, argTypes[source], cls.typeParams.filter((_, index) => !expr.typeArgs[index]), inferred);
2632
+ }
2633
+ for (const name of cls.typeParams)
2634
+ if (!inferred.has(name)) {
2635
+ this.report(expr.span, `Cannot infer ${cls.name} type argument ${name}; supply explicit type arguments`, 'GENERIC');
2636
+ inferred.set(name, errorTy);
2637
+ }
2638
+ this.checkConstraints(cls, def.file, inferred, expr.span);
2639
+ if (cls.record)
2640
+ for (const field of cls.fields)
2641
+ if (!this.immutableData(this.resolveType(field.type, def.file, inferred)))
2642
+ this.report(expr.span, 'Record construction requires deeply immutable field values', 'RECORD');
2643
+ if (cls.record)
2644
+ cls.fields.forEach((field, index) => {
2645
+ const source = plan.sourceIndices[index];
2646
+ if (source === undefined)
2647
+ return;
2648
+ const actual = argTypes[source], argument = expr.args[source];
2649
+ if (!this.immutableInput(argument, actual))
2650
+ this.report(argument.span, 'Freeze a mutable collection before storing it in a record', 'RECORD');
2651
+ });
2652
+ this.planInjections(expr, cls.fields, def.file, inferred, plan, context);
2653
+ context.flow.assertDistinct(expr.args.map((argument, index) => ({ origins: this.placesOf(argument, context), span: argument.span,
2654
+ exclusive: cls.fields[plan.sourceIndices.indexOf(index)]?.ownership === 'own' })), (span, message) => this.report(span, message, 'BORROW'));
2655
+ for (let i = 0; i < cls.fields.length; i++) {
2656
+ const source = plan.sourceIndices[i];
2657
+ if (source === undefined)
2658
+ continue;
2659
+ const expected = this.resolveType(cls.fields[i].type, def.file, inferred);
2660
+ if (!this.assignable(argTypes[source], expected))
2661
+ this.report(expr.args[source].span, `Expected ${tyName(expected)}, got ${tyName(argTypes[source])}`);
2662
+ if (this.isReference(argTypes[source])) {
2663
+ this.checkBorrowEscape(expr.args[source], context, false);
2664
+ if (cls.fields[i].mutable && argTypes[source].readonly)
2665
+ this.report(expr.args[source].span, 'Read-only references cannot become mutable constructor storage', 'MUTABILITY');
2666
+ }
2667
+ this.checkArgumentOwnership(expr.args[source], cls.fields[i], context);
2668
+ }
2669
+ for (const error of this.effectiveErrors(cls, def.file, inferred))
2670
+ this.checkAllowedError(error, expr.span, context);
2671
+ return { id: def.id, name: cls.name, kind: 'class', def,
2672
+ args: cls.typeParams.map(name => inferred.get(name) ?? errorTy), nullable: false };
2673
+ }
2674
+ if (def?.node.kind === 'function') {
2675
+ fn = def.node;
2676
+ fnFile = def.file;
2677
+ if (fn.endpoint?.streams)
2678
+ this.report(expr.span, 'Streaming endpoints are invoked through HTTP; extract an ordinary helper for reusable work', 'HTTP');
2679
+ if (!fn.body && !fn.externC)
2680
+ this.report(expr.callee.span, `${fn.name} has no body and cannot be called`, 'CALL');
2681
+ }
2682
+ if (def?.node.kind === 'interceptor') {
2683
+ this.report(expr.callee.span, `${def.name} is an interceptor; apply it with [${def.name}]`, 'INTERCEPTOR');
2684
+ return errorTy;
2685
+ }
2686
+ }
2687
+ else if (expr.callee.kind === 'member') {
2688
+ const receiver = receiverType;
2689
+ const methodName = expr.callee.name;
2690
+ const def = receiver.def;
2691
+ if (def?.node.kind === 'class' || def?.node.kind === 'interceptor') {
2692
+ if (def.node.kind === 'interceptor' && methodName === 'around') {
2693
+ this.report(expr.callee.span, 'around is invoked through an interceptor annotation', 'INTERCEPTOR');
2694
+ return errorTy;
2695
+ }
2696
+ fn = def.node.methods.find(item => item.name === methodName);
2697
+ ownerParams = new Map(def.node.typeParams.map((name, index) => [name, receiver.args[index] ?? errorTy]));
2698
+ if (!fn) {
2699
+ const defaultMethod = this.defaults.get(def.id)?.get(methodName);
2700
+ fn = defaultMethod?.method;
2701
+ if (defaultMethod)
2702
+ ownerParams = new Map(defaultMethod.params);
2703
+ }
2704
+ fnFile = fn?.span.file ?? def.file;
2705
+ if (fn)
2706
+ this.checkMemberVisibility(def.id, def.name, methodName, expr.callee.span, context);
2707
+ }
2708
+ else if (def?.node.kind === 'interface') {
2709
+ const entry = this.interfaceMethods(receiver, new Set()).get(methodName)?.[0];
2710
+ fn = entry?.method;
2711
+ fnFile = entry?.file ?? def.file;
2712
+ ownerParams = new Map(entry?.params);
2713
+ if (entry)
2714
+ this.checkMemberVisibility(entry.from, def.name, methodName, expr.callee.span, context);
2715
+ }
2716
+ if (!fn)
2717
+ this.report(expr.callee.span, `${tyName(receiver)} has no method ${methodName}`);
2718
+ }
2719
+ if (!fn) {
2720
+ if (expr.callee.kind === 'name') {
2721
+ const name = expr.callee.name;
2722
+ if (!this.project.scopes.get(context.file)?.has(name) && !context.locals.has(name))
2723
+ this.report(expr.callee.span, `Unknown name ${name}`, 'NAME');
2724
+ else
2725
+ this.report(expr.callee.span, 'Expression is not callable');
2726
+ }
2727
+ return errorTy;
2728
+ }
2729
+ if (fn.externC && !context.unsafe)
2730
+ this.report(expr.span, `Call to extern C function ${fn.name} requires unsafe { ... }`, 'FFI');
2731
+ if (fn.externC && !fn.nativePure && !(fn.valueAbi && fn.uses?.length)) {
2732
+ const native = this.project.scopes.get(fnFile)?.get(fn.name);
2733
+ this.requireUse(`C:${native?.id ?? fn.name}`, `C.${fn.name}`, expr.span, context);
2734
+ }
2735
+ const plan = this.planCall(expr, fn.params, fn.name);
2736
+ if (expr.typeArgs.length && expr.typeArgs.length !== fn.typeParams.length)
2737
+ this.report(expr.span, `${fn.name} expects ${fn.typeParams.length} type arguments`);
2738
+ const params = new Map(ownerParams);
2739
+ for (let i = 0; i < fn.typeParams.length; i++) {
2740
+ if (expr.typeArgs[i])
2741
+ params.set(fn.typeParams[i], this.resolveType(expr.typeArgs[i], context.file, context.types));
2742
+ }
2743
+ for (let i = 0; i < fn.params.length; i++) {
2744
+ const source = plan.sourceIndices[i];
2745
+ if (source === undefined)
2746
+ continue;
2747
+ const param = fn.params[i];
2748
+ const argument = expr.args[source];
2749
+ this.inferCallType(param.type, argTypes[source], fn.typeParams.filter((_, index) => !expr.typeArgs[index]), params);
2750
+ const expected = this.resolveType(param.type, fnFile, params);
2751
+ if (!this.assignable(argTypes[source], expected))
2752
+ this.report(argument.span, `Expected ${tyName(expected)}, got ${tyName(argTypes[source])}`);
2753
+ this.checkArgumentOwnership(argument, param, context);
2754
+ if (fn.typeConstraints?.[param.type.name]?.some(bound => bound.name === 'Data') && !this.immutableInput(argument, argTypes[source]))
2755
+ this.report(argument.span, 'Freeze collection values before passing them as immutable Data', 'FREEZE');
2756
+ }
2757
+ for (const name of fn.typeParams)
2758
+ if (!params.has(name)) {
2759
+ this.report(expr.span, `Cannot infer ${fn.name} type argument ${name}; supply explicit type arguments`, 'GENERIC');
2760
+ params.set(name, errorTy);
2761
+ }
2762
+ this.checkConstraints(fn, fnFile, params, expr.span);
2763
+ const owner = expr.callee.kind === 'member' ? [...this.project.definitions.values()].find(def => 'methods' in def.node && def.node.methods.includes(fn)) : undefined;
2764
+ const contract = this.effectiveContract(fn, fnFile, owner, params);
2765
+ plan.mutatesReceiver = contract.changes.some(change => change === 'self' || change.startsWith('self.'));
2766
+ for (const [key, effect] of contract.uses)
2767
+ this.requireUse(key, `${effect.source}.${effect.operation}`, expr.span, context, effect);
2768
+ for (const changed of contract.changes) {
2769
+ const root = changed.split('.')[0];
2770
+ if (root === 'self' && expr.callee.kind === 'member')
2771
+ this.requireMutation(expr, expr.callee.object, context, `${fn.name} changes self`);
2772
+ else {
2773
+ const index = fn.params.findIndex(param => param.name === root);
2774
+ const source = plan.sourceIndices[index];
2775
+ if (source !== undefined)
2776
+ this.requireMutation(expr, expr.args[source], context, `${fn.name} changes ${root}`);
2777
+ }
2778
+ }
2779
+ this.planInjections(expr, fn.params, fnFile, params, plan, context);
2780
+ context.flow.assertDistinct([
2781
+ ...fn.params.flatMap((param, index) => {
2782
+ const source = plan.sourceIndices[index];
2783
+ return source === undefined ? [] : [{ origins: this.placesOf(expr.args[source], context),
2784
+ exclusive: param.ownership !== 'managed', span: expr.args[source].span }];
2785
+ }),
2786
+ ...fn.params.flatMap((param, index) => {
2787
+ if (!param.injected)
2788
+ return [];
2789
+ const source = plan.injectionSources?.[index];
2790
+ const key = plan.bindingKeys[index];
2791
+ const name = source?.startsWith('self.') ? source.slice(5) : source;
2792
+ const origins = name ? context.flow.origins(name) : key ? this.bindingOrigins(key, context, expr.span) : new Set();
2793
+ return [{ origins, exclusive: false, span: expr.span }];
2794
+ }),
2795
+ ...(expr.callee.kind === 'member' ? [{ origins: this.placesOf(expr.callee.object, context),
2796
+ exclusive: contract.changes.some(path => path === 'self' || path.startsWith('self.')), span: expr.callee.span }] : []),
2797
+ ], (span, message) => this.report(span, message, 'BORROW'));
2798
+ for (const thrown of this.effectiveErrors(fn, fnFile, params))
2799
+ this.checkAllowedError(thrown, expr.span, context);
2800
+ const result = this.resolveType(fn.returns, fnFile, params);
2801
+ return this.isReference(result) && fn.returnOwnership !== 'own' && !this.functionIsFresh(fn, fnFile, owner && 'fields' in owner.node ? new Set(fieldsOf(owner.node).map(field => field.name)) : new Set()) ?
2802
+ { ...result, readonly: true } : result;
2803
+ }
2804
+ checkNext(expr, argTypes, context) {
2805
+ const next = context.next;
2806
+ if (!next) {
2807
+ this.report(expr.callee.span, 'next is only available inside an interceptor around body', 'NEXT');
2808
+ return errorTy;
2809
+ }
2810
+ if (expr.typeArgs.length)
2811
+ this.report(expr.span, 'next inherits the target type arguments', 'NEXT');
2812
+ const plan = { sourceIndices: next.params.map(() => undefined),
2813
+ bindingKeys: next.params.map(() => undefined) };
2814
+ const targets = new Set();
2815
+ expr.args.forEach((arg, source) => {
2816
+ const label = expr.argLabels[source];
2817
+ const index = next.params.findIndex(param => param.name === label);
2818
+ if (!label)
2819
+ this.report(arg.span, 'next overrides require argument labels', 'NEXT');
2820
+ else if (index < 0 || next.params[index].injected)
2821
+ this.report(arg.span, `next has no mapped parameter ${label}`, 'NEXT');
2822
+ else if (plan.sourceIndices[index] !== undefined)
2823
+ this.report(arg.span, `Duplicate next override ${label}`, 'NEXT');
2824
+ else {
2825
+ const target = next.indices?.[index] ?? index;
2826
+ if (targets.has(target))
2827
+ this.report(arg.span, 'Multiple next overrides map to the same target parameter', 'NEXT');
2828
+ targets.add(target);
2829
+ plan.sourceIndices[index] = source;
2830
+ if (!this.assignable(argTypes[source], next.types[index]))
2831
+ this.report(arg.span, `next override ${label} expects ${tyName(next.types[index])}, got ${tyName(argTypes[source])}`, 'INTERCEPTOR');
2832
+ const param = next.params[index];
2833
+ if (param.ownership === 'own')
2834
+ this.checkArgumentOwnership(arg, param, context);
2835
+ else if (arg.kind === 'name' && context.locals.get(arg.name)?.ownership === 'own' && param.ownership !== 'borrow')
2836
+ this.report(arg.span, `Cannot copy owned value ${arg.name} to next`, 'OWN');
2837
+ if (param.ownership === 'borrow' && arg.kind === 'name' &&
2838
+ !context.borrowed.has(arg.name) && !['own', 'borrow'].includes(context.locals.get(arg.name)?.ownership ?? 'managed'))
2839
+ this.report(arg.span, `Passing ${arg.name} to next requires borrow`, 'BORROW');
2840
+ }
2841
+ });
2842
+ next.params.forEach((param, index) => {
2843
+ if (param.ownership !== 'own')
2844
+ return;
2845
+ const local = context.locals.get(param.name);
2846
+ if (plan.sourceIndices[index] === undefined && local?.moved)
2847
+ this.report(expr.span, `next cannot forward moved parameter ${param.name}`, 'OWN');
2848
+ if (local)
2849
+ this.moveOwnedLocal(param.name, expr.span, context);
2850
+ });
2851
+ this.callPlans.set(expr, plan);
2852
+ return next.returns;
2853
+ }
2854
+ checkArgumentOwnership(argument, param, context) {
2855
+ if (param.ownership === 'own') {
2856
+ if (argument.kind === 'name') {
2857
+ const local = context.locals.get(argument.name);
2858
+ if (local?.ownership === 'own') {
2859
+ if (local.moved)
2860
+ this.report(argument.span, `Cannot move ${argument.name} twice`, 'OWN');
2861
+ this.moveOwnedLocal(argument.name, argument.span, context);
2862
+ }
2863
+ else
2864
+ this.report(argument.span, 'Owned field requires an owned argument', 'OWN');
2865
+ }
2866
+ else if (!['own', 'fresh'].includes(this.ownershipOf(argument, context)))
2867
+ this.report(argument.span, 'Owned field requires a new object or owned argument', 'OWN');
2868
+ }
2869
+ else if (this.ownershipOf(argument, context) === 'own' && param.ownership !== 'borrow')
2870
+ this.report(argument.span, 'Cannot copy an owned value into managed storage', 'OWN');
2871
+ if (param.ownership === 'borrow') {
2872
+ if (context.flow.hasTaskCapture(this.placesOf(argument, context)))
2873
+ this.report(argument.span, 'Cannot pass mutable access while a task uses the value; wait for the task first', 'CONCURRENCY');
2874
+ if (this.expressionTypes.get(argument)?.readonly)
2875
+ this.report(argument.span, 'Cannot borrow a read-only argument', 'BORROW');
2876
+ const name = sourceName(argument);
2877
+ if (name && !context.flow.activeGrant(name) && !['own', 'borrow'].includes(context.locals.get(name)?.ownership ?? 'managed'))
2878
+ this.report(argument.span, `Passing ${name} to borrow parameter requires borrow`, 'BORROW');
2879
+ if (!name && !['own', 'fresh'].includes(this.ownershipOf(argument, context)))
2880
+ this.report(argument.span, 'A borrow argument needs mutable access to its origin', 'BORROW');
2881
+ }
2882
+ }
2883
+ checkBorrowEscape(expr, context, scoped = true) {
2884
+ context.flow.escape(this.placesOf(expr, context), expr.span, (span, message) => this.report(span, message, 'BORROW'), scoped);
2885
+ }
2886
+ moveOwnedLocal(name, span, context) {
2887
+ const local = context.locals.get(name);
2888
+ if (local?.ownership !== 'own')
2889
+ return;
2890
+ context.flow.assertMove(name, span, (at, message) => this.report(at, message, 'BORROW'));
2891
+ local.moved = true;
2892
+ }
2893
+ bindingKey(name, args) {
2894
+ return name + (args.length ? `<${args.map(typeName).join(',')}>` : '');
2895
+ }
2896
+ }