/Users/alexjokela/projects/lattice/src/phase_check.c
Line | Count | Source |
1 | | #include "phase_check.h" |
2 | | #include "ds/hashmap.h" |
3 | | #include <stdlib.h> |
4 | | #include <string.h> |
5 | | #include <stdio.h> |
6 | | #include <stdarg.h> |
7 | | |
8 | | typedef struct { |
9 | | AstMode mode; |
10 | | LatVec errors; /* vec of char* */ |
11 | | LatMap *scope_stack; /* array of LatMap (string -> AstPhase) */ |
12 | | size_t scope_count; |
13 | | size_t scope_cap; |
14 | | LatMap struct_defs; /* name -> StructDecl* */ |
15 | | LatMap fn_defs; /* name -> FnDecl* */ |
16 | | } PhaseChecker; |
17 | | |
18 | 9 | static void pc_init(PhaseChecker *pc, AstMode mode) { |
19 | 9 | pc->mode = mode; |
20 | 9 | pc->errors = lat_vec_new(sizeof(char *)); |
21 | 9 | pc->scope_cap = 8; |
22 | 9 | pc->scope_count = 1; |
23 | 9 | pc->scope_stack = malloc(pc->scope_cap * sizeof(LatMap)); |
24 | 9 | pc->scope_stack[0] = lat_map_new(sizeof(AstPhase)); |
25 | 9 | pc->struct_defs = lat_map_new(sizeof(StructDecl *)); |
26 | 9 | pc->fn_defs = lat_map_new(sizeof(FnDecl *)); |
27 | 9 | } |
28 | | |
29 | 9 | static void pc_free(PhaseChecker *pc) { |
30 | 18 | for (size_t i = 0; i < pc->scope_count; i++) |
31 | 9 | lat_map_free(&pc->scope_stack[i]); |
32 | 9 | free(pc->scope_stack); |
33 | 9 | lat_map_free(&pc->struct_defs); |
34 | 9 | lat_map_free(&pc->fn_defs); |
35 | 9 | } |
36 | | |
37 | 15 | static void pc_push_scope(PhaseChecker *pc) { |
38 | 15 | if (pc->scope_count >= pc->scope_cap) { |
39 | 0 | pc->scope_cap *= 2; |
40 | 0 | pc->scope_stack = realloc(pc->scope_stack, pc->scope_cap * sizeof(LatMap)); |
41 | 0 | } |
42 | 15 | pc->scope_stack[pc->scope_count++] = lat_map_new(sizeof(AstPhase)); |
43 | 15 | } |
44 | | |
45 | 15 | static void pc_pop_scope(PhaseChecker *pc) { |
46 | 15 | if (pc->scope_count > 1) { |
47 | 15 | pc->scope_count--; |
48 | 15 | lat_map_free(&pc->scope_stack[pc->scope_count]); |
49 | 15 | } |
50 | 15 | } |
51 | | |
52 | 33 | static void pc_define(PhaseChecker *pc, const char *name, AstPhase phase) { |
53 | 33 | lat_map_set(&pc->scope_stack[pc->scope_count - 1], name, &phase); |
54 | 33 | } |
55 | | |
56 | 87 | static AstPhase pc_lookup(const PhaseChecker *pc, const char *name) { |
57 | 105 | for (size_t i = pc->scope_count; i > 0; i--) { |
58 | 102 | AstPhase *p = lat_map_get(&pc->scope_stack[i - 1], name); |
59 | 102 | if (p) return *p; |
60 | 102 | } |
61 | 3 | return PHASE_UNSPECIFIED; |
62 | 87 | } |
63 | | |
64 | 0 | static void pc_error(PhaseChecker *pc, const char *fmt, ...) { |
65 | 0 | char *msg = NULL; |
66 | 0 | va_list args; |
67 | 0 | va_start(args, fmt); |
68 | 0 | (void)vasprintf(&msg, fmt, args); |
69 | 0 | va_end(args); |
70 | 0 | lat_vec_push(&pc->errors, &msg); |
71 | 0 | } |
72 | | |
73 | | /* Forward declarations */ |
74 | | static AstPhase pc_check_expr(PhaseChecker *pc, const Expr *expr); |
75 | | static void pc_check_stmt(PhaseChecker *pc, const Stmt *stmt); |
76 | | static void pc_check_spawn_stmt(PhaseChecker *pc, const Stmt *stmt); |
77 | | static void pc_check_spawn_expr(PhaseChecker *pc, const Expr *expr); |
78 | | |
79 | 258 | static AstPhase pc_check_expr(PhaseChecker *pc, const Expr *expr) { |
80 | 258 | switch (expr->tag) { |
81 | 42 | case EXPR_INT_LIT: case EXPR_FLOAT_LIT: case EXPR_STRING_LIT: case EXPR_BOOL_LIT: |
82 | 42 | case EXPR_NIL_LIT: |
83 | 42 | return PHASE_UNSPECIFIED; |
84 | | |
85 | 84 | case EXPR_IDENT: |
86 | 84 | return pc_lookup(pc, expr->as.str_val); |
87 | | |
88 | 12 | case EXPR_BINOP: |
89 | 12 | pc_check_expr(pc, expr->as.binop.left); |
90 | 12 | pc_check_expr(pc, expr->as.binop.right); |
91 | 12 | return PHASE_UNSPECIFIED; |
92 | | |
93 | 0 | case EXPR_UNARYOP: |
94 | 0 | pc_check_expr(pc, expr->as.unaryop.operand); |
95 | 0 | return PHASE_UNSPECIFIED; |
96 | | |
97 | 3 | case EXPR_CALL: { |
98 | 3 | pc_check_expr(pc, expr->as.call.func); |
99 | 3 | AstPhase arg_phases[expr->as.call.arg_count]; |
100 | 6 | for (size_t i = 0; i < expr->as.call.arg_count; i++) |
101 | 3 | arg_phases[i] = pc_check_expr(pc, expr->as.call.args[i]); |
102 | | /* In strict mode, check argument phases against function parameter constraints */ |
103 | 3 | if (pc->mode == MODE_STRICT && expr->as.call.func->tag == EXPR_IDENT) { |
104 | 3 | const char *fn_name = expr->as.call.func->as.str_val; |
105 | 3 | FnDecl **fdp = lat_map_get(&pc->fn_defs, fn_name); |
106 | 3 | if (fdp) { |
107 | 3 | FnDecl *fd = *fdp; |
108 | | /* Check all overloads — error only if no overload matches */ |
109 | 3 | bool any_match = false; |
110 | 3 | for (FnDecl *cand = fd; cand; cand = cand->next_overload) { |
111 | 3 | bool match = true; |
112 | 3 | size_t check = expr->as.call.arg_count < cand->param_count |
113 | 3 | ? expr->as.call.arg_count : cand->param_count; |
114 | 6 | for (size_t i = 0; i < check; i++) { |
115 | 3 | if (cand->params[i].is_variadic) break; |
116 | 3 | AstPhase pp = cand->params[i].ty.phase; |
117 | 3 | AstPhase ap = arg_phases[i]; |
118 | 3 | if (pp == PHASE_FLUID && ap == PHASE_CRYSTAL) { match = false; break; } |
119 | 3 | if (pp == PHASE_CRYSTAL && ap == PHASE_FLUID) { match = false; break; } |
120 | 3 | } |
121 | 3 | if (match) { any_match = true; break; } |
122 | 3 | } |
123 | 3 | if (!any_match) |
124 | 0 | pc_error(pc, "strict mode: no matching overload for '%s' with given argument phases", fn_name); |
125 | 3 | } |
126 | 3 | } |
127 | 3 | return PHASE_UNSPECIFIED; |
128 | 42 | } |
129 | | |
130 | 3 | case EXPR_METHOD_CALL: |
131 | 3 | pc_check_expr(pc, expr->as.method_call.object); |
132 | 3 | for (size_t i = 0; i < expr->as.method_call.arg_count; i++) |
133 | 0 | pc_check_expr(pc, expr->as.method_call.args[i]); |
134 | 3 | return PHASE_UNSPECIFIED; |
135 | | |
136 | 51 | case EXPR_FIELD_ACCESS: { |
137 | 51 | AstPhase obj_phase = pc_check_expr(pc, expr->as.field_access.object); |
138 | 51 | return (obj_phase == PHASE_CRYSTAL) ? PHASE_CRYSTAL : obj_phase; |
139 | 42 | } |
140 | | |
141 | 12 | case EXPR_INDEX: |
142 | 12 | pc_check_expr(pc, expr->as.index.object); |
143 | 12 | pc_check_expr(pc, expr->as.index.index); |
144 | 12 | return PHASE_UNSPECIFIED; |
145 | | |
146 | 3 | case EXPR_ARRAY: |
147 | 6 | for (size_t i = 0; i < expr->as.array.count; i++) |
148 | 3 | pc_check_expr(pc, expr->as.array.elems[i]); |
149 | 3 | return PHASE_UNSPECIFIED; |
150 | | |
151 | 0 | case EXPR_SPREAD: |
152 | 0 | pc_check_expr(pc, expr->as.spread_expr); |
153 | 0 | return PHASE_UNSPECIFIED; |
154 | | |
155 | 0 | case EXPR_TUPLE: |
156 | 0 | for (size_t i = 0; i < expr->as.tuple.count; i++) |
157 | 0 | pc_check_expr(pc, expr->as.tuple.elems[i]); |
158 | 0 | return PHASE_CRYSTAL; |
159 | | |
160 | 9 | case EXPR_STRUCT_LIT: |
161 | 30 | for (size_t i = 0; i < expr->as.struct_lit.field_count; i++) |
162 | 21 | pc_check_expr(pc, expr->as.struct_lit.fields[i].value); |
163 | 9 | return PHASE_UNSPECIFIED; |
164 | | |
165 | 12 | case EXPR_FREEZE: { |
166 | 12 | AstPhase inner = pc_check_expr(pc, expr->as.freeze.expr); |
167 | 12 | if (expr->as.freeze.contract) |
168 | 0 | pc_check_expr(pc, expr->as.freeze.contract); |
169 | 12 | if (pc->mode == MODE_STRICT && inner == PHASE_CRYSTAL) |
170 | 0 | pc_error(pc, "strict mode: cannot freeze an already crystal value"); |
171 | 12 | return PHASE_CRYSTAL; |
172 | 42 | } |
173 | | |
174 | 6 | case EXPR_THAW: { |
175 | 6 | AstPhase inner = pc_check_expr(pc, expr->as.freeze_expr); |
176 | 6 | if (pc->mode == MODE_STRICT && inner == PHASE_FLUID) |
177 | 0 | pc_error(pc, "strict mode: cannot thaw an already fluid value"); |
178 | 6 | return PHASE_FLUID; |
179 | 42 | } |
180 | | |
181 | 3 | case EXPR_CLONE: |
182 | 3 | return pc_check_expr(pc, expr->as.freeze_expr); |
183 | | |
184 | 0 | case EXPR_ANNEAL: |
185 | 0 | pc_check_expr(pc, expr->as.anneal.expr); |
186 | 0 | pc_check_expr(pc, expr->as.anneal.closure); |
187 | 0 | return PHASE_CRYSTAL; |
188 | | |
189 | 0 | case EXPR_FORGE: |
190 | 0 | pc_push_scope(pc); |
191 | 0 | for (size_t i = 0; i < expr->as.block.count; i++) |
192 | 0 | pc_check_stmt(pc, expr->as.block.stmts[i]); |
193 | 0 | pc_pop_scope(pc); |
194 | 0 | return PHASE_CRYSTAL; |
195 | | |
196 | 0 | case EXPR_CRYSTALLIZE: |
197 | 0 | pc_check_expr(pc, expr->as.crystallize.expr); |
198 | 0 | pc_push_scope(pc); |
199 | 0 | for (size_t i = 0; i < expr->as.crystallize.body_count; i++) |
200 | 0 | pc_check_stmt(pc, expr->as.crystallize.body[i]); |
201 | 0 | pc_pop_scope(pc); |
202 | 0 | return PHASE_UNSPECIFIED; |
203 | | |
204 | 0 | case EXPR_SUBLIMATE: |
205 | 0 | return pc_check_expr(pc, expr->as.freeze_expr); |
206 | | |
207 | 0 | case EXPR_IF: |
208 | 0 | pc_check_expr(pc, expr->as.if_expr.cond); |
209 | 0 | pc_push_scope(pc); |
210 | 0 | for (size_t i = 0; i < expr->as.if_expr.then_count; i++) |
211 | 0 | pc_check_stmt(pc, expr->as.if_expr.then_stmts[i]); |
212 | 0 | pc_pop_scope(pc); |
213 | 0 | if (expr->as.if_expr.else_stmts) { |
214 | 0 | pc_push_scope(pc); |
215 | 0 | for (size_t i = 0; i < expr->as.if_expr.else_count; i++) |
216 | 0 | pc_check_stmt(pc, expr->as.if_expr.else_stmts[i]); |
217 | 0 | pc_pop_scope(pc); |
218 | 0 | } |
219 | 0 | return PHASE_UNSPECIFIED; |
220 | | |
221 | 0 | case EXPR_BLOCK: |
222 | 0 | pc_push_scope(pc); |
223 | 0 | for (size_t i = 0; i < expr->as.block.count; i++) |
224 | 0 | pc_check_stmt(pc, expr->as.block.stmts[i]); |
225 | 0 | pc_pop_scope(pc); |
226 | 0 | return PHASE_UNSPECIFIED; |
227 | | |
228 | 0 | case EXPR_CLOSURE: |
229 | 0 | if (expr->as.closure.default_values) { |
230 | 0 | for (size_t i = 0; i < expr->as.closure.param_count; i++) |
231 | 0 | if (expr->as.closure.default_values[i]) |
232 | 0 | pc_check_expr(pc, expr->as.closure.default_values[i]); |
233 | 0 | } |
234 | 0 | return PHASE_UNSPECIFIED; |
235 | | |
236 | 3 | case EXPR_RANGE: |
237 | 3 | pc_check_expr(pc, expr->as.range.start); |
238 | 3 | pc_check_expr(pc, expr->as.range.end); |
239 | 3 | return PHASE_UNSPECIFIED; |
240 | | |
241 | 15 | case EXPR_PRINT: |
242 | 30 | for (size_t i = 0; i < expr->as.print.arg_count; i++) |
243 | 15 | pc_check_expr(pc, expr->as.print.args[i]); |
244 | 15 | return PHASE_UNSPECIFIED; |
245 | | |
246 | 0 | case EXPR_TRY_CATCH: |
247 | 0 | pc_push_scope(pc); |
248 | 0 | for (size_t i = 0; i < expr->as.try_catch.try_count; i++) |
249 | 0 | pc_check_stmt(pc, expr->as.try_catch.try_stmts[i]); |
250 | 0 | pc_pop_scope(pc); |
251 | 0 | pc_push_scope(pc); |
252 | 0 | pc_define(pc, expr->as.try_catch.catch_var, PHASE_FLUID); |
253 | 0 | for (size_t i = 0; i < expr->as.try_catch.catch_count; i++) |
254 | 0 | pc_check_stmt(pc, expr->as.try_catch.catch_stmts[i]); |
255 | 0 | pc_pop_scope(pc); |
256 | 0 | return PHASE_UNSPECIFIED; |
257 | | |
258 | 0 | case EXPR_SCOPE: |
259 | 0 | pc_push_scope(pc); |
260 | 0 | for (size_t i = 0; i < expr->as.block.count; i++) |
261 | 0 | pc_check_stmt(pc, expr->as.block.stmts[i]); |
262 | 0 | pc_pop_scope(pc); |
263 | 0 | return PHASE_UNSPECIFIED; |
264 | | |
265 | 0 | case EXPR_INTERP_STRING: |
266 | 0 | for (size_t i = 0; i < expr->as.interp.count; i++) |
267 | 0 | pc_check_expr(pc, expr->as.interp.exprs[i]); |
268 | 0 | return PHASE_UNSPECIFIED; |
269 | | |
270 | 0 | case EXPR_SPAWN: |
271 | 0 | if (pc->mode == MODE_STRICT) { |
272 | 0 | pc_push_scope(pc); |
273 | 0 | for (size_t i = 0; i < expr->as.block.count; i++) |
274 | 0 | pc_check_spawn_stmt(pc, expr->as.block.stmts[i]); |
275 | 0 | pc_pop_scope(pc); |
276 | 0 | } else { |
277 | 0 | pc_push_scope(pc); |
278 | 0 | for (size_t i = 0; i < expr->as.block.count; i++) |
279 | 0 | pc_check_stmt(pc, expr->as.block.stmts[i]); |
280 | 0 | pc_pop_scope(pc); |
281 | 0 | } |
282 | 0 | return PHASE_UNSPECIFIED; |
283 | | |
284 | 0 | case EXPR_MATCH: |
285 | 0 | pc_check_expr(pc, expr->as.match_expr.scrutinee); |
286 | 0 | for (size_t i = 0; i < expr->as.match_expr.arm_count; i++) { |
287 | 0 | MatchArm *arm = &expr->as.match_expr.arms[i]; |
288 | 0 | if (arm->guard) pc_check_expr(pc, arm->guard); |
289 | 0 | for (size_t j = 0; j < arm->body_count; j++) |
290 | 0 | pc_check_stmt(pc, arm->body[j]); |
291 | 0 | } |
292 | 0 | return PHASE_UNSPECIFIED; |
293 | | |
294 | 0 | case EXPR_ENUM_VARIANT: |
295 | 0 | for (size_t i = 0; i < expr->as.enum_variant.arg_count; i++) |
296 | 0 | pc_check_expr(pc, expr->as.enum_variant.args[i]); |
297 | 0 | return PHASE_UNSPECIFIED; |
298 | | |
299 | 0 | case EXPR_TRY_PROPAGATE: |
300 | 0 | pc_check_expr(pc, expr->as.try_propagate_expr); |
301 | 0 | return PHASE_UNSPECIFIED; |
302 | | |
303 | 0 | case EXPR_SELECT: |
304 | 0 | for (size_t i = 0; i < expr->as.select_expr.arm_count; i++) { |
305 | 0 | SelectArm *arm = &expr->as.select_expr.arms[i]; |
306 | 0 | if (arm->channel_expr) pc_check_expr(pc, arm->channel_expr); |
307 | 0 | if (arm->timeout_expr) pc_check_expr(pc, arm->timeout_expr); |
308 | 0 | pc_push_scope(pc); |
309 | 0 | if (arm->binding_name) |
310 | 0 | pc_define(pc, arm->binding_name, PHASE_UNSPECIFIED); |
311 | 0 | for (size_t j = 0; j < arm->body_count; j++) |
312 | 0 | pc_check_stmt(pc, arm->body[j]); |
313 | 0 | pc_pop_scope(pc); |
314 | 0 | } |
315 | 0 | return PHASE_UNSPECIFIED; |
316 | 258 | } |
317 | 0 | return PHASE_UNSPECIFIED; |
318 | 258 | } |
319 | | |
320 | 66 | static void pc_check_stmt(PhaseChecker *pc, const Stmt *stmt) { |
321 | 66 | switch (stmt->tag) { |
322 | 27 | case STMT_BINDING: { |
323 | 27 | AstPhase value_phase = pc_check_expr(pc, stmt->as.binding.value); |
324 | 27 | if (pc->mode == MODE_STRICT) { |
325 | 27 | switch (stmt->as.binding.phase) { |
326 | 0 | case PHASE_UNSPECIFIED: |
327 | 0 | pc_error(pc, "strict mode: use 'flux' or 'fix' instead of 'let' for binding '%s'", |
328 | 0 | stmt->as.binding.name); |
329 | 0 | break; |
330 | 15 | case PHASE_FLUID: |
331 | 15 | if (value_phase == PHASE_CRYSTAL) |
332 | 0 | pc_error(pc, "cannot bind crystal value with flux for '%s'", |
333 | 0 | stmt->as.binding.name); |
334 | 15 | pc_define(pc, stmt->as.binding.name, PHASE_FLUID); |
335 | 15 | break; |
336 | 12 | case PHASE_CRYSTAL: |
337 | 12 | pc_define(pc, stmt->as.binding.name, PHASE_CRYSTAL); |
338 | 12 | break; |
339 | 27 | } |
340 | 27 | } else { |
341 | 0 | AstPhase eff = stmt->as.binding.phase; |
342 | 0 | if (eff == PHASE_UNSPECIFIED) eff = value_phase; |
343 | 0 | pc_define(pc, stmt->as.binding.name, eff); |
344 | 0 | } |
345 | 27 | break; |
346 | 27 | } |
347 | 27 | case STMT_ASSIGN: |
348 | 18 | if (pc->mode == MODE_STRICT && stmt->as.assign.target->tag == EXPR_IDENT) { |
349 | 3 | AstPhase p = pc_lookup(pc, stmt->as.assign.target->as.str_val); |
350 | 3 | if (p == PHASE_CRYSTAL) |
351 | 0 | pc_error(pc, "strict mode: cannot assign to crystal binding '%s'", |
352 | 0 | stmt->as.assign.target->as.str_val); |
353 | 3 | } |
354 | 18 | pc_check_expr(pc, stmt->as.assign.target); |
355 | 18 | pc_check_expr(pc, stmt->as.assign.value); |
356 | 18 | break; |
357 | 18 | case STMT_EXPR: |
358 | 18 | pc_check_expr(pc, stmt->as.expr); |
359 | 18 | break; |
360 | 0 | case STMT_RETURN: |
361 | 0 | if (stmt->as.return_expr) pc_check_expr(pc, stmt->as.return_expr); |
362 | 0 | break; |
363 | 3 | case STMT_FOR: |
364 | 3 | pc_check_expr(pc, stmt->as.for_loop.iter); |
365 | 3 | pc_push_scope(pc); |
366 | 3 | pc_define(pc, stmt->as.for_loop.var, PHASE_UNSPECIFIED); |
367 | 9 | for (size_t i = 0; i < stmt->as.for_loop.body_count; i++) |
368 | 6 | pc_check_stmt(pc, stmt->as.for_loop.body[i]); |
369 | 3 | pc_pop_scope(pc); |
370 | 3 | break; |
371 | 0 | case STMT_WHILE: |
372 | 0 | pc_check_expr(pc, stmt->as.while_loop.cond); |
373 | 0 | pc_push_scope(pc); |
374 | 0 | for (size_t i = 0; i < stmt->as.while_loop.body_count; i++) |
375 | 0 | pc_check_stmt(pc, stmt->as.while_loop.body[i]); |
376 | 0 | pc_pop_scope(pc); |
377 | 0 | break; |
378 | 0 | case STMT_LOOP: |
379 | 0 | pc_push_scope(pc); |
380 | 0 | for (size_t i = 0; i < stmt->as.loop.body_count; i++) |
381 | 0 | pc_check_stmt(pc, stmt->as.loop.body[i]); |
382 | 0 | pc_pop_scope(pc); |
383 | 0 | break; |
384 | 0 | case STMT_BREAK: |
385 | 0 | case STMT_CONTINUE: |
386 | 0 | break; |
387 | 0 | case STMT_DESTRUCTURE: { |
388 | 0 | pc_check_expr(pc, stmt->as.destructure.value); |
389 | 0 | AstPhase eff = stmt->as.destructure.phase; |
390 | 0 | if (pc->mode == MODE_STRICT && eff == PHASE_UNSPECIFIED) |
391 | 0 | pc_error(pc, "strict mode: destructure requires explicit phase (flux/fix)"); |
392 | 0 | for (size_t i = 0; i < stmt->as.destructure.name_count; i++) |
393 | 0 | pc_define(pc, stmt->as.destructure.names[i], eff); |
394 | 0 | if (stmt->as.destructure.rest_name) |
395 | 0 | pc_define(pc, stmt->as.destructure.rest_name, eff); |
396 | 0 | break; |
397 | 0 | } |
398 | 0 | case STMT_IMPORT: |
399 | | /* Import bindings are unphased */ |
400 | 0 | if (stmt->as.import.alias) |
401 | 0 | pc_define(pc, stmt->as.import.alias, PHASE_UNSPECIFIED); |
402 | 0 | if (stmt->as.import.selective_names) { |
403 | 0 | for (size_t i = 0; i < stmt->as.import.selective_count; i++) |
404 | 0 | pc_define(pc, stmt->as.import.selective_names[i], PHASE_UNSPECIFIED); |
405 | 0 | } |
406 | 0 | break; |
407 | 0 | case STMT_DEFER: |
408 | 0 | for (size_t i = 0; i < stmt->as.defer.body_count; i++) |
409 | 0 | pc_check_stmt(pc, stmt->as.defer.body[i]); |
410 | 0 | break; |
411 | 66 | } |
412 | 66 | } |
413 | | |
414 | 12 | static void pc_check_fn(PhaseChecker *pc, const FnDecl *f) { |
415 | 12 | pc_push_scope(pc); |
416 | 15 | for (size_t i = 0; i < f->param_count; i++) |
417 | 3 | pc_define(pc, f->params[i].name, f->params[i].ty.phase); |
418 | 72 | for (size_t i = 0; i < f->body_count; i++) |
419 | 60 | pc_check_stmt(pc, f->body[i]); |
420 | 12 | pc_pop_scope(pc); |
421 | 12 | } |
422 | | |
423 | | /* Spawn-specific checking */ |
424 | | |
425 | 0 | static void pc_check_spawn_expr(PhaseChecker *pc, const Expr *expr) { |
426 | 0 | switch (expr->tag) { |
427 | 0 | case EXPR_IDENT: |
428 | 0 | if (pc_lookup(pc, expr->as.str_val) == PHASE_FLUID) |
429 | 0 | pc_error(pc, "strict mode: cannot use fluid binding '%s' across thread boundary in spawn", |
430 | 0 | expr->as.str_val); |
431 | 0 | break; |
432 | 0 | case EXPR_BINOP: |
433 | 0 | pc_check_spawn_expr(pc, expr->as.binop.left); |
434 | 0 | pc_check_spawn_expr(pc, expr->as.binop.right); |
435 | 0 | break; |
436 | 0 | case EXPR_UNARYOP: |
437 | 0 | pc_check_spawn_expr(pc, expr->as.unaryop.operand); |
438 | 0 | break; |
439 | 0 | case EXPR_CALL: |
440 | 0 | pc_check_spawn_expr(pc, expr->as.call.func); |
441 | 0 | for (size_t i = 0; i < expr->as.call.arg_count; i++) |
442 | 0 | pc_check_spawn_expr(pc, expr->as.call.args[i]); |
443 | 0 | break; |
444 | 0 | case EXPR_METHOD_CALL: |
445 | 0 | pc_check_spawn_expr(pc, expr->as.method_call.object); |
446 | 0 | for (size_t i = 0; i < expr->as.method_call.arg_count; i++) |
447 | 0 | pc_check_spawn_expr(pc, expr->as.method_call.args[i]); |
448 | 0 | break; |
449 | 0 | case EXPR_FIELD_ACCESS: |
450 | 0 | pc_check_spawn_expr(pc, expr->as.field_access.object); |
451 | 0 | break; |
452 | 0 | case EXPR_INDEX: |
453 | 0 | pc_check_spawn_expr(pc, expr->as.index.object); |
454 | 0 | pc_check_spawn_expr(pc, expr->as.index.index); |
455 | 0 | break; |
456 | 0 | case EXPR_FREEZE: |
457 | 0 | pc_check_spawn_expr(pc, expr->as.freeze.expr); |
458 | 0 | if (expr->as.freeze.contract) |
459 | 0 | pc_check_spawn_expr(pc, expr->as.freeze.contract); |
460 | 0 | break; |
461 | 0 | case EXPR_THAW: case EXPR_CLONE: |
462 | 0 | pc_check_spawn_expr(pc, expr->as.freeze_expr); |
463 | 0 | break; |
464 | 0 | case EXPR_ANNEAL: |
465 | 0 | pc_check_spawn_expr(pc, expr->as.anneal.expr); |
466 | 0 | pc_check_spawn_expr(pc, expr->as.anneal.closure); |
467 | 0 | break; |
468 | 0 | case EXPR_PRINT: |
469 | 0 | for (size_t i = 0; i < expr->as.print.arg_count; i++) |
470 | 0 | pc_check_spawn_expr(pc, expr->as.print.args[i]); |
471 | 0 | break; |
472 | 0 | case EXPR_INTERP_STRING: |
473 | 0 | for (size_t i = 0; i < expr->as.interp.count; i++) |
474 | 0 | pc_check_spawn_expr(pc, expr->as.interp.exprs[i]); |
475 | 0 | break; |
476 | 0 | case EXPR_ARRAY: |
477 | 0 | for (size_t i = 0; i < expr->as.array.count; i++) |
478 | 0 | pc_check_spawn_expr(pc, expr->as.array.elems[i]); |
479 | 0 | break; |
480 | 0 | case EXPR_SPREAD: |
481 | 0 | pc_check_spawn_expr(pc, expr->as.spread_expr); |
482 | 0 | break; |
483 | 0 | case EXPR_TUPLE: |
484 | 0 | for (size_t i = 0; i < expr->as.tuple.count; i++) |
485 | 0 | pc_check_spawn_expr(pc, expr->as.tuple.elems[i]); |
486 | 0 | break; |
487 | 0 | case EXPR_STRUCT_LIT: |
488 | 0 | for (size_t i = 0; i < expr->as.struct_lit.field_count; i++) |
489 | 0 | pc_check_spawn_expr(pc, expr->as.struct_lit.fields[i].value); |
490 | 0 | break; |
491 | 0 | default: |
492 | 0 | pc_check_expr(pc, expr); |
493 | 0 | break; |
494 | 0 | } |
495 | 0 | } |
496 | | |
497 | 0 | static void pc_check_spawn_stmt(PhaseChecker *pc, const Stmt *stmt) { |
498 | 0 | switch (stmt->tag) { |
499 | 0 | case STMT_EXPR: |
500 | 0 | pc_check_spawn_expr(pc, stmt->as.expr); |
501 | 0 | break; |
502 | 0 | case STMT_BINDING: |
503 | 0 | pc_check_spawn_expr(pc, stmt->as.binding.value); |
504 | 0 | if (pc->mode == MODE_STRICT && stmt->as.binding.phase == PHASE_UNSPECIFIED) |
505 | 0 | pc_error(pc, "strict mode: use 'flux' or 'fix' instead of 'let' for binding '%s'", |
506 | 0 | stmt->as.binding.name); |
507 | 0 | pc_define(pc, stmt->as.binding.name, stmt->as.binding.phase); |
508 | 0 | break; |
509 | 0 | case STMT_ASSIGN: |
510 | 0 | pc_check_spawn_expr(pc, stmt->as.assign.target); |
511 | 0 | pc_check_spawn_expr(pc, stmt->as.assign.value); |
512 | 0 | break; |
513 | 0 | default: |
514 | 0 | pc_check_stmt(pc, stmt); |
515 | 0 | break; |
516 | 0 | } |
517 | 0 | } |
518 | | |
519 | | /* ── Phase-dispatch registration for phase checker ── */ |
520 | | |
521 | 0 | static bool pc_phase_signatures_match(const FnDecl *a, const FnDecl *b) { |
522 | 0 | if (a->param_count != b->param_count) return false; |
523 | 0 | for (size_t i = 0; i < a->param_count; i++) { |
524 | 0 | if (a->params[i].ty.phase != b->params[i].ty.phase) return false; |
525 | 0 | } |
526 | 0 | return true; |
527 | 0 | } |
528 | | |
529 | 12 | static void pc_register_fn(LatMap *fn_defs, FnDecl *new_fn) { |
530 | 12 | FnDecl **existing = lat_map_get(fn_defs, new_fn->name); |
531 | 12 | if (!existing) { |
532 | 12 | lat_map_set(fn_defs, new_fn->name, &new_fn); |
533 | 12 | return; |
534 | 12 | } |
535 | 0 | FnDecl *head = *existing; |
536 | 0 | if (pc_phase_signatures_match(head, new_fn)) { |
537 | 0 | new_fn->next_overload = head->next_overload; |
538 | 0 | lat_map_set(fn_defs, new_fn->name, &new_fn); |
539 | 0 | return; |
540 | 0 | } |
541 | 0 | for (FnDecl *prev = head; prev->next_overload; prev = prev->next_overload) { |
542 | 0 | if (pc_phase_signatures_match(prev->next_overload, new_fn)) { |
543 | 0 | new_fn->next_overload = prev->next_overload->next_overload; |
544 | 0 | prev->next_overload = new_fn; |
545 | 0 | return; |
546 | 0 | } |
547 | 0 | } |
548 | 0 | new_fn->next_overload = head; |
549 | 0 | lat_map_set(fn_defs, new_fn->name, &new_fn); |
550 | 0 | } |
551 | | |
552 | | /* ── Public API ── */ |
553 | | |
554 | 9 | LatVec phase_check(const Program *prog) { |
555 | 9 | PhaseChecker pc; |
556 | 9 | pc_init(&pc, prog->mode); |
557 | | |
558 | | /* Register structs and functions */ |
559 | 30 | for (size_t i = 0; i < prog->item_count; i++) { |
560 | 21 | if (prog->items[i].tag == ITEM_STRUCT) { |
561 | 9 | StructDecl *ptr = &prog->items[i].as.struct_decl; |
562 | 9 | lat_map_set(&pc.struct_defs, ptr->name, &ptr); |
563 | 12 | } else if (prog->items[i].tag == ITEM_FUNCTION) { |
564 | 12 | FnDecl *ptr = &prog->items[i].as.fn_decl; |
565 | 12 | pc_register_fn(&pc.fn_defs, ptr); |
566 | 12 | } |
567 | 21 | } |
568 | | |
569 | | /* Check all items */ |
570 | 30 | for (size_t i = 0; i < prog->item_count; i++) { |
571 | 21 | switch (prog->items[i].tag) { |
572 | 12 | case ITEM_FUNCTION: |
573 | 12 | pc_check_fn(&pc, &prog->items[i].as.fn_decl); |
574 | 12 | break; |
575 | 9 | case ITEM_STRUCT: |
576 | 9 | break; |
577 | 0 | case ITEM_STMT: |
578 | 0 | pc_check_stmt(&pc, prog->items[i].as.stmt); |
579 | 0 | break; |
580 | 0 | case ITEM_TEST: |
581 | 0 | for (size_t j = 0; j < prog->items[i].as.test_decl.body_count; j++) |
582 | 0 | pc_check_stmt(&pc, prog->items[i].as.test_decl.body[j]); |
583 | 0 | break; |
584 | 0 | case ITEM_ENUM: |
585 | 0 | break; |
586 | 0 | case ITEM_TRAIT: |
587 | 0 | break; |
588 | 0 | case ITEM_IMPL: |
589 | 0 | for (size_t j = 0; j < prog->items[i].as.impl_block.method_count; j++) |
590 | 0 | pc_check_fn(&pc, &prog->items[i].as.impl_block.methods[j]); |
591 | 0 | break; |
592 | 21 | } |
593 | 21 | } |
594 | | |
595 | 9 | LatVec errors = pc.errors; |
596 | 9 | pc.errors = lat_vec_new(sizeof(char *)); /* prevent double-free */ |
597 | 9 | pc_free(&pc); |
598 | 9 | return errors; |
599 | 9 | } |