@virune/compiler 1.1.0-rc.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 (300) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +2 -0
  3. package/dist/src/analysis/control-flow.d.ts +9 -0
  4. package/dist/src/analysis/control-flow.d.ts.map +1 -0
  5. package/dist/src/analysis/control-flow.js +47 -0
  6. package/dist/src/analysis/control-flow.js.map +1 -0
  7. package/dist/src/ast/nodes.d.ts +495 -0
  8. package/dist/src/ast/nodes.d.ts.map +1 -0
  9. package/dist/src/ast/nodes.js +2 -0
  10. package/dist/src/ast/nodes.js.map +1 -0
  11. package/dist/src/binder/symbols.d.ts +36 -0
  12. package/dist/src/binder/symbols.d.ts.map +1 -0
  13. package/dist/src/binder/symbols.js +30 -0
  14. package/dist/src/binder/symbols.js.map +1 -0
  15. package/dist/src/checker/builtin-members.d.ts +4 -0
  16. package/dist/src/checker/builtin-members.d.ts.map +1 -0
  17. package/dist/src/checker/builtin-members.js +175 -0
  18. package/dist/src/checker/builtin-members.js.map +1 -0
  19. package/dist/src/checker/checker.d.ts +148 -0
  20. package/dist/src/checker/checker.d.ts.map +1 -0
  21. package/dist/src/checker/checker.js +2985 -0
  22. package/dist/src/checker/checker.js.map +1 -0
  23. package/dist/src/checker/effect-registry.d.ts +14 -0
  24. package/dist/src/checker/effect-registry.d.ts.map +1 -0
  25. package/dist/src/checker/effect-registry.js +11 -0
  26. package/dist/src/checker/effect-registry.js.map +1 -0
  27. package/dist/src/checker/type-operations.d.ts +27 -0
  28. package/dist/src/checker/type-operations.d.ts.map +1 -0
  29. package/dist/src/checker/type-operations.js +268 -0
  30. package/dist/src/checker/type-operations.js.map +1 -0
  31. package/dist/src/codegen/emitter.d.ts +83 -0
  32. package/dist/src/codegen/emitter.d.ts.map +1 -0
  33. package/dist/src/codegen/emitter.js +1029 -0
  34. package/dist/src/codegen/emitter.js.map +1 -0
  35. package/dist/src/codegen/helpers.d.ts +7 -0
  36. package/dist/src/codegen/helpers.d.ts.map +1 -0
  37. package/dist/src/codegen/helpers.js +48 -0
  38. package/dist/src/codegen/helpers.js.map +1 -0
  39. package/dist/src/codegen/runtime-imports.d.ts +6 -0
  40. package/dist/src/codegen/runtime-imports.d.ts.map +1 -0
  41. package/dist/src/codegen/runtime-imports.js +45 -0
  42. package/dist/src/codegen/runtime-imports.js.map +1 -0
  43. package/dist/src/codegen/writer.d.ts +14 -0
  44. package/dist/src/codegen/writer.d.ts.map +1 -0
  45. package/dist/src/codegen/writer.js +56 -0
  46. package/dist/src/codegen/writer.js.map +1 -0
  47. package/dist/src/compiler.d.ts +24 -0
  48. package/dist/src/compiler.d.ts.map +1 -0
  49. package/dist/src/compiler.js +97 -0
  50. package/dist/src/compiler.js.map +1 -0
  51. package/dist/src/diagnostics/codes.d.ts +18 -0
  52. package/dist/src/diagnostics/codes.d.ts.map +1 -0
  53. package/dist/src/diagnostics/codes.js +43 -0
  54. package/dist/src/diagnostics/codes.js.map +1 -0
  55. package/dist/src/diagnostics/diagnostic.d.ts +41 -0
  56. package/dist/src/diagnostics/diagnostic.d.ts.map +1 -0
  57. package/dist/src/diagnostics/diagnostic.js +50 -0
  58. package/dist/src/diagnostics/diagnostic.js.map +1 -0
  59. package/dist/src/diagnostics/render.d.ts +44 -0
  60. package/dist/src/diagnostics/render.d.ts.map +1 -0
  61. package/dist/src/diagnostics/render.js +44 -0
  62. package/dist/src/diagnostics/render.js.map +1 -0
  63. package/dist/src/experimental-api.d.ts +19 -0
  64. package/dist/src/experimental-api.d.ts.map +1 -0
  65. package/dist/src/experimental-api.js +17 -0
  66. package/dist/src/experimental-api.js.map +1 -0
  67. package/dist/src/hir/lower.d.ts +6 -0
  68. package/dist/src/hir/lower.d.ts.map +1 -0
  69. package/dist/src/hir/lower.js +124 -0
  70. package/dist/src/hir/lower.js.map +1 -0
  71. package/dist/src/hir/nodes.d.ts +12 -0
  72. package/dist/src/hir/nodes.d.ts.map +1 -0
  73. package/dist/src/hir/nodes.js +2 -0
  74. package/dist/src/hir/nodes.js.map +1 -0
  75. package/dist/src/index.d.ts +2 -0
  76. package/dist/src/index.d.ts.map +1 -0
  77. package/dist/src/index.js +2 -0
  78. package/dist/src/index.js.map +1 -0
  79. package/dist/src/interop/checked-api.d.ts +21 -0
  80. package/dist/src/interop/checked-api.d.ts.map +1 -0
  81. package/dist/src/interop/checked-api.js +46 -0
  82. package/dist/src/interop/checked-api.js.map +1 -0
  83. package/dist/src/interop/decision.d.ts +24 -0
  84. package/dist/src/interop/decision.d.ts.map +1 -0
  85. package/dist/src/interop/decision.js +96 -0
  86. package/dist/src/interop/decision.js.map +1 -0
  87. package/dist/src/interop/frontend-component-emission.d.ts +17 -0
  88. package/dist/src/interop/frontend-component-emission.d.ts.map +1 -0
  89. package/dist/src/interop/frontend-component-emission.js +271 -0
  90. package/dist/src/interop/frontend-component-emission.js.map +1 -0
  91. package/dist/src/interop/jsx-view-validation.d.ts +20 -0
  92. package/dist/src/interop/jsx-view-validation.d.ts.map +1 -0
  93. package/dist/src/interop/jsx-view-validation.js +847 -0
  94. package/dist/src/interop/jsx-view-validation.js.map +1 -0
  95. package/dist/src/interop/operation-api.d.ts +32 -0
  96. package/dist/src/interop/operation-api.d.ts.map +1 -0
  97. package/dist/src/interop/operation-api.js +61 -0
  98. package/dist/src/interop/operation-api.js.map +1 -0
  99. package/dist/src/interop/operation.d.ts +136 -0
  100. package/dist/src/interop/operation.d.ts.map +1 -0
  101. package/dist/src/interop/operation.js +575 -0
  102. package/dist/src/interop/operation.js.map +1 -0
  103. package/dist/src/interop/repetition-host-locator.d.ts +25 -0
  104. package/dist/src/interop/repetition-host-locator.d.ts.map +1 -0
  105. package/dist/src/interop/repetition-host-locator.js +61 -0
  106. package/dist/src/interop/repetition-host-locator.js.map +1 -0
  107. package/dist/src/interop/repetition-host-usage-validation.d.ts +11 -0
  108. package/dist/src/interop/repetition-host-usage-validation.d.ts.map +1 -0
  109. package/dist/src/interop/repetition-host-usage-validation.js +89 -0
  110. package/dist/src/interop/repetition-host-usage-validation.js.map +1 -0
  111. package/dist/src/interop/types.d.ts +365 -0
  112. package/dist/src/interop/types.d.ts.map +1 -0
  113. package/dist/src/interop/types.js +2 -0
  114. package/dist/src/interop/types.js.map +1 -0
  115. package/dist/src/mir/lower.d.ts +4 -0
  116. package/dist/src/mir/lower.d.ts.map +1 -0
  117. package/dist/src/mir/lower.js +131 -0
  118. package/dist/src/mir/lower.js.map +1 -0
  119. package/dist/src/mir/nodes.d.ts +36 -0
  120. package/dist/src/mir/nodes.d.ts.map +1 -0
  121. package/dist/src/mir/nodes.js +2 -0
  122. package/dist/src/mir/nodes.js.map +1 -0
  123. package/dist/src/project/incremental.d.ts +9 -0
  124. package/dist/src/project/incremental.d.ts.map +1 -0
  125. package/dist/src/project/incremental.js +15 -0
  126. package/dist/src/project/incremental.js.map +1 -0
  127. package/dist/src/project/project.d.ts +98 -0
  128. package/dist/src/project/project.d.ts.map +1 -0
  129. package/dist/src/project/project.js +884 -0
  130. package/dist/src/project/project.js.map +1 -0
  131. package/dist/src/public-api.d.ts +45 -0
  132. package/dist/src/public-api.d.ts.map +1 -0
  133. package/dist/src/public-api.js +37 -0
  134. package/dist/src/public-api.js.map +1 -0
  135. package/dist/src/reference/evaluator.d.ts +13 -0
  136. package/dist/src/reference/evaluator.d.ts.map +1 -0
  137. package/dist/src/reference/evaluator.js +304 -0
  138. package/dist/src/reference/evaluator.js.map +1 -0
  139. package/dist/src/selfhost/bootstrap-artifact-normalizer.d.ts +65 -0
  140. package/dist/src/selfhost/bootstrap-artifact-normalizer.d.ts.map +1 -0
  141. package/dist/src/selfhost/bootstrap-artifact-normalizer.js +226 -0
  142. package/dist/src/selfhost/bootstrap-artifact-normalizer.js.map +1 -0
  143. package/dist/src/selfhost/bootstrap-artifact-snapshot.d.ts +23 -0
  144. package/dist/src/selfhost/bootstrap-artifact-snapshot.d.ts.map +1 -0
  145. package/dist/src/selfhost/bootstrap-artifact-snapshot.js +122 -0
  146. package/dist/src/selfhost/bootstrap-artifact-snapshot.js.map +1 -0
  147. package/dist/src/selfhost/bootstrap-compiler-selection.d.ts +36 -0
  148. package/dist/src/selfhost/bootstrap-compiler-selection.d.ts.map +1 -0
  149. package/dist/src/selfhost/bootstrap-compiler-selection.js +52 -0
  150. package/dist/src/selfhost/bootstrap-compiler-selection.js.map +1 -0
  151. package/dist/src/selfhost/bootstrap-execution-probe.d.ts +42 -0
  152. package/dist/src/selfhost/bootstrap-execution-probe.d.ts.map +1 -0
  153. package/dist/src/selfhost/bootstrap-execution-probe.js +115 -0
  154. package/dist/src/selfhost/bootstrap-execution-probe.js.map +1 -0
  155. package/dist/src/selfhost/bootstrap-rollback-decision.d.ts +40 -0
  156. package/dist/src/selfhost/bootstrap-rollback-decision.d.ts.map +1 -0
  157. package/dist/src/selfhost/bootstrap-rollback-decision.js +144 -0
  158. package/dist/src/selfhost/bootstrap-rollback-decision.js.map +1 -0
  159. package/dist/src/selfhost/bootstrap-shadow-history.d.ts +49 -0
  160. package/dist/src/selfhost/bootstrap-shadow-history.d.ts.map +1 -0
  161. package/dist/src/selfhost/bootstrap-shadow-history.js +348 -0
  162. package/dist/src/selfhost/bootstrap-shadow-history.js.map +1 -0
  163. package/dist/src/selfhost/bootstrap-shadow-report.d.ts +43 -0
  164. package/dist/src/selfhost/bootstrap-shadow-report.d.ts.map +1 -0
  165. package/dist/src/selfhost/bootstrap-shadow-report.js +94 -0
  166. package/dist/src/selfhost/bootstrap-shadow-report.js.map +1 -0
  167. package/dist/src/selfhost/bootstrap-stage-executor.d.ts +53 -0
  168. package/dist/src/selfhost/bootstrap-stage-executor.d.ts.map +1 -0
  169. package/dist/src/selfhost/bootstrap-stage-executor.js +159 -0
  170. package/dist/src/selfhost/bootstrap-stage-executor.js.map +1 -0
  171. package/dist/src/selfhost/bootstrap-stage-loader.d.ts +14 -0
  172. package/dist/src/selfhost/bootstrap-stage-loader.d.ts.map +1 -0
  173. package/dist/src/selfhost/bootstrap-stage-loader.js +112 -0
  174. package/dist/src/selfhost/bootstrap-stage-loader.js.map +1 -0
  175. package/dist/src/selfhost/bootstrap-stage-pipeline.d.ts +8 -0
  176. package/dist/src/selfhost/bootstrap-stage-pipeline.d.ts.map +1 -0
  177. package/dist/src/selfhost/bootstrap-stage-pipeline.js +85 -0
  178. package/dist/src/selfhost/bootstrap-stage-pipeline.js.map +1 -0
  179. package/dist/src/selfhost/bootstrap-stage-runner.d.ts +47 -0
  180. package/dist/src/selfhost/bootstrap-stage-runner.d.ts.map +1 -0
  181. package/dist/src/selfhost/bootstrap-stage-runner.js +106 -0
  182. package/dist/src/selfhost/bootstrap-stage-runner.js.map +1 -0
  183. package/dist/src/selfhost/clean-bootstrap-evidence.d.ts +83 -0
  184. package/dist/src/selfhost/clean-bootstrap-evidence.d.ts.map +1 -0
  185. package/dist/src/selfhost/clean-bootstrap-evidence.js +233 -0
  186. package/dist/src/selfhost/clean-bootstrap-evidence.js.map +1 -0
  187. package/dist/src/selfhost/compiler-facade.d.ts +33 -0
  188. package/dist/src/selfhost/compiler-facade.d.ts.map +1 -0
  189. package/dist/src/selfhost/compiler-facade.js +65 -0
  190. package/dist/src/selfhost/compiler-facade.js.map +1 -0
  191. package/dist/src/selfhost/contract.d.ts +151 -0
  192. package/dist/src/selfhost/contract.d.ts.map +1 -0
  193. package/dist/src/selfhost/contract.js +335 -0
  194. package/dist/src/selfhost/contract.js.map +1 -0
  195. package/dist/src/selfhost/differential-artifacts.d.ts +8 -0
  196. package/dist/src/selfhost/differential-artifacts.d.ts.map +1 -0
  197. package/dist/src/selfhost/differential-artifacts.js +50 -0
  198. package/dist/src/selfhost/differential-artifacts.js.map +1 -0
  199. package/dist/src/selfhost/differential-harness.d.ts +91 -0
  200. package/dist/src/selfhost/differential-harness.d.ts.map +1 -0
  201. package/dist/src/selfhost/differential-harness.js +246 -0
  202. package/dist/src/selfhost/differential-harness.js.map +1 -0
  203. package/dist/src/selfhost/full-language-inventory-runner.d.ts +49 -0
  204. package/dist/src/selfhost/full-language-inventory-runner.d.ts.map +1 -0
  205. package/dist/src/selfhost/full-language-inventory-runner.js +225 -0
  206. package/dist/src/selfhost/full-language-inventory-runner.js.map +1 -0
  207. package/dist/src/selfhost/full-language-inventory.d.ts +42 -0
  208. package/dist/src/selfhost/full-language-inventory.d.ts.map +1 -0
  209. package/dist/src/selfhost/full-language-inventory.js +163 -0
  210. package/dist/src/selfhost/full-language-inventory.js.map +1 -0
  211. package/dist/src/selfhost/interop-resolution-witness.d.ts +35 -0
  212. package/dist/src/selfhost/interop-resolution-witness.d.ts.map +1 -0
  213. package/dist/src/selfhost/interop-resolution-witness.js +186 -0
  214. package/dist/src/selfhost/interop-resolution-witness.js.map +1 -0
  215. package/dist/src/selfhost/kernel-model-host-adapter.d.ts +28 -0
  216. package/dist/src/selfhost/kernel-model-host-adapter.d.ts.map +1 -0
  217. package/dist/src/selfhost/kernel-model-host-adapter.js +36 -0
  218. package/dist/src/selfhost/kernel-model-host-adapter.js.map +1 -0
  219. package/dist/src/selfhost/legacy-adapter.d.ts +8 -0
  220. package/dist/src/selfhost/legacy-adapter.d.ts.map +1 -0
  221. package/dist/src/selfhost/legacy-adapter.js +186 -0
  222. package/dist/src/selfhost/legacy-adapter.js.map +1 -0
  223. package/dist/src/selfhost/module-graph.d.ts +57 -0
  224. package/dist/src/selfhost/module-graph.d.ts.map +1 -0
  225. package/dist/src/selfhost/module-graph.js +267 -0
  226. package/dist/src/selfhost/module-graph.js.map +1 -0
  227. package/dist/src/selfhost/mvp-adapter.d.ts +19 -0
  228. package/dist/src/selfhost/mvp-adapter.d.ts.map +1 -0
  229. package/dist/src/selfhost/mvp-adapter.js +186 -0
  230. package/dist/src/selfhost/mvp-adapter.js.map +1 -0
  231. package/dist/src/selfhost/node-executor.d.ts +6 -0
  232. package/dist/src/selfhost/node-executor.d.ts.map +1 -0
  233. package/dist/src/selfhost/node-executor.js +138 -0
  234. package/dist/src/selfhost/node-executor.js.map +1 -0
  235. package/dist/src/selfhost/project-compiler-adapter.d.ts +77 -0
  236. package/dist/src/selfhost/project-compiler-adapter.d.ts.map +1 -0
  237. package/dist/src/selfhost/project-compiler-adapter.js +358 -0
  238. package/dist/src/selfhost/project-compiler-adapter.js.map +1 -0
  239. package/dist/src/selfhost/project-differential-adapter.d.ts +15 -0
  240. package/dist/src/selfhost/project-differential-adapter.d.ts.map +1 -0
  241. package/dist/src/selfhost/project-differential-adapter.js +65 -0
  242. package/dist/src/selfhost/project-differential-adapter.js.map +1 -0
  243. package/dist/src/selfhost/promotion-evidence.d.ts +62 -0
  244. package/dist/src/selfhost/promotion-evidence.d.ts.map +1 -0
  245. package/dist/src/selfhost/promotion-evidence.js +332 -0
  246. package/dist/src/selfhost/promotion-evidence.js.map +1 -0
  247. package/dist/src/selfhost/promotion-policy-replay-v2.d.ts +54 -0
  248. package/dist/src/selfhost/promotion-policy-replay-v2.d.ts.map +1 -0
  249. package/dist/src/selfhost/promotion-policy-replay-v2.js +343 -0
  250. package/dist/src/selfhost/promotion-policy-replay-v2.js.map +1 -0
  251. package/dist/src/selfhost/promotion-shadow-history-v2.d.ts +55 -0
  252. package/dist/src/selfhost/promotion-shadow-history-v2.d.ts.map +1 -0
  253. package/dist/src/selfhost/promotion-shadow-history-v2.js +227 -0
  254. package/dist/src/selfhost/promotion-shadow-history-v2.js.map +1 -0
  255. package/dist/src/selfhost/promotion-subject.d.ts +34 -0
  256. package/dist/src/selfhost/promotion-subject.d.ts.map +1 -0
  257. package/dist/src/selfhost/promotion-subject.js +141 -0
  258. package/dist/src/selfhost/promotion-subject.js.map +1 -0
  259. package/dist/src/selfhost/source-manifest.d.ts +31 -0
  260. package/dist/src/selfhost/source-manifest.d.ts.map +1 -0
  261. package/dist/src/selfhost/source-manifest.js +194 -0
  262. package/dist/src/selfhost/source-manifest.js.map +1 -0
  263. package/dist/src/selfhost/stage-compiler-facade.d.ts +25 -0
  264. package/dist/src/selfhost/stage-compiler-facade.d.ts.map +1 -0
  265. package/dist/src/selfhost/stage-compiler-facade.js +100 -0
  266. package/dist/src/selfhost/stage-compiler-facade.js.map +1 -0
  267. package/dist/src/semantic-evidence/snapshot.d.ts +114 -0
  268. package/dist/src/semantic-evidence/snapshot.d.ts.map +1 -0
  269. package/dist/src/semantic-evidence/snapshot.js +336 -0
  270. package/dist/src/semantic-evidence/snapshot.js.map +1 -0
  271. package/dist/src/source.d.ts +25 -0
  272. package/dist/src/source.d.ts.map +1 -0
  273. package/dist/src/source.js +10 -0
  274. package/dist/src/source.js.map +1 -0
  275. package/dist/src/syntax/cst-to-ast.d.ts +108 -0
  276. package/dist/src/syntax/cst-to-ast.d.ts.map +1 -0
  277. package/dist/src/syntax/cst-to-ast.js +383 -0
  278. package/dist/src/syntax/cst-to-ast.js.map +1 -0
  279. package/dist/src/syntax/documentation.d.ts +11 -0
  280. package/dist/src/syntax/documentation.d.ts.map +1 -0
  281. package/dist/src/syntax/documentation.js +261 -0
  282. package/dist/src/syntax/documentation.js.map +1 -0
  283. package/dist/src/syntax/parser.d.ts +114 -0
  284. package/dist/src/syntax/parser.d.ts.map +1 -0
  285. package/dist/src/syntax/parser.js +970 -0
  286. package/dist/src/syntax/parser.js.map +1 -0
  287. package/dist/src/syntax/syntax-metadata.d.ts +8 -0
  288. package/dist/src/syntax/syntax-metadata.d.ts.map +1 -0
  289. package/dist/src/syntax/syntax-metadata.js +14 -0
  290. package/dist/src/syntax/syntax-metadata.js.map +1 -0
  291. package/dist/src/syntax/tokens.d.ts +96 -0
  292. package/dist/src/syntax/tokens.d.ts.map +1 -0
  293. package/dist/src/syntax/tokens.js +260 -0
  294. package/dist/src/syntax/tokens.js.map +1 -0
  295. package/dist/src/types/types.d.ts +97 -0
  296. package/dist/src/types/types.d.ts.map +1 -0
  297. package/dist/src/types/types.js +105 -0
  298. package/dist/src/types/types.js.map +1 -0
  299. package/package.json +47 -0
  300. package/schema/diagnostics-v1.schema.json +75 -0
@@ -0,0 +1,2985 @@
1
+ import { analyzeControlFlow } from '../analysis/control-flow.js';
2
+ import { DiagnosticBag } from '../diagnostics/diagnostic.js';
3
+ import { Scope, SymbolFactory } from '../binder/symbols.js';
4
+ import { TypeArena } from '../types/types.js';
5
+ import { builtinMember } from './builtin-members.js';
6
+ import { EffectRegistry } from './effect-registry.js';
7
+ import { TypeOperations } from './type-operations.js';
8
+ export class TypeChecker {
9
+ arena = new TypeArena();
10
+ diagnostics = new DiagnosticBag();
11
+ globalScope = new Scope();
12
+ #factory = new SymbolFactory();
13
+ #symbols = new Map();
14
+ #namedTypes = new Map();
15
+ #recordDeclarations = new Map();
16
+ #enumDeclarations = new Map();
17
+ #typeAliasDeclarations = new Map();
18
+ #newtypeDeclarations = new Map();
19
+ #effects = new EffectRegistry();
20
+ #variants = new Map();
21
+ #currentFunction;
22
+ #loopDepth = 0;
23
+ #currentComponentChildrenSlots = 0;
24
+ #signatureOnlyNodeIds;
25
+ #typeOnlyNodeIds;
26
+ #platform;
27
+ #moduleId;
28
+ #types;
29
+ #jsInteropProvider;
30
+ #containingFile;
31
+ #interopUsages = [];
32
+ #callableProjections = [];
33
+ #objectCallableProjections = [];
34
+ #frontendCallableProjections = [];
35
+ #moduleWitnesses = [];
36
+ #semanticOrder = 0;
37
+ #requiresJavaScriptInitialization = false;
38
+ constructor(options = {}) {
39
+ this.#signatureOnlyNodeIds = options.signatureOnlyNodeIds ?? new Set();
40
+ this.#typeOnlyNodeIds = options.typeOnlyNodeIds ?? new Set();
41
+ this.#platform = options.platform ?? 'neutral';
42
+ this.#moduleId = options.moduleId ?? '<module>';
43
+ this.#containingFile = options.containingFile ?? this.#moduleId;
44
+ this.#jsInteropProvider = options.jsInteropProvider;
45
+ this.#types = new TypeOperations({ arena: this.arena, diagnostics: this.diagnostics });
46
+ }
47
+ check(module) {
48
+ this.registerBuiltins(module.span);
49
+ this.registerForeignImports(module);
50
+ this.registerTypes(module);
51
+ this.finalizeTypes();
52
+ this.registerValues(module);
53
+ for (const declaration of module.declarations)
54
+ this.checkDeclaration(declaration);
55
+ return { arena: this.arena, diagnostics: this.diagnostics, globalScope: this.globalScope, symbols: this.#symbols, namedTypes: this.#namedTypes, frontendCallableProjections: this.#frontendCallableProjections, interop: { usages: this.#interopUsages, usageIR: this.#interopUsages.map(stableForeignUsage), callableProjections: this.#callableProjections, objectCallableProjections: this.#objectCallableProjections, moduleWitnesses: this.#moduleWitnesses, requiresJavaScriptInitialization: this.#requiresJavaScriptInitialization } };
56
+ }
57
+ registerForeignImports(module) {
58
+ for (const declaration of module.imports) {
59
+ if (declaration.sourceKind !== 'javascript')
60
+ continue;
61
+ if (declaration.public)
62
+ this.diagnostics.error('L4207', 'JavaScript imports cannot be publicly re-exported; wrap the value in a Virune newtype or adapter', declaration.span);
63
+ if (/^\.{1,2}\//u.test(declaration.source) && declaration.source.endsWith('.ts') && !declaration.source.endsWith('.interop.ts'))
64
+ this.diagnostics.error('L4210', 'Direct TypeScript imports are not executed at runtime; rename the file to *.interop.ts so Virune can type-check and emit it', declaration.span);
65
+ if (declaration.typeOnly && (declaration.defaultImport !== undefined || declaration.namespaceImport !== undefined)) {
66
+ this.diagnostics.error('L4208', 'Type-only JavaScript imports support named imports only in v1', declaration.span);
67
+ continue;
68
+ }
69
+ this.#requiresJavaScriptInitialization = this.#requiresJavaScriptInitialization || !declaration.typeOnly;
70
+ const bindings = [];
71
+ if (declaration.defaultImport !== undefined)
72
+ bindings.push({ local: declaration.defaultImport, imported: 'default', kind: declaration.typeOnly ? 'type-only' : 'default', span: declaration.span });
73
+ else if (declaration.namespaceImport !== undefined)
74
+ bindings.push({ local: declaration.namespaceImport, imported: '*', kind: declaration.typeOnly ? 'type-only' : 'namespace', span: declaration.span });
75
+ else if (declaration.items.length > 0)
76
+ for (const item of declaration.items)
77
+ bindings.push({ local: item.local, imported: item.imported, kind: declaration.typeOnly ? 'type-only' : 'named', span: item.span });
78
+ else
79
+ bindings.push({ kind: 'side-effect', span: declaration.span });
80
+ if (this.#jsInteropProvider === undefined) {
81
+ this.diagnostics.error('L4200', `JavaScript import ${declaration.source} requires a JavaScript interop provider`, declaration.span);
82
+ continue;
83
+ }
84
+ for (const binding of bindings) {
85
+ try {
86
+ const resolution = this.#jsInteropProvider.resolveImport({ containingFile: this.#containingFile, moduleSpecifier: declaration.source, kind: binding.kind, ...(binding.imported === undefined ? {} : { importedName: binding.imported }), platform: this.#platform });
87
+ this.#moduleWitnesses.push(resolution.witness);
88
+ if (binding.kind === 'named' && resolution.witness.runtimeFormat === 'commonjs') {
89
+ this.diagnostics.error('L4211', `Named imports from CommonJS module ${declaration.source} are not portable; use a default or namespace import, or a TypeScript interop adapter`, binding.span);
90
+ continue;
91
+ }
92
+ if (resolution.type?.category === 'any') {
93
+ this.diagnostics.error('L4212', `JavaScript import ${binding.imported ?? declaration.source} resolves to TypeScript any; expose unknown through an interop adapter or use unsafe extern js`, binding.span);
94
+ continue;
95
+ }
96
+ if (binding.local === undefined || resolution.type === undefined)
97
+ continue;
98
+ const typeId = this.arena.foreign(resolution.type);
99
+ const symbol = this.#factory.create(binding.local, 'import', typeId, binding.span, { public: declaration.public, typeOnly: declaration.typeOnly });
100
+ if (!this.globalScope.define(symbol))
101
+ this.diagnostics.error('L1001', `Duplicate definition ${binding.local}`, binding.span);
102
+ else
103
+ this.#symbols.set(symbol.id, symbol);
104
+ this.#interopUsages.push({ kind: 'import', nodeId: declaration.id, span: declaration.span, foreignType: resolution.type, runtimeImport: resolution.runtime, moduleWitness: resolution.witness });
105
+ }
106
+ catch (error) {
107
+ this.diagnostics.error('L4201', `Cannot resolve JavaScript import ${declaration.source}: ${error instanceof Error ? error.message : String(error)}`, declaration.span);
108
+ }
109
+ }
110
+ }
111
+ }
112
+ registerBuiltins(span) {
113
+ for (const effect of ['Console', 'Task', 'File', 'Process', 'Network', 'Timer', 'Clock', 'Storage', 'Dom', 'Random', 'JavaScript'])
114
+ this.#effects.registerBuiltin(effect);
115
+ for (const [name, type] of [
116
+ ['Bool', this.arena.bool], ['Int', this.arena.int], ['Float', this.arena.float], ['BigInt', this.arena.bigint], ['String', this.arena.string],
117
+ ['Unit', this.arena.unit], ['Unknown', this.arena.unknown], ['Never', this.arena.never],
118
+ ])
119
+ this.#namedTypes.set(name, type);
120
+ const jsError = this.arena.add({ kind: 'named', name: 'JsError', definitionId: 'std:JsError', declarationKind: 'record', arguments: [], fields: new Map([['name', this.arena.string], ['message', this.arena.string]]) });
121
+ this.#namedTypes.set('JsError', jsError);
122
+ const jsonError = this.arena.add({ kind: 'named', name: 'JsonError', definitionId: 'std:JsonError', declarationKind: 'record', arguments: [], fields: new Map([['path', this.arena.string], ['expected', this.arena.string], ['actual', this.arena.string], ['message', this.arena.string]]) });
123
+ this.#namedTypes.set('JsonError', jsonError);
124
+ const duration = this.arena.add({ kind: 'named', name: 'Duration', definitionId: 'std:Duration', declarationKind: 'newtype', arguments: [], underlying: this.arena.int });
125
+ this.#namedTypes.set('Duration', duration);
126
+ const taskTimeoutError = this.arena.add({ kind: 'named', name: 'TaskTimeoutError', definitionId: 'std:TaskTimeoutError', declarationKind: 'record', arguments: [], fields: new Map([['milliseconds', this.arena.int]]) });
127
+ this.#namedTypes.set('TaskTimeoutError', taskTimeoutError);
128
+ const supervisorRestartLimitError = this.arena.add({ kind: 'named', name: 'SupervisorRestartLimitError', definitionId: 'std:SupervisorRestartLimitError', declarationKind: 'record', arguments: [], fields: new Map([['restarts', this.arena.int]]) });
129
+ this.#namedTypes.set('SupervisorRestartLimitError', supervisorRestartLimitError);
130
+ const bytes = this.arena.add({ kind: 'named', name: 'Bytes', definitionId: 'std:Bytes', declarationKind: 'newtype', arguments: [], underlying: this.arena.unknown, derives: new Set(['Eq', 'Hash', 'Debug']) });
131
+ const mutableBytes = this.arena.add({ kind: 'named', name: 'MutableBytes', definitionId: 'std:MutableBytes', declarationKind: 'newtype', arguments: [], underlying: this.arena.unknown, mustUse: true });
132
+ this.#namedTypes.set('Bytes', bytes);
133
+ this.#namedTypes.set('MutableBytes', mutableBytes);
134
+ const byteOrder = this.arena.add({ kind: 'named', name: 'ByteOrder', definitionId: 'std:ByteOrder', declarationKind: 'enum', arguments: [], variants: new Map([['BigEndian', []], ['LittleEndian', []]]), derives: new Set(['Eq', 'Hash', 'Debug']) });
135
+ this.#namedTypes.set('ByteOrder', byteOrder);
136
+ const bytesError = this.arena.add({ kind: 'named', name: 'BytesError', definitionId: 'std:BytesError', declarationKind: 'record', arguments: [], fields: new Map([['message', this.arena.string]]), derives: new Set(['Eq', 'Hash', 'Debug']) });
137
+ this.#namedTypes.set('BytesError', bytesError);
138
+ const integerRangeError = this.arena.add({ kind: 'named', name: 'IntegerRangeError', definitionId: 'std:IntegerRangeError', declarationKind: 'record', arguments: [], fields: new Map([['type', this.arena.string], ['value', this.arena.string]]), derives: new Set(['Eq', 'Hash', 'Debug']) });
139
+ this.#namedTypes.set('IntegerRangeError', integerRangeError);
140
+ for (const [name, underlying] of [['Byte', this.arena.int], ['Int8', this.arena.int], ['UInt8', this.arena.int], ['Int16', this.arena.int], ['UInt16', this.arena.int], ['Int32', this.arena.int], ['UInt32', this.arena.int], ['Int64', this.arena.bigint], ['UInt64', this.arena.bigint]]) {
141
+ this.#namedTypes.set(name, this.arena.add({ kind: 'named', name, definitionId: `std:${name}`, declarationKind: 'newtype', arguments: [], underlying, derives: new Set(['Eq', 'Hash', 'Debug']) }));
142
+ }
143
+ const httpBody = this.arena.add({ kind: 'named', name: 'HttpBody', definitionId: 'std:HttpBody', declarationKind: 'enum', arguments: [], variants: new Map([['Empty', []], ['Text', [this.arena.string]], ['Bytes', [bytes]]]), derives: new Set(['Eq', 'Hash', 'Debug']) });
144
+ this.#namedTypes.set('HttpBody', httpBody);
145
+ const httpResponse = this.arena.add({ kind: 'named', name: 'HttpResponse', definitionId: 'std:HttpResponse', declarationKind: 'record', arguments: [], fields: new Map([
146
+ ['status', this.arena.int], ['ok', this.arena.bool], ['headers', this.arena.map(this.arena.string, this.arena.string)], ['body', httpBody],
147
+ ]) });
148
+ this.#namedTypes.set('HttpResponse', httpResponse);
149
+ const fileHandle = this.arena.add({ kind: 'named', name: 'FileHandle', definitionId: 'std:FileHandle', declarationKind: 'newtype', arguments: [], underlying: this.arena.unknown, mustUse: true });
150
+ this.#namedTypes.set('FileHandle', fileHandle);
151
+ const t = this.arena.variable('T');
152
+ const e = this.arena.variable('E');
153
+ this.defineBuiltin('Unit', this.arena.unit, span);
154
+ this.defineBuiltin('expect', this.arena.function([this.arena.bool], this.arena.unit), span);
155
+ this.defineBuiltin('panic', this.arena.function([this.arena.string], this.arena.never), span);
156
+ this.defineBuiltin('Some', this.arena.function([t], this.arena.option(t), ['T']), span);
157
+ this.defineBuiltin('Ok', this.arena.function([t], this.arena.result(t, e), ['T', 'E']), span);
158
+ this.defineBuiltin('Err', this.arena.function([e], this.arena.result(t, e), ['T', 'E']), span);
159
+ this.defineBuiltin('None', this.arena.option(t), span);
160
+ for (const namespace of ['List', 'Map', 'Set', 'Int', 'Float', 'String', 'Json', 'Console', 'Debug', 'Option', 'Result', 'Validation', 'Task', 'Duration', 'Queue', 'Stack', 'Stream', 'File', 'Path', 'Process', 'Http', 'HttpBody', 'Fetch', 'Timer', 'Storage', 'Dom', 'Crypto', 'Url', 'Bytes', 'MutableBytes', 'ByteOrder', 'Byte', 'Int8', 'UInt8', 'Int16', 'UInt16', 'Int32', 'UInt32', 'Int64', 'UInt64'])
161
+ this.defineBuiltin(namespace, this.arena.unknown, span);
162
+ }
163
+ defineBuiltin(name, typeId, span) {
164
+ const symbol = this.#factory.create(name, 'builtin', typeId, span);
165
+ this.globalScope.defineAllowParent(symbol);
166
+ this.#symbols.set(symbol.id, symbol);
167
+ }
168
+ registerTypes(module) {
169
+ for (const declaration of module.declarations) {
170
+ let declarationKind;
171
+ if (declaration.kind === 'RecordDeclaration')
172
+ declarationKind = 'record';
173
+ else if (declaration.kind === 'EnumDeclaration')
174
+ declarationKind = 'enum';
175
+ else if (declaration.kind === 'NewtypeDeclaration')
176
+ declarationKind = 'newtype';
177
+ else if (declaration.kind === 'TypeAliasDeclaration')
178
+ declarationKind = 'alias';
179
+ if (declarationKind === undefined)
180
+ continue;
181
+ const typeDeclaration = declaration;
182
+ if (this.#namedTypes.has(typeDeclaration.name)) {
183
+ this.diagnostics.error('L1001', `Duplicate type name ${typeDeclaration.name}`, typeDeclaration.span);
184
+ continue;
185
+ }
186
+ const typeId = this.arena.add({ kind: 'named', name: typeDeclaration.name, definitionId: typeDeclaration.definitionId ?? `${this.#moduleId}#${typeDeclaration.name}`, declarationKind, arguments: [] });
187
+ this.#namedTypes.set(typeDeclaration.name, typeId);
188
+ const symbol = this.#factory.create(typeDeclaration.name, 'type', typeId, typeDeclaration.span, { declaration: typeDeclaration, public: typeDeclaration.public, typeOnly: this.#typeOnlyNodeIds.has(typeDeclaration.id) });
189
+ typeDeclaration.symbolId = symbol.id;
190
+ this.#symbols.set(symbol.id, symbol);
191
+ if (!this.globalScope.define(symbol))
192
+ this.diagnostics.error('L1002', `Name ${typeDeclaration.name} is already defined`, typeDeclaration.span);
193
+ if (declaration.kind === 'RecordDeclaration')
194
+ this.#recordDeclarations.set(declaration.name, declaration);
195
+ if (declaration.kind === 'EnumDeclaration')
196
+ this.#enumDeclarations.set(declaration.name, declaration);
197
+ if (declaration.kind === 'TypeAliasDeclaration')
198
+ this.#typeAliasDeclarations.set(declaration.name, declaration);
199
+ if (declaration.kind === 'NewtypeDeclaration')
200
+ this.#newtypeDeclarations.set(declaration.name, declaration);
201
+ }
202
+ }
203
+ finalizeTypes() {
204
+ for (const [name, declaration] of this.#recordDeclarations) {
205
+ const typeParameters = new Map(declaration.typeParameters.map(item => [item.name, this.arena.variable(item.name)]));
206
+ const fields = new Map();
207
+ for (const field of declaration.fields) {
208
+ if (fields.has(field.name))
209
+ this.diagnostics.error('L1003', `Duplicate field ${field.name}`, field.span);
210
+ const fieldType = this.resolveTypeReference(field.type, typeParameters);
211
+ if (this.containsOpenEffect(fieldType))
212
+ this.diagnostics.error('L2113', 'uses * callbacks are non-escaping and cannot be stored in record fields', field.span);
213
+ fields.set(field.name, fieldType);
214
+ }
215
+ this.arena.setNamedDetails(this.#namedTypes.get(name), { fields, derives: new Set(declaration.derives), mustUse: declaration.attributes.some(attribute => attribute.name === 'mustUse') });
216
+ }
217
+ for (const [name, declaration] of this.#newtypeDeclarations) {
218
+ const underlying = this.resolveTypeReference(declaration.underlying, new Map());
219
+ if (this.containsOpenEffect(underlying))
220
+ this.diagnostics.error('L2113', 'uses * callbacks are non-escaping and cannot be stored in newtypes', declaration.span);
221
+ this.arena.setNamedDetails(this.#namedTypes.get(name), { underlying, mustUse: declaration.attributes.some(attribute => attribute.name === 'mustUse') });
222
+ }
223
+ for (const [name, declaration] of this.#typeAliasDeclarations) {
224
+ const underlying = this.resolveTypeReference(declaration.target, new Map());
225
+ if (this.containsOpenEffect(underlying))
226
+ this.diagnostics.error('L2113', 'uses * callbacks are non-escaping and cannot be stored in type aliases', declaration.span);
227
+ this.arena.setNamedDetails(this.#namedTypes.get(name), { underlying });
228
+ }
229
+ for (const [name, declaration] of this.#enumDeclarations) {
230
+ const typeParameters = new Map(declaration.typeParameters.map(item => [item.name, this.arena.variable(item.name)]));
231
+ const variants = new Map();
232
+ for (const variant of declaration.variants) {
233
+ if (variants.has(variant.name) || this.globalScope.lookupCurrent(variant.name) !== undefined)
234
+ this.diagnostics.error('L1004', `Duplicate enum variant ${variant.name}`, variant.span);
235
+ const values = variant.values.map(type => this.resolveTypeReference(type, typeParameters));
236
+ if (values.some(value => this.containsOpenEffect(value)))
237
+ this.diagnostics.error('L2113', 'uses * callbacks are non-escaping and cannot be stored in enum payloads', variant.span);
238
+ variants.set(variant.name, values);
239
+ }
240
+ this.arena.setNamedDetails(this.#namedTypes.get(name), { variants, derives: new Set(declaration.derives), mustUse: declaration.attributes.some(attribute => attribute.name === 'mustUse') });
241
+ }
242
+ }
243
+ registerValues(module) {
244
+ for (const declaration of module.declarations) {
245
+ if (declaration.kind === 'FunctionDeclaration') {
246
+ const typeParameters = new Map(declaration.typeParameters.map(item => [item.name, this.arena.variable(item.name)]));
247
+ const parameters = declaration.parameters.map(item => this.resolveTypeReference(item.type, typeParameters));
248
+ const result = declaration.returnType === undefined ? this.arena.error : this.resolveTypeReference(declaration.returnType, typeParameters);
249
+ const typeId = this.arena.function(parameters, result, declaration.typeParameters.map(item => item.name), declaration.async, declaration.effects);
250
+ const symbol = this.defineValue(declaration.name, 'function', typeId, declaration.span, declaration.public, declaration);
251
+ if (symbol !== undefined)
252
+ declaration.symbolId = symbol.id;
253
+ }
254
+ else if (declaration.kind === 'ComponentDeclaration') {
255
+ const parameters = declaration.parameters.map(item => this.resolveTypeReference(item.type, new Map()));
256
+ const typeId = this.arena.function(parameters, this.arena.error, [], false, declaration.effects);
257
+ const symbol = this.defineValue(declaration.name, 'component', typeId, declaration.span, declaration.public, declaration);
258
+ if (symbol !== undefined)
259
+ declaration.symbolId = symbol.id;
260
+ }
261
+ else if (declaration.kind === 'TopLevelLetDeclaration') {
262
+ const typeId = declaration.annotation === undefined ? this.arena.error : this.resolveTypeReference(declaration.annotation, new Map());
263
+ const symbol = this.defineValue(declaration.name, 'variable', typeId, declaration.span, declaration.public, declaration, declaration.constant);
264
+ if (symbol !== undefined)
265
+ declaration.symbolId = symbol.id;
266
+ }
267
+ else if (declaration.kind === 'ExternDeclaration') {
268
+ for (const fn of declaration.functions) {
269
+ const parameters = fn.parameters.map(item => this.resolveTypeReference(item.type, new Map()));
270
+ const result = this.resolveTypeReference(fn.returnType, new Map());
271
+ const symbol = this.defineValue(fn.name, 'extern', this.arena.function(parameters, result, [], fn.async, fn.effects), fn.span, false, fn);
272
+ if (symbol !== undefined)
273
+ fn.symbolId = symbol.id;
274
+ }
275
+ }
276
+ }
277
+ for (const [enumName, declaration] of this.#enumDeclarations) {
278
+ const enumType = this.#namedTypes.get(enumName);
279
+ const type = this.arena.get(enumType);
280
+ if (type.kind !== 'named' || type.variants === undefined)
281
+ continue;
282
+ const genericArguments = declaration.typeParameters.map(parameter => this.arena.variable(parameter.name));
283
+ const constructedEnumType = genericArguments.length === 0 ? enumType : this.arena.namedInstance(type, genericArguments);
284
+ for (const variant of declaration.variants) {
285
+ const valueTypes = type.variants.get(variant.name) ?? [];
286
+ const typeId = valueTypes.length === 0 ? constructedEnumType : this.arena.function(valueTypes, constructedEnumType, declaration.typeParameters.map(item => item.name));
287
+ const symbol = this.defineValue(variant.name, 'variant', typeId, variant.span, declaration.public, declaration);
288
+ if (symbol !== undefined) {
289
+ variant.symbolId = symbol.id;
290
+ this.#variants.set(variant.name, { enumType: constructedEnumType, valueTypes, symbol });
291
+ }
292
+ }
293
+ }
294
+ }
295
+ defineValue(name, kind, typeId, span, publicValue, declaration, constant = false) {
296
+ const symbol = this.#factory.create(name, kind, typeId, span, { declaration, public: publicValue, typeOnly: this.#typeOnlyNodeIds.has(declaration.id), constant });
297
+ if (!this.globalScope.define(symbol)) {
298
+ this.diagnostics.error('L1005', `Name ${name} is already defined`, span);
299
+ return undefined;
300
+ }
301
+ this.#symbols.set(symbol.id, symbol);
302
+ return symbol;
303
+ }
304
+ checkDeclaration(declaration) {
305
+ if (this.#signatureOnlyNodeIds.has(declaration.id))
306
+ return;
307
+ this.checkAttributes(declaration);
308
+ switch (declaration.kind) {
309
+ case 'FunctionDeclaration':
310
+ this.checkFunction(declaration);
311
+ break;
312
+ case 'ComponentDeclaration':
313
+ this.checkComponent(declaration);
314
+ break;
315
+ case 'TopLevelLetDeclaration': {
316
+ if (declaration.public && !declaration.constant)
317
+ this.diagnostics.error('L2080', 'Only const declarations can be public', declaration.span);
318
+ if (declaration.public && declaration.annotation === undefined)
319
+ this.diagnostics.error('L2081', 'Public const declarations require an explicit type', declaration.span);
320
+ const annotated = declaration.annotation === undefined ? undefined : this.resolveTypeReference(declaration.annotation, new Map());
321
+ const valueType = this.checkExpression(declaration.value, this.globalScope, annotated);
322
+ const expected = annotated ?? valueType;
323
+ if (this.containsOpenEffect(expected))
324
+ this.diagnostics.error('L2113', 'uses * callbacks are non-escaping and cannot be stored in top-level declarations', declaration.span);
325
+ if (!this.isAssignable(valueType, expected))
326
+ this.typeMismatch(valueType, expected, declaration.value.span);
327
+ if (declaration.constant && !this.isConstantExpression(declaration.value))
328
+ this.diagnostics.error('L2082', 'const initializer must be evaluable without function calls, mutable state, async work, or external values', declaration.value.span);
329
+ declaration.inferredTypeId = expected;
330
+ if (declaration.symbolId !== undefined)
331
+ this.#symbols.get(declaration.symbolId).typeId = expected;
332
+ break;
333
+ }
334
+ case 'TestDeclaration':
335
+ this.checkTest(declaration);
336
+ break;
337
+ case 'RecordDeclaration':
338
+ this.checkRecordFieldAttributes(declaration);
339
+ this.checkDerives(declaration.derives, declaration.span, declaration.name);
340
+ break;
341
+ case 'EnumDeclaration':
342
+ this.checkDerives(declaration.derives, declaration.span, declaration.name);
343
+ break;
344
+ case 'ExternDeclaration':
345
+ this.checkExtern(declaration);
346
+ break;
347
+ default: break;
348
+ }
349
+ }
350
+ checkFunction(declaration) {
351
+ this.validateEffects(declaration.effects, declaration.span);
352
+ if (declaration.public && declaration.returnType === undefined)
353
+ this.diagnostics.error('L2001', 'Public functions require an explicit return type', declaration.span);
354
+ if ((declaration.async || declaration.typeParameters.length > 0) && declaration.returnType === undefined)
355
+ this.diagnostics.error('L2002', 'Async and generic functions require an explicit return type', declaration.span);
356
+ const scope = new Scope(this.globalScope);
357
+ const typeParameters = new Map(declaration.typeParameters.map(item => [item.name, this.arena.variable(item.name)]));
358
+ for (const parameter of declaration.parameters) {
359
+ if (parameter.optional)
360
+ this.diagnostics.error('L2114', 'Optional parameters are supported only in extern js declarations', parameter.span);
361
+ const typeId = this.resolveTypeReference(parameter.type, typeParameters);
362
+ const parameterType = this.arena.get(typeId);
363
+ if (this.containsOpenEffect(typeId) && !(parameterType.kind === 'function' && parameterType.effects.includes('*')))
364
+ this.diagnostics.error('L2113', 'uses * is allowed only on a direct non-escaping callback parameter', parameter.span);
365
+ const symbol = this.#factory.create(parameter.name, 'parameter', typeId, parameter.span, { declaration });
366
+ if (!scope.define(symbol))
367
+ this.diagnostics.error('L1006', `Parameter ${parameter.name} shadows an existing name`, parameter.span);
368
+ else {
369
+ parameter.symbolId = symbol.id;
370
+ this.#symbols.set(symbol.id, symbol);
371
+ }
372
+ }
373
+ const resolvedReturnType = declaration.returnType === undefined ? undefined : this.resolveTypeReference(declaration.returnType, typeParameters);
374
+ if (resolvedReturnType !== undefined && this.containsOpenEffect(resolvedReturnType))
375
+ this.diagnostics.error('L2113', 'uses * callbacks are non-escaping and cannot be returned', declaration.returnType?.span ?? declaration.span);
376
+ const context = { declaration, scope, async: declaration.async, returnTypes: [], typeParameters, effects: new Set(declaration.effects), ...(resolvedReturnType === undefined ? {} : { returnType: resolvedReturnType }) };
377
+ if (declaration.public) {
378
+ for (const parameter of declaration.parameters) {
379
+ const symbol = parameter.symbolId === undefined ? undefined : this.#symbols.get(parameter.symbolId);
380
+ if (symbol !== undefined && this.containsForeignType(symbol.typeId))
381
+ this.diagnostics.error('L4209', `Public function ${declaration.name} cannot expose JavaScript foreign type ${this.arena.display(symbol.typeId)}; wrap it in a Virune newtype`, parameter.span);
382
+ }
383
+ if (context.returnType !== undefined && this.containsForeignType(context.returnType))
384
+ this.diagnostics.error('L4209', `Public function ${declaration.name} cannot return JavaScript foreign type ${this.arena.display(context.returnType)}; wrap it in a Virune newtype`, declaration.span);
385
+ }
386
+ if (declaration.attributes.some(attribute => attribute.name === 'jsExport')) {
387
+ for (const parameter of declaration.parameters) {
388
+ const symbol = parameter.symbolId === undefined ? undefined : this.#symbols.get(parameter.symbolId);
389
+ if (symbol !== undefined && !this.isSafeFfiType(symbol.typeId))
390
+ this.diagnostics.error('L4213', `@jsExport parameter ${parameter.name} has type ${this.arena.display(symbol.typeId)}, which cannot be fully validated at the JavaScript boundary; use Unknown and decode explicitly`, parameter.span);
391
+ }
392
+ if (context.returnType !== undefined && !this.isSafeFfiType(context.returnType))
393
+ this.diagnostics.error('L4213', `@jsExport return type ${this.arena.display(context.returnType)} cannot be fully validated at the JavaScript boundary; use Unknown and encode explicitly`, declaration.returnType?.span ?? declaration.span);
394
+ }
395
+ const previous = this.#currentFunction;
396
+ const previousLoopDepth = this.#loopDepth;
397
+ this.#currentFunction = context;
398
+ this.#loopDepth = 0;
399
+ if (declaration.expressionBody)
400
+ context.returnTypes.push(this.checkExpression(declaration.body, scope, context.returnType));
401
+ else
402
+ this.checkBlock(declaration.body, scope);
403
+ this.#currentFunction = previous;
404
+ this.#loopDepth = previousLoopDepth;
405
+ const inferred = context.returnType ?? this.commonType(context.returnTypes, declaration.span);
406
+ if (declaration.returnType !== undefined)
407
+ for (const typeId of context.returnTypes)
408
+ if (!this.isAssignable(typeId, inferred))
409
+ this.typeMismatch(typeId, inferred, declaration.span);
410
+ if (!declaration.expressionBody) {
411
+ const controlFlow = analyzeControlFlow(declaration.body, this.arena);
412
+ if (inferred !== this.arena.unit && !controlFlow.alwaysTerminates)
413
+ this.diagnostics.error('L3001', `Function ${declaration.name} does not return on every path`, declaration.span);
414
+ for (const span of controlFlow.unreachable)
415
+ this.diagnostics.error('L3006', 'Unreachable statement', span);
416
+ }
417
+ declaration.inferredTypeId = inferred;
418
+ if (declaration.symbolId !== undefined) {
419
+ const symbol = this.#symbols.get(declaration.symbolId);
420
+ const functionType = this.arena.get(symbol.typeId);
421
+ if (functionType.kind === 'function')
422
+ symbol.typeId = this.arena.function(functionType.parameters, inferred, functionType.typeParameters, functionType.async, functionType.effects);
423
+ }
424
+ }
425
+ checkComponent(declaration) {
426
+ this.validateEffects(declaration.effects, declaration.span);
427
+ if (declaration.effects.includes('*'))
428
+ this.diagnostics.error('L2113', 'uses * is allowed only on non-escaping callback parameters', declaration.span);
429
+ if (declaration.public)
430
+ this.diagnostics.error('L4301', 'Components cannot be public; use private or internal visibility', declaration.span);
431
+ if (!declaration.effects.includes('JavaScript'))
432
+ this.diagnostics.error('L4302', 'Components require an explicit uses JavaScript effect', declaration.span);
433
+ const scope = new Scope(this.globalScope);
434
+ for (const parameter of declaration.parameters) {
435
+ if (parameter.optional)
436
+ this.diagnostics.error('L2114', 'Optional parameters are supported only in extern js declarations', parameter.span);
437
+ const typeId = this.resolveTypeReference(parameter.type, new Map());
438
+ const parameterType = this.arena.get(typeId);
439
+ if (this.containsOpenEffect(typeId) && !(parameterType.kind === 'function' && parameterType.effects.includes('*')))
440
+ this.diagnostics.error('L2113', 'uses * is allowed only on a direct non-escaping callback parameter', parameter.span);
441
+ const symbol = this.#factory.create(parameter.name, 'parameter', typeId, parameter.span, { declaration });
442
+ if (!scope.define(symbol))
443
+ this.diagnostics.error('L1006', `Parameter ${parameter.name} shadows an existing name`, parameter.span);
444
+ else {
445
+ parameter.symbolId = symbol.id;
446
+ this.#symbols.set(symbol.id, symbol);
447
+ }
448
+ }
449
+ const context = { declaration, scope, async: false, returnTypes: [], typeParameters: new Map(), effects: new Set(declaration.effects) };
450
+ const previous = this.#currentFunction;
451
+ const previousLoopDepth = this.#loopDepth;
452
+ const previousChildrenSlots = this.#currentComponentChildrenSlots;
453
+ this.#currentFunction = context;
454
+ this.#loopDepth = 0;
455
+ this.#currentComponentChildrenSlots = 0;
456
+ this.checkBlock(declaration.body, scope);
457
+ const controlFlow = analyzeControlFlow(declaration.body, this.arena);
458
+ if (!controlFlow.alwaysTerminates)
459
+ this.diagnostics.error('L4304', `Component ${declaration.name} must return view on every path`, declaration.span);
460
+ for (const span of controlFlow.unreachable)
461
+ this.diagnostics.error('L3006', 'Unreachable statement', span);
462
+ this.#currentFunction = previous;
463
+ this.#loopDepth = previousLoopDepth;
464
+ this.#currentComponentChildrenSlots = previousChildrenSlots;
465
+ }
466
+ checkTest(declaration) {
467
+ const scope = new Scope(this.globalScope);
468
+ const previous = this.#currentFunction;
469
+ const previousLoopDepth = this.#loopDepth;
470
+ this.#currentFunction = { declaration, scope, async: declaration.async, returnType: this.arena.unit, returnTypes: [], typeParameters: new Map(), effects: new Set(['*']) };
471
+ this.#loopDepth = 0;
472
+ this.checkBlock(declaration.body, scope);
473
+ this.#currentFunction = previous;
474
+ this.#loopDepth = previousLoopDepth;
475
+ }
476
+ checkExtern(declaration) {
477
+ for (const fn of declaration.functions) {
478
+ this.validateEffects(fn.effects, fn.span);
479
+ let optionalSeen = false;
480
+ for (const parameter of fn.parameters) {
481
+ if (parameter.optional)
482
+ optionalSeen = true;
483
+ else if (optionalSeen)
484
+ this.diagnostics.error('L2115', 'Optional extern parameters must be trailing', parameter.span);
485
+ const parameterType = this.resolveTypeReference(parameter.type, new Map());
486
+ if (!declaration.unsafe && !this.isSafeFfiType(parameterType))
487
+ this.diagnostics.error('L4213', `Safe extern parameter ${parameter.name} has type ${this.arena.display(parameterType)}, which cannot be fully validated; use Unknown or an adapter`, parameter.span);
488
+ }
489
+ const returnType = this.resolveTypeReference(fn.returnType, new Map());
490
+ if (!declaration.unsafe) {
491
+ const type = this.arena.get(returnType);
492
+ if (type.kind !== 'result' && !(type.kind === 'future' && this.arena.get(type.value).kind === 'result'))
493
+ this.diagnostics.error('L4001', `Safe extern function ${fn.name} must return Result`, fn.span);
494
+ if (!this.isSafeFfiType(returnType))
495
+ this.diagnostics.error('L4213', `Safe extern return type ${this.arena.display(returnType)} cannot be fully validated; use Result<Unknown, E> or an adapter`, fn.returnType.span);
496
+ }
497
+ }
498
+ }
499
+ checkAttributes(declaration) {
500
+ const seen = new Set();
501
+ for (const attribute of declaration.attributes) {
502
+ if (seen.has(attribute.name))
503
+ this.diagnostics.error('L2051', `Duplicate attribute @${attribute.name}`, attribute.span);
504
+ seen.add(attribute.name);
505
+ if (attribute.name === 'jsExport') {
506
+ if (declaration.kind !== 'FunctionDeclaration')
507
+ this.diagnostics.error('L2052', '@jsExport can be used only on functions', attribute.span);
508
+ else {
509
+ if (!declaration.public)
510
+ this.diagnostics.error('L2053', '@jsExport function must be public', attribute.span);
511
+ if (declaration.typeParameters.length > 0)
512
+ this.diagnostics.error('L2054', '@jsExport does not support generic functions', attribute.span);
513
+ if (attribute.arguments.length > 0)
514
+ this.diagnostics.error('L2055', '@jsExport does not accept arguments', attribute.span);
515
+ }
516
+ }
517
+ else if (attribute.name === 'mustUse') {
518
+ if (!['RecordDeclaration', 'EnumDeclaration', 'NewtypeDeclaration'].includes(declaration.kind))
519
+ this.diagnostics.error('L2090', '@mustUse can be used only on record, enum, or newtype declarations', attribute.span);
520
+ if (attribute.arguments.length > 0)
521
+ this.diagnostics.error('L2091', '@mustUse does not accept arguments', attribute.span);
522
+ }
523
+ else if (attribute.name === 'json') {
524
+ const strict = attribute.arguments.length === 1 && attribute.arguments[0]?.kind === 'IdentifierExpression' && attribute.arguments[0].name === 'strict';
525
+ if (declaration.kind !== 'RecordDeclaration')
526
+ this.diagnostics.error('L2056', '@json can be used only on records', attribute.span);
527
+ else {
528
+ if (!strict)
529
+ this.diagnostics.error('L2057', '@json currently accepts only the strict argument', attribute.span);
530
+ if (!declaration.derives.includes('Json'))
531
+ this.diagnostics.error('L2058', '@json(strict) requires derives Json', attribute.span);
532
+ }
533
+ }
534
+ else if (attribute.name === 'repetitionHost') {
535
+ if (declaration.kind !== 'ExternDeclaration')
536
+ this.diagnostics.error('L2130', '@repetitionHost can be used only on extern js declarations', attribute.span);
537
+ else {
538
+ const exportName = attribute.arguments[0];
539
+ const version = attribute.arguments[1];
540
+ const validShape = attribute.arguments.length === 2
541
+ && exportName?.kind === 'LiteralExpression' && exportName.literalKind === 'String'
542
+ && version?.kind === 'LiteralExpression' && version.literalKind === 'Int';
543
+ if (!validShape)
544
+ this.diagnostics.error('L2131', '@repetitionHost requires exactly a String literal export name and Int literal protocol version', attribute.span);
545
+ else if (version.value !== 1)
546
+ this.diagnostics.error('L2132', '@repetitionHost currently supports only protocol version 1', attribute.span);
547
+ if (declaration.unsafe)
548
+ this.diagnostics.error('L2133', '@repetitionHost cannot target unsafe extern js declarations', attribute.span);
549
+ }
550
+ }
551
+ else
552
+ this.diagnostics.error('L2059', `Unknown attribute @${attribute.name}`, attribute.span);
553
+ }
554
+ }
555
+ checkRecordFieldAttributes(declaration) {
556
+ const jsonNames = new Set();
557
+ const recordTypeId = this.#namedTypes.get(declaration.name);
558
+ const recordType = recordTypeId === undefined ? undefined : this.arena.get(recordTypeId);
559
+ for (const field of declaration.fields) {
560
+ const seen = new Set();
561
+ for (const attribute of field.attributes) {
562
+ if (seen.has(attribute.name))
563
+ this.diagnostics.error('L2078', `Duplicate field attribute @${attribute.name}`, attribute.span);
564
+ seen.add(attribute.name);
565
+ if (attribute.name === 'jsOptional') {
566
+ if (attribute.arguments.length > 0)
567
+ this.diagnostics.error('L2116', '@jsOptional does not accept arguments', attribute.span);
568
+ const fieldType = recordType?.kind === 'named' ? recordType.fields?.get(field.name) : undefined;
569
+ if (fieldType === undefined || this.arena.get(fieldType).kind !== 'option')
570
+ this.diagnostics.error('L2116', '@jsOptional can be used only on Option fields', attribute.span);
571
+ continue;
572
+ }
573
+ if (!declaration.derives.includes('Json'))
574
+ this.diagnostics.error('L2079', `@${attribute.name} requires ${declaration.name} to derive Json`, attribute.span);
575
+ if (attribute.name === 'jsonName') {
576
+ const argument = attribute.arguments[0];
577
+ if (attribute.arguments.length !== 1 || argument?.kind !== 'LiteralExpression' || argument.literalKind !== 'String') {
578
+ this.diagnostics.error('L2080', '@jsonName requires exactly one String literal', attribute.span);
579
+ continue;
580
+ }
581
+ const name = String(argument.value);
582
+ if (jsonNames.has(name))
583
+ this.diagnostics.error('L2081', `Duplicate JSON field name ${name}`, attribute.span);
584
+ jsonNames.add(name);
585
+ }
586
+ else if (attribute.name === 'jsonDefault') {
587
+ const argument = attribute.arguments[0];
588
+ if (attribute.arguments.length !== 1 || argument === undefined || !this.isJsonDefaultExpression(argument)) {
589
+ this.diagnostics.error('L2082', '@jsonDefault requires exactly one compile-time constant expression', attribute.span);
590
+ continue;
591
+ }
592
+ const expected = recordType?.kind === 'named' ? recordType.fields?.get(field.name) : undefined;
593
+ const actual = this.checkExpression(argument, this.globalScope, expected);
594
+ if (expected !== undefined && !this.isAssignable(actual, expected))
595
+ this.typeMismatch(actual, expected, argument.span);
596
+ }
597
+ else
598
+ this.diagnostics.error('L2083', `Unknown field attribute @${attribute.name}`, attribute.span);
599
+ }
600
+ if (!seen.has('jsonName')) {
601
+ if (jsonNames.has(field.name))
602
+ this.diagnostics.error('L2081', `Duplicate JSON field name ${field.name}`, field.span);
603
+ jsonNames.add(field.name);
604
+ }
605
+ }
606
+ }
607
+ isJsonDefaultExpression(expression) {
608
+ switch (expression.kind) {
609
+ case 'LiteralExpression': return true;
610
+ case 'IdentifierExpression': return expression.name === 'None' || expression.name === 'Unit';
611
+ case 'ListExpression': return expression.items.every(item => this.isJsonDefaultExpression(item));
612
+ case 'TupleExpression': return expression.items.every(item => this.isJsonDefaultExpression(item));
613
+ case 'RecordExpression': return expression.entries.every(item => this.isJsonDefaultExpression(item.value));
614
+ case 'CallExpression': return expression.callee.kind === 'IdentifierExpression' && ['Some', 'Ok', 'Err'].includes(expression.callee.name) && expression.arguments.every(item => this.isJsonDefaultExpression(item));
615
+ default: return false;
616
+ }
617
+ }
618
+ checkDerives(derives, span, typeName) {
619
+ const seen = new Set();
620
+ for (const derive of derives) {
621
+ if (seen.has(derive))
622
+ this.diagnostics.error('L2060', `Duplicate derive ${derive} on ${typeName}`, span);
623
+ seen.add(derive);
624
+ if (!['Eq', 'Hash', 'Debug', 'Json'].includes(derive))
625
+ this.diagnostics.error('L2003', `Unknown derive ${derive} on ${typeName}`, span);
626
+ }
627
+ const typeId = this.#namedTypes.get(typeName);
628
+ const type = typeId === undefined ? undefined : this.arena.get(typeId);
629
+ if (type?.kind !== 'named')
630
+ return;
631
+ const components = type.fields === undefined ? [...(type.variants?.values() ?? [])].flat() : [...type.fields.values()];
632
+ if (derives.includes('Eq'))
633
+ for (const component of components)
634
+ if (!this.supportsDerivedEq(component, typeId)) {
635
+ this.diagnostics.error('L2061', `${typeName} cannot derive Eq because ${this.arena.display(component)} does not support derived equality`, span);
636
+ break;
637
+ }
638
+ if (derives.includes('Hash')) {
639
+ if (!derives.includes('Eq'))
640
+ this.diagnostics.error('L2092', `${typeName} must derive Eq before Hash`, span);
641
+ for (const component of components)
642
+ if (!this.supportsHash(component)) {
643
+ this.diagnostics.error('L2093', `${typeName} cannot derive Hash because ${this.arena.display(component)} is not hashable`, span);
644
+ break;
645
+ }
646
+ }
647
+ if (derives.includes('Json'))
648
+ for (const component of components)
649
+ if (!this.supportsJson(component)) {
650
+ this.diagnostics.error('L2062', `${typeName} cannot derive Json because ${this.arena.display(component)} is not JSON-compatible`, span);
651
+ break;
652
+ }
653
+ }
654
+ checkBlock(block, parent) {
655
+ const scope = new Scope(parent);
656
+ for (const statement of block.statements)
657
+ this.checkStatement(statement, scope);
658
+ }
659
+ checkStatement(statement, scope) {
660
+ switch (statement.kind) {
661
+ case 'LetStatement': {
662
+ const annotated = statement.annotation === undefined ? undefined : this.resolveTypeReference(statement.annotation, new Map());
663
+ const valueType = this.checkExpression(statement.value, scope, annotated);
664
+ const expected = annotated ?? valueType;
665
+ if (!this.isAssignable(valueType, expected))
666
+ this.typeMismatch(valueType, expected, statement.value.span);
667
+ const symbol = this.#factory.create(statement.name, 'variable', expected, statement.span, { mutable: statement.mutable, declaration: statement });
668
+ if (!scope.define(symbol))
669
+ this.diagnostics.error('L1007', `Local name ${statement.name} shadows an existing name`, statement.span);
670
+ else {
671
+ statement.symbolId = symbol.id;
672
+ statement.inferredTypeId = expected;
673
+ this.#symbols.set(symbol.id, symbol);
674
+ }
675
+ break;
676
+ }
677
+ case 'ReturnStatement': {
678
+ if (this.#currentFunction?.declaration.kind === 'ComponentDeclaration') {
679
+ if (statement.value?.kind === 'ViewExpression')
680
+ this.checkViewExpression(statement.value, scope);
681
+ else {
682
+ if (statement.value !== undefined)
683
+ this.checkExpression(statement.value, scope);
684
+ this.diagnostics.error('L4303', 'Component returns must directly return view', statement.span);
685
+ }
686
+ break;
687
+ }
688
+ const typeId = statement.value === undefined ? this.arena.unit : this.checkExpression(statement.value, scope, this.#currentFunction?.returnType);
689
+ this.#currentFunction?.returnTypes.push(typeId);
690
+ if (this.#currentFunction?.returnType !== undefined && !this.isAssignable(typeId, this.#currentFunction.returnType))
691
+ this.typeMismatch(typeId, this.#currentFunction.returnType, statement.span);
692
+ break;
693
+ }
694
+ case 'IfStatement':
695
+ this.requireBool(this.checkExpression(statement.condition, scope, this.arena.bool), statement.condition.span);
696
+ this.checkBlock(statement.thenBlock, scope);
697
+ if (statement.elseBranch?.kind === 'BlockStatement')
698
+ this.checkBlock(statement.elseBranch, scope);
699
+ else if (statement.elseBranch !== undefined)
700
+ this.checkStatement(statement.elseBranch, scope);
701
+ break;
702
+ case 'ForStatement': {
703
+ const iterableType = this.arena.get(this.checkExpression(statement.iterable, scope));
704
+ let itemType = this.arena.error;
705
+ if (iterableType.kind === 'list' || iterableType.kind === 'set')
706
+ itemType = iterableType.element;
707
+ else if (iterableType.kind === 'map')
708
+ itemType = this.arena.tuple([iterableType.key, iterableType.value]);
709
+ else if (iterableType.kind === 'primitive' && iterableType.name === 'String')
710
+ itemType = this.arena.string;
711
+ else
712
+ this.diagnostics.error('L2004', 'for requires an iterable List, Set, Map, or String value', statement.iterable.span);
713
+ const child = new Scope(scope);
714
+ const symbol = this.#factory.create(statement.name, 'variable', itemType, statement.span, { declaration: statement });
715
+ if (!child.define(symbol))
716
+ this.diagnostics.error('L1008', `Loop variable ${statement.name} shadows an existing name`, statement.span);
717
+ else {
718
+ statement.symbolId = symbol.id;
719
+ this.#symbols.set(symbol.id, symbol);
720
+ }
721
+ this.#loopDepth++;
722
+ this.checkBlock(statement.body, child);
723
+ this.#loopDepth--;
724
+ break;
725
+ }
726
+ case 'WhileStatement':
727
+ this.requireBool(this.checkExpression(statement.condition, scope, this.arena.bool), statement.condition.span);
728
+ this.#loopDepth++;
729
+ this.checkBlock(statement.body, scope);
730
+ this.#loopDepth--;
731
+ break;
732
+ case 'BreakStatement':
733
+ if (this.#loopDepth === 0)
734
+ this.diagnostics.error('L2095', 'break can be used only inside a loop', statement.span);
735
+ break;
736
+ case 'ContinueStatement':
737
+ if (this.#loopDepth === 0)
738
+ this.diagnostics.error('L2096', 'continue can be used only inside a loop', statement.span);
739
+ break;
740
+ case 'DiscardStatement':
741
+ this.checkExpression(statement.expression, scope);
742
+ break;
743
+ case 'AssignmentStatement': {
744
+ if (statement.invalidTarget !== undefined) {
745
+ this.checkExpression(statement.invalidTarget, scope);
746
+ this.checkExpression(statement.value, scope);
747
+ this.diagnostics.error('L2118', 'Invalid assignment target; expected a mutable name, member, or index target', statement.span);
748
+ break;
749
+ }
750
+ const target = scope.lookup(statement.name);
751
+ if (target === undefined)
752
+ this.diagnostics.error('L1009', `Unknown name ${statement.name}`, statement.span);
753
+ else if (!target.mutable)
754
+ this.diagnostics.error('L2010', `Cannot assign to immutable name ${statement.name}`, statement.span);
755
+ else {
756
+ statement.targetSymbolId = target.id;
757
+ const valueType = this.checkExpression(statement.value, scope, target.typeId);
758
+ if (this.containsOpenEffect(valueType))
759
+ this.diagnostics.error('L2113', 'uses * callbacks are non-escaping and cannot be assigned', statement.span);
760
+ if (!this.isAssignable(valueType, target.typeId))
761
+ this.typeMismatch(valueType, target.typeId, statement.value.span);
762
+ }
763
+ break;
764
+ }
765
+ case 'MemberAssignmentStatement': {
766
+ const targetTypeId = this.checkExpression(statement.target, scope);
767
+ const targetType = this.arena.get(targetTypeId);
768
+ if (targetType.kind !== 'foreign') {
769
+ this.checkExpression(statement.value, scope);
770
+ this.diagnostics.error('L2119', 'Member assignment requires a proven writable facet', statement.span);
771
+ break;
772
+ }
773
+ this.requireEffects(['JavaScript'], statement.span);
774
+ const prepared = this.prepareInteropValue(statement.value, scope);
775
+ const provider = this.currentInteropProvider(targetType.snapshot);
776
+ let resolution;
777
+ if (provider !== undefined && prepared.argument.kind !== 'unknown' && provider.resolveWriteUsage !== undefined) {
778
+ try {
779
+ resolution = provider.resolveWriteUsage(targetType.ref, { kind: 'property', property: statement.field, value: prepared.argument });
780
+ }
781
+ catch {
782
+ resolution = undefined;
783
+ }
784
+ }
785
+ const objectEvidence = provider === undefined ? undefined : this.validateWriteEvidence(resolution, prepared, provider);
786
+ if (objectEvidence === undefined) {
787
+ this.diagnostics.error('L2119', 'Member assignment requires a proven writable facet', statement.span);
788
+ break;
789
+ }
790
+ if (objectEvidence !== null)
791
+ this.commitContextualObjects([objectEvidence]);
792
+ statement.foreignWrite = true;
793
+ this.#interopUsages.push({ kind: 'write-property', nodeId: statement.id, span: statement.span, foreignType: targetType.snapshot, property: statement.field });
794
+ break;
795
+ }
796
+ case 'IndexAssignmentStatement': {
797
+ const targetTypeId = this.checkExpression(statement.target, scope);
798
+ const indexTypeId = this.checkExpression(statement.index, scope);
799
+ const targetType = this.arena.get(targetTypeId);
800
+ if (targetType.kind !== 'foreign') {
801
+ this.checkExpression(statement.value, scope);
802
+ this.diagnostics.error('L2120', 'Index assignment requires a proven writable facet', statement.span);
803
+ break;
804
+ }
805
+ this.requireEffects(['JavaScript'], statement.span);
806
+ const provider = this.currentInteropProvider(targetType.snapshot);
807
+ const index = this.interopArgumentType(indexTypeId, statement.index, statement.index.span);
808
+ const prepared = this.prepareInteropValue(statement.value, scope);
809
+ let resolution;
810
+ if (provider !== undefined && index.kind !== 'unknown' && prepared.argument.kind !== 'unknown' && provider.resolveWriteUsage !== undefined) {
811
+ try {
812
+ resolution = provider.resolveWriteUsage(targetType.ref, { kind: 'index', index, value: prepared.argument });
813
+ }
814
+ catch {
815
+ resolution = undefined;
816
+ }
817
+ }
818
+ const objectEvidence = provider === undefined ? undefined : this.validateWriteEvidence(resolution, prepared, provider);
819
+ if (objectEvidence === undefined) {
820
+ this.diagnostics.error('L2120', 'Index assignment requires a proven writable facet', statement.span);
821
+ break;
822
+ }
823
+ if (objectEvidence !== null)
824
+ this.commitContextualObjects([objectEvidence]);
825
+ statement.foreignWrite = true;
826
+ this.#interopUsages.push({ kind: 'write-index', nodeId: statement.id, span: statement.span, foreignType: targetType.snapshot });
827
+ break;
828
+ }
829
+ case 'DeferStatement': {
830
+ if (this.#currentFunction === undefined)
831
+ this.diagnostics.error('L2070', 'defer can be used only inside a function or test', statement.span);
832
+ const deferredType = this.checkExpression(statement.expression, scope);
833
+ if (!this.arena.equals(deferredType, this.arena.unit) && !this.arena.equals(deferredType, this.arena.never))
834
+ this.diagnostics.error('L2071', `defer expression must produce Unit, received ${this.arena.display(deferredType)}`, statement.expression.span);
835
+ break;
836
+ }
837
+ case 'ExpressionStatement': {
838
+ const result = this.checkExpression(statement.expression, scope);
839
+ if (this.isMustUse(result))
840
+ this.diagnostics.error('L2097', `Value of type ${this.arena.display(result)} must be used; bind it, return it, await it, handle it, or write discard`, statement.span);
841
+ break;
842
+ }
843
+ }
844
+ }
845
+ checkExpression(expression, scope, expected) {
846
+ let typeId;
847
+ switch (expression.kind) {
848
+ case 'LiteralExpression':
849
+ typeId = this.literalType(expression);
850
+ break;
851
+ case 'IdentifierExpression': {
852
+ if (expression.name === 'None' && expected !== undefined && this.arena.get(expected).kind === 'option') {
853
+ expression.inferredTypeId = expected;
854
+ return expected;
855
+ }
856
+ const symbol = scope.lookup(expression.name);
857
+ if (symbol === undefined) {
858
+ this.diagnostics.error('L1010', `Unknown name ${expression.name}`, expression.span);
859
+ typeId = this.arena.error;
860
+ }
861
+ else {
862
+ expression.symbolId = symbol.id;
863
+ if (symbol.kind === 'component') {
864
+ this.diagnostics.error('L4305', `Component ${expression.name} cannot be used as an ordinary Virune value; use it as a View tag`, expression.span);
865
+ typeId = this.arena.error;
866
+ break;
867
+ }
868
+ typeId = symbol.typeId;
869
+ if (symbol.typeOnly)
870
+ this.diagnostics.error('L1012', `Type-only import ${expression.name} cannot be used as a value`, expression.span);
871
+ if (this.#currentFunction?.declaration.kind === 'LambdaExpression' && this.containsOpenEffect(symbol.typeId) && symbol.declaration !== this.#currentFunction.declaration)
872
+ this.diagnostics.error('L2113', 'uses * callbacks are non-escaping and cannot be captured by lambdas', expression.span);
873
+ if (expected !== undefined && symbol.kind === 'variant') {
874
+ const substitutions = new Map();
875
+ this.unify(typeId, expected, substitutions);
876
+ typeId = this.substitute(typeId, substitutions);
877
+ }
878
+ }
879
+ break;
880
+ }
881
+ case 'WildcardExpression':
882
+ this.diagnostics.error('L2011', 'Wildcard can only be used in patterns or pipeline placeholders', expression.span);
883
+ typeId = this.arena.error;
884
+ break;
885
+ case 'CallExpression':
886
+ typeId = this.checkCall(expression, scope, expected);
887
+ break;
888
+ case 'FieldExpression':
889
+ typeId = this.checkField(expression, scope);
890
+ break;
891
+ case 'IndexExpression':
892
+ typeId = this.checkForeignIndex(expression, scope);
893
+ break;
894
+ case 'BinaryExpression':
895
+ typeId = this.checkBinary(expression, scope);
896
+ break;
897
+ case 'UnaryExpression':
898
+ typeId = this.checkUnary(expression, scope);
899
+ break;
900
+ case 'PipelineExpression':
901
+ typeId = this.checkPipeline(expression, scope);
902
+ break;
903
+ case 'TryExpression':
904
+ typeId = this.checkTry(expression, scope);
905
+ break;
906
+ case 'AwaitExpression':
907
+ typeId = this.checkAwait(expression, scope);
908
+ break;
909
+ case 'RecordExpression':
910
+ typeId = this.checkRecord(expression, scope, expected);
911
+ break;
912
+ case 'RecordUpdateExpression':
913
+ typeId = this.checkRecordUpdate(expression, scope);
914
+ break;
915
+ case 'ContextualAggregateExpression':
916
+ typeId = this.checkContextualAggregate(expression, scope, expected);
917
+ break;
918
+ case 'ListExpression':
919
+ typeId = this.checkList(expression, scope, expected);
920
+ break;
921
+ case 'TupleExpression':
922
+ typeId = this.arena.tuple(expression.items.map(item => this.checkExpression(item, scope)));
923
+ break;
924
+ case 'ConditionalExpression': {
925
+ this.requireBool(this.checkExpression(expression.condition, scope, this.arena.bool), expression.condition.span);
926
+ const left = this.checkExpression(expression.thenExpression, scope, expected);
927
+ const right = this.checkExpression(expression.elseExpression, scope, expected);
928
+ typeId = this.commonType([left, right], expression.span);
929
+ break;
930
+ }
931
+ case 'MatchExpression':
932
+ typeId = this.checkMatch(expression, scope, expected);
933
+ break;
934
+ case 'LambdaExpression':
935
+ typeId = this.checkLambda(expression, scope, expected);
936
+ break;
937
+ case 'ParallelExpression':
938
+ typeId = this.checkParallel(expression, scope);
939
+ break;
940
+ case 'ViewExpression':
941
+ this.diagnostics.error('L4300', 'view is compiler-controlled and can be used only as a direct component return', expression.span);
942
+ typeId = this.arena.error;
943
+ break;
944
+ }
945
+ typeId = this.applyExpectedForeignBridge(expression, typeId, expected);
946
+ expression.inferredTypeId = typeId;
947
+ return typeId;
948
+ }
949
+ checkViewExpression(expression, scope) {
950
+ this.checkViewBlock(expression.body, scope);
951
+ }
952
+ checkViewBlock(block, scope, insideRepetition = false) {
953
+ for (const child of block.children) {
954
+ switch (child.kind) {
955
+ case 'ViewTextChild': break;
956
+ case 'ViewExpressionChild':
957
+ this.checkExpression(child.expression, scope);
958
+ break;
959
+ case 'ViewChildrenSlot':
960
+ if (insideRepetition) {
961
+ this.diagnostics.error('L4311', 'children cannot appear inside View repetition', child.span);
962
+ break;
963
+ }
964
+ this.#currentComponentChildrenSlots++;
965
+ if (this.#currentComponentChildrenSlots > 1)
966
+ this.diagnostics.error('L4306', 'A component may contain at most one children slot', child.span);
967
+ break;
968
+ case 'ViewElement': {
969
+ const root = child.tag[0];
970
+ const native = child.tag.length === 1 && root !== undefined && this.globalScope.lookup(root)?.kind === 'component';
971
+ const viewCallbackIndexes = [];
972
+ for (let propertyIndex = 0; propertyIndex < child.properties.length; propertyIndex += 1) {
973
+ const property = child.properties[propertyIndex];
974
+ if (!native && this.isFrontendViewCallback(property.value)) {
975
+ viewCallbackIndexes.push(propertyIndex);
976
+ continue;
977
+ }
978
+ const typeId = this.checkExpression(property.value, scope);
979
+ if (native)
980
+ continue;
981
+ const boundary = this.nativeCallableBoundary(typeId, property.value, true);
982
+ if (boundary === undefined || boundary.version !== 'virune-callable-shim/v1' || boundary.parameters.includes('Int'))
983
+ continue;
984
+ this.requireEffects(boundary.effects, property.value.span);
985
+ this.#frontendCallableProjections.push({ viewElementNodeId: child.id, propertyIndex, property: property.name, descriptor: boundary });
986
+ }
987
+ for (const propertyIndex of viewCallbackIndexes)
988
+ this.checkFrontendViewCallback(child, propertyIndex, scope);
989
+ if (child.children !== undefined)
990
+ this.checkViewBlock(child.children, scope, insideRepetition);
991
+ break;
992
+ }
993
+ case 'ViewConditional':
994
+ this.requireBool(this.checkExpression(child.condition, scope, this.arena.bool), child.condition.span);
995
+ this.checkViewBlock(child.thenBlock, scope, insideRepetition);
996
+ if (child.elseBranch?.kind === 'ViewBlock')
997
+ this.checkViewBlock(child.elseBranch, scope, insideRepetition);
998
+ else if (child.elseBranch !== undefined)
999
+ this.checkViewConditional(child.elseBranch, scope, insideRepetition);
1000
+ break;
1001
+ case 'ViewRepetition':
1002
+ this.checkViewRepetition(child, scope);
1003
+ break;
1004
+ }
1005
+ }
1006
+ }
1007
+ isFrontendViewCallback(expression) {
1008
+ return expression.kind === 'LambdaExpression' && expression.expressionBody && expression.body.kind === 'ViewExpression';
1009
+ }
1010
+ frontendExternalViewTag(tag) {
1011
+ const root = tag[0];
1012
+ if (root === undefined)
1013
+ return undefined;
1014
+ const symbol = this.globalScope.lookup(root);
1015
+ if (symbol === undefined)
1016
+ return undefined;
1017
+ let type = this.arena.get(symbol.typeId);
1018
+ if (type.kind !== 'foreign')
1019
+ return undefined;
1020
+ const provider = this.currentInteropProvider(type.snapshot);
1021
+ if (provider === undefined)
1022
+ return undefined;
1023
+ let snapshot = type.snapshot;
1024
+ for (const property of tag.slice(1)) {
1025
+ let next;
1026
+ try {
1027
+ next = provider.getProperty(snapshot.ref, property);
1028
+ }
1029
+ catch {
1030
+ next = undefined;
1031
+ }
1032
+ if (!this.isCurrentForeignSnapshot(next, provider, false))
1033
+ return undefined;
1034
+ snapshot = next;
1035
+ type = this.arena.get(this.arena.foreign(snapshot));
1036
+ }
1037
+ return { ref: snapshot.ref, provider };
1038
+ }
1039
+ frontendJsxPropertyArgument(element, propertyIndex) {
1040
+ const property = element.properties[propertyIndex];
1041
+ if (property === undefined)
1042
+ return undefined;
1043
+ if (this.isFrontendViewCallback(property.value)) {
1044
+ return { kind: 'contextual-callable', parameterCount: property.value.parameters.length, async: property.value.async };
1045
+ }
1046
+ const projection = this.#frontendCallableProjections.find(item => item.viewElementNodeId === element.id && item.propertyIndex === propertyIndex && item.property === property.name);
1047
+ if (projection?.descriptor !== undefined) {
1048
+ return {
1049
+ kind: 'native-callable',
1050
+ callable: {
1051
+ parameters: projection.descriptor.parameters,
1052
+ result: projection.descriptor.result,
1053
+ async: projection.descriptor.async,
1054
+ },
1055
+ };
1056
+ }
1057
+ const typeId = property.value.inferredTypeId;
1058
+ if (typeId === undefined)
1059
+ return undefined;
1060
+ const type = this.arena.get(typeId);
1061
+ if (type.kind === 'foreign') {
1062
+ const provider = this.currentInteropProvider(type.snapshot);
1063
+ return provider !== undefined && this.isCurrentForeignSnapshot(type.snapshot, provider, false)
1064
+ ? { kind: 'foreign', type: type.ref }
1065
+ : undefined;
1066
+ }
1067
+ if (type.kind !== 'primitive' || !['Bool', 'Int', 'Float', 'BigInt', 'String', 'Unit'].includes(type.name))
1068
+ return undefined;
1069
+ const primitive = type.name;
1070
+ const literal = this.interopLiteralValue(property.value);
1071
+ return literal === undefined ? { kind: 'native-primitive', primitive } : { kind: 'native-primitive', primitive, literal };
1072
+ }
1073
+ checkFrontendViewCallback(element, propertyIndex, scope) {
1074
+ const property = element.properties[propertyIndex];
1075
+ if (property === undefined || !this.isFrontendViewCallback(property.value))
1076
+ return;
1077
+ const lambda = property.value;
1078
+ const root = element.tag[0];
1079
+ const rootSymbol = root === undefined ? undefined : this.globalScope.lookup(root);
1080
+ const rootType = rootSymbol === undefined ? undefined : this.arena.get(rootSymbol.typeId);
1081
+ if (rootType?.kind !== 'foreign') {
1082
+ this.checkExpression(lambda, scope);
1083
+ return;
1084
+ }
1085
+ if (lambda.async) {
1086
+ this.diagnostics.error('L4300', 'View-producing External JSX callbacks must be synchronous', lambda.span);
1087
+ return;
1088
+ }
1089
+ if (lambda.parameters.some(parameter => parameter.annotation !== undefined)) {
1090
+ this.diagnostics.error('L4300', 'View-producing External JSX callback parameters must use TypeScript contextual typing', lambda.span);
1091
+ return;
1092
+ }
1093
+ if (lambda.returnType !== undefined) {
1094
+ this.diagnostics.error('L4300', 'View-producing External JSX callbacks cannot declare a Virune return type', lambda.returnType.span);
1095
+ return;
1096
+ }
1097
+ const target = this.frontendExternalViewTag(element.tag);
1098
+ const resolver = target?.provider.resolveJsxCallbackUsage;
1099
+ if (target === undefined || resolver === undefined) {
1100
+ this.diagnostics.error('L4308', `Cannot resolve contextual View callback for External JSX property ${property.name}`, property.span);
1101
+ return;
1102
+ }
1103
+ const properties = [];
1104
+ for (let index = 0; index < element.properties.length; index += 1) {
1105
+ const item = element.properties[index];
1106
+ const value = this.frontendJsxPropertyArgument(element, index);
1107
+ if (value === undefined) {
1108
+ this.diagnostics.error('L4308', `Cannot prove sibling JSX property ${item.name} while resolving contextual View callback ${property.name}`, item.span);
1109
+ return;
1110
+ }
1111
+ properties.push({ property: item.name, value });
1112
+ }
1113
+ let resolution;
1114
+ try {
1115
+ resolution = resolver.call(target.provider, target.ref, {
1116
+ properties: Object.freeze(properties),
1117
+ callbackIndex: propertyIndex,
1118
+ parameterCount: lambda.parameters.length,
1119
+ async: false,
1120
+ });
1121
+ }
1122
+ catch {
1123
+ resolution = undefined;
1124
+ }
1125
+ if (!isRecord(resolution) || !hasExactEnumerableKeys(resolution, ['parameters']) || !Array.isArray(resolution.parameters) || resolution.parameters.length !== lambda.parameters.length) {
1126
+ this.diagnostics.error('L4308', `Cannot prove contextual View callback parameters for External JSX property ${property.name}`, property.span);
1127
+ return;
1128
+ }
1129
+ const parameterTypes = [];
1130
+ for (const parameter of resolution.parameters) {
1131
+ if (!this.isCurrentForeignSnapshot(parameter, target.provider, false) || parameter.category !== 'object') {
1132
+ this.diagnostics.error('L4308', `Contextual View callback property ${property.name} requires concrete External object parameter evidence`, property.span);
1133
+ return;
1134
+ }
1135
+ parameterTypes.push(this.arena.foreign(parameter));
1136
+ }
1137
+ this.checkFrontendViewLambda(lambda, scope, parameterTypes);
1138
+ const effects = Object.freeze([...new Set(lambda.effects)].sort(compareText));
1139
+ this.requireEffects(effects, lambda.span);
1140
+ this.#frontendCallableProjections.push({
1141
+ viewElementNodeId: element.id,
1142
+ propertyIndex,
1143
+ property: property.name,
1144
+ viewCallback: { parameterCount: lambda.parameters.length, effects },
1145
+ });
1146
+ }
1147
+ checkFrontendViewLambda(expression, scope, contextualParameters) {
1148
+ const child = new Scope(scope);
1149
+ expression.parameters.forEach((parameter, index) => {
1150
+ const typeId = contextualParameters[index] ?? this.arena.error;
1151
+ const symbol = this.#factory.create(parameter.name, 'parameter', typeId, parameter.span, { declaration: expression });
1152
+ if (!child.define(symbol))
1153
+ this.diagnostics.error('L1012', `Lambda parameter ${parameter.name} shadows an existing name`, parameter.span);
1154
+ else {
1155
+ parameter.symbolId = symbol.id;
1156
+ this.#symbols.set(symbol.id, symbol);
1157
+ }
1158
+ });
1159
+ this.validateEffects(expression.effects, expression.span);
1160
+ const previous = this.#currentFunction;
1161
+ this.#currentFunction = {
1162
+ declaration: expression,
1163
+ scope: child,
1164
+ async: false,
1165
+ returnTypes: [],
1166
+ typeParameters: new Map(),
1167
+ effects: new Set(expression.effects),
1168
+ };
1169
+ this.checkViewExpression(expression.body, child);
1170
+ this.#currentFunction = previous;
1171
+ }
1172
+ checkViewConditional(conditional, scope, insideRepetition = false) {
1173
+ this.requireBool(this.checkExpression(conditional.condition, scope, this.arena.bool), conditional.condition.span);
1174
+ this.checkViewBlock(conditional.thenBlock, scope, insideRepetition);
1175
+ if (conditional.elseBranch?.kind === 'ViewBlock')
1176
+ this.checkViewBlock(conditional.elseBranch, scope, insideRepetition);
1177
+ else if (conditional.elseBranch !== undefined)
1178
+ this.checkViewConditional(conditional.elseBranch, scope, insideRepetition);
1179
+ }
1180
+ checkViewRepetition(repetition, scope) {
1181
+ delete repetition.checkedEvidence;
1182
+ const sourceTypeId = this.checkExpression(repetition.source, scope);
1183
+ const sourceType = this.arena.get(sourceTypeId);
1184
+ let sourceKind;
1185
+ let itemType = this.arena.error;
1186
+ if (sourceType.kind === 'list') {
1187
+ sourceKind = 'native-list';
1188
+ itemType = sourceType.element;
1189
+ }
1190
+ else if (sourceType.kind === 'foreign' && sourceType.snapshot.category === 'array') {
1191
+ const provider = this.currentInteropProvider(sourceType.snapshot);
1192
+ let element;
1193
+ if (provider?.resolveArrayElement !== undefined) {
1194
+ try {
1195
+ element = provider.resolveArrayElement(sourceType.ref);
1196
+ }
1197
+ catch {
1198
+ element = undefined;
1199
+ }
1200
+ }
1201
+ if (provider !== undefined && this.isCurrentForeignSnapshot(element, provider, false)) {
1202
+ this.requireEffects(['JavaScript'], repetition.span);
1203
+ sourceKind = 'external-array';
1204
+ itemType = this.arena.foreign(element);
1205
+ }
1206
+ }
1207
+ if (sourceKind === undefined)
1208
+ this.diagnostics.error('L4310', 'View repetition requires a native List or proven External Array source', repetition.source.span);
1209
+ const childScope = new Scope(scope);
1210
+ const item = this.#factory.create(repetition.itemName, 'variable', itemType, repetition.span, { declaration: repetition });
1211
+ let itemSymbolId;
1212
+ if (!childScope.define(item))
1213
+ this.diagnostics.error('L1008', `View repetition item ${repetition.itemName} shadows an existing name`, repetition.span);
1214
+ else {
1215
+ itemSymbolId = item.id;
1216
+ this.#symbols.set(item.id, item);
1217
+ }
1218
+ let indexSymbolId;
1219
+ if (repetition.indexName !== undefined) {
1220
+ const index = this.#factory.create(repetition.indexName, 'variable', this.arena.int, repetition.span, { declaration: repetition });
1221
+ if (!childScope.define(index))
1222
+ this.diagnostics.error('L1008', `View repetition index ${repetition.indexName} shadows an existing name`, repetition.span);
1223
+ else {
1224
+ indexSymbolId = index.id;
1225
+ this.#symbols.set(index.id, index);
1226
+ }
1227
+ }
1228
+ const identityTypeId = this.checkExpression(repetition.identity, childScope);
1229
+ const identityType = this.arena.get(identityTypeId);
1230
+ const identityValid = identityTypeId !== this.arena.error && identityType.kind === 'primitive' && (identityType.name === 'String' || identityType.name === 'Int');
1231
+ if (identityTypeId !== this.arena.error && !identityValid)
1232
+ this.diagnostics.error('L4312', `View repetition identity must be String or Int, received ${this.arena.display(identityTypeId)}`, repetition.identity.span);
1233
+ if (identityValid && sourceKind !== undefined && itemSymbolId !== undefined && (repetition.indexName === undefined || indexSymbolId !== undefined)) {
1234
+ repetition.checkedEvidence = { sourceKind, itemSymbolId, indexSymbolId };
1235
+ }
1236
+ this.checkViewBlock(repetition.body, childScope, true);
1237
+ }
1238
+ checkContextualAggregate(expression, scope, expected) {
1239
+ const prepared = this.prepareContextualObject(expression, scope);
1240
+ const expectedType = expected === undefined ? undefined : this.arena.get(expected);
1241
+ if (expected !== undefined && expectedType?.kind === 'foreign') {
1242
+ const provider = this.currentInteropProvider(expectedType.snapshot);
1243
+ let resolution;
1244
+ if (provider?.resolveObjectUsage !== undefined) {
1245
+ try {
1246
+ resolution = provider.resolveObjectUsage(expectedType.ref, prepared.usage);
1247
+ }
1248
+ catch {
1249
+ resolution = undefined;
1250
+ }
1251
+ }
1252
+ const validated = provider === undefined ? undefined : this.validateContextualObjectEvidence(resolution, prepared, provider);
1253
+ if (validated !== undefined) {
1254
+ this.commitContextualObjects([validated]);
1255
+ return expected;
1256
+ }
1257
+ }
1258
+ this.diagnostics.error('L2122', 'Contextual aggregate requires a supported expected aggregate facet', expression.span);
1259
+ return this.arena.error;
1260
+ }
1261
+ checkForeignIndex(expression, scope) {
1262
+ const targetTypeId = this.checkExpression(expression.target, scope);
1263
+ const indexTypeId = this.checkExpression(expression.index, scope);
1264
+ const targetType = this.arena.get(targetTypeId);
1265
+ if (targetType.kind !== 'foreign') {
1266
+ this.diagnostics.error('L2121', 'Index access requires a proven index facet', expression.span);
1267
+ return this.arena.error;
1268
+ }
1269
+ this.requireEffects(['JavaScript'], expression.span);
1270
+ const provider = this.currentInteropProvider(targetType.snapshot);
1271
+ const index = this.interopArgumentType(indexTypeId, expression.index, expression.index.span);
1272
+ let resolution;
1273
+ if (provider !== undefined && index.kind !== 'unknown' && provider.resolveIndexUsage !== undefined) {
1274
+ try {
1275
+ resolution = provider.resolveIndexUsage(targetType.ref, { index });
1276
+ }
1277
+ catch {
1278
+ resolution = undefined;
1279
+ }
1280
+ }
1281
+ if (provider === undefined || !this.isCurrentForeignSnapshot(resolution?.result, provider, false)) {
1282
+ this.diagnostics.error('L2121', 'Index access requires a proven index facet', expression.span);
1283
+ return this.arena.error;
1284
+ }
1285
+ expression.foreignIndex = true;
1286
+ this.#interopUsages.push({ kind: 'index', nodeId: expression.id, span: expression.span, foreignType: resolution.result });
1287
+ return this.arena.foreign(resolution.result);
1288
+ }
1289
+ checkForeignCall(expression, scope, callee) {
1290
+ this.requireEffects(['JavaScript'], expression.span);
1291
+ if (expression.typeArguments.length > 0)
1292
+ this.diagnostics.error('L4203', 'Explicit Virune type arguments are not supported for JavaScript calls; use a TypeScript interop adapter', expression.span);
1293
+ let preparedArguments = expression.arguments.map((argument, index) => this.prepareForeignCallValue(argument, scope, index === expression.arguments.length - 1));
1294
+ let interopArguments = preparedArguments.map(argument => argument.argument);
1295
+ let callableBoundaries = preparedArguments.map(argument => argument.boundary);
1296
+ let target = { kind: 'value' };
1297
+ if (expression.callee.kind === 'FieldExpression') {
1298
+ const receiverTypeId = expression.callee.target.inferredTypeId;
1299
+ const receiver = receiverTypeId === undefined ? undefined : this.arena.get(receiverTypeId);
1300
+ if (receiver?.kind === 'foreign')
1301
+ target = { kind: 'member', receiver: receiver.ref, property: expression.callee.field };
1302
+ }
1303
+ else if (expression.callee.kind === 'IndexExpression') {
1304
+ const receiverTypeId = expression.callee.target.inferredTypeId;
1305
+ const indexTypeId = expression.callee.index.inferredTypeId;
1306
+ const receiver = receiverTypeId === undefined ? undefined : this.arena.get(receiverTypeId);
1307
+ if (receiver?.kind === 'foreign' && indexTypeId !== undefined) {
1308
+ const index = this.interopArgumentType(indexTypeId, expression.callee.index, expression.callee.index.span);
1309
+ if (index.kind !== 'unknown')
1310
+ target = { kind: 'indexed-member', receiver: receiver.ref, index };
1311
+ }
1312
+ }
1313
+ const provider = this.currentInteropProvider(callee);
1314
+ if (provider === undefined) {
1315
+ this.diagnostics.error('L4204', `Cannot resolve JavaScript call for ${callee.display}; use a TypeScript interop adapter`, expression.span);
1316
+ return this.arena.error;
1317
+ }
1318
+ let usage = { target, arguments: interopArguments };
1319
+ if (interopArguments.some(argument => this.interopArgumentHasContextualCallable(argument))) {
1320
+ if (provider.resolveCallUsage === undefined) {
1321
+ this.diagnostics.error('L4204', `Cannot contextually resolve JavaScript callback for ${callee.display}; use a TypeScript interop adapter`, expression.span);
1322
+ return this.arena.error;
1323
+ }
1324
+ let contextualResolution;
1325
+ try {
1326
+ contextualResolution = provider.resolveCallUsage(callee.ref, usage);
1327
+ }
1328
+ catch {
1329
+ contextualResolution = undefined;
1330
+ }
1331
+ if (contextualResolution === undefined || !this.isValidForeignInvocationResolution(contextualResolution, provider, target, false, true)) {
1332
+ this.diagnostics.error('L4204', `Cannot resolve contextual JavaScript callback for ${callee.display}; use a TypeScript interop adapter`, expression.span);
1333
+ return this.arena.error;
1334
+ }
1335
+ const contextualEvidence = this.validateContextualCallableArgumentEvidence(contextualResolution.contextualCallableArguments, interopArguments, provider);
1336
+ if (contextualEvidence === undefined) {
1337
+ this.diagnostics.error('L4204', `Cannot prove contextual JavaScript callback for ${callee.display}; use a TypeScript interop adapter`, expression.span);
1338
+ return this.arena.error;
1339
+ }
1340
+ const evidenceByIndex = new Map(contextualEvidence.map(evidence => [evidence.index, evidence]));
1341
+ const finalized = [];
1342
+ for (let index = 0; index < preparedArguments.length; index++) {
1343
+ const prepared = preparedArguments[index];
1344
+ if (prepared.argument.kind !== 'contextual-callable') {
1345
+ finalized.push(prepared);
1346
+ continue;
1347
+ }
1348
+ const argument = expression.arguments[index];
1349
+ const evidence = evidenceByIndex.get(index);
1350
+ if (argument?.kind !== 'LambdaExpression' || evidence === undefined) {
1351
+ this.diagnostics.error('L4204', `Cannot prove contextual JavaScript callback for ${callee.display}; use a TypeScript interop adapter`, expression.span);
1352
+ return this.arena.error;
1353
+ }
1354
+ const parameterTypes = evidence.target.parameters.map(parameter => this.contextualCallableTypeId(parameter, provider));
1355
+ if (parameterTypes.some(parameter => parameter === undefined)) {
1356
+ this.diagnostics.error('L4204', `Cannot safely project contextual JavaScript callback parameters for ${callee.display}`, argument.span);
1357
+ return this.arena.error;
1358
+ }
1359
+ const typeId = this.checkLambda(argument, scope, undefined, parameterTypes);
1360
+ argument.inferredTypeId = typeId;
1361
+ const projection = this.externalInlineCallableProjection(typeId, argument);
1362
+ if (projection === undefined) {
1363
+ this.diagnostics.error('L4206', `Contextually typed native callback ${this.arena.display(typeId)} cannot cross the generated JavaScript callable boundary`, argument.span);
1364
+ return this.arena.error;
1365
+ }
1366
+ finalized.push({ expression: argument, argument: { kind: 'native-callable', callable: projection.callable }, boundary: projection.boundary, point: prepared.point });
1367
+ }
1368
+ const finalizedObjects = this.finalizeContextualObjectArguments(contextualResolution.objectArguments, finalized, provider, scope);
1369
+ if (finalizedObjects === undefined) {
1370
+ this.diagnostics.error('L4204', `Cannot prove contextual JavaScript object callback for ${callee.display}; use a TypeScript interop adapter`, expression.span);
1371
+ return this.arena.error;
1372
+ }
1373
+ preparedArguments = [...finalizedObjects];
1374
+ interopArguments = preparedArguments.map(argument => argument.argument);
1375
+ callableBoundaries = preparedArguments.map(argument => argument.boundary);
1376
+ usage = { target, arguments: interopArguments };
1377
+ }
1378
+ let resolution;
1379
+ let invocationKind = 'call';
1380
+ const wholeUsageResolution = provider.resolveCallUsage !== undefined;
1381
+ try {
1382
+ resolution = wholeUsageResolution
1383
+ ? provider.resolveCallUsage(callee.ref, usage)
1384
+ : provider.resolveCall(callee.ref, interopArguments);
1385
+ }
1386
+ catch {
1387
+ this.diagnostics.error('L4204', `Cannot resolve JavaScript call for ${callee.display}; use a TypeScript interop adapter`, expression.span);
1388
+ return this.arena.error;
1389
+ }
1390
+ if (resolution !== undefined) {
1391
+ if (!this.isValidForeignInvocationResolution(resolution, provider, usage.target, false, wholeUsageResolution)) {
1392
+ this.diagnostics.error('L4204', `Cannot prove JavaScript call for ${callee.display}; use a TypeScript interop adapter`, expression.span);
1393
+ return this.arena.error;
1394
+ }
1395
+ }
1396
+ else if (provider.resolveConstructUsage !== undefined) {
1397
+ let constructResolution;
1398
+ try {
1399
+ constructResolution = provider.resolveConstructUsage(callee.ref, usage);
1400
+ }
1401
+ catch {
1402
+ constructResolution = undefined;
1403
+ }
1404
+ if (constructResolution !== undefined) {
1405
+ if (!this.isValidForeignInvocationResolution(constructResolution, provider, usage.target, true, true)) {
1406
+ this.diagnostics.error('L4204', `Cannot prove JavaScript construct for ${callee.display}; use a TypeScript interop adapter`, expression.span);
1407
+ return this.arena.error;
1408
+ }
1409
+ resolution = constructResolution;
1410
+ invocationKind = 'construct';
1411
+ }
1412
+ }
1413
+ if (resolution === undefined) {
1414
+ this.diagnostics.error('L4204', `Cannot resolve JavaScript call or construct for ${callee.display}; use a TypeScript interop adapter`, expression.span);
1415
+ return this.arena.error;
1416
+ }
1417
+ if (invocationKind === 'construct' && expression.arguments.some((argument, index) => argument.kind === 'LambdaExpression' && argument.parameters.length === 0 && callableBoundaries[index]?.version === 'virune-callable-shim/v1')) {
1418
+ this.diagnostics.error('L4204', `Zero-argument inline generated callbacks are not supported for JavaScript construct ${callee.display}; use a TypeScript interop adapter`, expression.span);
1419
+ return this.arena.error;
1420
+ }
1421
+ if (resolution.contextualCallableArguments !== undefined && resolution.contextualCallableArguments.length !== 0) {
1422
+ this.diagnostics.error('L4204', `Final JavaScript call retained provisional callback evidence for ${callee.display}`, expression.span);
1423
+ return this.arena.error;
1424
+ }
1425
+ const callableEvidence = this.validateCallableArgumentEvidence(resolution.callableArguments, interopArguments, callableBoundaries, provider);
1426
+ const objectEvidence = this.validateObjectArgumentEvidence(resolution.objectArguments, preparedArguments, provider);
1427
+ if (callableEvidence === undefined || objectEvidence === undefined) {
1428
+ this.diagnostics.error('L4204', `Cannot prove generated boundary for JavaScript ${invocationKind} ${callee.display}; use a TypeScript interop adapter`, expression.span);
1429
+ return this.arena.error;
1430
+ }
1431
+ const pendingObjects = this.commitContextualObjects(objectEvidence);
1432
+ const minimum = resolution.minimumArgumentCount ?? Math.max(0, resolution.parameterCount - resolution.optionalParameterCount);
1433
+ if (expression.arguments.length < minimum || (!resolution.rest && expression.arguments.length > resolution.parameterCount))
1434
+ this.diagnostics.error('L4205', `JavaScript ${invocationKind} expects ${minimum}${resolution.rest ? '+' : `..${resolution.parameterCount}`} arguments, received ${expression.arguments.length}`, expression.span);
1435
+ for (const evidence of callableEvidence) {
1436
+ const argument = expression.arguments.at(evidence.index);
1437
+ const prepared = preparedArguments.at(evidence.index);
1438
+ const boundary = callableBoundaries.at(evidence.index);
1439
+ if (argument === undefined || prepared === undefined || boundary === undefined) {
1440
+ this.diagnostics.error('L4204', `Cannot prove generated callback boundary for JavaScript ${invocationKind} ${callee.display}; use a TypeScript interop adapter`, expression.span);
1441
+ return this.arena.error;
1442
+ }
1443
+ this.requireEffects(boundary.effects, argument.span);
1444
+ if (boundary.version === 'virune-callable-shim/v3')
1445
+ this.requireEffects(boundary.result.effects, argument.span);
1446
+ this.#callableProjections.push({
1447
+ callNodeId: expression.id,
1448
+ argumentIndex: evidence.index,
1449
+ nodeId: argument.id,
1450
+ span: argument.span,
1451
+ beforeUsageIndex: this.adjustedUsageIndex(prepared.point, pendingObjects),
1452
+ descriptor: boundary,
1453
+ });
1454
+ }
1455
+ if (invocationKind === 'call')
1456
+ expression.foreignCall = true;
1457
+ else
1458
+ expression.foreignConstruct = true;
1459
+ this.#interopUsages.push({ kind: invocationKind, nodeId: expression.id, span: expression.span, foreignType: resolution.result, receiverMode: invocationKind === 'call' ? resolution.receiverMode : 'none', ...(invocationKind === 'call' ? { mayReject: resolution.mayReject } : {}) });
1460
+ return this.arena.foreign(resolution.result);
1461
+ }
1462
+ semanticPoint() {
1463
+ return { usageIndex: this.#interopUsages.length, order: this.#semanticOrder++ };
1464
+ }
1465
+ interopArgumentHasContextualCallable(argument) {
1466
+ return argument.kind === 'contextual-callable'
1467
+ || argument.kind === 'contextual-object' && argument.object.entries.some(entry => this.interopArgumentHasContextualCallable(entry.value));
1468
+ }
1469
+ prepareForeignCallValue(expression, scope, allowContextual) {
1470
+ if (allowContextual && expression.kind === 'LambdaExpression' && expression.parameters.length > 0 && expression.parameters.every(parameter => parameter.annotation === undefined)) {
1471
+ return { expression, argument: { kind: 'contextual-callable', parameterCount: expression.parameters.length, async: expression.async }, point: this.semanticPoint() };
1472
+ }
1473
+ return this.prepareInteropValue(expression, scope, true);
1474
+ }
1475
+ prepareInteropValue(expression, scope, allowInlineZeroArgument = false) {
1476
+ if (expression.kind === 'ContextualAggregateExpression') {
1477
+ const object = this.prepareContextualObject(expression, scope);
1478
+ return { expression, argument: { kind: 'contextual-object', object: object.usage }, object, point: this.semanticPoint() };
1479
+ }
1480
+ const typeId = this.checkExpression(expression, scope);
1481
+ const boundary = this.nativeCallableBoundary(typeId, expression, allowInlineZeroArgument);
1482
+ const callable = boundary === undefined
1483
+ ? undefined
1484
+ : boundary.version === 'virune-callable-shim/v3'
1485
+ ? { parameters: boundary.parameters, result: { kind: 'callable', callable: { parameters: boundary.result.parameters, result: boundary.result.result, async: false } }, async: false }
1486
+ : { parameters: boundary.parameters, result: boundary.result, async: boundary.async };
1487
+ const argument = callable === undefined
1488
+ ? this.interopArgumentType(typeId, expression, expression.span)
1489
+ : { kind: 'native-callable', callable };
1490
+ return { expression, argument, ...(boundary === undefined ? {} : { boundary }), point: this.semanticPoint() };
1491
+ }
1492
+ prepareContextualObjectValue(expression, scope) {
1493
+ if (expression.kind === 'LambdaExpression' && expression.parameters.length > 0 && expression.parameters.every(parameter => parameter.annotation === undefined)) {
1494
+ return { expression, argument: { kind: 'contextual-callable', parameterCount: expression.parameters.length, async: expression.async }, point: this.semanticPoint() };
1495
+ }
1496
+ return this.prepareInteropValue(expression, scope);
1497
+ }
1498
+ prepareContextualObject(expression, scope) {
1499
+ const supplied = new Set();
1500
+ const entries = [];
1501
+ for (const entry of expression.entries) {
1502
+ if (supplied.has(entry.name))
1503
+ this.diagnostics.error('L2025', `Duplicate contextual aggregate field ${entry.name}`, entry.span);
1504
+ supplied.add(entry.name);
1505
+ entries.push(this.prepareContextualObjectValue(entry.value, scope));
1506
+ }
1507
+ return {
1508
+ expression,
1509
+ entries,
1510
+ usage: { entries: expression.entries.map((entry, index) => ({ property: entry.name, value: entries[index].argument })) },
1511
+ point: this.semanticPoint(),
1512
+ };
1513
+ }
1514
+ finalizeContextualObjectArguments(raw, preparedArguments, provider, scope) {
1515
+ const expected = preparedArguments.flatMap((argument, index) => argument.object === undefined ? [] : [{ index, object: argument.object }]);
1516
+ if (expected.length === 0)
1517
+ return raw === undefined || Array.isArray(raw) && raw.length === 0 ? Object.freeze([...preparedArguments]) : undefined;
1518
+ if (!Array.isArray(raw) || raw.length !== expected.length)
1519
+ return undefined;
1520
+ const finalized = [...preparedArguments];
1521
+ for (let position = 0; position < expected.length; position++) {
1522
+ const item = raw[position];
1523
+ const expectedItem = expected[position];
1524
+ if (!isRecord(item) || !hasExactEnumerableKeys(item, ['index', 'object']) || item.index !== expectedItem.index)
1525
+ return undefined;
1526
+ const object = this.finalizeContextualObjectCallbacks(item.object, expectedItem.object, provider, scope);
1527
+ const prepared = finalized[expectedItem.index];
1528
+ if (object === undefined || prepared?.object === undefined)
1529
+ return undefined;
1530
+ finalized[expectedItem.index] = { ...prepared, argument: { kind: 'contextual-object', object: object.usage }, object };
1531
+ }
1532
+ return Object.freeze(finalized);
1533
+ }
1534
+ finalizeContextualObjectCallbacks(raw, prepared, provider, scope) {
1535
+ if (!isRecord(raw) || !hasExactEnumerableKeys(raw, ['entries', 'result']) || !Array.isArray(raw.entries))
1536
+ return undefined;
1537
+ if (!this.isCurrentForeignSnapshot(raw.result, provider, false) || raw.entries.length !== prepared.entries.length)
1538
+ return undefined;
1539
+ const finalized = [];
1540
+ for (let index = 0; index < prepared.entries.length; index++) {
1541
+ const entry = raw.entries[index];
1542
+ const preparedEntry = prepared.entries[index];
1543
+ const property = prepared.expression.entries[index]?.name;
1544
+ if (!isRecord(entry) || property === undefined || entry.index !== index || entry.property !== property)
1545
+ return undefined;
1546
+ if (preparedEntry.argument.kind === 'contextual-callable') {
1547
+ if (!hasExactEnumerableKeys(entry, ['callable', 'index', 'property']))
1548
+ return undefined;
1549
+ const callable = entry.callable;
1550
+ if (!isRecord(callable) || !hasExactEnumerableKeys(callable, ['parameters', 'result']) || !Array.isArray(callable.parameters) || callable.parameters.length !== preparedEntry.argument.parameterCount)
1551
+ return undefined;
1552
+ if (!callable.parameters.every(parameter => typeof parameter !== 'string' && parameter.category === 'object' && this.isCurrentForeignSnapshot(parameter, provider, false)))
1553
+ return undefined;
1554
+ const contextualResult = canonicalContextualCallableResult(callable.result);
1555
+ if (contextualResult?.kind !== 'deferred' || preparedEntry.expression.kind !== 'LambdaExpression')
1556
+ return undefined;
1557
+ const parameterTypes = callable.parameters.map(parameter => this.contextualCallableTypeId(parameter, provider));
1558
+ if (parameterTypes.some(parameter => parameter === undefined))
1559
+ return undefined;
1560
+ const typeId = this.checkLambda(preparedEntry.expression, scope, undefined, parameterTypes);
1561
+ preparedEntry.expression.inferredTypeId = typeId;
1562
+ const projection = this.externalInlineCallableProjection(typeId, preparedEntry.expression);
1563
+ if (projection === undefined)
1564
+ return undefined;
1565
+ finalized.push({ expression: preparedEntry.expression, argument: { kind: 'native-callable', callable: projection.callable }, boundary: projection.boundary, point: preparedEntry.point });
1566
+ }
1567
+ else if (preparedEntry.object !== undefined) {
1568
+ if (!hasExactEnumerableKeys(entry, ['index', 'object', 'property']))
1569
+ return undefined;
1570
+ const object = this.finalizeContextualObjectCallbacks(entry.object, preparedEntry.object, provider, scope);
1571
+ if (object === undefined)
1572
+ return undefined;
1573
+ finalized.push({ ...preparedEntry, argument: { kind: 'contextual-object', object: object.usage }, object });
1574
+ }
1575
+ else {
1576
+ const expectedKeys = preparedEntry.boundary === undefined ? ['index', 'property'] : ['callable', 'index', 'property'];
1577
+ if (!hasExactEnumerableKeys(entry, expectedKeys))
1578
+ return undefined;
1579
+ finalized.push(preparedEntry);
1580
+ }
1581
+ }
1582
+ return {
1583
+ expression: prepared.expression,
1584
+ entries: Object.freeze(finalized),
1585
+ usage: { entries: prepared.expression.entries.map((entry, index) => ({ property: entry.name, value: finalized[index].argument })) },
1586
+ point: prepared.point,
1587
+ };
1588
+ }
1589
+ validateObjectArgumentEvidence(raw, preparedArguments, provider) {
1590
+ const expected = preparedArguments.flatMap((argument, index) => argument.object === undefined ? [] : [{ index, object: argument.object }]);
1591
+ if (expected.length === 0)
1592
+ return raw === undefined || Array.isArray(raw) && raw.length === 0 ? [] : undefined;
1593
+ if (!Array.isArray(raw) || raw.length !== expected.length)
1594
+ return undefined;
1595
+ const validated = [];
1596
+ for (let position = 0; position < expected.length; position++) {
1597
+ const item = raw[position];
1598
+ const expectedItem = expected[position];
1599
+ if (!isRecord(item) || !hasExactEnumerableKeys(item, ['index', 'object']) || item.index !== expectedItem.index)
1600
+ return undefined;
1601
+ const object = this.validateContextualObjectEvidence(item.object, expectedItem.object, provider);
1602
+ if (object === undefined)
1603
+ return undefined;
1604
+ validated.push(object);
1605
+ }
1606
+ return Object.freeze(validated);
1607
+ }
1608
+ validateContextualObjectEvidence(raw, prepared, provider) {
1609
+ if (!isRecord(raw) || !hasExactEnumerableKeys(raw, ['entries', 'result']) || !Array.isArray(raw.entries))
1610
+ return undefined;
1611
+ const result = raw.result;
1612
+ if (!this.isCurrentForeignSnapshot(result, provider, false) || raw.entries.length !== prepared.entries.length)
1613
+ return undefined;
1614
+ const nested = [];
1615
+ const entries = [];
1616
+ for (let index = 0; index < prepared.entries.length; index++) {
1617
+ const entry = raw.entries[index];
1618
+ const preparedEntry = prepared.entries[index];
1619
+ const property = prepared.expression.entries[index]?.name;
1620
+ if (!isRecord(entry) || property === undefined || entry.index !== index || entry.property !== property)
1621
+ return undefined;
1622
+ if (preparedEntry.boundary !== undefined) {
1623
+ if (!hasExactEnumerableKeys(entry, ['callable', 'index', 'property']))
1624
+ return undefined;
1625
+ const callable = entry.callable;
1626
+ if (!isRecord(callable) || !hasExactEnumerableKeys(callable, ['parameters', 'result']) || !Array.isArray(callable.parameters))
1627
+ return undefined;
1628
+ let parameters;
1629
+ if (preparedEntry.boundary.version === 'virune-callable-shim/v1' || preparedEntry.boundary.version === 'virune-callable-shim/v3') {
1630
+ const primitives = [];
1631
+ for (const parameter of callable.parameters) {
1632
+ if (!isContextualCallablePrimitive(parameter))
1633
+ return undefined;
1634
+ primitives.push(parameter);
1635
+ }
1636
+ parameters = Object.freeze(primitives);
1637
+ }
1638
+ else {
1639
+ if (!callable.parameters.every(parameter => typeof parameter !== 'string' && parameter.category === 'object' && this.isCurrentForeignSnapshot(parameter, provider, false)))
1640
+ return undefined;
1641
+ parameters = Object.freeze(callable.parameters);
1642
+ }
1643
+ const contextualResult = canonicalContextualCallableResult(callable.result);
1644
+ if (contextualResult === undefined || !this.callableBoundaryMatchesContext(preparedEntry.boundary, parameters, contextualResult))
1645
+ return undefined;
1646
+ entries.push({ index, property, callable: Object.freeze({ parameters, result: contextualResult }) });
1647
+ nested.push(undefined);
1648
+ }
1649
+ else if (preparedEntry.object !== undefined) {
1650
+ if (!hasExactEnumerableKeys(entry, ['index', 'object', 'property']))
1651
+ return undefined;
1652
+ const child = this.validateContextualObjectEvidence(entry.object, preparedEntry.object, provider);
1653
+ if (child === undefined)
1654
+ return undefined;
1655
+ entries.push({ index, property, object: child.resolution });
1656
+ nested.push(child);
1657
+ }
1658
+ else {
1659
+ if (!hasExactEnumerableKeys(entry, ['index', 'property']))
1660
+ return undefined;
1661
+ entries.push({ index, property });
1662
+ nested.push(undefined);
1663
+ }
1664
+ }
1665
+ return { prepared, resolution: { result, entries: Object.freeze(entries) }, nested: Object.freeze(nested) };
1666
+ }
1667
+ validateWriteEvidence(raw, prepared, provider) {
1668
+ if (!isRecord(raw) || raw.accepted !== true)
1669
+ return undefined;
1670
+ if (prepared.object === undefined)
1671
+ return hasExactEnumerableKeys(raw, ['accepted']) ? null : undefined;
1672
+ if (!hasExactEnumerableKeys(raw, ['accepted', 'objectValue']))
1673
+ return undefined;
1674
+ return this.validateContextualObjectEvidence(raw.objectValue, prepared.object, provider);
1675
+ }
1676
+ commitContextualObjects(validatedRoots) {
1677
+ const pendingObjects = [];
1678
+ const pendingProjections = [];
1679
+ const collect = (validated) => {
1680
+ for (let index = 0; index < validated.prepared.entries.length; index++) {
1681
+ const nested = validated.nested[index];
1682
+ if (nested !== undefined)
1683
+ collect(nested);
1684
+ const preparedEntry = validated.prepared.entries[index];
1685
+ const descriptor = preparedEntry.boundary;
1686
+ const property = validated.prepared.expression.entries[index]?.name;
1687
+ if (descriptor !== undefined && property !== undefined)
1688
+ pendingProjections.push({ prepared: preparedEntry, owner: validated.prepared, entryIndex: index, property, descriptor });
1689
+ }
1690
+ validated.prepared.expression.foreignObject = true;
1691
+ validated.prepared.expression.inferredTypeId = this.arena.foreign(validated.resolution.result);
1692
+ pendingObjects.push({ point: validated.prepared.point, usage: { kind: 'object', nodeId: validated.prepared.expression.id, span: validated.prepared.expression.span, foreignType: validated.resolution.result } });
1693
+ };
1694
+ for (const root of validatedRoots)
1695
+ collect(root);
1696
+ pendingObjects.sort((left, right) => compareSemanticPoint(left.point, right.point));
1697
+ let inserted = 0;
1698
+ for (const pending of pendingObjects) {
1699
+ this.#interopUsages.splice(pending.point.usageIndex + inserted, 0, pending.usage);
1700
+ inserted++;
1701
+ }
1702
+ for (const projection of pendingProjections) {
1703
+ this.requireEffects(projection.descriptor.effects, projection.prepared.expression.span);
1704
+ if (projection.descriptor.version === 'virune-callable-shim/v3')
1705
+ this.requireEffects(projection.descriptor.result.effects, projection.prepared.expression.span);
1706
+ this.#objectCallableProjections.push({
1707
+ objectNodeId: projection.owner.expression.id,
1708
+ entryIndex: projection.entryIndex,
1709
+ property: projection.property,
1710
+ beforeUsageIndex: this.adjustedUsageIndex(projection.prepared.point, pendingObjects),
1711
+ descriptor: projection.descriptor,
1712
+ });
1713
+ }
1714
+ return Object.freeze(pendingObjects);
1715
+ }
1716
+ adjustedUsageIndex(point, pendingObjects) {
1717
+ let before = 0;
1718
+ for (const pending of pendingObjects)
1719
+ if (compareSemanticPoint(pending.point, point) < 0)
1720
+ before++;
1721
+ return point.usageIndex + before;
1722
+ }
1723
+ currentInteropProvider(snapshot) {
1724
+ const provider = this.#jsInteropProvider;
1725
+ if (provider === undefined || !isRecord(snapshot) || !isRecord(snapshot.ref))
1726
+ return undefined;
1727
+ return snapshot.ref.providerId === provider.id && snapshot.ref.generation === provider.generation ? provider : undefined;
1728
+ }
1729
+ isCurrentForeignSnapshot(snapshot, provider, allowUnknown) {
1730
+ if (!isRecord(snapshot) || !isRecord(snapshot.ref))
1731
+ return false;
1732
+ if (snapshot.ref.providerId !== provider.id || snapshot.ref.generation !== provider.generation || typeof snapshot.ref.id !== 'string' || snapshot.ref.id.length === 0)
1733
+ return false;
1734
+ if (!Number.isSafeInteger(snapshot.ref.generation) || typeof snapshot.display !== 'string' || snapshot.display.length === 0)
1735
+ return false;
1736
+ const categories = allowUnknown
1737
+ ? ['primitive', 'literal', 'object', 'function', 'constructor', 'promise', 'array', 'tuple', 'union', 'unknown']
1738
+ : ['primitive', 'literal', 'object', 'function', 'constructor', 'promise', 'array', 'tuple', 'union'];
1739
+ if (typeof snapshot.category !== 'string' || !categories.includes(snapshot.category))
1740
+ return false;
1741
+ const primitives = ['boolean', 'string', 'number', 'bigint', 'void', 'undefined', 'null'];
1742
+ if (snapshot.primitive !== undefined && (typeof snapshot.primitive !== 'string' || !primitives.includes(snapshot.primitive)))
1743
+ return false;
1744
+ if (snapshot.category === 'primitive' && snapshot.primitive === undefined)
1745
+ return false;
1746
+ if (snapshot.primitive !== undefined && snapshot.category !== 'primitive' && snapshot.category !== 'literal')
1747
+ return false;
1748
+ if (snapshot.mustUse !== undefined && typeof snapshot.mustUse !== 'boolean')
1749
+ return false;
1750
+ if (snapshot.origin !== undefined) {
1751
+ if (!isRecord(snapshot.origin) || typeof snapshot.origin.moduleSpecifier !== 'string' || snapshot.origin.moduleSpecifier.length === 0)
1752
+ return false;
1753
+ for (const field of ['packageName', 'packageVersion', 'declarationPath', 'exportName']) {
1754
+ const value = snapshot.origin[field];
1755
+ if (value !== undefined && typeof value !== 'string')
1756
+ return false;
1757
+ }
1758
+ }
1759
+ if (snapshot.navigation !== undefined && (!isRecord(snapshot.navigation) || typeof snapshot.navigation.declarationPath !== 'string'))
1760
+ return false;
1761
+ try {
1762
+ if (provider.display(snapshot.ref) !== snapshot.display)
1763
+ return false;
1764
+ }
1765
+ catch {
1766
+ return false;
1767
+ }
1768
+ return true;
1769
+ }
1770
+ isValidForeignInvocationResolution(resolution, provider, target, construct, wholeUsage) {
1771
+ if (!isRecord(resolution) || !this.isCurrentForeignSnapshot(resolution.result, provider, !construct))
1772
+ return false;
1773
+ if (!Number.isSafeInteger(resolution.parameterCount) || resolution.parameterCount < 0)
1774
+ return false;
1775
+ if (!Number.isSafeInteger(resolution.optionalParameterCount) || resolution.optionalParameterCount < 0 || resolution.optionalParameterCount > resolution.parameterCount)
1776
+ return false;
1777
+ const arityLowerBound = Math.max(0, resolution.parameterCount - resolution.optionalParameterCount);
1778
+ if (resolution.minimumArgumentCount !== undefined && (!Number.isSafeInteger(resolution.minimumArgumentCount)
1779
+ || resolution.minimumArgumentCount < arityLowerBound
1780
+ || resolution.minimumArgumentCount > resolution.parameterCount))
1781
+ return false;
1782
+ if (wholeUsage && resolution.minimumArgumentCount === undefined)
1783
+ return false;
1784
+ if (typeof resolution.rest !== 'boolean' || typeof resolution.mayReject !== 'boolean')
1785
+ return false;
1786
+ if (resolution.receiverMode !== 'none' && resolution.receiverMode !== 'preserve-this')
1787
+ return false;
1788
+ if (wholeUsage) {
1789
+ const expectedReceiverMode = construct || target.kind === 'value' ? 'none' : 'preserve-this';
1790
+ if (resolution.receiverMode !== expectedReceiverMode)
1791
+ return false;
1792
+ }
1793
+ return !construct || resolution.receiverMode === 'none';
1794
+ }
1795
+ nativeCallableBoundary(typeId, expression, allowInlineZeroArgument = false) {
1796
+ const type = this.arena.get(typeId);
1797
+ if (type.kind !== 'function' || type.typeParameters.length !== 0 || type.effects.some(effect => effect === '*' || !this.#effects.has(effect)))
1798
+ return undefined;
1799
+ if (expression.kind === 'LambdaExpression') {
1800
+ if (!allowInlineZeroArgument || expression.async || expression.parameters.length !== 0 || type.parameters.length !== 0)
1801
+ return undefined;
1802
+ }
1803
+ else {
1804
+ if (expression.kind !== 'IdentifierExpression' || expression.symbolId === undefined)
1805
+ return undefined;
1806
+ const symbol = this.#symbols.get(expression.symbolId);
1807
+ const declaration = symbol?.declaration;
1808
+ if (symbol?.kind !== 'function' || declaration?.kind !== 'FunctionDeclaration')
1809
+ return undefined;
1810
+ const functionDeclaration = declaration;
1811
+ if (functionDeclaration.typeParameters.length !== 0 || functionDeclaration.attributes.some(attribute => attribute.name === 'jsExport'))
1812
+ return undefined;
1813
+ }
1814
+ const parameters = [];
1815
+ for (const parameter of type.parameters) {
1816
+ const primitive = this.nativeCallablePrimitive(parameter);
1817
+ if (primitive === undefined)
1818
+ return undefined;
1819
+ parameters.push(primitive);
1820
+ }
1821
+ const result = this.nativeCallablePrimitive(type.result);
1822
+ const effects = Object.freeze([...new Set(type.effects)].sort(compareText));
1823
+ if (result !== undefined) {
1824
+ return Object.freeze({
1825
+ version: 'virune-callable-shim/v1',
1826
+ parameters: Object.freeze(parameters),
1827
+ result,
1828
+ async: type.async,
1829
+ effects,
1830
+ contextMode: 'root-argument',
1831
+ });
1832
+ }
1833
+ if (type.async)
1834
+ return undefined;
1835
+ const nested = this.arena.get(type.result);
1836
+ if (nested.kind !== 'function' || nested.async || nested.typeParameters.length !== 0 || nested.effects.some(effect => effect === '*' || !this.#effects.has(effect)))
1837
+ return undefined;
1838
+ const nestedParameters = [];
1839
+ for (const parameter of nested.parameters) {
1840
+ const primitive = this.nativeCallablePrimitive(parameter);
1841
+ if (primitive === undefined)
1842
+ return undefined;
1843
+ nestedParameters.push(primitive);
1844
+ }
1845
+ const nestedResult = this.nativeCallablePrimitive(nested.result);
1846
+ if (nestedResult === undefined)
1847
+ return undefined;
1848
+ const nestedBoundary = Object.freeze({
1849
+ version: 'virune-callable-shim/v1',
1850
+ parameters: Object.freeze(nestedParameters),
1851
+ result: nestedResult,
1852
+ async: false,
1853
+ effects: Object.freeze([...new Set(nested.effects)].sort(compareText)),
1854
+ contextMode: 'root-argument',
1855
+ });
1856
+ return Object.freeze({
1857
+ version: 'virune-callable-shim/v3',
1858
+ parameters: Object.freeze(parameters),
1859
+ result: nestedBoundary,
1860
+ async: false,
1861
+ effects,
1862
+ contextMode: 'root-argument',
1863
+ });
1864
+ }
1865
+ nativeCallablePrimitive(typeId) {
1866
+ const type = this.arena.get(typeId);
1867
+ if (type.kind !== 'primitive')
1868
+ return undefined;
1869
+ return ['Bool', 'Int', 'Float', 'BigInt', 'String', 'Unit'].includes(type.name) ? type.name : undefined;
1870
+ }
1871
+ externalInlineCallableProjection(typeId, expression) {
1872
+ const type = this.arena.get(typeId);
1873
+ if (type.kind !== 'function' || type.async !== expression.async || type.typeParameters.length !== 0 || type.parameters.length === 0 || type.effects.some(effect => effect === '*' || !this.#effects.has(effect)))
1874
+ return undefined;
1875
+ const parameters = [];
1876
+ for (const parameter of type.parameters) {
1877
+ const foreign = this.currentExternalObjectRef(parameter);
1878
+ if (foreign === undefined)
1879
+ return undefined;
1880
+ parameters.push({ kind: 'foreign', type: foreign });
1881
+ }
1882
+ const resultType = this.arena.get(type.result);
1883
+ const result = resultType.kind === 'primitive' && resultType.name === 'Never' ? 'Never' : this.currentExternalRef(type.result);
1884
+ if (result === undefined)
1885
+ return undefined;
1886
+ return Object.freeze({
1887
+ callable: Object.freeze({
1888
+ parameters: Object.freeze(parameters),
1889
+ result: result === 'Never' ? result : { kind: 'foreign', type: result },
1890
+ async: type.async,
1891
+ }),
1892
+ boundary: Object.freeze({
1893
+ version: 'virune-callable-shim/v2',
1894
+ parameters: Object.freeze(parameters.map(() => 'External')),
1895
+ result: result === 'Never' ? 'Never' : 'External',
1896
+ async: type.async,
1897
+ effects: Object.freeze([...new Set(type.effects)].sort(compareText)),
1898
+ contextMode: 'root-argument',
1899
+ }),
1900
+ });
1901
+ }
1902
+ currentExternalRef(typeId) {
1903
+ const type = this.arena.get(typeId);
1904
+ if (type.kind !== 'foreign')
1905
+ return undefined;
1906
+ const provider = this.currentInteropProvider(type.snapshot);
1907
+ return provider !== undefined && this.isCurrentForeignSnapshot(type.snapshot, provider, false) ? type.ref : undefined;
1908
+ }
1909
+ currentExternalObjectRef(typeId) {
1910
+ const type = this.arena.get(typeId);
1911
+ if (type.kind !== 'foreign' || type.snapshot.category !== 'object')
1912
+ return undefined;
1913
+ const provider = this.currentInteropProvider(type.snapshot);
1914
+ return provider !== undefined && this.isCurrentForeignSnapshot(type.snapshot, provider, false) ? type.ref : undefined;
1915
+ }
1916
+ validateContextualCallableArgumentEvidence(raw, interopArguments, provider) {
1917
+ const expectedIndexes = interopArguments.flatMap((argument, index) => argument.kind === 'contextual-callable' ? [index] : []);
1918
+ if (expectedIndexes.length === 0)
1919
+ return raw === undefined || Array.isArray(raw) && raw.length === 0 ? [] : undefined;
1920
+ if (!Array.isArray(raw) || raw.length !== expectedIndexes.length)
1921
+ return undefined;
1922
+ const result = [];
1923
+ for (let position = 0; position < raw.length; position++) {
1924
+ const item = raw[position];
1925
+ const expectedIndex = expectedIndexes[position];
1926
+ if (!isRecord(item) || !hasExactEnumerableKeys(item, ['index', 'target']) || item.index !== expectedIndex || expectedIndex === undefined)
1927
+ return undefined;
1928
+ const target = item.target;
1929
+ const expectedArgument = interopArguments[expectedIndex];
1930
+ if (!isRecord(target) || !hasExactEnumerableKeys(target, ['parameters', 'result']) || !Array.isArray(target.parameters) || expectedArgument?.kind !== 'contextual-callable' || target.parameters.length !== expectedArgument.parameterCount)
1931
+ return undefined;
1932
+ const parameters = target.parameters.every(parameter => typeof parameter !== 'string' && parameter.category === 'object' && this.isCurrentForeignSnapshot(parameter, provider, false))
1933
+ ? Object.freeze(target.parameters)
1934
+ : undefined;
1935
+ const contextualResult = canonicalContextualCallableResult(target.result);
1936
+ if (parameters === undefined || contextualResult?.kind !== 'deferred')
1937
+ return undefined;
1938
+ result.push(Object.freeze({ index: expectedIndex, target: Object.freeze({ parameters, result: contextualResult }) }));
1939
+ }
1940
+ return Object.freeze(result);
1941
+ }
1942
+ validateCallableArgumentEvidence(raw, interopArguments, boundaries, provider) {
1943
+ const expectedIndexes = interopArguments.flatMap((argument, index) => argument.kind === 'native-callable' ? [index] : []);
1944
+ if (expectedIndexes.length === 0)
1945
+ return raw === undefined || Array.isArray(raw) && raw.length === 0 ? [] : undefined;
1946
+ if (!Array.isArray(raw) || raw.length !== expectedIndexes.length)
1947
+ return undefined;
1948
+ const result = [];
1949
+ for (let position = 0; position < raw.length; position++) {
1950
+ const item = raw[position];
1951
+ if (!isRecord(item) || !hasExactEnumerableKeys(item, ['index', 'target']))
1952
+ return undefined;
1953
+ const index = item.index;
1954
+ const expectedIndex = expectedIndexes.at(position);
1955
+ if (typeof index !== 'number' || !Number.isSafeInteger(index) || index < 0 || expectedIndex === undefined || index !== expectedIndex)
1956
+ return undefined;
1957
+ const target = item.target;
1958
+ const boundary = boundaries.at(index);
1959
+ if (!isRecord(target) || !hasExactEnumerableKeys(target, ['parameters', 'result']) || !Array.isArray(target.parameters) || boundary === undefined)
1960
+ return undefined;
1961
+ const contextualResult = canonicalContextualCallableResult(target.result);
1962
+ if (contextualResult === undefined)
1963
+ return undefined;
1964
+ let parameters;
1965
+ if (boundary.version === 'virune-callable-shim/v1' || boundary.version === 'virune-callable-shim/v3') {
1966
+ const primitives = [];
1967
+ for (const parameter of target.parameters) {
1968
+ if (!isContextualCallablePrimitive(parameter))
1969
+ return undefined;
1970
+ primitives.push(parameter);
1971
+ }
1972
+ parameters = Object.freeze(primitives);
1973
+ }
1974
+ else {
1975
+ if (!target.parameters.every(parameter => typeof parameter !== 'string' && parameter.category === 'object' && this.isCurrentForeignSnapshot(parameter, provider, false)))
1976
+ return undefined;
1977
+ parameters = Object.freeze(target.parameters);
1978
+ }
1979
+ if (!this.callableBoundaryMatchesContext(boundary, parameters, contextualResult))
1980
+ return undefined;
1981
+ result.push(Object.freeze({ index, target: Object.freeze({ parameters, result: contextualResult }) }));
1982
+ }
1983
+ return Object.freeze(result);
1984
+ }
1985
+ contextualCallableTypeId(value, provider) {
1986
+ return typeof value !== 'string' && value.category === 'object' && this.isCurrentForeignSnapshot(value, provider, false)
1987
+ ? this.arena.foreign(value)
1988
+ : undefined;
1989
+ }
1990
+ callableBoundaryMatchesContext(boundary, parameters, result) {
1991
+ if (boundary.parameters.length !== parameters.length || result.kind === 'deferred')
1992
+ return false;
1993
+ if (boundary.version === 'virune-callable-shim/v3') {
1994
+ if (boundary.async || result.kind !== 'callable' || parameters.some(parameter => typeof parameter !== 'string'))
1995
+ return false;
1996
+ if (boundary.parameters.some((parameter, index) => !callableParameterMatchesContext(parameter, parameters[index])))
1997
+ return false;
1998
+ return this.callableBoundaryMatchesContext(boundary.result, result.callable.parameters, result.callable.result);
1999
+ }
2000
+ if (boundary.version === 'virune-callable-shim/v2') {
2001
+ return boundary.parameters.every(parameter => parameter === 'External')
2002
+ && parameters.every(parameter => typeof parameter !== 'string')
2003
+ && result.kind === 'external';
2004
+ }
2005
+ if (parameters.some(parameter => typeof parameter !== 'string'))
2006
+ return false;
2007
+ if (boundary.parameters.some((parameter, index) => !callableParameterMatchesContext(parameter, parameters[index])))
2008
+ return false;
2009
+ if (result.kind === 'external' || result.kind === 'callable')
2010
+ return false;
2011
+ if (result.kind === 'void')
2012
+ return !boundary.async && boundary.result === 'Unit';
2013
+ if (boundary.async !== (result.kind === 'promise'))
2014
+ return false;
2015
+ if (boundary.result === 'Unit')
2016
+ return result.value === 'undefined' || result.kind === 'promise' && result.value === 'void';
2017
+ return callablePrimitiveExternalName(boundary.result) === result.value;
2018
+ }
2019
+ interopArgumentType(typeId, expression, span) {
2020
+ const type = this.arena.get(typeId);
2021
+ if (type.kind === 'foreign')
2022
+ return { kind: 'foreign', type: type.ref };
2023
+ if (type.kind === 'primitive') {
2024
+ if (type.name === 'Unknown' || type.name === 'Never' || type.name === 'InvalidType')
2025
+ return { kind: 'unknown' };
2026
+ const literal = this.interopLiteralValue(expression);
2027
+ return literal === undefined ? { kind: 'native-primitive', primitive: type.name } : { kind: 'native-primitive', primitive: type.name, literal };
2028
+ }
2029
+ this.diagnostics.error('L4206', `Native value of type ${this.arena.display(typeId)} must be explicitly encoded before passing it to JavaScript`, span);
2030
+ return { kind: 'unknown' };
2031
+ }
2032
+ interopLiteralValue(expression) {
2033
+ let literal;
2034
+ let negative = false;
2035
+ if (expression.kind === 'LiteralExpression')
2036
+ literal = expression;
2037
+ else if (expression.kind === 'UnaryExpression' && expression.operator === '-' && expression.operand.kind === 'LiteralExpression' && ['Int', 'Float', 'BigInt'].includes(expression.operand.literalKind)) {
2038
+ literal = expression.operand;
2039
+ negative = true;
2040
+ }
2041
+ if (literal === undefined)
2042
+ return undefined;
2043
+ if (literal.literalKind === 'String' && typeof literal.value === 'string')
2044
+ return { kind: 'String', value: literal.value };
2045
+ if (literal.literalKind === 'Bool' && typeof literal.value === 'boolean')
2046
+ return { kind: 'Bool', value: literal.value };
2047
+ if ((literal.literalKind === 'Int' || literal.literalKind === 'Float') && typeof literal.value === 'number') {
2048
+ const value = negative ? -literal.value : literal.value;
2049
+ return Number.isFinite(value) ? { kind: literal.literalKind, value } : undefined;
2050
+ }
2051
+ if (literal.literalKind === 'BigInt' && typeof literal.value === 'bigint') {
2052
+ const value = negative ? -literal.value : literal.value;
2053
+ return { kind: 'BigInt', value: value.toString(10) };
2054
+ }
2055
+ return undefined;
2056
+ }
2057
+ applyExpectedForeignBridge(expression, actual, expected) {
2058
+ if (expected === undefined)
2059
+ return actual;
2060
+ const source = this.arena.get(actual);
2061
+ if (source.kind !== 'foreign')
2062
+ return actual;
2063
+ const provider = this.currentInteropProvider(source.snapshot);
2064
+ if (provider === undefined || !this.isCurrentForeignSnapshot(source.snapshot, provider, true))
2065
+ return actual;
2066
+ const bridge = this.primitiveBridge(source.snapshot, expected);
2067
+ if (bridge === undefined)
2068
+ return actual;
2069
+ expression.foreignBridge = bridge;
2070
+ this.#interopUsages.push({ kind: 'bridge', nodeId: expression.id, span: expression.span, foreignType: source.snapshot, bridge: { kind: 'primitive', bridge, targetType: expected } });
2071
+ return expected;
2072
+ }
2073
+ primitiveBridge(source, target) {
2074
+ if (source.primitive === 'string' && this.arena.equals(target, this.arena.string))
2075
+ return 'string';
2076
+ if (source.primitive === 'boolean' && this.arena.equals(target, this.arena.bool))
2077
+ return 'bool';
2078
+ if (source.primitive === 'number' && this.arena.equals(target, this.arena.float))
2079
+ return 'float';
2080
+ if (source.primitive === 'bigint' && this.arena.equals(target, this.arena.bigint))
2081
+ return 'bigint';
2082
+ if (source.primitive === 'void' && this.arena.equals(target, this.arena.unit))
2083
+ return 'unit';
2084
+ if (source.category === 'unknown' && this.arena.equals(target, this.arena.unknown))
2085
+ return 'unknown';
2086
+ return undefined;
2087
+ }
2088
+ literalType(expression) {
2089
+ switch (expression.literalKind) {
2090
+ case 'String': return this.arena.string;
2091
+ case 'Int':
2092
+ if (!Number.isSafeInteger(expression.value))
2093
+ this.diagnostics.error('L2065', 'Int literal is outside the JavaScript safe integer range; use BigInt', expression.span);
2094
+ return this.arena.int;
2095
+ case 'Float': return this.arena.float;
2096
+ case 'BigInt': return this.arena.bigint;
2097
+ case 'Bool': return this.arena.bool;
2098
+ }
2099
+ }
2100
+ checkCall(expression, scope, expected) {
2101
+ if (expression.callee.kind === 'IdentifierExpression') {
2102
+ const component = scope.lookup(expression.callee.name);
2103
+ if (component?.kind === 'component') {
2104
+ expression.callee.symbolId = component.id;
2105
+ this.diagnostics.error('L4305', `Component ${expression.callee.name} cannot be called as an ordinary Virune function; use it as a View tag`, expression.callee.span);
2106
+ for (const argument of expression.arguments)
2107
+ this.checkExpression(argument, scope);
2108
+ return this.arena.error;
2109
+ }
2110
+ }
2111
+ const calleeTypeId = this.checkExpression(expression.callee, scope);
2112
+ const calleeType = this.arena.get(calleeTypeId);
2113
+ if (calleeType.kind === 'foreign')
2114
+ return this.checkForeignCall(expression, scope, calleeType.snapshot);
2115
+ if (calleeType.kind !== 'function') {
2116
+ this.diagnostics.error('L2012', `Value of type ${this.arena.display(calleeTypeId)} is not callable`, expression.callee.span);
2117
+ expression.arguments.forEach(arg => this.checkExpression(arg, scope));
2118
+ return this.arena.error;
2119
+ }
2120
+ this.requireEffects(calleeType.effects.filter(effect => effect !== '*'), expression.span);
2121
+ if (expression.arguments.length !== calleeType.parameters.length)
2122
+ this.diagnostics.error('L2013', `Expected ${calleeType.parameters.length} arguments, received ${expression.arguments.length}`, expression.span);
2123
+ const substitutions = new Map();
2124
+ const argumentTypes = [];
2125
+ if (expression.typeArguments.length > 0) {
2126
+ if (expression.typeArguments.length !== calleeType.typeParameters.length)
2127
+ this.diagnostics.error('L2046', `Expected ${calleeType.typeParameters.length} type arguments, received ${expression.typeArguments.length}`, expression.span);
2128
+ expression.typeArguments.forEach((argument, index) => {
2129
+ const name = calleeType.typeParameters[index];
2130
+ if (name !== undefined)
2131
+ substitutions.set(name, this.resolveTypeReference(argument, this.#currentFunction?.typeParameters ?? new Map()));
2132
+ });
2133
+ }
2134
+ expression.arguments.forEach((argument, index) => {
2135
+ const parameter = calleeType.parameters[index] ?? this.arena.error;
2136
+ const argumentType = this.checkExpression(argument, scope, parameter);
2137
+ argumentTypes.push(argumentType);
2138
+ this.unify(parameter, argumentType, substitutions);
2139
+ const substituted = this.substitute(parameter, substitutions);
2140
+ if (!this.isAssignable(argumentType, substituted))
2141
+ this.typeMismatch(argumentType, substituted, argument.span);
2142
+ const argumentFunction = this.arena.get(argumentType);
2143
+ if (argumentFunction.kind === 'function')
2144
+ this.requireEffects(argumentFunction.effects.filter(effect => effect !== '*'), argument.span);
2145
+ });
2146
+ if (expected !== undefined)
2147
+ this.unify(calleeType.result, expected, substitutions);
2148
+ const result = this.substitute(calleeType.result, substitutions);
2149
+ if (expression.callee.kind === 'FieldExpression' && expression.callee.target.kind === 'IdentifierExpression' && expression.callee.target.name === 'Json') {
2150
+ const resultType = this.arena.get(result);
2151
+ const jsonTarget = expression.callee.field === 'decode' && resultType.kind === 'result' ? resultType.value : expression.callee.field === 'encode' ? argumentTypes[0] : undefined;
2152
+ if (jsonTarget !== undefined) {
2153
+ if (this.containsTypeVariable(jsonTarget))
2154
+ this.diagnostics.error('L2047', `Cannot infer JSON target type for Json.${expression.callee.field}`, expression.span);
2155
+ else if (!this.supportsJson(jsonTarget))
2156
+ this.diagnostics.error('L2048', `Type ${this.arena.display(jsonTarget)} does not support Json`, expression.span);
2157
+ }
2158
+ }
2159
+ if (expression.callee.kind === 'FieldExpression' && expression.callee.target.kind === 'IdentifierExpression') {
2160
+ const namespace = expression.callee.target.name;
2161
+ const target = argumentTypes[0];
2162
+ if (namespace === 'Debug' && target !== undefined && !this.supportsDerive(target, 'Debug'))
2163
+ this.diagnostics.error('L2063', `Type ${this.arena.display(target)} does not support Debug`, expression.span);
2164
+ if (namespace === 'Map') {
2165
+ const key = substitutions.get('T') ?? (expression.callee.field === 'empty' ? undefined : expression.callee.field === 'size' || expression.callee.field === 'keys' || expression.callee.field === 'values' || expression.callee.field === 'entries' || expression.callee.field === 'mapValues' ? this.mapKeyOf(argumentTypes[0]) : argumentTypes[1]);
2166
+ if (key !== undefined && (!this.supportsEq(key) || !this.supportsHash(key)))
2167
+ this.diagnostics.error('L2109', `Map key type ${this.arena.display(key)} must support structural Eq and Hash`, expression.span);
2168
+ }
2169
+ if (namespace === 'Set') {
2170
+ const element = substitutions.get('T') ?? (expression.callee.field === 'from' ? this.listElementOf(argumentTypes[0]) : expression.callee.field === 'empty' ? undefined : this.setElementOf(argumentTypes[0]) ?? argumentTypes[1]);
2171
+ if (element !== undefined && (!this.supportsEq(element) || !this.supportsHash(element)))
2172
+ this.diagnostics.error('L2110', `Set element type ${this.arena.display(element)} must support structural Eq and Hash`, expression.span);
2173
+ }
2174
+ if (namespace === 'List' && expression.callee.field === 'unique') {
2175
+ const element = this.listElementOf(argumentTypes[0]);
2176
+ if (element !== undefined && (!this.supportsEq(element) || !this.supportsHash(element)))
2177
+ this.diagnostics.error('L2111', `List.unique element type ${this.arena.display(element)} must support structural Eq and Hash`, expression.span);
2178
+ }
2179
+ if (namespace === 'List' && expression.callee.field === 'uniqueBy') {
2180
+ const key = substitutions.get('U');
2181
+ if (key !== undefined && (!this.supportsEq(key) || !this.supportsHash(key)))
2182
+ this.diagnostics.error('L2111', `List.uniqueBy key type ${this.arena.display(key)} must support structural Eq and Hash`, expression.span);
2183
+ }
2184
+ }
2185
+ return calleeType.async ? this.arena.future(result) : result;
2186
+ }
2187
+ checkField(expression, scope) {
2188
+ if (expression.target.kind === 'IdentifierExpression') {
2189
+ const namespace = expression.target.name;
2190
+ this.checkBuiltinPlatform(namespace, expression.span);
2191
+ const builtin = this.builtinMember(namespace, expression.field);
2192
+ if (builtin !== undefined) {
2193
+ this.checkExpression(expression.target, scope);
2194
+ return builtin;
2195
+ }
2196
+ const symbol = scope.lookup(namespace);
2197
+ if (symbol?.kind === 'type') {
2198
+ const named = this.arena.get(symbol.typeId);
2199
+ if (named.kind === 'named' && named.declarationKind === 'newtype' && named.underlying !== undefined && expression.field === 'create') {
2200
+ expression.target.symbolId = symbol.id;
2201
+ if (symbol.typeOnly)
2202
+ this.diagnostics.error('L1012', `Type-only import ${namespace} cannot be used as a value`, expression.span);
2203
+ if (symbol.declaration !== undefined && this.#signatureOnlyNodeIds.has(symbol.declaration.id))
2204
+ this.diagnostics.error('L2117', `newtype constructor ${namespace}.create is private to its declaring module`, expression.span);
2205
+ return this.arena.function([named.underlying], symbol.typeId);
2206
+ }
2207
+ if (named.kind === 'named' && named.declarationKind === 'enum' && named.variants?.has(expression.field)) {
2208
+ expression.target.symbolId = symbol.id;
2209
+ if (symbol.typeOnly)
2210
+ this.diagnostics.error('L1012', `Type-only import ${namespace} cannot be used as a value`, expression.span);
2211
+ const values = named.variants.get(expression.field) ?? [];
2212
+ return values.length === 0 ? symbol.typeId : this.arena.function(values, symbol.typeId);
2213
+ }
2214
+ }
2215
+ }
2216
+ const targetTypeId = this.checkExpression(expression.target, scope);
2217
+ const targetType = this.arena.get(targetTypeId);
2218
+ if (targetType.kind === 'foreign') {
2219
+ this.requireEffects(['JavaScript'], expression.span);
2220
+ const property = this.#jsInteropProvider?.getProperty(targetType.ref, expression.field);
2221
+ if (property === undefined) {
2222
+ this.diagnostics.error('L4202', `Foreign type ${targetType.snapshot.display} has no accessible property ${expression.field}`, expression.span);
2223
+ return this.arena.error;
2224
+ }
2225
+ this.#interopUsages.push({ kind: 'property', nodeId: expression.id, span: expression.span, foreignType: property });
2226
+ return this.arena.foreign(property);
2227
+ }
2228
+ if (targetType.kind === 'named' && targetType.fields?.has(expression.field))
2229
+ return targetType.fields.get(expression.field);
2230
+ if (targetType.kind === 'list' && expression.field === 'length')
2231
+ return this.arena.int;
2232
+ this.diagnostics.error('L2014', `Type ${this.arena.display(targetTypeId)} has no field ${expression.field}`, expression.span);
2233
+ return this.arena.error;
2234
+ }
2235
+ builtinMember(namespace, member) {
2236
+ return builtinMember(this.arena, this.#namedTypes, namespace, member);
2237
+ }
2238
+ checkBuiltinPlatform(namespace, span) {
2239
+ const nodeOnly = new Set(['File', 'Path', 'Process', 'Http', 'Crypto']);
2240
+ const browserOnly = new Set(['Fetch', 'Storage', 'Dom']);
2241
+ if (nodeOnly.has(namespace) && this.#platform !== 'node')
2242
+ this.diagnostics.error('L4010', `${namespace} is only available on the node platform`, span);
2243
+ if (browserOnly.has(namespace) && this.#platform !== 'browser')
2244
+ this.diagnostics.error('L4011', `${namespace} is only available on the browser platform`, span);
2245
+ }
2246
+ requireEffects(required, span) {
2247
+ if (required.length === 0)
2248
+ return;
2249
+ const allowed = this.#currentFunction?.effects;
2250
+ for (const effect of required) {
2251
+ if (allowed?.has('*') === true || allowed?.has(effect) === true)
2252
+ continue;
2253
+ this.diagnostics.error('L2076', `Effect ${effect} is not declared by the enclosing function; add uses ${effect}`, span);
2254
+ }
2255
+ }
2256
+ validateEffects(effects, span) {
2257
+ for (const effect of effects)
2258
+ if (effect !== '*' && !this.#effects.has(effect))
2259
+ this.diagnostics.error('L2085', `Unknown effect ${effect}; Virune supports only built-in effects`, span);
2260
+ }
2261
+ checkBinary(expression, scope) {
2262
+ const booleanOperands = expression.operator === '&&' || expression.operator === '||';
2263
+ const left = this.checkExpression(expression.left, scope, booleanOperands ? this.arena.bool : undefined);
2264
+ const right = this.checkExpression(expression.right, scope, booleanOperands ? this.arena.bool : undefined);
2265
+ if (['&&', '||'].includes(expression.operator)) {
2266
+ this.requireBool(left, expression.left.span);
2267
+ this.requireBool(right, expression.right.span);
2268
+ return this.arena.bool;
2269
+ }
2270
+ if (['==', '!='].includes(expression.operator)) {
2271
+ if (!this.arena.equals(left, right))
2272
+ this.typeMismatch(right, left, expression.right.span);
2273
+ else if (!this.supportsEq(left))
2274
+ this.diagnostics.error('L2049', `Type ${this.arena.display(left)} does not support equality`, expression.span);
2275
+ return this.arena.bool;
2276
+ }
2277
+ if (['<', '<=', '>', '>='].includes(expression.operator)) {
2278
+ if (!this.arena.equals(left, right) || ![this.arena.int, this.arena.float, this.arena.bigint, this.arena.string].includes(left))
2279
+ this.diagnostics.error('L2015', 'Comparison requires operands of the same ordered type', expression.span);
2280
+ return this.arena.bool;
2281
+ }
2282
+ if (!this.arena.equals(left, right)) {
2283
+ this.typeMismatch(right, left, expression.right.span);
2284
+ return this.arena.error;
2285
+ }
2286
+ if (expression.operator === '+' && left === this.arena.string)
2287
+ return this.arena.string;
2288
+ if (![this.arena.int, this.arena.float, this.arena.bigint].includes(left)) {
2289
+ this.diagnostics.error('L2016', `Operator ${expression.operator} cannot be applied to ${this.arena.display(left)}`, expression.span);
2290
+ return this.arena.error;
2291
+ }
2292
+ return left;
2293
+ }
2294
+ checkUnary(expression, scope) {
2295
+ const operand = this.checkExpression(expression.operand, scope, expression.operator === '!' ? this.arena.bool : undefined);
2296
+ if (expression.operator === '!') {
2297
+ this.requireBool(operand, expression.operand.span);
2298
+ return this.arena.bool;
2299
+ }
2300
+ if (![this.arena.int, this.arena.float, this.arena.bigint].includes(operand))
2301
+ this.diagnostics.error('L2017', 'Unary minus requires a numeric value', expression.span);
2302
+ return operand;
2303
+ }
2304
+ checkPipeline(expression, scope) {
2305
+ const leftType = this.checkExpression(expression.left, scope);
2306
+ if (expression.right.kind === 'CallExpression') {
2307
+ const calleeTypeId = this.checkExpression(expression.right.callee, scope);
2308
+ const callee = this.arena.get(calleeTypeId);
2309
+ if (callee.kind !== 'function')
2310
+ return this.arena.error;
2311
+ this.requireEffects(callee.effects.filter(effect => effect !== '*'), expression.right.span);
2312
+ const args = [expression.left, ...expression.right.arguments];
2313
+ if (callee.parameters.length !== args.length)
2314
+ this.diagnostics.error('L2018', `Pipeline target expects ${callee.parameters.length} arguments`, expression.right.span);
2315
+ const substitutions = new Map();
2316
+ if (expression.right.typeArguments.length > 0) {
2317
+ if (expression.right.typeArguments.length !== callee.typeParameters.length)
2318
+ this.diagnostics.error('L2046', `Expected ${callee.typeParameters.length} type arguments, received ${expression.right.typeArguments.length}`, expression.right.span);
2319
+ expression.right.typeArguments.forEach((argument, index) => { const name = callee.typeParameters[index]; if (name !== undefined)
2320
+ substitutions.set(name, this.resolveTypeReference(argument, this.#currentFunction?.typeParameters ?? new Map())); });
2321
+ }
2322
+ for (let index = 0; index < args.length; index++) {
2323
+ const argument = args[index];
2324
+ const parameter = callee.parameters[index] ?? this.arena.error;
2325
+ const actual = index === 0 ? leftType : this.checkExpression(argument, scope, this.substitute(parameter, substitutions));
2326
+ this.unify(parameter, actual, substitutions);
2327
+ const expectedParameter = this.substitute(parameter, substitutions);
2328
+ if (!this.isAssignable(actual, expectedParameter))
2329
+ this.typeMismatch(actual, expectedParameter, argument.span);
2330
+ const callback = this.arena.get(actual);
2331
+ if (callback.kind === 'function')
2332
+ this.requireEffects(callback.effects.filter(effect => effect !== '*'), argument.span);
2333
+ }
2334
+ const result = this.substitute(callee.result, substitutions);
2335
+ return callee.async ? this.arena.future(result) : result;
2336
+ }
2337
+ const calleeTypeId = this.checkExpression(expression.right, scope);
2338
+ const callee = this.arena.get(calleeTypeId);
2339
+ if (callee.kind !== 'function' || callee.parameters.length !== 1) {
2340
+ this.diagnostics.error('L2019', 'Pipeline target must be a function accepting the piped value', expression.right.span);
2341
+ return this.arena.error;
2342
+ }
2343
+ this.requireEffects(callee.effects.filter(effect => effect !== '*'), expression.right.span);
2344
+ if (!this.isAssignable(leftType, callee.parameters[0]))
2345
+ this.typeMismatch(leftType, callee.parameters[0], expression.left.span);
2346
+ return callee.async ? this.arena.future(callee.result) : callee.result;
2347
+ }
2348
+ checkTry(expression, scope) {
2349
+ const operandId = this.checkExpression(expression.operand, scope);
2350
+ const operand = this.arena.get(operandId);
2351
+ const returnId = this.#currentFunction?.returnType;
2352
+ if (returnId === undefined) {
2353
+ this.diagnostics.error('L2020', '? requires an enclosing function with a known return type', expression.span);
2354
+ return this.arena.error;
2355
+ }
2356
+ const returnType = this.arena.get(returnId);
2357
+ if (operand.kind === 'result' && returnType.kind === 'result') {
2358
+ if (!this.arena.equals(operand.error, returnType.error))
2359
+ this.typeMismatch(operand.error, returnType.error, expression.span);
2360
+ return operand.value;
2361
+ }
2362
+ if (operand.kind === 'option' && returnType.kind === 'option')
2363
+ return operand.value;
2364
+ this.diagnostics.error('L2021', `Cannot propagate ${this.arena.display(operandId)} from function returning ${this.arena.display(returnId)}`, expression.span);
2365
+ return this.arena.error;
2366
+ }
2367
+ checkAwait(expression, scope) {
2368
+ if (!this.#currentFunction?.async)
2369
+ this.diagnostics.error('L2022', 'await can only be used in an async function or test', expression.span);
2370
+ const operandId = this.checkExpression(expression.operand, scope);
2371
+ const operand = this.arena.get(operandId);
2372
+ if (operand.kind === 'foreign') {
2373
+ this.requireEffects(['JavaScript'], expression.span);
2374
+ const awaited = this.#jsInteropProvider?.getAwaitedType(operand.ref);
2375
+ if (awaited === undefined) {
2376
+ this.diagnostics.error('L2023', `await requires Future or JavaScript PromiseLike, received ${this.arena.display(operandId)}`, expression.span);
2377
+ return this.arena.error;
2378
+ }
2379
+ this.#interopUsages.push({ kind: 'await', nodeId: expression.id, span: expression.span, foreignType: awaited, mayReject: true });
2380
+ return this.arena.foreign(awaited);
2381
+ }
2382
+ if (operand.kind !== 'future') {
2383
+ this.diagnostics.error('L2023', `await requires Future, received ${this.arena.display(operandId)}`, expression.span);
2384
+ return this.arena.error;
2385
+ }
2386
+ return operand.value;
2387
+ }
2388
+ checkRecord(expression, scope, expected) {
2389
+ const typeId = this.#namedTypes.get(expression.name);
2390
+ const type = typeId === undefined ? undefined : this.arena.get(typeId);
2391
+ if (typeId === undefined || type?.kind !== 'named' || type.declarationKind !== 'record' || type.fields === undefined) {
2392
+ this.diagnostics.error('L2024', `Unknown record type ${expression.name}`, expression.span);
2393
+ expression.entries.forEach(entry => this.checkExpression(entry.value, scope));
2394
+ return this.arena.error;
2395
+ }
2396
+ const declaration = this.#recordDeclarations.get(expression.name);
2397
+ const substitutions = new Map();
2398
+ if (declaration !== undefined && expression.typeArguments.length > 0) {
2399
+ if (expression.typeArguments.length !== declaration.typeParameters.length)
2400
+ this.diagnostics.error('L2046', `Expected ${declaration.typeParameters.length} type arguments, received ${expression.typeArguments.length}`, expression.span);
2401
+ expression.typeArguments.forEach((argument, index) => {
2402
+ const parameter = declaration.typeParameters[index];
2403
+ if (parameter !== undefined)
2404
+ substitutions.set(parameter.name, this.resolveTypeReference(argument, this.#currentFunction?.typeParameters ?? new Map()));
2405
+ });
2406
+ }
2407
+ if (expected !== undefined) {
2408
+ const expectedType = this.arena.get(expected);
2409
+ if (expectedType.kind === 'named' && expectedType.name === expression.name && declaration !== undefined) {
2410
+ declaration.typeParameters.forEach((parameter, index) => {
2411
+ const expectedArgument = expectedType.arguments[index] ?? this.arena.error;
2412
+ const explicitArgument = substitutions.get(parameter.name);
2413
+ if (explicitArgument === undefined)
2414
+ substitutions.set(parameter.name, expectedArgument);
2415
+ });
2416
+ }
2417
+ }
2418
+ const supplied = new Set();
2419
+ for (const entry of expression.entries) {
2420
+ if (supplied.has(entry.name))
2421
+ this.diagnostics.error('L2025', `Duplicate record field ${entry.name}`, entry.span);
2422
+ supplied.add(entry.name);
2423
+ const rawExpected = type.fields.get(entry.name);
2424
+ const preliminaryExpected = rawExpected === undefined ? undefined : this.substitute(rawExpected, substitutions);
2425
+ const actual = this.checkExpression(entry.value, scope, preliminaryExpected);
2426
+ if (rawExpected === undefined)
2427
+ this.diagnostics.error('L2026', `Record ${expression.name} has no field ${entry.name}`, entry.span);
2428
+ else {
2429
+ this.unify(rawExpected, actual, substitutions);
2430
+ const fieldExpected = this.substitute(rawExpected, substitutions);
2431
+ if (!this.isAssignable(actual, fieldExpected))
2432
+ this.typeMismatch(actual, fieldExpected, entry.value.span);
2433
+ }
2434
+ }
2435
+ for (const field of type.fields.keys())
2436
+ if (!supplied.has(field))
2437
+ this.diagnostics.error('L2027', `Missing record field ${field}`, expression.span);
2438
+ if (declaration === undefined || declaration.typeParameters.length === 0)
2439
+ return typeId;
2440
+ const argumentsList = declaration.typeParameters.map(parameter => substitutions.get(parameter.name) ?? this.arena.error);
2441
+ declaration.typeParameters.forEach((parameter, index) => { if (argumentsList[index] === this.arena.error)
2442
+ this.diagnostics.error('L2045', `Cannot infer type argument ${parameter.name} for ${expression.name}`, expression.span); });
2443
+ return this.arena.namedInstance(type, argumentsList, { fields: new Map([...type.fields].map(([name, field]) => [name, this.substitute(field, substitutions)])) });
2444
+ }
2445
+ checkRecordUpdate(expression, scope) {
2446
+ const base = this.checkExpression(expression.base, scope);
2447
+ const type = this.arena.get(base);
2448
+ if (type.kind !== 'named' || type.declarationKind !== 'record' || type.fields === undefined) {
2449
+ this.diagnostics.error('L2028', 'Record update requires a record value', expression.base.span);
2450
+ return this.arena.error;
2451
+ }
2452
+ for (const entry of expression.entries) {
2453
+ const expected = type.fields.get(entry.name);
2454
+ const actual = this.checkExpression(entry.value, scope, expected);
2455
+ if (expected === undefined)
2456
+ this.diagnostics.error('L2029', `Record has no field ${entry.name}`, entry.span);
2457
+ else if (!this.isAssignable(actual, expected))
2458
+ this.typeMismatch(actual, expected, entry.value.span);
2459
+ }
2460
+ return base;
2461
+ }
2462
+ checkList(expression, scope, expected) {
2463
+ if (expression.items.length === 0) {
2464
+ const expectedType = expected === undefined ? undefined : this.arena.get(expected);
2465
+ if (expected !== undefined && expectedType?.kind === 'list')
2466
+ return expected;
2467
+ this.diagnostics.error('L2030', 'Empty List requires an explicit type annotation', expression.span);
2468
+ return this.arena.list(this.arena.error);
2469
+ }
2470
+ const itemTypes = expression.items.map(item => this.checkExpression(item, scope));
2471
+ const element = this.commonType(itemTypes, expression.span);
2472
+ return this.arena.list(element);
2473
+ }
2474
+ checkMatch(expression, scope, expected) {
2475
+ const target = this.checkExpression(expression.target, scope);
2476
+ const resultTypes = [];
2477
+ const covered = new Set();
2478
+ const seenPatterns = new Set();
2479
+ let wildcard = false;
2480
+ for (const arm of expression.arms) {
2481
+ const child = new Scope(scope);
2482
+ const key = this.checkPattern(arm.pattern, target, child);
2483
+ if (arm.guard === undefined && key === '*')
2484
+ wildcard = true;
2485
+ else if (arm.guard === undefined && key !== undefined) {
2486
+ if (seenPatterns.has(key))
2487
+ this.diagnostics.error('L3002', `Unreachable duplicate pattern ${key}`, arm.pattern.span);
2488
+ seenPatterns.add(key);
2489
+ covered.add(key.includes('(') ? key.slice(0, key.indexOf('(')) : key);
2490
+ }
2491
+ if (arm.guard !== undefined)
2492
+ this.requireBool(this.checkExpression(arm.guard, child, this.arena.bool), arm.guard.span);
2493
+ resultTypes.push(this.checkExpression(arm.expression, child, expected));
2494
+ }
2495
+ const targetType = this.arena.get(target);
2496
+ if (!wildcard) {
2497
+ if (target === this.arena.bool && (!covered.has('true') || !covered.has('false')))
2498
+ this.diagnostics.error('L3003', 'Bool match must cover true and false', expression.span);
2499
+ if (targetType.kind === 'named' && targetType.declarationKind === 'enum' && targetType.variants !== undefined)
2500
+ for (const name of targetType.variants.keys())
2501
+ if (!covered.has(name))
2502
+ this.diagnostics.error('L3004', `Non-exhaustive match: missing ${name}`, expression.span);
2503
+ if (targetType.kind === 'option')
2504
+ for (const name of ['Some', 'None'])
2505
+ if (!covered.has(name))
2506
+ this.diagnostics.error('L3004', `Non-exhaustive match: missing ${name}`, expression.span);
2507
+ if (targetType.kind === 'result')
2508
+ for (const name of ['Ok', 'Err'])
2509
+ if (!covered.has(name))
2510
+ this.diagnostics.error('L3004', `Non-exhaustive match: missing ${name}`, expression.span);
2511
+ if ([this.arena.int, this.arena.string].includes(target))
2512
+ this.diagnostics.error('L3005', 'Int and String matches require a wildcard arm', expression.span);
2513
+ }
2514
+ return this.commonType(resultTypes, expression.span);
2515
+ }
2516
+ checkPattern(pattern, target, scope) {
2517
+ switch (pattern.kind) {
2518
+ case 'WildcardPattern': return '*';
2519
+ case 'BindingPattern': {
2520
+ const symbol = this.#factory.create(pattern.name, 'variable', target, pattern.span, { declaration: pattern });
2521
+ if (!scope.define(symbol))
2522
+ this.diagnostics.error('L1011', `Pattern name ${pattern.name} shadows an existing name`, pattern.span);
2523
+ else {
2524
+ pattern.symbolId = symbol.id;
2525
+ this.#symbols.set(symbol.id, symbol);
2526
+ }
2527
+ return '*';
2528
+ }
2529
+ case 'LiteralPattern': {
2530
+ const literalType = pattern.literalKind === 'Bool' ? this.arena.bool : pattern.literalKind === 'Int' ? this.arena.int : this.arena.string;
2531
+ if (!this.arena.equals(literalType, target))
2532
+ this.typeMismatch(literalType, target, pattern.span);
2533
+ return String(pattern.value);
2534
+ }
2535
+ case 'VariantPattern': {
2536
+ let values;
2537
+ let enumType;
2538
+ if (pattern.name === 'Some') {
2539
+ const t = this.arena.get(target);
2540
+ if (t.kind === 'option') {
2541
+ values = [t.value];
2542
+ enumType = target;
2543
+ }
2544
+ }
2545
+ else if (pattern.name === 'None') {
2546
+ const t = this.arena.get(target);
2547
+ if (t.kind === 'option') {
2548
+ values = [];
2549
+ enumType = target;
2550
+ }
2551
+ }
2552
+ else if (pattern.name === 'Ok') {
2553
+ const t = this.arena.get(target);
2554
+ if (t.kind === 'result') {
2555
+ values = [t.value];
2556
+ enumType = target;
2557
+ }
2558
+ }
2559
+ else if (pattern.name === 'Err') {
2560
+ const t = this.arena.get(target);
2561
+ if (t.kind === 'result') {
2562
+ values = [t.error];
2563
+ enumType = target;
2564
+ }
2565
+ }
2566
+ else {
2567
+ const variant = this.#variants.get(pattern.name);
2568
+ if (variant !== undefined) {
2569
+ const substitutions = new Map();
2570
+ this.unify(variant.enumType, target, substitutions);
2571
+ values = variant.valueTypes.map(value => this.substitute(value, substitutions));
2572
+ enumType = this.substitute(variant.enumType, substitutions);
2573
+ pattern.symbolId = variant.symbol.id;
2574
+ }
2575
+ }
2576
+ if (values === undefined || enumType === undefined || !this.arena.equals(enumType, target)) {
2577
+ this.diagnostics.error('L2031', `Variant ${pattern.name} does not belong to ${this.arena.display(target)}`, pattern.span);
2578
+ return pattern.name;
2579
+ }
2580
+ if (values.length !== pattern.values.length)
2581
+ this.diagnostics.error('L2032', `Variant ${pattern.name} expects ${values.length} patterns`, pattern.span);
2582
+ const childKeys = pattern.values.map((item, index) => this.checkPattern(item, values[index] ?? this.arena.error, scope) ?? '?');
2583
+ return `${pattern.name}(${childKeys.join(',')})`;
2584
+ }
2585
+ case 'RecordPattern': {
2586
+ const typeId = this.#namedTypes.get(pattern.name);
2587
+ const type = typeId === undefined ? undefined : this.arena.get(typeId);
2588
+ if (typeId === undefined || !this.arena.equals(typeId, target) || type?.kind !== 'named' || type.fields === undefined)
2589
+ this.diagnostics.error('L2033', `Record pattern ${pattern.name} does not match ${this.arena.display(target)}`, pattern.span);
2590
+ else
2591
+ for (const field of pattern.fields) {
2592
+ const fieldType = type.fields.get(field.name);
2593
+ if (fieldType === undefined)
2594
+ this.diagnostics.error('L2034', `Unknown field ${field.name}`, field.span);
2595
+ else
2596
+ this.checkPattern(field.pattern, fieldType, scope);
2597
+ }
2598
+ return '*';
2599
+ }
2600
+ case 'RangePattern': {
2601
+ if (!this.arena.equals(target, this.arena.int))
2602
+ this.typeMismatch(this.arena.int, target, pattern.span);
2603
+ if (pattern.start > pattern.end)
2604
+ this.diagnostics.error('L2073', 'Range pattern start must not exceed its end', pattern.span);
2605
+ return `${pattern.start}..=${pattern.end}`;
2606
+ }
2607
+ case 'ListPattern': {
2608
+ const targetType = this.arena.get(target);
2609
+ if (targetType.kind !== 'list') {
2610
+ this.diagnostics.error('L2074', `List pattern does not match ${this.arena.display(target)}`, pattern.span);
2611
+ return '*';
2612
+ }
2613
+ for (const item of pattern.items)
2614
+ this.checkPattern(item, targetType.element, scope);
2615
+ if (pattern.rest?.kind === 'BindingPattern') {
2616
+ const symbol = this.#factory.create(pattern.rest.name, 'variable', target, pattern.rest.span, { declaration: pattern.rest });
2617
+ if (!scope.define(symbol))
2618
+ this.diagnostics.error('L1011', `Pattern name ${pattern.rest.name} shadows an existing name`, pattern.rest.span);
2619
+ else {
2620
+ pattern.rest.symbolId = symbol.id;
2621
+ this.#symbols.set(symbol.id, symbol);
2622
+ }
2623
+ }
2624
+ return '*';
2625
+ }
2626
+ case 'TuplePattern': {
2627
+ const targetType = this.arena.get(target);
2628
+ if (targetType.kind !== 'tuple') {
2629
+ this.diagnostics.error('L2111', `Tuple pattern does not match ${this.arena.display(target)}`, pattern.span);
2630
+ return '*';
2631
+ }
2632
+ if (targetType.items.length !== pattern.items.length)
2633
+ this.diagnostics.error('L2112', `Tuple pattern expects ${targetType.items.length} items, received ${pattern.items.length}`, pattern.span);
2634
+ pattern.items.forEach((item, index) => this.checkPattern(item, targetType.items[index] ?? this.arena.error, scope));
2635
+ return '*';
2636
+ }
2637
+ case 'OrPattern': {
2638
+ if (pattern.alternatives.some(item => this.patternHasBinding(item)))
2639
+ this.diagnostics.error('L2075', 'OR patterns cannot bind names in Virune 1.0', pattern.span);
2640
+ return pattern.alternatives.map(item => this.checkPattern(item, target, new Scope(scope)) ?? '?').join('|');
2641
+ }
2642
+ }
2643
+ }
2644
+ patternHasBinding(pattern) {
2645
+ switch (pattern.kind) {
2646
+ case 'BindingPattern': return true;
2647
+ case 'VariantPattern': return pattern.values.some(item => this.patternHasBinding(item));
2648
+ case 'RecordPattern': return pattern.fields.some(field => this.patternHasBinding(field.pattern));
2649
+ case 'ListPattern': return pattern.items.some(item => this.patternHasBinding(item)) || pattern.rest?.kind === 'BindingPattern';
2650
+ case 'TuplePattern': return pattern.items.some(item => this.patternHasBinding(item));
2651
+ case 'OrPattern': return pattern.alternatives.some(item => this.patternHasBinding(item));
2652
+ default: return false;
2653
+ }
2654
+ }
2655
+ checkLambda(expression, scope, expected, contextualParameters) {
2656
+ const expectedType = expected === undefined ? undefined : this.arena.get(expected);
2657
+ const child = new Scope(scope);
2658
+ const parameters = [];
2659
+ expression.parameters.forEach((parameter, index) => {
2660
+ const contextual = contextualParameters?.[index];
2661
+ const typeId = parameter.annotation === undefined ? contextual ?? (expectedType?.kind === 'function' ? expectedType.parameters[index] ?? this.arena.error : this.arena.error) : this.resolveTypeReference(parameter.annotation, new Map());
2662
+ if (typeId === this.arena.error)
2663
+ this.diagnostics.error('L2035', `Cannot infer type of lambda parameter ${parameter.name}`, parameter.span);
2664
+ const symbol = this.#factory.create(parameter.name, 'parameter', typeId, parameter.span, { declaration: expression });
2665
+ if (!child.define(symbol))
2666
+ this.diagnostics.error('L1012', `Lambda parameter ${parameter.name} shadows an existing name`, parameter.span);
2667
+ else {
2668
+ parameter.symbolId = symbol.id;
2669
+ this.#symbols.set(symbol.id, symbol);
2670
+ }
2671
+ parameters.push(typeId);
2672
+ });
2673
+ if (expectedType?.kind === 'function' && expression.async !== expectedType.async)
2674
+ this.diagnostics.error('L2083', `Lambda async mode does not match expected ${expectedType.async ? 'async ' : ''}function`, expression.span);
2675
+ const expectedResult = expression.returnType === undefined ? expectedType?.kind === 'function' ? expectedType.result : undefined : this.resolveTypeReference(expression.returnType, new Map());
2676
+ if (!expression.expressionBody && expectedResult === undefined)
2677
+ this.diagnostics.error('L2084', 'Block lambdas require a return type unless the expected function type supplies one', expression.span);
2678
+ const effects = expression.effects.length > 0 ? expression.effects : expectedType?.kind === 'function' ? expectedType.effects : [];
2679
+ const previous = this.#currentFunction;
2680
+ const context = { declaration: expression, scope: child, async: expression.async, returnTypes: [], typeParameters: new Map(), effects: new Set(effects), ...(expectedResult === undefined ? {} : { returnType: expectedResult }) };
2681
+ this.#currentFunction = context;
2682
+ let result;
2683
+ if (expression.expressionBody) {
2684
+ result = this.checkExpression(expression.body, child, expectedResult);
2685
+ context.returnTypes.push(result);
2686
+ }
2687
+ else {
2688
+ this.checkBlock(expression.body, child);
2689
+ result = expectedResult ?? this.commonType(context.returnTypes, expression.span);
2690
+ const controlFlow = analyzeControlFlow(expression.body, this.arena);
2691
+ if (result !== this.arena.unit && !controlFlow.alwaysTerminates)
2692
+ this.diagnostics.error('L3001', 'Lambda does not return on every path', expression.span);
2693
+ for (const span of controlFlow.unreachable)
2694
+ this.diagnostics.error('L3006', 'Unreachable statement', span);
2695
+ }
2696
+ this.#currentFunction = previous;
2697
+ if (expectedResult !== undefined && !this.isAssignable(result, expectedResult))
2698
+ this.typeMismatch(result, expectedResult, expression.span);
2699
+ return this.arena.function(parameters, expectedResult ?? result, [], expression.async, effects);
2700
+ }
2701
+ checkParallel(expression, scope) {
2702
+ const fields = new Map();
2703
+ let commonError;
2704
+ for (const entry of expression.entries) {
2705
+ if (fields.has(entry.name))
2706
+ this.diagnostics.error('L2036', `Duplicate parallel entry ${entry.name}`, entry.span);
2707
+ const futureId = this.checkExpression(entry.value, scope);
2708
+ const future = this.arena.get(futureId);
2709
+ if (future.kind !== 'future') {
2710
+ this.diagnostics.error('L2037', 'parallel entries must be async expressions', entry.value.span);
2711
+ fields.set(entry.name, this.arena.error);
2712
+ continue;
2713
+ }
2714
+ if (expression.tryMode) {
2715
+ const result = this.arena.get(future.value);
2716
+ if (result.kind !== 'result') {
2717
+ this.diagnostics.error('L2038', 'parallel try entries must return Result', entry.value.span);
2718
+ fields.set(entry.name, this.arena.error);
2719
+ continue;
2720
+ }
2721
+ if (commonError !== undefined && !this.arena.equals(commonError, result.error))
2722
+ this.diagnostics.error('L2039', 'parallel try entries must share the same error type', entry.value.span);
2723
+ commonError ??= result.error;
2724
+ fields.set(entry.name, result.value);
2725
+ }
2726
+ else
2727
+ fields.set(entry.name, future.value);
2728
+ }
2729
+ const record = this.arena.add({ kind: 'named', name: `$Parallel${expression.id}`, definitionId: `${this.#moduleId}#$Parallel${expression.id}`, declarationKind: 'record', arguments: [], fields });
2730
+ return this.arena.future(expression.tryMode ? this.arena.result(record, commonError ?? this.arena.error) : record);
2731
+ }
2732
+ resolveTypeReference(reference, typeParameters) {
2733
+ if (reference.functionType !== undefined) {
2734
+ const functionType = reference.functionType;
2735
+ this.validateEffects(functionType.effects, reference.span);
2736
+ let resolved = this.arena.function(functionType.parameters.map(parameter => this.resolveTypeReference(parameter, typeParameters)), this.resolveTypeReference(functionType.result, typeParameters), [], functionType.async, functionType.effects);
2737
+ if (reference.optional)
2738
+ resolved = this.arena.option(resolved);
2739
+ reference.resolvedTypeId = resolved;
2740
+ return resolved;
2741
+ }
2742
+ let typeId = typeParameters.get(reference.name) ?? this.#namedTypes.get(reference.name);
2743
+ if (typeId === undefined) {
2744
+ const imported = this.globalScope.lookup(reference.name);
2745
+ if (imported?.typeOnly === true) {
2746
+ const importedType = this.arena.get(imported.typeId);
2747
+ if (importedType.kind === 'foreign')
2748
+ typeId = imported.typeId;
2749
+ }
2750
+ }
2751
+ const args = reference.arguments.map(item => this.resolveTypeReference(item, typeParameters));
2752
+ if (reference.name === 'List')
2753
+ typeId = args.length === 1 ? this.arena.list(args[0]) : this.invalidArity(reference, 1, args.length);
2754
+ else if (reference.name === 'Map')
2755
+ typeId = args.length === 2 ? this.arena.map(args[0], args[1]) : this.invalidArity(reference, 2, args.length);
2756
+ else if (reference.name === 'Set')
2757
+ typeId = args.length === 1 ? this.arena.set(args[0]) : this.invalidArity(reference, 1, args.length);
2758
+ else if (reference.name === 'Option')
2759
+ typeId = args.length === 1 ? this.arena.option(args[0]) : this.invalidArity(reference, 1, args.length);
2760
+ else if (reference.name === 'Result')
2761
+ typeId = args.length === 2 ? this.arena.result(args[0], args[1]) : this.invalidArity(reference, 2, args.length);
2762
+ else if (reference.name === 'Validation')
2763
+ typeId = args.length === 2 ? this.arena.result(args[0], this.arena.list(args[1])) : this.invalidArity(reference, 2, args.length);
2764
+ else if (reference.name === '$Tuple')
2765
+ typeId = args.length >= 2 ? this.arena.tuple(args) : this.invalidArity(reference, 2, args.length);
2766
+ else if (reference.name === 'Stream')
2767
+ typeId = args.length === 1 ? this.arena.add({ kind: 'named', name: 'Stream', definitionId: 'std:Stream', declarationKind: 'alias', arguments: [args[0]] }) : this.invalidArity(reference, 1, args.length);
2768
+ else if (typeId === undefined) {
2769
+ this.diagnostics.error('L2040', `Unknown type ${reference.name}`, reference.span);
2770
+ typeId = this.arena.error;
2771
+ }
2772
+ else if (args.length > 0) {
2773
+ const named = this.arena.get(typeId);
2774
+ if (named.kind === 'named') {
2775
+ const declaration = this.#recordDeclarations.get(reference.name) ?? this.#enumDeclarations.get(reference.name) ?? this.#typeAliasDeclarations.get(reference.name);
2776
+ const parameters = declaration?.typeParameters ?? [];
2777
+ if (parameters.length !== args.length)
2778
+ this.diagnostics.error('L2041', `${reference.name} expects ${parameters.length} type arguments, received ${args.length}`, reference.span);
2779
+ const substitutions = new Map(parameters.map((item, index) => [item.name, args[index] ?? this.arena.error]));
2780
+ typeId = this.arena.add({ ...named, arguments: args, ...(named.fields === undefined ? {} : { fields: new Map([...named.fields].map(([field, fieldType]) => [field, this.substitute(fieldType, substitutions)])) }), ...(named.variants === undefined ? {} : { variants: new Map([...named.variants].map(([variant, values]) => [variant, values.map(value => this.substitute(value, substitutions))])) }), ...(named.underlying === undefined ? {} : { underlying: this.substitute(named.underlying, substitutions) }) });
2781
+ }
2782
+ }
2783
+ const resolvedNamed = this.arena.get(typeId);
2784
+ if (resolvedNamed.kind === 'named' && resolvedNamed.declarationKind === 'alias' && resolvedNamed.underlying !== undefined)
2785
+ typeId = resolvedNamed.underlying;
2786
+ if (reference.optional)
2787
+ typeId = this.arena.option(typeId);
2788
+ reference.resolvedTypeId = typeId;
2789
+ return typeId;
2790
+ }
2791
+ containsForeignType(typeId, seen = new Set()) {
2792
+ if (seen.has(typeId))
2793
+ return false;
2794
+ seen.add(typeId);
2795
+ const type = this.arena.get(typeId);
2796
+ switch (type.kind) {
2797
+ case 'foreign': return true;
2798
+ case 'function': return type.parameters.some(item => this.containsForeignType(item, seen)) || this.containsForeignType(type.result, seen);
2799
+ case 'list':
2800
+ case 'set': return this.containsForeignType(type.element, seen);
2801
+ case 'map': return this.containsForeignType(type.key, seen) || this.containsForeignType(type.value, seen);
2802
+ case 'tuple': return type.items.some(item => this.containsForeignType(item, seen));
2803
+ case 'option':
2804
+ case 'future': return this.containsForeignType(type.value, seen);
2805
+ case 'result': return this.containsForeignType(type.value, seen) || this.containsForeignType(type.error, seen);
2806
+ case 'named': return type.arguments.some(item => this.containsForeignType(item, seen)) || (type.underlying !== undefined && this.containsForeignType(type.underlying, seen));
2807
+ default: return false;
2808
+ }
2809
+ }
2810
+ containsOpenEffect(typeId, seen = new Set()) {
2811
+ if (seen.has(typeId))
2812
+ return false;
2813
+ seen.add(typeId);
2814
+ const type = this.arena.get(typeId);
2815
+ switch (type.kind) {
2816
+ case 'function': return type.effects.includes('*') || type.parameters.some(item => this.containsOpenEffect(item, seen)) || this.containsOpenEffect(type.result, seen);
2817
+ case 'list':
2818
+ case 'set': return this.containsOpenEffect(type.element, seen);
2819
+ case 'map': return this.containsOpenEffect(type.key, seen) || this.containsOpenEffect(type.value, seen);
2820
+ case 'tuple': return type.items.some(item => this.containsOpenEffect(item, seen));
2821
+ case 'option':
2822
+ case 'future': return this.containsOpenEffect(type.value, seen);
2823
+ case 'result': return this.containsOpenEffect(type.value, seen) || this.containsOpenEffect(type.error, seen);
2824
+ case 'named': return type.arguments.some(item => this.containsOpenEffect(item, seen)) || (type.underlying !== undefined && this.containsOpenEffect(type.underlying, seen)) || [...(type.fields?.values() ?? [])].some(item => this.containsOpenEffect(item, seen)) || [...(type.variants?.values() ?? [])].flat().some(item => this.containsOpenEffect(item, seen));
2825
+ default: return false;
2826
+ }
2827
+ }
2828
+ isSafeFfiType(typeId, seen = new Set()) {
2829
+ const type = this.arena.get(typeId);
2830
+ if (type.kind === 'primitive')
2831
+ return type.name !== 'Never' && type.name !== 'InvalidType';
2832
+ if (seen.has(typeId))
2833
+ return false;
2834
+ seen.add(typeId);
2835
+ switch (type.kind) {
2836
+ case 'function':
2837
+ case 'foreign':
2838
+ case 'typeVariable': return false;
2839
+ case 'list': return this.isSafeFfiType(type.element, seen);
2840
+ case 'tuple': return type.items.every(item => this.isSafeFfiType(item, new Set(seen)));
2841
+ case 'option':
2842
+ case 'future': return this.isSafeFfiType(type.value, seen);
2843
+ case 'result': return this.isSafeFfiType(type.value, new Set(seen)) && this.isSafeFfiType(type.error, new Set(seen));
2844
+ case 'map': return this.isSafeFfiPrimitiveKey(type.key) && this.isSafeFfiType(type.value, seen);
2845
+ case 'set': return this.isSafeFfiPrimitiveKey(type.element);
2846
+ case 'named': {
2847
+ if (type.arguments.some(item => this.arena.get(item).kind === 'typeVariable'))
2848
+ return false;
2849
+ if (type.underlying !== undefined)
2850
+ return this.isSafeFfiType(type.underlying, seen);
2851
+ if (type.fields !== undefined)
2852
+ return [...type.fields.values()].every(item => this.isSafeFfiType(item, new Set(seen)));
2853
+ if (type.variants !== undefined)
2854
+ return [...type.variants.values()].flat().every(item => this.isSafeFfiType(item, new Set(seen)));
2855
+ return false;
2856
+ }
2857
+ }
2858
+ }
2859
+ isSafeFfiPrimitiveKey(typeId) {
2860
+ const type = this.arena.get(typeId);
2861
+ return type.kind === 'primitive' && ['String', 'Int', 'BigInt', 'Bool'].includes(type.name);
2862
+ }
2863
+ invalidArity(reference, expected, actual) { this.diagnostics.error('L2041', `${reference.name} expects ${expected} type arguments, received ${actual}`, reference.span); return this.arena.error; }
2864
+ isAssignable(source, target) { return this.#types.isAssignable(source, target); }
2865
+ listElementOf(typeId) { return this.#types.listElementOf(typeId); }
2866
+ setElementOf(typeId) { return this.#types.setElementOf(typeId); }
2867
+ mapKeyOf(typeId) { return this.#types.mapKeyOf(typeId); }
2868
+ isMustUse(typeId, seen = new Set()) { return this.#types.isMustUse(typeId, seen); }
2869
+ supportsHash(typeId, seen = new Set()) { return this.#types.supportsHash(typeId, seen); }
2870
+ containsTypeVariable(typeId, seen = new Set()) { return this.#types.containsTypeVariable(typeId, seen); }
2871
+ supportsDerive(typeId, derive, seen = new Set()) { return this.#types.supportsDerive(typeId, derive, seen); }
2872
+ supportsJson(typeId, seen = new Set()) { return this.#types.supportsJson(typeId, seen); }
2873
+ supportsDerivedEq(typeId, owner, seen = new Set()) { return this.#types.supportsDerivedEq(typeId, owner, seen); }
2874
+ supportsEq(typeId, seen = new Set()) { return this.#types.supportsEq(typeId, seen); }
2875
+ commonType(types, span) { return this.#types.commonType(types, span); }
2876
+ isConstantExpression(expression) {
2877
+ switch (expression.kind) {
2878
+ case 'LiteralExpression': return true;
2879
+ case 'IdentifierExpression': return expression.symbolId !== undefined && this.#symbols.get(expression.symbolId)?.constant === true;
2880
+ case 'UnaryExpression': return this.isConstantExpression(expression.operand);
2881
+ case 'BinaryExpression': return this.isConstantExpression(expression.left) && this.isConstantExpression(expression.right);
2882
+ case 'ListExpression':
2883
+ case 'TupleExpression': return expression.items.every(item => this.isConstantExpression(item));
2884
+ case 'RecordExpression': return expression.entries.every(entry => this.isConstantExpression(entry.value));
2885
+ case 'ConditionalExpression': return this.isConstantExpression(expression.condition) && this.isConstantExpression(expression.thenExpression) && this.isConstantExpression(expression.elseExpression);
2886
+ default: return false;
2887
+ }
2888
+ }
2889
+ requireBool(typeId, span) { if (!this.arena.equals(typeId, this.arena.bool))
2890
+ this.typeMismatch(typeId, this.arena.bool, span); }
2891
+ typeMismatch(actual, expected, span) { this.diagnostics.error('L2043', `${this.arena.display(actual)} cannot be used as ${this.arena.display(expected)}`, span); }
2892
+ unify(pattern, actual, substitutions) { this.#types.unify(pattern, actual, substitutions); }
2893
+ substitute(typeId, substitutions) { return this.#types.substitute(typeId, substitutions); }
2894
+ }
2895
+ function isRecord(value) {
2896
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
2897
+ }
2898
+ function hasExactEnumerableKeys(value, expected) {
2899
+ const actual = Object.keys(value).sort(compareText);
2900
+ const canonicalExpected = [...expected].sort(compareText);
2901
+ return actual.length === canonicalExpected.length && actual.every((key, index) => key === canonicalExpected[index]);
2902
+ }
2903
+ function isContextualCallablePrimitive(value) {
2904
+ return typeof value === 'string' && ['boolean', 'string', 'number', 'bigint', 'undefined'].includes(value);
2905
+ }
2906
+ function canonicalContextualCallableResult(value) {
2907
+ if (!isRecord(value) || typeof value.kind !== 'string')
2908
+ return undefined;
2909
+ if (value.kind === 'void' || value.kind === 'external' || value.kind === 'deferred')
2910
+ return hasExactEnumerableKeys(value, ['kind']) ? Object.freeze({ kind: value.kind }) : undefined;
2911
+ if (value.kind === 'callable') {
2912
+ if (!hasExactEnumerableKeys(value, ['callable', 'kind']) || !isRecord(value.callable) || !hasExactEnumerableKeys(value.callable, ['parameters', 'result']) || !Array.isArray(value.callable.parameters))
2913
+ return undefined;
2914
+ const parameters = [];
2915
+ for (const parameter of value.callable.parameters) {
2916
+ if (!isContextualCallablePrimitive(parameter))
2917
+ return undefined;
2918
+ parameters.push(parameter);
2919
+ }
2920
+ const result = canonicalContextualCallableResult(value.callable.result);
2921
+ if (result === undefined || (result.kind !== 'void' && result.kind !== 'value'))
2922
+ return undefined;
2923
+ return Object.freeze({ kind: 'callable', callable: Object.freeze({ parameters: Object.freeze(parameters), result }) });
2924
+ }
2925
+ if (value.kind !== 'value' && value.kind !== 'promise')
2926
+ return undefined;
2927
+ if (!hasExactEnumerableKeys(value, ['kind', 'value']))
2928
+ return undefined;
2929
+ if (value.kind === 'promise' && value.value === 'void')
2930
+ return Object.freeze({ kind: 'promise', value: 'void' });
2931
+ if (!isContextualCallablePrimitive(value.value))
2932
+ return undefined;
2933
+ return value.kind === 'value'
2934
+ ? Object.freeze({ kind: 'value', value: value.value })
2935
+ : Object.freeze({ kind: 'promise', value: value.value });
2936
+ }
2937
+ function callableParameterMatchesContext(native, target) {
2938
+ return native === 'Bool' ? target === 'boolean'
2939
+ : native === 'String' ? target === 'string'
2940
+ : native === 'Float' ? target === 'number'
2941
+ : native === 'BigInt' ? target === 'bigint'
2942
+ : native === 'Unit' ? target === 'undefined'
2943
+ : false;
2944
+ }
2945
+ function callablePrimitiveExternalName(native) {
2946
+ return native === 'Bool' ? 'boolean'
2947
+ : native === 'String' ? 'string'
2948
+ : native === 'Int' || native === 'Float' ? 'number'
2949
+ : native === 'BigInt' ? 'bigint'
2950
+ : native === 'Unit' ? 'undefined'
2951
+ : undefined;
2952
+ }
2953
+ function compareText(left, right) {
2954
+ return left < right ? -1 : left > right ? 1 : 0;
2955
+ }
2956
+ function compareSemanticPoint(left, right) {
2957
+ if (left.usageIndex !== right.usageIndex)
2958
+ return left.usageIndex - right.usageIndex;
2959
+ return left.order - right.order;
2960
+ }
2961
+ function stableForeignUsage(usage) {
2962
+ const snapshot = usage.foreignType;
2963
+ const foreignType = {
2964
+ display: snapshot.display,
2965
+ category: snapshot.category,
2966
+ ...(snapshot.canonicalIdentity === undefined ? {} : { canonicalIdentity: snapshot.canonicalIdentity }),
2967
+ ...(snapshot.primitive === undefined ? {} : { primitive: snapshot.primitive }),
2968
+ ...(snapshot.mustUse === undefined ? {} : { mustUse: snapshot.mustUse }),
2969
+ ...(snapshot.origin === undefined ? {} : { origin: snapshot.origin }),
2970
+ };
2971
+ return {
2972
+ kind: usage.kind,
2973
+ nodeId: usage.nodeId,
2974
+ span: usage.span,
2975
+ foreignType,
2976
+ ...(usage.runtimeImport === undefined ? {} : { runtimeImport: usage.runtimeImport }),
2977
+ ...(usage.moduleWitness === undefined ? {} : { moduleWitness: usage.moduleWitness }),
2978
+ ...(usage.receiverMode === undefined ? {} : { receiverMode: usage.receiverMode }),
2979
+ ...(usage.mayReject === undefined ? {} : { mayReject: usage.mayReject }),
2980
+ ...(usage.property === undefined ? {} : { property: usage.property }),
2981
+ ...(usage.bridge === undefined ? {} : { bridge: usage.bridge }),
2982
+ };
2983
+ }
2984
+ export function checkModule(module, options = {}) { return new TypeChecker(options).check(module); }
2985
+ //# sourceMappingURL=checker.js.map