@elaraai/east 0.0.1-beta.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 (251) hide show
  1. package/LICENSE.md +682 -0
  2. package/README.md +276 -0
  3. package/dist/src/analyze.d.ts +95 -0
  4. package/dist/src/analyze.d.ts.map +1 -0
  5. package/dist/src/analyze.js +1110 -0
  6. package/dist/src/analyze.js.map +1 -0
  7. package/dist/src/ast.d.ts +263 -0
  8. package/dist/src/ast.d.ts.map +1 -0
  9. package/dist/src/ast.js +151 -0
  10. package/dist/src/ast.js.map +1 -0
  11. package/dist/src/ast_to_ir.d.ts +24 -0
  12. package/dist/src/ast_to_ir.d.ts.map +1 -0
  13. package/dist/src/ast_to_ir.js +834 -0
  14. package/dist/src/ast_to_ir.js.map +1 -0
  15. package/dist/src/builtins.d.ts +18 -0
  16. package/dist/src/builtins.d.ts.map +1 -0
  17. package/dist/src/builtins.js +1105 -0
  18. package/dist/src/builtins.js.map +1 -0
  19. package/dist/src/comparison.d.ts +28 -0
  20. package/dist/src/comparison.d.ts.map +1 -0
  21. package/dist/src/comparison.js +1017 -0
  22. package/dist/src/comparison.js.map +1 -0
  23. package/dist/src/compile.d.ts +22 -0
  24. package/dist/src/compile.d.ts.map +1 -0
  25. package/dist/src/compile.js +3260 -0
  26. package/dist/src/compile.js.map +1 -0
  27. package/dist/src/containers/ref.d.ts +106 -0
  28. package/dist/src/containers/ref.d.ts.map +1 -0
  29. package/dist/src/containers/ref.js +100 -0
  30. package/dist/src/containers/ref.js.map +1 -0
  31. package/dist/src/containers/sortedmap.d.ts +165 -0
  32. package/dist/src/containers/sortedmap.d.ts.map +1 -0
  33. package/dist/src/containers/sortedmap.js +237 -0
  34. package/dist/src/containers/sortedmap.js.map +1 -0
  35. package/dist/src/containers/sortedset.d.ts +185 -0
  36. package/dist/src/containers/sortedset.d.ts.map +1 -0
  37. package/dist/src/containers/sortedset.js +312 -0
  38. package/dist/src/containers/sortedset.js.map +1 -0
  39. package/dist/src/containers/variant.d.ts +131 -0
  40. package/dist/src/containers/variant.d.ts.map +1 -0
  41. package/dist/src/containers/variant.js +68 -0
  42. package/dist/src/containers/variant.js.map +1 -0
  43. package/dist/src/datetime_format/parse.d.ts +50 -0
  44. package/dist/src/datetime_format/parse.d.ts.map +1 -0
  45. package/dist/src/datetime_format/parse.js +908 -0
  46. package/dist/src/datetime_format/parse.js.map +1 -0
  47. package/dist/src/datetime_format/print.d.ts +35 -0
  48. package/dist/src/datetime_format/print.d.ts.map +1 -0
  49. package/dist/src/datetime_format/print.js +157 -0
  50. package/dist/src/datetime_format/print.js.map +1 -0
  51. package/dist/src/datetime_format/tokenize.d.ts +76 -0
  52. package/dist/src/datetime_format/tokenize.d.ts.map +1 -0
  53. package/dist/src/datetime_format/tokenize.js +271 -0
  54. package/dist/src/datetime_format/tokenize.js.map +1 -0
  55. package/dist/src/datetime_format/types.d.ts +99 -0
  56. package/dist/src/datetime_format/types.d.ts.map +1 -0
  57. package/dist/src/datetime_format/types.js +103 -0
  58. package/dist/src/datetime_format/types.js.map +1 -0
  59. package/dist/src/datetime_format/validate.d.ts +51 -0
  60. package/dist/src/datetime_format/validate.d.ts.map +1 -0
  61. package/dist/src/datetime_format/validate.js +208 -0
  62. package/dist/src/datetime_format/validate.js.map +1 -0
  63. package/dist/src/default.d.ts +21 -0
  64. package/dist/src/default.d.ts.map +1 -0
  65. package/dist/src/default.js +82 -0
  66. package/dist/src/default.js.map +1 -0
  67. package/dist/src/eastir.d.ts +33 -0
  68. package/dist/src/eastir.d.ts.map +1 -0
  69. package/dist/src/eastir.js +92 -0
  70. package/dist/src/eastir.js.map +1 -0
  71. package/dist/src/error.d.ts +13 -0
  72. package/dist/src/error.d.ts.map +1 -0
  73. package/dist/src/error.js +8 -0
  74. package/dist/src/error.js.map +1 -0
  75. package/dist/src/expr/array.d.ts +1711 -0
  76. package/dist/src/expr/array.d.ts.map +1 -0
  77. package/dist/src/expr/array.js +1805 -0
  78. package/dist/src/expr/array.js.map +1 -0
  79. package/dist/src/expr/ast.d.ts +17 -0
  80. package/dist/src/expr/ast.d.ts.map +1 -0
  81. package/dist/src/expr/ast.js +302 -0
  82. package/dist/src/expr/ast.js.map +1 -0
  83. package/dist/src/expr/blob.d.ts +141 -0
  84. package/dist/src/expr/blob.d.ts.map +1 -0
  85. package/dist/src/expr/blob.js +198 -0
  86. package/dist/src/expr/blob.js.map +1 -0
  87. package/dist/src/expr/block.d.ts +201 -0
  88. package/dist/src/expr/block.d.ts.map +1 -0
  89. package/dist/src/expr/block.js +1505 -0
  90. package/dist/src/expr/block.js.map +1 -0
  91. package/dist/src/expr/boolean.d.ts +207 -0
  92. package/dist/src/expr/boolean.d.ts.map +1 -0
  93. package/dist/src/expr/boolean.js +261 -0
  94. package/dist/src/expr/boolean.js.map +1 -0
  95. package/dist/src/expr/datetime.d.ts +544 -0
  96. package/dist/src/expr/datetime.d.ts.map +1 -0
  97. package/dist/src/expr/datetime.js +980 -0
  98. package/dist/src/expr/datetime.js.map +1 -0
  99. package/dist/src/expr/dict.d.ts +1242 -0
  100. package/dist/src/expr/dict.d.ts.map +1 -0
  101. package/dist/src/expr/dict.js +1492 -0
  102. package/dist/src/expr/dict.js.map +1 -0
  103. package/dist/src/expr/expr.d.ts +95 -0
  104. package/dist/src/expr/expr.d.ts.map +1 -0
  105. package/dist/src/expr/expr.js +171 -0
  106. package/dist/src/expr/expr.js.map +1 -0
  107. package/dist/src/expr/float.d.ts +357 -0
  108. package/dist/src/expr/float.d.ts.map +1 -0
  109. package/dist/src/expr/float.js +637 -0
  110. package/dist/src/expr/float.js.map +1 -0
  111. package/dist/src/expr/function.d.ts +46 -0
  112. package/dist/src/expr/function.d.ts.map +1 -0
  113. package/dist/src/expr/function.js +58 -0
  114. package/dist/src/expr/function.js.map +1 -0
  115. package/dist/src/expr/index.d.ts +450 -0
  116. package/dist/src/expr/index.d.ts.map +1 -0
  117. package/dist/src/expr/index.js +423 -0
  118. package/dist/src/expr/index.js.map +1 -0
  119. package/dist/src/expr/integer.d.ts +256 -0
  120. package/dist/src/expr/integer.d.ts.map +1 -0
  121. package/dist/src/expr/integer.js +311 -0
  122. package/dist/src/expr/integer.js.map +1 -0
  123. package/dist/src/expr/libs/array.d.ts +106 -0
  124. package/dist/src/expr/libs/array.d.ts.map +1 -0
  125. package/dist/src/expr/libs/array.js +140 -0
  126. package/dist/src/expr/libs/array.js.map +1 -0
  127. package/dist/src/expr/libs/blob.d.ts +42 -0
  128. package/dist/src/expr/libs/blob.d.ts.map +1 -0
  129. package/dist/src/expr/libs/blob.js +70 -0
  130. package/dist/src/expr/libs/blob.js.map +1 -0
  131. package/dist/src/expr/libs/datetime.d.ts +479 -0
  132. package/dist/src/expr/libs/datetime.d.ts.map +1 -0
  133. package/dist/src/expr/libs/datetime.js +624 -0
  134. package/dist/src/expr/libs/datetime.js.map +1 -0
  135. package/dist/src/expr/libs/dict.d.ts +66 -0
  136. package/dist/src/expr/libs/dict.d.ts.map +1 -0
  137. package/dist/src/expr/libs/dict.js +77 -0
  138. package/dist/src/expr/libs/dict.js.map +1 -0
  139. package/dist/src/expr/libs/float.d.ts +299 -0
  140. package/dist/src/expr/libs/float.d.ts.map +1 -0
  141. package/dist/src/expr/libs/float.js +564 -0
  142. package/dist/src/expr/libs/float.js.map +1 -0
  143. package/dist/src/expr/libs/integer.d.ts +228 -0
  144. package/dist/src/expr/libs/integer.d.ts.map +1 -0
  145. package/dist/src/expr/libs/integer.js +398 -0
  146. package/dist/src/expr/libs/integer.js.map +1 -0
  147. package/dist/src/expr/libs/set.d.ts +59 -0
  148. package/dist/src/expr/libs/set.d.ts.map +1 -0
  149. package/dist/src/expr/libs/set.js +69 -0
  150. package/dist/src/expr/libs/set.js.map +1 -0
  151. package/dist/src/expr/libs/string.d.ts +71 -0
  152. package/dist/src/expr/libs/string.d.ts.map +1 -0
  153. package/dist/src/expr/libs/string.js +75 -0
  154. package/dist/src/expr/libs/string.js.map +1 -0
  155. package/dist/src/expr/never.d.ts +15 -0
  156. package/dist/src/expr/never.d.ts.map +1 -0
  157. package/dist/src/expr/never.js +12 -0
  158. package/dist/src/expr/never.js.map +1 -0
  159. package/dist/src/expr/null.d.ts +15 -0
  160. package/dist/src/expr/null.d.ts.map +1 -0
  161. package/dist/src/expr/null.js +12 -0
  162. package/dist/src/expr/null.js.map +1 -0
  163. package/dist/src/expr/ref.d.ts +103 -0
  164. package/dist/src/expr/ref.d.ts.map +1 -0
  165. package/dist/src/expr/ref.js +131 -0
  166. package/dist/src/expr/ref.js.map +1 -0
  167. package/dist/src/expr/regex_validation.d.ts +25 -0
  168. package/dist/src/expr/regex_validation.d.ts.map +1 -0
  169. package/dist/src/expr/regex_validation.js +130 -0
  170. package/dist/src/expr/regex_validation.js.map +1 -0
  171. package/dist/src/expr/set.d.ts +1071 -0
  172. package/dist/src/expr/set.d.ts.map +1 -0
  173. package/dist/src/expr/set.js +1137 -0
  174. package/dist/src/expr/set.js.map +1 -0
  175. package/dist/src/expr/string.d.ts +414 -0
  176. package/dist/src/expr/string.d.ts.map +1 -0
  177. package/dist/src/expr/string.js +683 -0
  178. package/dist/src/expr/string.js.map +1 -0
  179. package/dist/src/expr/struct.d.ts +48 -0
  180. package/dist/src/expr/struct.d.ts.map +1 -0
  181. package/dist/src/expr/struct.js +65 -0
  182. package/dist/src/expr/struct.js.map +1 -0
  183. package/dist/src/expr/types.d.ts +68 -0
  184. package/dist/src/expr/types.d.ts.map +1 -0
  185. package/dist/src/expr/types.js +6 -0
  186. package/dist/src/expr/types.js.map +1 -0
  187. package/dist/src/expr/variant.d.ts +137 -0
  188. package/dist/src/expr/variant.d.ts.map +1 -0
  189. package/dist/src/expr/variant.js +105 -0
  190. package/dist/src/expr/variant.js.map +1 -0
  191. package/dist/src/fuzz.d.ts +80 -0
  192. package/dist/src/fuzz.d.ts.map +1 -0
  193. package/dist/src/fuzz.js +300 -0
  194. package/dist/src/fuzz.js.map +1 -0
  195. package/dist/src/index.d.ts +21 -0
  196. package/dist/src/index.d.ts.map +1 -0
  197. package/dist/src/index.js +21 -0
  198. package/dist/src/index.js.map +1 -0
  199. package/dist/src/internal.d.ts +36 -0
  200. package/dist/src/internal.d.ts.map +1 -0
  201. package/dist/src/internal.js +11 -0
  202. package/dist/src/internal.js.map +1 -0
  203. package/dist/src/ir.d.ts +1571 -0
  204. package/dist/src/ir.d.ts.map +1 -0
  205. package/dist/src/ir.js +56 -0
  206. package/dist/src/ir.js.map +1 -0
  207. package/dist/src/location.d.ts +48 -0
  208. package/dist/src/location.d.ts.map +1 -0
  209. package/dist/src/location.js +62 -0
  210. package/dist/src/location.js.map +1 -0
  211. package/dist/src/platform.d.ts +21 -0
  212. package/dist/src/platform.d.ts.map +1 -0
  213. package/dist/src/platform.js +8 -0
  214. package/dist/src/platform.js.map +1 -0
  215. package/dist/src/serialization/beast.d.ts +39 -0
  216. package/dist/src/serialization/beast.d.ts.map +1 -0
  217. package/dist/src/serialization/beast.js +555 -0
  218. package/dist/src/serialization/beast.js.map +1 -0
  219. package/dist/src/serialization/beast2-stream.d.ts +38 -0
  220. package/dist/src/serialization/beast2-stream.d.ts.map +1 -0
  221. package/dist/src/serialization/beast2-stream.js +665 -0
  222. package/dist/src/serialization/beast2-stream.js.map +1 -0
  223. package/dist/src/serialization/beast2.d.ts +41 -0
  224. package/dist/src/serialization/beast2.d.ts.map +1 -0
  225. package/dist/src/serialization/beast2.js +489 -0
  226. package/dist/src/serialization/beast2.js.map +1 -0
  227. package/dist/src/serialization/binary-utils.d.ts +151 -0
  228. package/dist/src/serialization/binary-utils.d.ts.map +1 -0
  229. package/dist/src/serialization/binary-utils.js +929 -0
  230. package/dist/src/serialization/binary-utils.js.map +1 -0
  231. package/dist/src/serialization/east.d.ts +84 -0
  232. package/dist/src/serialization/east.d.ts.map +1 -0
  233. package/dist/src/serialization/east.js +1802 -0
  234. package/dist/src/serialization/east.js.map +1 -0
  235. package/dist/src/serialization/index.d.ts +11 -0
  236. package/dist/src/serialization/index.d.ts.map +1 -0
  237. package/dist/src/serialization/index.js +12 -0
  238. package/dist/src/serialization/index.js.map +1 -0
  239. package/dist/src/serialization/json.d.ts +36 -0
  240. package/dist/src/serialization/json.d.ts.map +1 -0
  241. package/dist/src/serialization/json.js +849 -0
  242. package/dist/src/serialization/json.js.map +1 -0
  243. package/dist/src/type_of_type.d.ts +115 -0
  244. package/dist/src/type_of_type.d.ts.map +1 -0
  245. package/dist/src/type_of_type.js +362 -0
  246. package/dist/src/type_of_type.js.map +1 -0
  247. package/dist/src/types.d.ts +648 -0
  248. package/dist/src/types.d.ts.map +1 -0
  249. package/dist/src/types.js +1631 -0
  250. package/dist/src/types.js.map +1 -0
  251. package/package.json +87 -0
@@ -0,0 +1,1110 @@
1
+ /**
2
+ * Copyright (c) 2025 Elara AI Pty Ltd
3
+ * Dual-licensed under AGPL-3.0 and commercial license. See LICENSE for details.
4
+ */
5
+ import { printLocationValue } from "./ir.js";
6
+ import { isTypeValueEqual, isSubtypeValue, expandTypeValue, toEastTypeValue } from "./type_of_type.js";
7
+ import { printTypeValue } from "./compile.js";
8
+ import { variant } from "./containers/variant.js";
9
+ import { ArrayType, IntegerType, StringType, StructType } from "./types.js";
10
+ import { Builtins } from "./builtins.js";
11
+ /**
12
+ * Analyze IR tree and produce enriched IR for JavaScript backend.
13
+ *
14
+ * This function:
15
+ * - Validates the IR (type checking, variable scope, etc.)
16
+ * - Computes which nodes require async compilation
17
+ * - Returns enriched IR with metadata populated (isAsync, etc.)
18
+ *
19
+ * @param ir - The IR tree to analyze
20
+ * @param platformDef - Platform function definitions with async metadata
21
+ * @param ctx - Variable context mapping variable names to their metadata
22
+ * @returns Enriched IR with metadata fields populated
23
+ * @throws {Error} If IR is invalid (type errors, undefined variables, etc.)
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * const ir = // ... parsed East program
28
+ * const platform = [
29
+ * log.implement(console.log),
30
+ * fetch.implementAsync(fetch)
31
+ * ];
32
+ *
33
+ * const enrichedIR = analyzeIR(ir, platform, {});
34
+ * const compiled = compile_internal(enrichedIR, {}, platform);
35
+ * ```
36
+ */
37
+ export function analyzeIR(ir, platformDef, ctx = {}) {
38
+ // Working data for tracking during analysis
39
+ const analysis = new Map();
40
+ // Build a lookup map for platform functions
41
+ const platformMap = new Map();
42
+ for (const p of platformDef) {
43
+ if (platformMap.has(p.name)) {
44
+ throw new Error(`Duplicate platform function definition for '${p.name}'`);
45
+ }
46
+ platformMap.set(p.name, { inputs: p.inputs, output: p.output, type: p.type });
47
+ }
48
+ // Track circular references
49
+ const visiting = new Set();
50
+ function visit(node, ctx, expectedReturnType) {
51
+ // Detect circular IR (we don't cache because we're building new nodes)
52
+ if (visiting.has(node)) {
53
+ const loc = node.value.location;
54
+ throw new Error(`Circular IR reference detected at ${printLocationValue(loc)}`);
55
+ }
56
+ visiting.add(node);
57
+ try {
58
+ const result = visitNode(node, ctx, expectedReturnType);
59
+ return result;
60
+ }
61
+ finally {
62
+ visiting.delete(node);
63
+ }
64
+ }
65
+ function visitNode(node, ctx, expectedReturnType) {
66
+ let isAsync = false;
67
+ if (node.type === "Value") {
68
+ // Validate that the type matches the value
69
+ if (node.value.type.type !== node.value.value.type) {
70
+ throw new Error(`Value node expected value of type .${node.value.type.type} ` +
71
+ `but got .${node.value.value.type} at ${printLocationValue(node.value.location)}`);
72
+ }
73
+ // Value is always sync (literal constant)
74
+ return {
75
+ ...node,
76
+ value: {
77
+ ...node.value,
78
+ isAsync: false,
79
+ }
80
+ };
81
+ }
82
+ else if (node.type === "Variable") {
83
+ const name = node.value.name;
84
+ const varMeta = ctx[name];
85
+ // Validate variable is in scope
86
+ if (varMeta === undefined) {
87
+ throw new Error(`Variable ${name} not in scope at ${printLocationValue(node.value.location)}`);
88
+ }
89
+ // Validate type matches
90
+ if (!isTypeValueEqual(varMeta.type, node.value.type)) {
91
+ throw new Error(`Variable ${name} has type ${printTypeValue(varMeta.type)} ` +
92
+ `but expected ${printTypeValue(node.value.type)} at ${printLocationValue(node.value.location)}`);
93
+ }
94
+ // Validate mutability matches
95
+ if (varMeta.mutable !== node.value.mutable) {
96
+ throw new Error(`Variable ${name} mutability mismatch: ` +
97
+ `context has ${varMeta.mutable ? 'mutable' : 'const'} ` +
98
+ `but IR expects ${node.value.mutable ? 'mutable' : 'const'} ` +
99
+ `at ${printLocationValue(node.value.location)}`);
100
+ }
101
+ // Detect capture: if variable is not in current scope but in parent, mark as captured
102
+ if (!Object.hasOwn(ctx, name)) {
103
+ // Variable is from a parent scope - walk up to find and mark it
104
+ let parentCtx = Object.getPrototypeOf(ctx);
105
+ while (parentCtx) {
106
+ if (Object.hasOwn(parentCtx, name)) {
107
+ // Found the defining scope - mark as captured in working memory
108
+ const varMeta = parentCtx[name];
109
+ varMeta.captured = true;
110
+ // Also mark the defining VariableIR's analysis as captured
111
+ const defVarAnalysis = analysis.get(varMeta.definedBy);
112
+ if (!defVarAnalysis) {
113
+ throw new Error(`Internal error: VariableIR node for ${name} not analyzed. ` +
114
+ `This should never happen - all VariableIR nodes should be analyzed when encountered.`);
115
+ }
116
+ defVarAnalysis.captured = true;
117
+ break;
118
+ }
119
+ parentCtx = Object.getPrototypeOf(parentCtx);
120
+ }
121
+ }
122
+ // Reading a variable is always synchronous
123
+ return {
124
+ ...node,
125
+ value: {
126
+ ...node.value,
127
+ isAsync: false,
128
+ }
129
+ };
130
+ }
131
+ else if (node.type === "Let") {
132
+ // Visit the value expression first
133
+ const valueInfo = visit(node.value.value, ctx, expectedReturnType);
134
+ // Validate value type exactly matches variable type
135
+ // (Subtyping should be handled by explicit As nodes)
136
+ if (node.value.variable.value.type.type !== "Never" && !isTypeValueEqual(valueInfo.value.type, node.value.variable.value.type)) {
137
+ throw new Error(`Let statement requires exact type match. ` +
138
+ `Variable ${node.value.variable.value.name} ` +
139
+ `has type ${printTypeValue(node.value.variable.value.type)} ` +
140
+ `but value has type ${printTypeValue(valueInfo.value.type)}. ` +
141
+ `Insert an As node if subtyping is intended. ` +
142
+ `at ${printLocationValue(node.value.location)}`);
143
+ }
144
+ // Analyze the VariableIR node itself (so it has an entry in the analysis map)
145
+ analysis.set(node.value.variable, {
146
+ captured: false
147
+ });
148
+ // Add variable to context for subsequent statements
149
+ const varName = node.value.variable.value.name;
150
+ ctx[varName] = {
151
+ type: node.value.variable.value.type,
152
+ mutable: node.value.variable.value.mutable,
153
+ definedBy: node.value.variable, // Store reference to the VariableIR
154
+ captured: false // Initially not captured
155
+ };
156
+ // Return analyzed Let node with analyzed value
157
+ return {
158
+ ...node,
159
+ value: {
160
+ ...node.value,
161
+ value: valueInfo,
162
+ isAsync: valueInfo.value.isAsync,
163
+ }
164
+ };
165
+ }
166
+ else if (node.type === "Assign") {
167
+ const varName = node.value.variable.value.name;
168
+ const varMeta = ctx[varName];
169
+ // Validate variable exists in scope
170
+ if (varMeta === undefined) {
171
+ throw new Error(`Cannot assign to variable ${varName} which is not in scope ` +
172
+ `at ${printLocationValue(node.value.location)}`);
173
+ }
174
+ // Validate variable is mutable in IR
175
+ if (!node.value.variable.value.mutable) {
176
+ throw new Error(`Cannot reassign const variable ${varName} ` +
177
+ `at ${printLocationValue(node.value.location)}`);
178
+ }
179
+ // Validate variable is mutable in context (matches IR)
180
+ if (!varMeta.mutable) {
181
+ throw new Error(`Cannot reassign variable ${varName} - context says it's const ` +
182
+ `at ${printLocationValue(node.value.location)}`);
183
+ }
184
+ // Visit the value expression
185
+ const valueInfo = visit(node.value.value, ctx, expectedReturnType);
186
+ // Validate value type exactly matches variable type
187
+ // (Subtyping should be handled by explicit As nodes)
188
+ if (valueInfo.value.type.type !== "Never" && !isTypeValueEqual(valueInfo.value.type, varMeta.type)) {
189
+ throw new Error(`Assign statement requires exact type match. ` +
190
+ `Variable ${varName} has type ${printTypeValue(varMeta.type)} ` +
191
+ `but value has type ${printTypeValue(valueInfo.value.type)}. ` +
192
+ `Insert an As node if subtyping is intended. ` +
193
+ `at ${printLocationValue(node.value.location)}`);
194
+ }
195
+ isAsync = valueInfo.value.isAsync; // Async if the value expression is async
196
+ }
197
+ else if (node.type === "Block") {
198
+ // Create a new scope inheriting from parent (the classic trick!)
199
+ const innerCtx = Object.create(ctx);
200
+ let lastType = variant("Null", null);
201
+ // Visit each statement in sequence
202
+ const analyzedStatements = [];
203
+ for (const statement of node.value.statements) {
204
+ // Note: We don't validate unreachable code here because the IR generator
205
+ // may produce statements after diverging control flow (e.g., return statements
206
+ // from if/else branches). The type system ensures correctness anyway.
207
+ const stmtInfo = visit(statement, innerCtx, expectedReturnType);
208
+ analyzedStatements.push(stmtInfo);
209
+ lastType = stmtInfo.value.type;
210
+ if (stmtInfo.value.isAsync) {
211
+ isAsync = true;
212
+ }
213
+ }
214
+ // Validate block evaluates to expected type
215
+ if (!isTypeValueEqual(lastType, node.value.type)) {
216
+ throw new Error(`Block evaluates to type ${printTypeValue(lastType)} ` +
217
+ `but expected ${printTypeValue(node.value.type)} ` +
218
+ `at ${printLocationValue(node.value.location)}`);
219
+ }
220
+ // Return analyzed Block with analyzed statements
221
+ return {
222
+ ...node,
223
+ value: {
224
+ ...node.value,
225
+ statements: analyzedStatements,
226
+ isAsync,
227
+ }
228
+ };
229
+ }
230
+ else if (node.type === "As") {
231
+ // Visit the child value
232
+ const valueInfo = visit(node.value.value, ctx, expectedReturnType);
233
+ // Validate subtyping: child type must be subtype of target type
234
+ if (!isSubtypeValue(valueInfo.value.type, node.value.type)) {
235
+ throw new Error(`Cannot cast value of type ${printTypeValue(valueInfo.value.type)} ` +
236
+ `to type ${printTypeValue(node.value.type)} ` +
237
+ `at ${printLocationValue(node.value.location)}`);
238
+ }
239
+ // More thorough checks for "unnecessary" As IR nodes:
240
+ // Cannot cast Never to a value
241
+ if (valueInfo.value.type.type === "Never") {
242
+ throw new Error(`Cannot cast .Never to type ${printTypeValue(node.value.type)} ` +
243
+ `at ${printLocationValue(node.value.location)}`);
244
+ }
245
+ if (isTypeValueEqual(valueInfo.value.type, node.value.type)) {
246
+ throw new Error(`Unnecessary As node: value is already of type ${printTypeValue(node.value.type)} ` +
247
+ `at ${printLocationValue(node.value.location)}`);
248
+ }
249
+ isAsync = valueInfo.value.isAsync; // Propagate async from child
250
+ }
251
+ else if (node.type === "Platform") {
252
+ // Look up platform function
253
+ const platformFn = platformMap.get(node.value.name);
254
+ if (!platformFn) {
255
+ throw new Error(`Platform function '${node.value.name}' not found ` +
256
+ `at ${printLocationValue(node.value.location)}`);
257
+ }
258
+ // Validate argument count
259
+ if (node.value.arguments.length !== platformFn.inputs.length) {
260
+ throw new Error(`Platform function '${node.value.name}' expects ${platformFn.inputs.length} arguments ` +
261
+ `but got ${node.value.arguments.length} ` +
262
+ `at ${printLocationValue(node.value.location)}`);
263
+ }
264
+ // Visit all arguments, check if any are async, and validate types
265
+ const analyzedArgs = [];
266
+ for (let i = 0; i < node.value.arguments.length; i++) {
267
+ const arg = node.value.arguments[i];
268
+ const argAnalyzed = visit(arg, ctx, expectedReturnType);
269
+ analyzedArgs.push(argAnalyzed);
270
+ if (argAnalyzed.value.isAsync) {
271
+ isAsync = true;
272
+ }
273
+ // Validate argument type exactly matches expected
274
+ // (Subtyping should be handled by explicit As nodes)
275
+ const expectedType = platformFn.inputs[i];
276
+ if (argAnalyzed.value.type.type !== "Never" && !isTypeValueEqual(argAnalyzed.value.type, expectedType)) {
277
+ throw new Error(`Platform function '${node.value.name}' argument ${i + 1} ` +
278
+ `requires exact type match. ` +
279
+ `Expected type ${printTypeValue(expectedType)} ` +
280
+ `but got ${printTypeValue(argAnalyzed.value.type)}. ` +
281
+ `Insert an As node if subtyping is intended. ` +
282
+ `at ${printLocationValue(node.value.location)}`);
283
+ }
284
+ }
285
+ // Platform function itself might be async
286
+ if (platformFn.type === 'async') {
287
+ isAsync = true;
288
+ }
289
+ // Validate return type matches
290
+ if (!isTypeValueEqual(node.value.type, platformFn.output)) {
291
+ throw new Error(`Platform function '${node.value.name}' return type ` +
292
+ `expected to be ${printTypeValue(platformFn.output)} ` +
293
+ `but IR has ${printTypeValue(node.value.type)} ` +
294
+ `at ${printLocationValue(node.value.location)}`);
295
+ }
296
+ // Return analyzed Platform node with analyzed arguments
297
+ return {
298
+ ...node,
299
+ value: {
300
+ ...node.value,
301
+ arguments: analyzedArgs,
302
+ isAsync,
303
+ }
304
+ };
305
+ }
306
+ else if (node.type === "Function") {
307
+ // Validate it has a Function type
308
+ if (node.value.type.type !== "Function") {
309
+ throw new Error(`Expected Function type, got ${printTypeValue(node.value.type)} ` +
310
+ `at ${printLocationValue(node.value.location)}`);
311
+ }
312
+ // Create new context for function body
313
+ const fnCtx = {};
314
+ // Add captured variables to context
315
+ for (const captureVar of node.value.captures) {
316
+ const outerVar = ctx[captureVar.value.name];
317
+ if (outerVar === undefined) {
318
+ throw new Error(`Captured variable ${captureVar.value.name} not in scope ` +
319
+ `at ${printLocationValue(node.value.location)}`);
320
+ }
321
+ if (!isTypeValueEqual(outerVar.type, captureVar.value.type)) {
322
+ throw new Error(`Captured variable ${captureVar.value.name} has type ${printTypeValue(outerVar.type)} ` +
323
+ `but expected ${printTypeValue(captureVar.value.type)} ` +
324
+ `at ${printLocationValue(node.value.location)}`);
325
+ }
326
+ if (outerVar.mutable !== captureVar.value.mutable) {
327
+ throw new Error(`Captured variable ${captureVar.value.name} mutability mismatch: ` +
328
+ `context has ${outerVar.mutable ? 'mutable' : 'const'} ` +
329
+ `but IR expects ${captureVar.value.mutable ? 'mutable' : 'const'} ` +
330
+ `at ${printLocationValue(node.value.location)}`);
331
+ }
332
+ // Mark the outer variable as captured (it's being closed over by this function)
333
+ outerVar.captured = true;
334
+ const defVarAnalysis = analysis.get(outerVar.definedBy);
335
+ if (!defVarAnalysis) {
336
+ throw new Error(`Internal error: VariableIR node for captured variable ${captureVar.value.name} not analyzed. ` +
337
+ `This should never happen - all VariableIR nodes should be analyzed when encountered.`);
338
+ }
339
+ defVarAnalysis.captured = true;
340
+ fnCtx[captureVar.value.name] = {
341
+ type: captureVar.value.type,
342
+ mutable: captureVar.value.mutable,
343
+ definedBy: outerVar.definedBy,
344
+ captured: false // In the function's context, they're not "captured" yet
345
+ };
346
+ }
347
+ // Add parameters to context
348
+ for (const param of node.value.parameters) {
349
+ // Analyze the parameter VariableIR node
350
+ analysis.set(param, {
351
+ captured: false
352
+ });
353
+ fnCtx[param.value.name] = {
354
+ type: param.value.type,
355
+ mutable: param.value.mutable,
356
+ definedBy: param, // Parameter VariableIR defines itself
357
+ captured: false
358
+ };
359
+ }
360
+ // Visit function body, passing expected return type for Return statement validation
361
+ const expectedOutput = node.value.type.value.output;
362
+ const bodyInfo = visit(node.value.body, fnCtx, expectedOutput);
363
+ // Validate body return type
364
+ // - If Never: OK (there was an explicit Return statement that was already validated)
365
+ // - Otherwise: must exactly match expected output type
366
+ if (bodyInfo.value.type.type !== "Never" && !isTypeValueEqual(bodyInfo.value.type, expectedOutput)) {
367
+ throw new Error(`Function body returns type ${printTypeValue(bodyInfo.value.type)} ` +
368
+ `but function signature expects ${printTypeValue(expectedOutput)} ` +
369
+ `at ${printLocationValue(node.value.location)}`);
370
+ }
371
+ // Creating a function is sync (it just captures variables at this stage)
372
+ return {
373
+ ...node,
374
+ value: {
375
+ ...node.value,
376
+ body: bodyInfo, // Use analyzed body
377
+ isAsync: false,
378
+ }
379
+ };
380
+ }
381
+ else if (node.type === "Call") {
382
+ // Visit function expression
383
+ const fnInfo = visit(node.value.function, ctx, expectedReturnType);
384
+ // Validate it's a function type
385
+ if (fnInfo.value.type.type !== "Function") {
386
+ throw new Error(`Call expects Function type, got ${printTypeValue(fnInfo.value.type)} ` +
387
+ `at ${printLocationValue(node.value.location)}`);
388
+ }
389
+ // Validate argument count
390
+ if (fnInfo.value.type.value.inputs.length !== node.value.arguments.length) {
391
+ throw new Error(`Function expects ${fnInfo.value.type.value.inputs.length} arguments, ` +
392
+ `got ${node.value.arguments.length} ` +
393
+ `at ${printLocationValue(node.value.location)}`);
394
+ }
395
+ // Visit and validate all arguments
396
+ isAsync = fnInfo.value.isAsync; // Start with function's async status
397
+ // Check if the function calls an async function internally
398
+ for (const platform of fnInfo.value.type.value.platforms) {
399
+ const platformDef = platformMap.get(platform);
400
+ if (!platformDef) {
401
+ throw new Error(`Unknown platform function ${platform} called by ${printTypeValue(fnInfo.value.type)} at ${printLocationValue(node.value.location)}`);
402
+ }
403
+ if (platformDef.type === 'async') {
404
+ isAsync = true;
405
+ }
406
+ }
407
+ // Analyze all arguments
408
+ const analyzedArgs = [];
409
+ for (let i = 0; i < node.value.arguments.length; i++) {
410
+ const arg = node.value.arguments[i];
411
+ const argInfo = visit(arg, ctx, expectedReturnType);
412
+ analyzedArgs.push(argInfo);
413
+ if (argInfo.value.isAsync) {
414
+ isAsync = true;
415
+ }
416
+ // Validate argument type exactly matches expected
417
+ const expectedType = fnInfo.value.type.value.inputs[i];
418
+ if (argInfo.value.type.type !== "Never" && !isTypeValueEqual(argInfo.value.type, expectedType)) {
419
+ throw new Error(`Function call argument ${i + 1} requires exact type match. ` +
420
+ `Expected type ${printTypeValue(expectedType)} ` +
421
+ `but got ${printTypeValue(argInfo.value.type)}. ` +
422
+ `Insert an As node if subtyping is intended. ` +
423
+ `at ${printLocationValue(node.value.location)}`);
424
+ }
425
+ }
426
+ // Validate return type matches
427
+ if (!isTypeValueEqual(node.value.type, fnInfo.value.type.value.output)) {
428
+ throw new Error(`Function call return type expected to be ${printTypeValue(fnInfo.value.type.value.output)} ` +
429
+ `but IR has ${printTypeValue(node.value.type)} ` +
430
+ `at ${printLocationValue(node.value.location)}`);
431
+ }
432
+ // Return analyzed Call with analyzed function and arguments
433
+ return {
434
+ ...node,
435
+ value: {
436
+ ...node.value,
437
+ function: fnInfo,
438
+ arguments: analyzedArgs,
439
+ isAsync,
440
+ }
441
+ };
442
+ }
443
+ else if (node.type === "Builtin") {
444
+ // Builtins are always synchronous themselves, but arguments might be async
445
+ isAsync = false;
446
+ const builtinName = node.value.builtin;
447
+ const builtin = Builtins[builtinName];
448
+ if (!builtin) {
449
+ throw new Error(`Unknown builtin function '${builtinName}' ` +
450
+ `at ${printLocationValue(node.value.location)}`);
451
+ }
452
+ if (node.value.arguments.length !== builtin.inputs.length) {
453
+ throw new Error(`Builtin function '${builtinName}' expects ${builtin.inputs.length} arguments, ` +
454
+ `but got ${node.value.arguments.length} ` +
455
+ `at ${printLocationValue(node.value.location)}`);
456
+ }
457
+ // Visit all arguments
458
+ const analyzedArgs = [];
459
+ for (const arg of node.value.arguments) {
460
+ const argInfo = visit(arg, ctx, expectedReturnType);
461
+ analyzedArgs.push(argInfo);
462
+ if (argInfo.value.isAsync) {
463
+ isAsync = true;
464
+ }
465
+ // TODO - validate the type parameters and arguments precisely
466
+ // At the moment this machinery works with AST/EastType, but we need the same for IR/EastTypeValue too
467
+ // (No builtin "captures" functions nor returns functions).
468
+ // For now, trust the frontend and IR generation
469
+ // Check for functions called by the builtin
470
+ // These are arguments where the builtin expects a Function type in the type template
471
+ const argIndex = analyzedArgs.length - 1;
472
+ const builtinArgTemplateType = builtin.inputs[argIndex];
473
+ if (typeof builtinArgTemplateType !== "string" && builtinArgTemplateType.type === "Function") {
474
+ // Validate argument is a Function
475
+ if (argInfo.value.type.type !== "Function") {
476
+ throw new Error(`Builtin function '${builtinName}' expects argument ${argIndex + 1} to be a Function, ` +
477
+ `but got ${printTypeValue(argInfo.value.type)} ` +
478
+ `at ${printLocationValue(argInfo.value.location)}`);
479
+ }
480
+ // If this function argument calls any async platform functions, mark the builtin as async
481
+ for (const platform of argInfo.value.type.value.platforms) {
482
+ const platformDef = platformMap.get(platform);
483
+ if (!platformDef) {
484
+ throw new Error(`Unknown platform function ${platform} called by builtin argument ${argIndex} ` +
485
+ `at ${printLocationValue(argInfo.value.location)}`);
486
+ }
487
+ if (platformDef.type === 'async') {
488
+ isAsync = true;
489
+ }
490
+ }
491
+ }
492
+ }
493
+ // Return analyzed Builtin with analyzed arguments
494
+ return {
495
+ ...node,
496
+ value: {
497
+ ...node.value,
498
+ arguments: analyzedArgs,
499
+ isAsync,
500
+ }
501
+ };
502
+ }
503
+ else if (node.type === "Return") {
504
+ // Validate we're inside a function
505
+ if (!expectedReturnType) {
506
+ throw new Error(`Return statement outside of function at ${printLocationValue(node.value.location)}`);
507
+ }
508
+ // Analyze the return value expression
509
+ const valueInfo = visit(node.value.value, ctx, expectedReturnType);
510
+ // Validate return value type exactly matches expected return type
511
+ if (valueInfo.value.type.type !== "Never" && !isTypeValueEqual(valueInfo.value.type, expectedReturnType)) {
512
+ throw new Error(`Return statement returns type ${printTypeValue(valueInfo.value.type)} ` +
513
+ `but function signature expects ${printTypeValue(expectedReturnType)} ` +
514
+ `at ${printLocationValue(node.value.location)}`);
515
+ }
516
+ // Return analyzed Return node with analyzed value
517
+ return {
518
+ ...node,
519
+ value: {
520
+ ...node.value,
521
+ value: valueInfo,
522
+ isAsync: valueInfo.value.isAsync,
523
+ }
524
+ };
525
+ }
526
+ else if (node.type === "NewRef") {
527
+ // Validate type is Ref
528
+ if (node.value.type.type !== "Ref") {
529
+ throw new Error(`NewRef node must have Ref type, got ${printTypeValue(node.value.type)} ` +
530
+ `at ${printLocationValue(node.value.location)}`);
531
+ }
532
+ const elementType = node.value.type.value;
533
+ const valueInfo = visit(node.value.value, ctx, expectedReturnType);
534
+ isAsync = valueInfo.value.isAsync;
535
+ // Validate element type exactly matches
536
+ if (valueInfo.value.type.type !== "Never" && !isTypeValueEqual(valueInfo.value.type, elementType)) {
537
+ throw new Error(`Ref value has type ${printTypeValue(valueInfo.value.type)} ` +
538
+ `but Ref expects ${printTypeValue(elementType)} ` +
539
+ `at ${printLocationValue(node.value.location)}`);
540
+ }
541
+ }
542
+ else if (node.type === "NewArray") {
543
+ // Validate type is Array
544
+ if (node.value.type.type !== "Array") {
545
+ throw new Error(`NewArray node must have Array type, got ${printTypeValue(node.value.type)} ` +
546
+ `at ${printLocationValue(node.value.location)}`);
547
+ }
548
+ const elementType = node.value.type.value;
549
+ isAsync = false;
550
+ // Visit all element values and validate types
551
+ for (let i = 0; i < node.value.values.length; i++) {
552
+ const valueExpr = node.value.values[i];
553
+ const valueInfo = visit(valueExpr, ctx, expectedReturnType);
554
+ if (valueInfo.value.isAsync) {
555
+ isAsync = true;
556
+ }
557
+ // Validate element type exactly matches
558
+ if (valueInfo.value.type.type !== "Never" && !isTypeValueEqual(valueInfo.value.type, elementType)) {
559
+ throw new Error(`Array element ${i} has type ${printTypeValue(valueInfo.value.type)} ` +
560
+ `but array expects ${printTypeValue(elementType)} ` +
561
+ `at ${printLocationValue(node.value.location)}`);
562
+ }
563
+ }
564
+ }
565
+ else if (node.type === "NewSet") {
566
+ // Validate type is Set
567
+ if (node.value.type.type !== "Set") {
568
+ throw new Error(`NewSet node must have Set type, got ${printTypeValue(node.value.type)} ` +
569
+ `at ${printLocationValue(node.value.location)}`);
570
+ }
571
+ const keyType = node.value.type.value;
572
+ isAsync = false;
573
+ // Visit all element values and validate types
574
+ for (let i = 0; i < node.value.values.length; i++) {
575
+ const keyExpr = node.value.values[i];
576
+ const keyInfo = visit(keyExpr, ctx, expectedReturnType);
577
+ if (keyInfo.value.isAsync) {
578
+ isAsync = true;
579
+ }
580
+ // Validate element type exactly matches
581
+ if (keyInfo.value.type.type !== "Never" && !isTypeValueEqual(keyInfo.value.type, keyType)) {
582
+ throw new Error(`Set element ${i} has type ${printTypeValue(keyInfo.value.type)} ` +
583
+ `but set expects ${printTypeValue(keyType)} ` +
584
+ `at ${printLocationValue(node.value.location)}`);
585
+ }
586
+ }
587
+ }
588
+ else if (node.type === "NewDict") {
589
+ // Validate type is Dict
590
+ if (node.value.type.type !== "Dict") {
591
+ throw new Error(`NewDict node must have Dict type, got ${printTypeValue(node.value.type)} ` +
592
+ `at ${printLocationValue(node.value.location)}`);
593
+ }
594
+ const keyType = node.value.type.value.key;
595
+ const valueType = node.value.type.value.value;
596
+ isAsync = false;
597
+ // Visit all key-value pairs and validate types
598
+ for (let i = 0; i < node.value.values.length; i++) {
599
+ const pair = node.value.values[i];
600
+ const keyInfo = visit(pair.key, ctx, expectedReturnType);
601
+ if (keyInfo.value.isAsync) {
602
+ isAsync = true;
603
+ }
604
+ const valInfo = visit(pair.value, ctx, expectedReturnType);
605
+ if (valInfo.value.isAsync) {
606
+ isAsync = true;
607
+ }
608
+ // Validate key type exactly matches
609
+ if (keyInfo.value.type.type !== "Never" && !isTypeValueEqual(keyInfo.value.type, keyType)) {
610
+ throw new Error(`Dict key ${i} has type ${printTypeValue(keyInfo.value.type)} ` +
611
+ `but dict expects ${printTypeValue(keyType)} ` +
612
+ `at ${printLocationValue(node.value.location)}`);
613
+ }
614
+ // Validate value type exactly matches
615
+ if (valInfo.value.type.type !== "Never" && !isTypeValueEqual(valInfo.value.type, valueType)) {
616
+ throw new Error(`Dict value ${i} has type ${printTypeValue(valInfo.value.type)} ` +
617
+ `but dict expects ${printTypeValue(valueType)} ` +
618
+ `at ${printLocationValue(node.value.location)}`);
619
+ }
620
+ }
621
+ }
622
+ else if (node.type === "ForArray") {
623
+ // Visit the array expression
624
+ const arrayInfo = visit(node.value.array, ctx, expectedReturnType);
625
+ // Validate it's an Array type (not Never)
626
+ if (arrayInfo.value.type.type !== "Array") {
627
+ throw new Error(`ForArray expects Array type, got ${printTypeValue(arrayInfo.value.type)} ` +
628
+ `at ${printLocationValue(node.value.location)}`);
629
+ }
630
+ const elementType = arrayInfo.value.type.value;
631
+ // Validate key variable is Integer type
632
+ if (node.value.key.value.type.type !== "Integer") {
633
+ throw new Error(`ForArray key must be Integer type, got ${printTypeValue(node.value.key.value.type)} ` +
634
+ `at ${printLocationValue(node.value.location)}`);
635
+ }
636
+ // Validate value variable matches array element type
637
+ if (!isTypeValueEqual(node.value.value.value.type, elementType)) {
638
+ throw new Error(`ForArray value variable has type ${printTypeValue(node.value.value.value.type)} ` +
639
+ `but array elements have type ${printTypeValue(elementType)} ` +
640
+ `at ${printLocationValue(node.value.location)}`);
641
+ }
642
+ // Analyze the key and value VariableIR nodes
643
+ analysis.set(node.value.key, {
644
+ captured: false
645
+ });
646
+ analysis.set(node.value.value, {
647
+ captured: false
648
+ });
649
+ // Create new scope for loop body with key and value variables
650
+ const loopCtx = Object.create(ctx);
651
+ loopCtx[node.value.key.value.name] = {
652
+ type: node.value.key.value.type,
653
+ mutable: node.value.key.value.mutable,
654
+ definedBy: node.value.key,
655
+ captured: false
656
+ };
657
+ loopCtx[node.value.value.value.name] = {
658
+ type: node.value.value.value.type,
659
+ mutable: node.value.value.value.mutable,
660
+ definedBy: node.value.value,
661
+ captured: false
662
+ };
663
+ // Visit loop body
664
+ const bodyInfo = visit(node.value.body, loopCtx, expectedReturnType);
665
+ // Return analyzed ForArray with analyzed array and body
666
+ return {
667
+ ...node,
668
+ value: {
669
+ ...node.value,
670
+ array: arrayInfo,
671
+ body: bodyInfo,
672
+ isAsync: arrayInfo.value.isAsync || bodyInfo.value.isAsync,
673
+ }
674
+ };
675
+ }
676
+ else if (node.type === "ForSet") {
677
+ // Visit the set expression
678
+ const setInfo = visit(node.value.set, ctx, expectedReturnType);
679
+ // Validate it's a Set type (not Never)
680
+ if (setInfo.value.type.type !== "Set") {
681
+ throw new Error(`ForSet expects Set type, got ${printTypeValue(setInfo.value.type)} ` +
682
+ `at ${printLocationValue(node.value.location)}`);
683
+ }
684
+ const elementType = setInfo.value.type.value;
685
+ // Validate key variable matches set element type
686
+ if (!isTypeValueEqual(node.value.key.value.type, elementType)) {
687
+ throw new Error(`ForSet key variable has type ${printTypeValue(node.value.key.value.type)} ` +
688
+ `but set elements have type ${printTypeValue(elementType)} ` +
689
+ `at ${printLocationValue(node.value.location)}`);
690
+ }
691
+ // Analyze the key VariableIR node
692
+ analysis.set(node.value.key, {
693
+ captured: false
694
+ });
695
+ // Create new scope for loop body with key variable
696
+ const loopCtx = Object.create(ctx);
697
+ loopCtx[node.value.key.value.name] = {
698
+ type: node.value.key.value.type,
699
+ mutable: node.value.key.value.mutable,
700
+ definedBy: node.value.key,
701
+ captured: false
702
+ };
703
+ // Visit loop body
704
+ const bodyInfo = visit(node.value.body, loopCtx, expectedReturnType);
705
+ isAsync = setInfo.value.isAsync || bodyInfo.value.isAsync;
706
+ }
707
+ else if (node.type === "ForDict") {
708
+ // Visit the dict expression
709
+ const dictInfo = visit(node.value.dict, ctx, expectedReturnType);
710
+ // Validate it's a Dict type (not Never)
711
+ if (dictInfo.value.type.type !== "Dict") {
712
+ throw new Error(`ForDict expects Dict type, got ${printTypeValue(dictInfo.value.type)} ` +
713
+ `at ${printLocationValue(node.value.location)}`);
714
+ }
715
+ const keyType = dictInfo.value.type.value.key;
716
+ const valueType = dictInfo.value.type.value.value;
717
+ // Validate key variable matches dict key type
718
+ if (!isTypeValueEqual(node.value.key.value.type, keyType)) {
719
+ throw new Error(`ForDict key variable has type ${printTypeValue(node.value.key.value.type)} ` +
720
+ `but dict keys have type ${printTypeValue(keyType)} ` +
721
+ `at ${printLocationValue(node.value.location)}`);
722
+ }
723
+ // Validate value variable matches dict value type
724
+ if (!isTypeValueEqual(node.value.value.value.type, valueType)) {
725
+ throw new Error(`ForDict value variable has type ${printTypeValue(node.value.value.value.type)} ` +
726
+ `but dict values have type ${printTypeValue(valueType)} ` +
727
+ `at ${printLocationValue(node.value.location)}`);
728
+ }
729
+ // Analyze the key and value VariableIR nodes
730
+ analysis.set(node.value.key, {
731
+ captured: false
732
+ });
733
+ analysis.set(node.value.value, {
734
+ captured: false
735
+ });
736
+ // Create new scope for loop body with key and value variables
737
+ const loopCtx = Object.create(ctx);
738
+ loopCtx[node.value.key.value.name] = {
739
+ type: node.value.key.value.type,
740
+ mutable: node.value.key.value.mutable,
741
+ definedBy: node.value.key,
742
+ captured: false
743
+ };
744
+ loopCtx[node.value.value.value.name] = {
745
+ type: node.value.value.value.type,
746
+ mutable: node.value.value.value.mutable,
747
+ definedBy: node.value.value,
748
+ captured: false
749
+ };
750
+ // Visit loop body
751
+ const bodyInfo = visit(node.value.body, loopCtx, expectedReturnType);
752
+ isAsync = dictInfo.value.isAsync || bodyInfo.value.isAsync;
753
+ }
754
+ else if (node.type === "IfElse") {
755
+ isAsync = false;
756
+ let allBranchesNever = true;
757
+ // Visit all if/else-if branches
758
+ const analyzedIfs = [];
759
+ for (let i = 0; i < node.value.ifs.length; i++) {
760
+ const branch = node.value.ifs[i];
761
+ // Visit and validate predicate is Boolean
762
+ const predicateInfo = visit(branch.predicate, ctx, expectedReturnType);
763
+ if (predicateInfo.value.isAsync) {
764
+ isAsync = true;
765
+ }
766
+ if (predicateInfo.value.type.type !== "Boolean") {
767
+ throw new Error(`IfElse predicate ${i} must be Boolean type, got ${printTypeValue(predicateInfo.value.type)} ` +
768
+ `at ${printLocationValue(node.value.location)}`);
769
+ }
770
+ // Visit branch body and validate type
771
+ const bodyInfo = visit(branch.body, ctx, expectedReturnType);
772
+ if (bodyInfo.value.isAsync) {
773
+ isAsync = true;
774
+ }
775
+ // Track if this branch returns normally
776
+ if (bodyInfo.value.type.type !== "Never") {
777
+ allBranchesNever = false;
778
+ }
779
+ // Branch body must be IfElse result type (or Never)
780
+ if (bodyInfo.value.type.type !== "Never" && !isTypeValueEqual(bodyInfo.value.type, node.value.type)) {
781
+ throw new Error(`IfElse branch ${i} returns type ${printTypeValue(bodyInfo.value.type)} ` +
782
+ `but IfElse expects ${printTypeValue(node.value.type)} ` +
783
+ `at ${printLocationValue(node.value.location)}`);
784
+ }
785
+ analyzedIfs.push({ predicate: predicateInfo, body: bodyInfo });
786
+ }
787
+ // Visit else body and validate type
788
+ const elseInfo = visit(node.value.else_body, ctx, expectedReturnType);
789
+ if (elseInfo.value.isAsync) {
790
+ isAsync = true;
791
+ }
792
+ // Track if else branch returns normally
793
+ if (elseInfo.value.type.type !== "Never") {
794
+ allBranchesNever = false;
795
+ }
796
+ // Branch body must be IfElse result type (or Never)
797
+ if (elseInfo.value.type.type !== "Never" && !isTypeValueEqual(elseInfo.value.type, node.value.type)) {
798
+ throw new Error(`IfElse else branch returns type ${printTypeValue(elseInfo.value.type)} ` +
799
+ `but IfElse expects ${printTypeValue(node.value.type)} ` +
800
+ `at ${printLocationValue(node.value.location)}`);
801
+ }
802
+ // If all branches diverge, IfElse must be Never
803
+ if (allBranchesNever && node.value.type.type !== "Never") {
804
+ throw new Error(`IfElse has all branches returning Never, so it must have type Never, ` +
805
+ `but has type ${printTypeValue(node.value.type)} ` +
806
+ `at ${printLocationValue(node.value.location)}`);
807
+ }
808
+ // If IfElse is Never, all branches must be Never
809
+ if (node.value.type.type === "Never" && !allBranchesNever) {
810
+ throw new Error(`IfElse has type Never but not all branches diverge ` +
811
+ `at ${printLocationValue(node.value.location)}`);
812
+ }
813
+ // Return analyzed IfElse with analyzed branches
814
+ return {
815
+ ...node,
816
+ value: {
817
+ ...node.value,
818
+ ifs: analyzedIfs.map(({ predicate, body }) => ({ predicate: predicate, body: body })),
819
+ else_body: elseInfo,
820
+ isAsync,
821
+ }
822
+ };
823
+ }
824
+ else if (node.type === "While") {
825
+ // Visit and validate predicate is Boolean or Never
826
+ const predicateInfo = visit(node.value.predicate, ctx, expectedReturnType);
827
+ if (predicateInfo.value.type.type !== "Boolean") {
828
+ throw new Error(`While predicate must be Boolean type, got ${printTypeValue(predicateInfo.value.type)} ` +
829
+ `at ${printLocationValue(node.value.location)}`);
830
+ }
831
+ // Visit loop body
832
+ const bodyInfo = visit(node.value.body, ctx, expectedReturnType);
833
+ // While returns Null, is async if predicate or body is async
834
+ return {
835
+ ...node,
836
+ value: {
837
+ ...node.value,
838
+ predicate: predicateInfo,
839
+ body: bodyInfo,
840
+ isAsync: predicateInfo.value.isAsync || bodyInfo.value.isAsync,
841
+ }
842
+ };
843
+ }
844
+ else if (node.type === "Continue") {
845
+ // Continue always has type Never (diverges control flow)
846
+ isAsync = false; // Continue is not async
847
+ }
848
+ else if (node.type === "Break") {
849
+ // Break always has type Never (diverges control flow)
850
+ isAsync = false; // Break is not async
851
+ }
852
+ else if (node.type === "Error") {
853
+ // Visit and validate message is String type
854
+ const messageInfo = visit(node.value.message, ctx, expectedReturnType);
855
+ if (messageInfo.value.type.type !== "String") {
856
+ throw new Error(`Error message must be String type, got ${printTypeValue(messageInfo.value.type)} ` +
857
+ `at ${printLocationValue(node.value.location)}`);
858
+ }
859
+ // Error always has type Never (throws exception, diverges control flow)
860
+ isAsync = messageInfo.value.isAsync; // Error is async if message is async
861
+ }
862
+ else if (node.type === "TryCatch") {
863
+ // Visit try body
864
+ const tryInfo = visit(node.value.try_body, ctx, expectedReturnType);
865
+ // Validate message variable is String type
866
+ if (node.value.message.value.type.type !== "String") {
867
+ throw new Error(`TryCatch message variable must be String type, got ${printTypeValue(node.value.message.value.type)} ` +
868
+ `at ${printLocationValue(node.value.location)}`);
869
+ }
870
+ // Validate stack variable is correct type
871
+ const stackType = toEastTypeValue(ArrayType(StructType({ filename: StringType, line: IntegerType, column: IntegerType })));
872
+ if (!isTypeValueEqual(node.value.stack.value.type, stackType)) {
873
+ throw new Error(`TryCatch stack variable must be ${printTypeValue(stackType)} type, got ${printTypeValue(node.value.stack.value.type)} ` +
874
+ `at ${printLocationValue(node.value.location)}`);
875
+ }
876
+ // Analyze the message and stack VariableIR nodes
877
+ analysis.set(node.value.message, {
878
+ captured: false
879
+ });
880
+ analysis.set(node.value.stack, {
881
+ captured: false
882
+ });
883
+ // Create new scope for catch body with message and stack variables
884
+ const catchCtx = Object.create(ctx);
885
+ catchCtx[node.value.message.value.name] = {
886
+ type: node.value.message.value.type,
887
+ mutable: node.value.message.value.mutable,
888
+ definedBy: node.value.message,
889
+ captured: false
890
+ };
891
+ catchCtx[node.value.stack.value.name] = {
892
+ type: node.value.stack.value.type,
893
+ mutable: node.value.stack.value.mutable,
894
+ definedBy: node.value.stack,
895
+ captured: false
896
+ };
897
+ // Visit catch body
898
+ const catchInfo = visit(node.value.catch_body, catchCtx, expectedReturnType);
899
+ // Both bodies must be TryCatch result type (or Never)
900
+ if (tryInfo.value.type.type !== "Never" && !isTypeValueEqual(tryInfo.value.type, node.value.type)) {
901
+ throw new Error(`TryCatch try body returns type ${printTypeValue(tryInfo.value.type)} ` +
902
+ `but TryCatch expects ${printTypeValue(node.value.type)} ` +
903
+ `at ${printLocationValue(node.value.location)}`);
904
+ }
905
+ if (catchInfo.value.type.type !== "Never" && !isTypeValueEqual(catchInfo.value.type, node.value.type)) {
906
+ throw new Error(`TryCatch catch body returns type ${printTypeValue(catchInfo.value.type)} ` +
907
+ `but TryCatch expects ${printTypeValue(node.value.type)} ` +
908
+ `at ${printLocationValue(node.value.location)}`);
909
+ }
910
+ // If both bodies diverge, TryCatch must be Never
911
+ const bothNever = tryInfo.value.type.type === "Never" && catchInfo.value.type.type === "Never";
912
+ if (bothNever && node.value.type.type !== "Never") {
913
+ throw new Error(`TryCatch has both try and catch bodies returning Never, so it must have type Never, ` +
914
+ `but has type ${printTypeValue(node.value.type)} ` +
915
+ `at ${printLocationValue(node.value.location)}`);
916
+ }
917
+ isAsync = tryInfo.value.isAsync || catchInfo.value.isAsync;
918
+ }
919
+ else if (node.type === "Struct") {
920
+ // Validate type is Struct
921
+ if (node.value.type.type !== "Struct") {
922
+ throw new Error(`Struct node must have Struct type, got ${printTypeValue(node.value.type)} ` +
923
+ `at ${printLocationValue(node.value.location)}`);
924
+ }
925
+ const structType = expandTypeValue(node.value.type);
926
+ isAsync = false;
927
+ if (structType.value.length !== node.value.fields.length) {
928
+ throw new Error(`Struct type has ${structType.value.length} fields but struct value has ` +
929
+ `${node.value.fields.length} fields ` +
930
+ `at ${printLocationValue(node.value.location)}`);
931
+ }
932
+ // Visit all field values and validate types
933
+ for (const [i, field] of node.value.fields.entries()) {
934
+ const fieldInfo = visit(field.value, ctx, expectedReturnType);
935
+ if (fieldInfo.value.isAsync) {
936
+ isAsync = true;
937
+ }
938
+ // Find corresponding field in struct type (in fixed order)
939
+ const typeField = structType.value[i];
940
+ if (typeField.name !== field.name) {
941
+ throw new Error(`Struct has field ${typeField.name} at position ${i}, but value does not ` +
942
+ `at ${printLocationValue(node.value.location)}`);
943
+ }
944
+ // Validate field type exactly matches
945
+ if (!isTypeValueEqual(fieldInfo.value.type, typeField.type)) {
946
+ throw new Error(`Struct field ${field.name} has type ${printTypeValue(fieldInfo.value.type)} ` +
947
+ `but struct type expects ${printTypeValue(typeField.type)} ` +
948
+ `at ${printLocationValue(node.value.location)}`);
949
+ }
950
+ }
951
+ }
952
+ else if (node.type === "GetField") {
953
+ // Visit the struct expression
954
+ const structInfo = visit(node.value.struct, ctx, expectedReturnType);
955
+ // Validate it's a Struct type
956
+ if (structInfo.value.type.type !== "Struct") {
957
+ throw new Error(`GetField expects Struct type, got ${printTypeValue(structInfo.value.type)} ` +
958
+ `at ${printLocationValue(node.value.location)}`);
959
+ }
960
+ // Find the field in struct type
961
+ const structType = expandTypeValue(structInfo.value.type);
962
+ const field = structType.value.find(f => f.name === node.value.field);
963
+ if (!field) {
964
+ throw new Error(`Struct does not have field ${node.value.field} ` +
965
+ `at ${printLocationValue(node.value.location)}`);
966
+ }
967
+ // Validate return type matches field type
968
+ if (!isTypeValueEqual(node.value.type, field.type)) {
969
+ throw new Error(`GetField result type ${printTypeValue(node.value.type)} ` +
970
+ `does not match field type ${printTypeValue(field.type)} ` +
971
+ `at ${printLocationValue(node.value.location)}`);
972
+ }
973
+ isAsync = structInfo.value.isAsync;
974
+ }
975
+ else if (node.type === "Variant") {
976
+ // Visit the value expression
977
+ const valueInfo = visit(node.value.value, ctx, expectedReturnType);
978
+ // Validate type is Variant
979
+ if (node.value.type.type !== "Variant") {
980
+ throw new Error(`Variant node must have Variant type, got ${printTypeValue(node.value.type)} ` +
981
+ `at ${printLocationValue(node.value.location)}`);
982
+ }
983
+ // Expand recursive types to get well-formed variant type
984
+ const variantType = expandTypeValue(node.value.type);
985
+ if (variantType.type !== "Variant") {
986
+ throw new Error(`Expanded Variant type is not Variant, got ${printTypeValue(variantType)} ` +
987
+ `at ${printLocationValue(node.value.location)}`);
988
+ }
989
+ // Find the case in variant type
990
+ const caseType = variantType.value.find((c) => c.name === node.value.case);
991
+ if (!caseType) {
992
+ throw new Error(`Variant type does not have case ${node.value.case} ` +
993
+ `at ${printLocationValue(node.value.location)}`);
994
+ }
995
+ // Validate value type exactly matches case type
996
+ if (!isTypeValueEqual(valueInfo.value.type, caseType.type)) {
997
+ throw new Error(`Variant case ${node.value.case} value has type ${printTypeValue(valueInfo.value.type)} ` +
998
+ `but variant type expects ${printTypeValue(caseType.type)} ` +
999
+ `at ${printLocationValue(node.value.location)}`);
1000
+ }
1001
+ isAsync = valueInfo.value.isAsync;
1002
+ }
1003
+ else if (node.type === "Match") {
1004
+ // Visit the variant expression
1005
+ const variantInfo = visit(node.value.variant, ctx, expectedReturnType);
1006
+ // Validate it's a Variant type
1007
+ if (variantInfo.value.type.type !== "Variant") {
1008
+ throw new Error(`Match expects Variant type, got ${printTypeValue(variantInfo.value.type)} ` +
1009
+ `at ${printLocationValue(node.value.location)}`);
1010
+ }
1011
+ // Expand recursive types to get well-formed variant type
1012
+ const variantType = expandTypeValue(variantInfo.value.type);
1013
+ if (variantType.type !== "Variant") {
1014
+ throw new Error(`Expanded Match variant type is not Variant, got ${printTypeValue(variantType)} ` +
1015
+ `at ${printLocationValue(node.value.location)}`);
1016
+ }
1017
+ // Validate case count matches
1018
+ if (variantType.value.length !== node.value.cases.length) {
1019
+ throw new Error(`Match has ${node.value.cases.length} cases but variant type has ` +
1020
+ `${variantType.value.length} cases ` +
1021
+ `at ${printLocationValue(node.value.location)}`);
1022
+ }
1023
+ isAsync = variantInfo.value.isAsync;
1024
+ let allCasesNever = true;
1025
+ // Visit all match cases
1026
+ for (const matchCase of node.value.cases) {
1027
+ // Find corresponding case in variant type
1028
+ const typeCase = variantType.value.find((c) => c.name === matchCase.case);
1029
+ if (!typeCase) {
1030
+ throw new Error(`Match has case ${matchCase.case} but variant type does not ` +
1031
+ `at ${printLocationValue(node.value.location)}`);
1032
+ }
1033
+ // Validate variable type matches case type
1034
+ if (!isTypeValueEqual(matchCase.variable.value.type, typeCase.type)) {
1035
+ throw new Error(`Match case ${matchCase.case} variable has type ${printTypeValue(matchCase.variable.value.type)} ` +
1036
+ `but variant case has type ${printTypeValue(typeCase.type)} ` +
1037
+ `at ${printLocationValue(node.value.location)}`);
1038
+ }
1039
+ // Analyze the variable VariableIR node
1040
+ analysis.set(matchCase.variable, {
1041
+ captured: false
1042
+ });
1043
+ // Create new scope for case body with variable
1044
+ const caseCtx = Object.create(ctx);
1045
+ caseCtx[matchCase.variable.value.name] = {
1046
+ type: matchCase.variable.value.type,
1047
+ mutable: matchCase.variable.value.mutable,
1048
+ definedBy: matchCase.variable,
1049
+ captured: false
1050
+ };
1051
+ // Visit case body
1052
+ const bodyInfo = visit(matchCase.body, caseCtx, expectedReturnType);
1053
+ if (bodyInfo.value.isAsync) {
1054
+ isAsync = true;
1055
+ }
1056
+ // Track if this case returns normally
1057
+ if (bodyInfo.value.type.type !== "Never") {
1058
+ allCasesNever = false;
1059
+ }
1060
+ // Case body must be Match result type (or Never)
1061
+ if (bodyInfo.value.type.type !== "Never" && !isTypeValueEqual(bodyInfo.value.type, node.value.type)) {
1062
+ throw new Error(`Match case ${matchCase.case} returns type ${printTypeValue(bodyInfo.value.type)} ` +
1063
+ `but Match expects ${printTypeValue(node.value.type)} ` +
1064
+ `at ${printLocationValue(node.value.location)}`);
1065
+ }
1066
+ }
1067
+ // If all cases diverge, Match must be Never
1068
+ if (allCasesNever && node.value.type.type !== "Never") {
1069
+ throw new Error(`Match has all cases returning Never, so it must have type Never, ` +
1070
+ `but has type ${printTypeValue(node.value.type)} ` +
1071
+ `at ${printLocationValue(node.value.location)}`);
1072
+ }
1073
+ // isAsync already set from loop above
1074
+ }
1075
+ else if (node.type === "UnwrapRecursive") {
1076
+ // Visit the value expression
1077
+ const valueInfo = visit(node.value.value, ctx, expectedReturnType);
1078
+ // Validate result type matches expanded type
1079
+ if (valueInfo.value.type.type !== "Never" && !isTypeValueEqual(node.value.type, valueInfo.value.type)) {
1080
+ throw new Error(`UnwrapRecursive result type ${printTypeValue(node.value.type)} ` +
1081
+ `does not match recursive type ${printTypeValue(valueInfo.value.type)} ` +
1082
+ `at ${printLocationValue(node.value.location)}`);
1083
+ }
1084
+ isAsync = valueInfo.value.isAsync;
1085
+ }
1086
+ else if (node.type === "WrapRecursive") {
1087
+ // Visit the value expression
1088
+ const valueInfo = visit(node.value.value, ctx, expectedReturnType);
1089
+ // Validate value type matches expanded recursive type
1090
+ if (valueInfo.value.type.type !== "Never" && !isTypeValueEqual(valueInfo.value.type, node.value.type)) {
1091
+ throw new Error(`WrapRecursive value has type ${printTypeValue(valueInfo.value.type)} ` +
1092
+ `but expects ${printTypeValue(node.value.type)} ` +
1093
+ `at ${printLocationValue(node.value.location)}`);
1094
+ }
1095
+ isAsync = valueInfo.value.isAsync;
1096
+ }
1097
+ else {
1098
+ throw new Error(`Unhandled IR type: ${node.type} at ${printLocationValue(node.value?.location || { file: "unknown", line: 0, column: 0 })}`);
1099
+ }
1100
+ return {
1101
+ ...node,
1102
+ value: {
1103
+ ...node.value,
1104
+ isAsync,
1105
+ }
1106
+ };
1107
+ }
1108
+ return visit(ir, ctx);
1109
+ }
1110
+ //# sourceMappingURL=analyze.js.map