@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.
- package/LICENSE.md +682 -0
- package/README.md +276 -0
- package/dist/src/analyze.d.ts +95 -0
- package/dist/src/analyze.d.ts.map +1 -0
- package/dist/src/analyze.js +1110 -0
- package/dist/src/analyze.js.map +1 -0
- package/dist/src/ast.d.ts +263 -0
- package/dist/src/ast.d.ts.map +1 -0
- package/dist/src/ast.js +151 -0
- package/dist/src/ast.js.map +1 -0
- package/dist/src/ast_to_ir.d.ts +24 -0
- package/dist/src/ast_to_ir.d.ts.map +1 -0
- package/dist/src/ast_to_ir.js +834 -0
- package/dist/src/ast_to_ir.js.map +1 -0
- package/dist/src/builtins.d.ts +18 -0
- package/dist/src/builtins.d.ts.map +1 -0
- package/dist/src/builtins.js +1105 -0
- package/dist/src/builtins.js.map +1 -0
- package/dist/src/comparison.d.ts +28 -0
- package/dist/src/comparison.d.ts.map +1 -0
- package/dist/src/comparison.js +1017 -0
- package/dist/src/comparison.js.map +1 -0
- package/dist/src/compile.d.ts +22 -0
- package/dist/src/compile.d.ts.map +1 -0
- package/dist/src/compile.js +3260 -0
- package/dist/src/compile.js.map +1 -0
- package/dist/src/containers/ref.d.ts +106 -0
- package/dist/src/containers/ref.d.ts.map +1 -0
- package/dist/src/containers/ref.js +100 -0
- package/dist/src/containers/ref.js.map +1 -0
- package/dist/src/containers/sortedmap.d.ts +165 -0
- package/dist/src/containers/sortedmap.d.ts.map +1 -0
- package/dist/src/containers/sortedmap.js +237 -0
- package/dist/src/containers/sortedmap.js.map +1 -0
- package/dist/src/containers/sortedset.d.ts +185 -0
- package/dist/src/containers/sortedset.d.ts.map +1 -0
- package/dist/src/containers/sortedset.js +312 -0
- package/dist/src/containers/sortedset.js.map +1 -0
- package/dist/src/containers/variant.d.ts +131 -0
- package/dist/src/containers/variant.d.ts.map +1 -0
- package/dist/src/containers/variant.js +68 -0
- package/dist/src/containers/variant.js.map +1 -0
- package/dist/src/datetime_format/parse.d.ts +50 -0
- package/dist/src/datetime_format/parse.d.ts.map +1 -0
- package/dist/src/datetime_format/parse.js +908 -0
- package/dist/src/datetime_format/parse.js.map +1 -0
- package/dist/src/datetime_format/print.d.ts +35 -0
- package/dist/src/datetime_format/print.d.ts.map +1 -0
- package/dist/src/datetime_format/print.js +157 -0
- package/dist/src/datetime_format/print.js.map +1 -0
- package/dist/src/datetime_format/tokenize.d.ts +76 -0
- package/dist/src/datetime_format/tokenize.d.ts.map +1 -0
- package/dist/src/datetime_format/tokenize.js +271 -0
- package/dist/src/datetime_format/tokenize.js.map +1 -0
- package/dist/src/datetime_format/types.d.ts +99 -0
- package/dist/src/datetime_format/types.d.ts.map +1 -0
- package/dist/src/datetime_format/types.js +103 -0
- package/dist/src/datetime_format/types.js.map +1 -0
- package/dist/src/datetime_format/validate.d.ts +51 -0
- package/dist/src/datetime_format/validate.d.ts.map +1 -0
- package/dist/src/datetime_format/validate.js +208 -0
- package/dist/src/datetime_format/validate.js.map +1 -0
- package/dist/src/default.d.ts +21 -0
- package/dist/src/default.d.ts.map +1 -0
- package/dist/src/default.js +82 -0
- package/dist/src/default.js.map +1 -0
- package/dist/src/eastir.d.ts +33 -0
- package/dist/src/eastir.d.ts.map +1 -0
- package/dist/src/eastir.js +92 -0
- package/dist/src/eastir.js.map +1 -0
- package/dist/src/error.d.ts +13 -0
- package/dist/src/error.d.ts.map +1 -0
- package/dist/src/error.js +8 -0
- package/dist/src/error.js.map +1 -0
- package/dist/src/expr/array.d.ts +1711 -0
- package/dist/src/expr/array.d.ts.map +1 -0
- package/dist/src/expr/array.js +1805 -0
- package/dist/src/expr/array.js.map +1 -0
- package/dist/src/expr/ast.d.ts +17 -0
- package/dist/src/expr/ast.d.ts.map +1 -0
- package/dist/src/expr/ast.js +302 -0
- package/dist/src/expr/ast.js.map +1 -0
- package/dist/src/expr/blob.d.ts +141 -0
- package/dist/src/expr/blob.d.ts.map +1 -0
- package/dist/src/expr/blob.js +198 -0
- package/dist/src/expr/blob.js.map +1 -0
- package/dist/src/expr/block.d.ts +201 -0
- package/dist/src/expr/block.d.ts.map +1 -0
- package/dist/src/expr/block.js +1505 -0
- package/dist/src/expr/block.js.map +1 -0
- package/dist/src/expr/boolean.d.ts +207 -0
- package/dist/src/expr/boolean.d.ts.map +1 -0
- package/dist/src/expr/boolean.js +261 -0
- package/dist/src/expr/boolean.js.map +1 -0
- package/dist/src/expr/datetime.d.ts +544 -0
- package/dist/src/expr/datetime.d.ts.map +1 -0
- package/dist/src/expr/datetime.js +980 -0
- package/dist/src/expr/datetime.js.map +1 -0
- package/dist/src/expr/dict.d.ts +1242 -0
- package/dist/src/expr/dict.d.ts.map +1 -0
- package/dist/src/expr/dict.js +1492 -0
- package/dist/src/expr/dict.js.map +1 -0
- package/dist/src/expr/expr.d.ts +95 -0
- package/dist/src/expr/expr.d.ts.map +1 -0
- package/dist/src/expr/expr.js +171 -0
- package/dist/src/expr/expr.js.map +1 -0
- package/dist/src/expr/float.d.ts +357 -0
- package/dist/src/expr/float.d.ts.map +1 -0
- package/dist/src/expr/float.js +637 -0
- package/dist/src/expr/float.js.map +1 -0
- package/dist/src/expr/function.d.ts +46 -0
- package/dist/src/expr/function.d.ts.map +1 -0
- package/dist/src/expr/function.js +58 -0
- package/dist/src/expr/function.js.map +1 -0
- package/dist/src/expr/index.d.ts +450 -0
- package/dist/src/expr/index.d.ts.map +1 -0
- package/dist/src/expr/index.js +423 -0
- package/dist/src/expr/index.js.map +1 -0
- package/dist/src/expr/integer.d.ts +256 -0
- package/dist/src/expr/integer.d.ts.map +1 -0
- package/dist/src/expr/integer.js +311 -0
- package/dist/src/expr/integer.js.map +1 -0
- package/dist/src/expr/libs/array.d.ts +106 -0
- package/dist/src/expr/libs/array.d.ts.map +1 -0
- package/dist/src/expr/libs/array.js +140 -0
- package/dist/src/expr/libs/array.js.map +1 -0
- package/dist/src/expr/libs/blob.d.ts +42 -0
- package/dist/src/expr/libs/blob.d.ts.map +1 -0
- package/dist/src/expr/libs/blob.js +70 -0
- package/dist/src/expr/libs/blob.js.map +1 -0
- package/dist/src/expr/libs/datetime.d.ts +479 -0
- package/dist/src/expr/libs/datetime.d.ts.map +1 -0
- package/dist/src/expr/libs/datetime.js +624 -0
- package/dist/src/expr/libs/datetime.js.map +1 -0
- package/dist/src/expr/libs/dict.d.ts +66 -0
- package/dist/src/expr/libs/dict.d.ts.map +1 -0
- package/dist/src/expr/libs/dict.js +77 -0
- package/dist/src/expr/libs/dict.js.map +1 -0
- package/dist/src/expr/libs/float.d.ts +299 -0
- package/dist/src/expr/libs/float.d.ts.map +1 -0
- package/dist/src/expr/libs/float.js +564 -0
- package/dist/src/expr/libs/float.js.map +1 -0
- package/dist/src/expr/libs/integer.d.ts +228 -0
- package/dist/src/expr/libs/integer.d.ts.map +1 -0
- package/dist/src/expr/libs/integer.js +398 -0
- package/dist/src/expr/libs/integer.js.map +1 -0
- package/dist/src/expr/libs/set.d.ts +59 -0
- package/dist/src/expr/libs/set.d.ts.map +1 -0
- package/dist/src/expr/libs/set.js +69 -0
- package/dist/src/expr/libs/set.js.map +1 -0
- package/dist/src/expr/libs/string.d.ts +71 -0
- package/dist/src/expr/libs/string.d.ts.map +1 -0
- package/dist/src/expr/libs/string.js +75 -0
- package/dist/src/expr/libs/string.js.map +1 -0
- package/dist/src/expr/never.d.ts +15 -0
- package/dist/src/expr/never.d.ts.map +1 -0
- package/dist/src/expr/never.js +12 -0
- package/dist/src/expr/never.js.map +1 -0
- package/dist/src/expr/null.d.ts +15 -0
- package/dist/src/expr/null.d.ts.map +1 -0
- package/dist/src/expr/null.js +12 -0
- package/dist/src/expr/null.js.map +1 -0
- package/dist/src/expr/ref.d.ts +103 -0
- package/dist/src/expr/ref.d.ts.map +1 -0
- package/dist/src/expr/ref.js +131 -0
- package/dist/src/expr/ref.js.map +1 -0
- package/dist/src/expr/regex_validation.d.ts +25 -0
- package/dist/src/expr/regex_validation.d.ts.map +1 -0
- package/dist/src/expr/regex_validation.js +130 -0
- package/dist/src/expr/regex_validation.js.map +1 -0
- package/dist/src/expr/set.d.ts +1071 -0
- package/dist/src/expr/set.d.ts.map +1 -0
- package/dist/src/expr/set.js +1137 -0
- package/dist/src/expr/set.js.map +1 -0
- package/dist/src/expr/string.d.ts +414 -0
- package/dist/src/expr/string.d.ts.map +1 -0
- package/dist/src/expr/string.js +683 -0
- package/dist/src/expr/string.js.map +1 -0
- package/dist/src/expr/struct.d.ts +48 -0
- package/dist/src/expr/struct.d.ts.map +1 -0
- package/dist/src/expr/struct.js +65 -0
- package/dist/src/expr/struct.js.map +1 -0
- package/dist/src/expr/types.d.ts +68 -0
- package/dist/src/expr/types.d.ts.map +1 -0
- package/dist/src/expr/types.js +6 -0
- package/dist/src/expr/types.js.map +1 -0
- package/dist/src/expr/variant.d.ts +137 -0
- package/dist/src/expr/variant.d.ts.map +1 -0
- package/dist/src/expr/variant.js +105 -0
- package/dist/src/expr/variant.js.map +1 -0
- package/dist/src/fuzz.d.ts +80 -0
- package/dist/src/fuzz.d.ts.map +1 -0
- package/dist/src/fuzz.js +300 -0
- package/dist/src/fuzz.js.map +1 -0
- package/dist/src/index.d.ts +21 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +21 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/internal.d.ts +36 -0
- package/dist/src/internal.d.ts.map +1 -0
- package/dist/src/internal.js +11 -0
- package/dist/src/internal.js.map +1 -0
- package/dist/src/ir.d.ts +1571 -0
- package/dist/src/ir.d.ts.map +1 -0
- package/dist/src/ir.js +56 -0
- package/dist/src/ir.js.map +1 -0
- package/dist/src/location.d.ts +48 -0
- package/dist/src/location.d.ts.map +1 -0
- package/dist/src/location.js +62 -0
- package/dist/src/location.js.map +1 -0
- package/dist/src/platform.d.ts +21 -0
- package/dist/src/platform.d.ts.map +1 -0
- package/dist/src/platform.js +8 -0
- package/dist/src/platform.js.map +1 -0
- package/dist/src/serialization/beast.d.ts +39 -0
- package/dist/src/serialization/beast.d.ts.map +1 -0
- package/dist/src/serialization/beast.js +555 -0
- package/dist/src/serialization/beast.js.map +1 -0
- package/dist/src/serialization/beast2-stream.d.ts +38 -0
- package/dist/src/serialization/beast2-stream.d.ts.map +1 -0
- package/dist/src/serialization/beast2-stream.js +665 -0
- package/dist/src/serialization/beast2-stream.js.map +1 -0
- package/dist/src/serialization/beast2.d.ts +41 -0
- package/dist/src/serialization/beast2.d.ts.map +1 -0
- package/dist/src/serialization/beast2.js +489 -0
- package/dist/src/serialization/beast2.js.map +1 -0
- package/dist/src/serialization/binary-utils.d.ts +151 -0
- package/dist/src/serialization/binary-utils.d.ts.map +1 -0
- package/dist/src/serialization/binary-utils.js +929 -0
- package/dist/src/serialization/binary-utils.js.map +1 -0
- package/dist/src/serialization/east.d.ts +84 -0
- package/dist/src/serialization/east.d.ts.map +1 -0
- package/dist/src/serialization/east.js +1802 -0
- package/dist/src/serialization/east.js.map +1 -0
- package/dist/src/serialization/index.d.ts +11 -0
- package/dist/src/serialization/index.d.ts.map +1 -0
- package/dist/src/serialization/index.js +12 -0
- package/dist/src/serialization/index.js.map +1 -0
- package/dist/src/serialization/json.d.ts +36 -0
- package/dist/src/serialization/json.d.ts.map +1 -0
- package/dist/src/serialization/json.js +849 -0
- package/dist/src/serialization/json.js.map +1 -0
- package/dist/src/type_of_type.d.ts +115 -0
- package/dist/src/type_of_type.d.ts.map +1 -0
- package/dist/src/type_of_type.js +362 -0
- package/dist/src/type_of_type.js.map +1 -0
- package/dist/src/types.d.ts +648 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +1631 -0
- package/dist/src/types.js.map +1 -0
- 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
|