v / vlib / v2 / gen / cleanc / stmt.v
1014 lines · 991 sloc · 32.42 KB · 3690f882f624c3d82b8c36d38434baf10f68ce4c
Raw
1// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4
5module cleanc
6
7import v2.ast
8import v2.types
9
10fn (mut g Gen) set_file_module(file ast.File) {
11 g.cur_file_name = file.name
12 g.cur_import_modules.clear()
13 for imp in file.imports {
14 mod_name := if imp.name.contains('.') { imp.name.all_after_last('.') } else { imp.name }
15 if imp.alias != '' {
16 g.cur_import_modules[imp.alias] = mod_name
17 }
18 if !imp.is_aliased && mod_name != '' {
19 g.cur_import_modules[mod_name] = mod_name
20 }
21 }
22 g.is_module_ident_cache.clear()
23 g.resolved_module_names.clear()
24 for stmt in file.stmts {
25 if stmt is ast.ModuleStmt {
26 g.cur_module = stmt.name.replace('.', '_')
27 return
28 }
29 }
30 g.cur_module = if file.mod != '' { file.mod.replace('.', '_') } else { 'main' }
31}
32
33fn (mut g Gen) set_file_cursor_module(fc ast.FileCursor) {
34 g.cur_file_name = fc.name()
35 g.cur_import_modules.clear()
36 imports := fc.imports()
37 for i in 0 .. imports.len() {
38 stmt := imports.at(i)
39 if stmt.kind() != .stmt_import {
40 continue
41 }
42 imp := stmt.import_stmt()
43 mod_name := if imp.name.contains('.') { imp.name.all_after_last('.') } else { imp.name }
44 if imp.alias != '' {
45 g.cur_import_modules[imp.alias] = mod_name
46 }
47 if !imp.is_aliased && mod_name != '' {
48 g.cur_import_modules[mod_name] = mod_name
49 }
50 }
51 g.cur_module = flat_file_module_name(fc)
52 g.is_module_ident_cache.clear()
53 g.resolved_module_names.clear()
54}
55
56fn (mut g Gen) restore_file_module_context(file_name string, module_name string, import_modules map[string]string) {
57 g.cur_file_name = file_name
58 g.cur_module = module_name
59 g.cur_import_modules = import_modules.clone()
60 g.is_module_ident_cache.clear()
61 g.resolved_module_names.clear()
62}
63
64fn (mut g Gen) gen_stmts(stmts []ast.Stmt) {
65 saved_file_name := g.cur_file_name
66 saved_module := g.cur_module
67 mut saved_import_modules := g.cur_import_modules.clone()
68 for i in 0 .. stmts.len {
69 g.gen_stmt(stmts[i])
70 // A ModuleStmt (or a spliced generated-fn body) can switch the module
71 // context mid-list; restore the source context so the following
72 // statements resolve against the original module. The cheap dirty
73 // check keeps the hot path free of per-statement map clones.
74 if g.cur_module != saved_module || g.cur_file_name != saved_file_name
75 || g.cur_import_modules.len != saved_import_modules.len {
76 g.cur_file_name = saved_file_name
77 g.cur_module = saved_module
78 g.cur_import_modules = saved_import_modules.clone()
79 g.is_module_ident_cache.clear()
80 g.resolved_module_names.clear()
81 }
82 }
83 g.cur_file_name = saved_file_name
84 g.cur_module = saved_module
85 g.cur_import_modules = saved_import_modules.move()
86 g.is_module_ident_cache.clear()
87 g.resolved_module_names.clear()
88}
89
90fn (mut g Gen) gen_scoped_stmts(stmts []ast.Stmt) {
91 mut saved_runtime_local_types := g.runtime_local_types.clone()
92 mut saved_runtime_decl_types := g.runtime_decl_types.clone()
93 mut saved_not_local_var_cache := g.not_local_var_cache.clone()
94 g.gen_stmts(stmts)
95 g.runtime_local_types = saved_runtime_local_types.move()
96 g.runtime_decl_types = saved_runtime_decl_types.move()
97 g.not_local_var_cache = saved_not_local_var_cache.move()
98}
99
100fn (mut g Gen) gen_scoped_expr_stmts(expr ast.Expr) {
101 mut saved_runtime_local_types := g.runtime_local_types.clone()
102 mut saved_runtime_decl_types := g.runtime_decl_types.clone()
103 mut saved_not_local_var_cache := g.not_local_var_cache.clone()
104 g.gen_stmts_from_expr(expr)
105 g.runtime_local_types = saved_runtime_local_types.move()
106 g.runtime_decl_types = saved_runtime_decl_types.move()
107 g.not_local_var_cache = saved_not_local_var_cache.move()
108}
109
110fn tuple_field_types_need_stmt_expr(field_types []string) bool {
111 for field_type in field_types {
112 if field_type.starts_with('Array_fixed_') && !field_type.ends_with('*') {
113 return true
114 }
115 }
116 return false
117}
118
119fn (mut g Gen) gen_tuple_field_expr_value(field_type string, expr ast.Expr) {
120 if g.is_interface_type(field_type) {
121 expr_type := g.get_expr_type(expr).trim_right('*')
122 if expr_type != '' && expr_type != field_type && !g.is_interface_type(expr_type) {
123 if g.gen_interface_cast(field_type, expr) {
124 return
125 }
126 }
127 }
128 g.expr(expr)
129}
130
131fn (mut g Gen) gen_tuple_field_stmt_expr_assign(tuple_name string, idx int, field_type string, expr ast.Expr) {
132 field_name := '${tuple_name}.arg${idx}'
133 if field_type.starts_with('Array_fixed_') && !field_type.ends_with('*') {
134 if expr is ast.CallExpr {
135 if call_ret := g.get_call_return_type(expr.lhs, expr.args) {
136 if call_ret == field_type {
137 wrapper_type := g.c_fn_return_type_from_v(field_type)
138 tmp_name := '_tuple_arr_${g.tmp_counter}'
139 g.tmp_counter++
140 g.sb.write_string('{ ${wrapper_type} ${tmp_name} = ')
141 g.expr(expr)
142 g.sb.write_string('; memcpy(${field_name}, ${tmp_name}.ret_arr, sizeof(${field_type})); } ')
143 return
144 }
145 }
146 }
147 if expr is ast.IfExpr {
148 g.gen_decl_if_expr(field_name, field_type, &expr)
149 return
150 }
151 g.sb.write_string('memcpy(${field_name}, ')
152 if expr is ast.ArrayInitExpr {
153 g.sb.write_string('((${field_type})')
154 g.expr(expr)
155 g.sb.write_string(')')
156 } else {
157 g.expr(expr)
158 }
159 g.sb.write_string(', sizeof(${field_type})); ')
160 return
161 }
162 g.sb.write_string('${field_name} = ')
163 g.gen_tuple_field_expr_value(field_type, expr)
164 g.sb.write_string('; ')
165}
166
167fn (mut g Gen) gen_tuple_value_stmt_expr(tuple_type string, tuple_exprs []ast.Expr, field_types []string) {
168 tmp_name := '_tuple_ret_${g.tmp_counter}'
169 g.tmp_counter++
170 g.sb.write_string('({ ${tuple_type} ${tmp_name} = (${tuple_type}){0}; ')
171 for i, field_type in field_types {
172 if i < tuple_exprs.len {
173 g.gen_tuple_field_stmt_expr_assign(tmp_name, i, field_type, tuple_exprs[i])
174 } else if field_type.starts_with('Array_fixed_') && !field_type.ends_with('*') {
175 g.sb.write_string('memset(${tmp_name}.arg${i}, 0, sizeof(${field_type})); ')
176 } else {
177 g.sb.write_string('${tmp_name}.arg${i} = ${zero_value_for_type(field_type)}; ')
178 }
179 }
180 g.sb.write_string('${tmp_name}; })')
181}
182
183fn (mut g Gen) gen_stmt(node ast.Stmt) {
184 if !stmt_has_valid_data(node) {
185 return
186 }
187 match node {
188 ast.FnDecl {
189 g.gen_fn_decl(node)
190 }
191 ast.AssignStmt {
192 g.gen_assign_stmt(node)
193 }
194 ast.ExprStmt {
195 if node.expr is ast.IfExpr {
196 g.write_indent()
197 if_expr := node.expr as ast.IfExpr
198 g.gen_if_expr_stmt(&if_expr)
199 return
200 }
201 if node.expr is ast.UnsafeExpr {
202 unsafe_expr := node.expr as ast.UnsafeExpr
203 if unsafe_expr.stmts.len > 1 {
204 g.write_indent()
205 g.sb.writeln('{')
206 g.indent++
207 }
208 for stmt in unsafe_expr.stmts {
209 g.gen_stmt(stmt)
210 }
211 if unsafe_expr.stmts.len > 1 {
212 g.indent--
213 g.write_indent()
214 g.sb.writeln('}')
215 }
216 return
217 }
218 if node.expr is ast.ComptimeExpr {
219 comptime_expr := node.expr as ast.ComptimeExpr
220 if comptime_expr.expr is ast.IfExpr {
221 g.gen_comptime_if_stmt(comptime_expr.expr as ast.IfExpr)
222 return
223 }
224 }
225 if !expr_has_valid_data(node.expr) {
226 return
227 }
228 g.write_indent()
229 g.expr(node.expr)
230 g.sb.writeln(';')
231 }
232 ast.ReturnStmt {
233 g.emit_scheduled_drops_at_return()
234 g.write_indent()
235 if g.is_tuple_alias(g.cur_fn_ret_type) {
236 if node.exprs.len == 1 {
237 expr := node.exprs[0]
238 if g.get_expr_type(expr) == g.cur_fn_ret_type {
239 g.sb.write_string('return ')
240 g.expr(expr)
241 g.sb.writeln(';')
242 return
243 }
244 }
245 mut tuple_exprs := shallow_copy_exprs(node.exprs)
246 if node.exprs.len == 1 && node.exprs[0] is ast.Tuple {
247 tuple_expr := node.exprs[0] as ast.Tuple
248 tuple_exprs = shallow_copy_exprs(tuple_expr.exprs)
249 }
250 field_types := g.tuple_aliases[g.cur_fn_ret_type] or { []string{} }
251 if tuple_field_types_need_stmt_expr(field_types) {
252 g.sb.write_string('return ')
253 g.gen_tuple_value_stmt_expr(g.cur_fn_ret_type, tuple_exprs, field_types)
254 g.sb.writeln(';')
255 return
256 }
257 g.sb.write_string('return ((${g.cur_fn_ret_type}){')
258 for i, field_type in field_types {
259 if i > 0 {
260 g.sb.write_string(', ')
261 }
262 g.sb.write_string('.arg${i} = ')
263 if i < tuple_exprs.len {
264 g.gen_tuple_field_expr_value(field_type, tuple_exprs[i])
265 } else {
266 g.sb.write_string(zero_value_for_type(field_type))
267 }
268 }
269 g.sb.writeln('});')
270 return
271 }
272 if node.exprs.len == 1 && node.exprs[0] is ast.IfExpr {
273 if_expr := node.exprs[0] as ast.IfExpr
274 g.gen_return_if_expr(if_expr, false)
275 return
276 }
277 if g.cur_fn_ret_type.starts_with('Array_fixed_') {
278 if node.exprs.len == 0 {
279 g.sb.writeln('return (${g.cur_fn_c_ret_type}){0};')
280 return
281 }
282 expr := node.exprs[0]
283 g.sb.write_string('return ({ ${g.cur_fn_c_ret_type} _ret = (${g.cur_fn_c_ret_type}){0}; ')
284 if expr is ast.CallExpr {
285 if call_ret := g.get_call_return_type(expr.lhs, expr.args) {
286 if call_ret == g.cur_fn_ret_type {
287 g.sb.write_string('${g.cur_fn_c_ret_type} _tmp = ')
288 g.expr(expr)
289 g.sb.writeln('; memcpy(_ret.ret_arr, _tmp.ret_arr, sizeof(${g.cur_fn_ret_type})); _ret; });')
290 return
291 }
292 }
293 }
294 if expr is ast.Ident || expr is ast.SelectorExpr || expr is ast.IndexExpr {
295 g.sb.write_string('memcpy(_ret.ret_arr, ')
296 g.expr(expr)
297 g.sb.writeln(', sizeof(${g.cur_fn_ret_type})); _ret; });')
298 return
299 }
300 g.sb.write_string('${g.cur_fn_ret_type} _arr = ')
301 g.expr(expr)
302 g.sb.writeln('; memcpy(_ret.ret_arr, _arr, sizeof(${g.cur_fn_ret_type})); _ret; });')
303 return
304 }
305 if g.cur_fn_ret_type.starts_with('_option_') {
306 if node.exprs.len == 0 {
307 g.sb.writeln('return (${g.cur_fn_ret_type}){ .state = 2 };')
308 return
309 }
310 expr := node.exprs[0]
311 if expr is ast.BasicLiteral && expr.value == '0' {
312 g.sb.writeln('return (${g.cur_fn_ret_type}){ .state = 2 };')
313 return
314 }
315 if is_none_expr(expr) || expr is ast.Type {
316 g.sb.writeln('return (${g.cur_fn_ret_type}){ .state = 2 };')
317 return
318 }
319 mut expr_type := g.get_expr_type(expr)
320 if (expr_type == '' || expr_type == 'int') && expr is ast.Ident {
321 expr_type = g.get_local_var_c_type(expr.name) or { expr_type }
322 }
323 if (expr_type == '' || expr_type == 'int') && expr is ast.CallExpr {
324 expr_type = g.get_call_return_type(expr.lhs, expr.args) or { expr_type }
325 }
326 value_type := option_value_type(g.cur_fn_ret_type)
327 if expr is ast.Ident {
328 err_ident := expr as ast.Ident
329 if err_ident.name == 'err' {
330 err_type := g.get_local_var_c_type(err_ident.name) or { 'IError' }
331 if err_type in ['IError', 'builtin__IError'] {
332 g.sb.write_string('return (${g.cur_fn_ret_type}){ .is_error=true, .err=')
333 g.expr(expr)
334 g.sb.writeln(' };')
335 return
336 }
337 }
338 }
339 if expr_type == 'IError' {
340 if value_type != '' && value_type != 'void' {
341 if expr is ast.CastExpr
342 && g.expr_type_to_c(expr.typ) in ['IError', 'builtin__IError'] {
343 concrete_type := g.concrete_type_for_interface_value('IError',
344 expr.expr)
345 if concrete_type == value_type {
346 g.sb.write_string('return ({ ${g.cur_fn_ret_type} _opt = (${g.cur_fn_ret_type}){ .state = 2 }; ${value_type} _val = ')
347 g.expr(expr.expr)
348 g.sb.writeln('; _option_ok(&_val, (_option*)&_opt, sizeof(_val)); _opt; });')
349 return
350 }
351 } else if expr is ast.CallOrCastExpr
352 && g.expr_type_to_c(expr.lhs) in ['IError', 'builtin__IError'] {
353 concrete_type := g.concrete_type_for_interface_value('IError',
354 expr.expr)
355 if concrete_type == value_type {
356 g.sb.write_string('return ({ ${g.cur_fn_ret_type} _opt = (${g.cur_fn_ret_type}){ .state = 2 }; ${value_type} _val = ')
357 g.expr(expr.expr)
358 g.sb.writeln('; _option_ok(&_val, (_option*)&_opt, sizeof(_val)); _opt; });')
359 return
360 }
361 }
362 }
363 g.sb.write_string('return (${g.cur_fn_ret_type}){ .is_error=true, .err=')
364 g.expr(expr)
365 g.sb.writeln(' };')
366 return
367 }
368 if expr_type == g.cur_fn_ret_type {
369 g.sb.write_string('return ')
370 g.expr(expr)
371 g.sb.writeln(';')
372 return
373 }
374 if value_type == '' || value_type == 'void' {
375 g.sb.writeln('return (${g.cur_fn_ret_type}){ .state = 2 };')
376 return
377 }
378 if value_type in g.tuple_aliases {
379 if node.exprs.len == 1 && expr_type == value_type && node.exprs[0] !is ast.Tuple {
380 g.sb.write_string('return ({ ${g.cur_fn_ret_type} _opt = (${g.cur_fn_ret_type}){ .state = 2 }; ${value_type} _val = ')
381 g.expr(expr)
382 g.sb.writeln('; _option_ok(&_val, (_option*)&_opt, sizeof(_val)); _opt; });')
383 return
384 }
385 field_types := g.tuple_aliases[value_type]
386 mut tuple_exprs := shallow_copy_exprs(node.exprs)
387 if node.exprs.len == 1 && node.exprs[0] is ast.Tuple {
388 tuple_expr := node.exprs[0] as ast.Tuple
389 tuple_exprs = shallow_copy_exprs(tuple_expr.exprs)
390 }
391 if tuple_field_types_need_stmt_expr(field_types) {
392 g.sb.write_string('return ({ ${g.cur_fn_ret_type} _opt = (${g.cur_fn_ret_type}){ .state = 2 }; ${value_type} _val = ')
393 g.gen_tuple_value_stmt_expr(value_type, tuple_exprs, field_types)
394 g.sb.writeln('; _option_ok(&_val, (_option*)&_opt, sizeof(_val)); _opt; });')
395 return
396 }
397 g.sb.write_string('return ({ ${g.cur_fn_ret_type} _opt = (${g.cur_fn_ret_type}){ .state = 2 }; ${value_type} _val = (${value_type}){')
398 for i, field_type in field_types {
399 if i > 0 {
400 g.sb.write_string(', ')
401 }
402 g.sb.write_string('.arg${i} = ')
403 if i < tuple_exprs.len {
404 g.expr(tuple_exprs[i])
405 } else {
406 g.sb.write_string(zero_value_for_type(field_type))
407 }
408 }
409 g.sb.writeln('}; _option_ok(&_val, (_option*)&_opt, sizeof(_val)); _opt; });')
410 return
411 }
412 g.sb.write_string('return ({ ${g.cur_fn_ret_type} _opt = (${g.cur_fn_ret_type}){ .state = 2 }; ${value_type} _val = ')
413 if value_type in g.sum_type_variants {
414 g.gen_type_cast_expr(value_type, expr)
415 } else if g.gen_auto_deref_value_param_arg(value_type, expr) {
416 } else {
417 g.expr(expr)
418 }
419 g.sb.writeln('; _option_ok(&_val, (_option*)&_opt, sizeof(_val)); _opt; });')
420 return
421 }
422 if g.cur_fn_ret_type.starts_with('_result_') {
423 if node.exprs.len == 0 {
424 g.sb.writeln('return (${g.cur_fn_ret_type}){ .is_error=false };')
425 return
426 }
427 expr := node.exprs[0]
428 if g.is_error_call_expr(expr) {
429 g.sb.write_string('return (${g.cur_fn_ret_type}){ .is_error=true, .err=')
430 g.expr(expr)
431 g.sb.writeln(' };')
432 return
433 }
434 // `return err` propagates the error from the or-block. A user local
435 // named `err` can still be a concrete error value and must be wrapped.
436 if expr is ast.Ident {
437 err_ident := expr as ast.Ident
438 if err_ident.name == 'err' {
439 err_type := g.get_local_var_c_type(err_ident.name) or { 'IError' }
440 if err_type in ['IError', 'builtin__IError'] {
441 g.sb.write_string('return (${g.cur_fn_ret_type}){ .is_error=true, .err=')
442 g.expr(expr)
443 g.sb.writeln(' };')
444 return
445 }
446 }
447 }
448 value_type := g.result_value_c_type(g.cur_fn_ret_type)
449 if value_type in g.tuple_aliases && node.exprs.len > 1 {
450 field_types := g.tuple_aliases[value_type]
451 if tuple_field_types_need_stmt_expr(field_types) {
452 g.sb.write_string('return ({ ${g.cur_fn_ret_type} _res = (${g.cur_fn_ret_type}){0}; ${value_type} _val = ')
453 g.gen_tuple_value_stmt_expr(value_type, node.exprs, field_types)
454 g.sb.writeln('; _result_ok(&_val, (_result*)&_res, sizeof(_val)); _res; });')
455 return
456 }
457 g.sb.write_string('return ({ ${g.cur_fn_ret_type} _res = (${g.cur_fn_ret_type}){0}; ${value_type} _val = (${value_type}){')
458 for i, field_type in field_types {
459 if i > 0 {
460 g.sb.write_string(', ')
461 }
462 g.sb.write_string('.arg${i} = ')
463 if i < node.exprs.len {
464 g.expr(node.exprs[i])
465 } else {
466 g.sb.write_string(zero_value_for_type(field_type))
467 }
468 }
469 g.sb.writeln('}; _result_ok(&_val, (_result*)&_res, sizeof(_val)); _res; });')
470 return
471 }
472 // `return T(x)` in `!T` functions can be an unlowered propagate path where `x` is already `_result_T`.
473 // In that case return `x` directly, instead of casting to `T` and re-wrapping.
474 if value_type != '' && expr is ast.CastExpr
475 && g.expr_type_to_c(expr.typ) == value_type {
476 inner_type := g.get_expr_type(expr.expr)
477 if inner_type == g.cur_fn_ret_type {
478 g.sb.write_string('return ')
479 g.expr(expr.expr)
480 g.sb.writeln(';')
481 return
482 }
483 }
484 mut expr_type := g.get_expr_type(expr)
485 if (expr_type == '' || expr_type == 'int') && expr is ast.Ident {
486 expr_type = g.get_local_var_c_type(expr.name) or { expr_type }
487 }
488 if expr_type == g.cur_fn_ret_type {
489 g.sb.write_string('return ')
490 g.expr(expr)
491 g.sb.writeln(';')
492 return
493 }
494 if is_ierror_c_type(expr_type) {
495 g.sb.write_string('return (${g.cur_fn_ret_type}){ .is_error=true, .err=')
496 g.expr(expr)
497 g.sb.writeln(' };')
498 return
499 }
500 if g.concrete_ierror_base_for_c_type(expr_type) != '' {
501 g.sb.write_string('return (${g.cur_fn_ret_type}){ .is_error=true, .err=')
502 g.gen_ierror_from_concrete_expr(expr, expr_type)
503 g.sb.writeln(' };')
504 return
505 }
506 // For CallExpr in result-returning function, check if the called
507 // function also returns the same result type (passthrough).
508 if expr is ast.CallExpr {
509 if call_ret := g.get_call_return_type(expr.lhs, expr.args) {
510 if call_ret == g.cur_fn_ret_type {
511 g.sb.write_string('return ')
512 g.expr(expr)
513 g.sb.writeln(';')
514 return
515 }
516 }
517 }
518 if value_type == '' || value_type == 'void' {
519 g.sb.writeln('return (${g.cur_fn_ret_type}){ .is_error=false };')
520 return
521 }
522 if value_type.starts_with('Array_fixed_') {
523 g.sb.write_string('return ({ ${g.cur_fn_ret_type} _res = (${g.cur_fn_ret_type}){0}; ${value_type} _val = {0}; ')
524 is_zero_fixed_array := expr is ast.ArrayInitExpr && expr.exprs.len == 0
525 && expr.init is ast.EmptyExpr
526 if !is_zero_fixed_array {
527 g.sb.write_string('memcpy(_val, ')
528 g.expr(expr)
529 g.sb.write_string(', sizeof(_val)); ')
530 }
531 g.sb.writeln('_result_ok(&_val, (_result*)&_res, sizeof(_val)); _res; });')
532 return
533 }
534 g.sb.write_string('return ({ ${g.cur_fn_ret_type} _res = (${g.cur_fn_ret_type}){0}; ${value_type} _val = ')
535 if value_type in g.sum_type_variants {
536 g.gen_type_cast_expr(value_type, expr)
537 } else if g.is_interface_type(value_type) {
538 // Mut params are pointers — dereference when returning as value
539 if expr is ast.Ident && expr.name in g.cur_fn_mut_params {
540 g.sb.write_string('(*')
541 g.expr(expr)
542 g.sb.write_string(')')
543 } else if !g.gen_interface_cast(value_type, expr) {
544 g.expr(expr)
545 }
546 } else {
547 // Dereference mut parameters when returning by value in result-wrapping
548 // (mut params are pointers, but _val expects a value)
549 if !g.gen_auto_deref_value_param_arg(value_type, expr) {
550 g.expr(expr)
551 }
552 }
553 g.sb.writeln('; _result_ok(&_val, (_result*)&_res, sizeof(_val)); _res; });')
554 return
555 }
556 g.sb.write_string('return')
557 if node.exprs.len > 0 {
558 g.sb.write_string(' ')
559 expr := node.exprs[0]
560 if g.gen_return_mut_param_value(expr) {
561 } else if g.cur_fn_ret_type in g.sum_type_variants {
562 g.gen_type_cast_expr(g.cur_fn_ret_type, expr)
563 } else if g.is_interface_type(g.cur_fn_ret_type) {
564 if g.get_expr_type(expr) == g.cur_fn_ret_type {
565 g.expr(expr)
566 } else {
567 g.gen_type_cast_expr(g.cur_fn_ret_type, expr)
568 }
569 } else if g.cur_fn_ret_type.ends_with('*') && expr is ast.ParenExpr
570 && expr.expr is ast.PrefixExpr && (expr.expr as ast.PrefixExpr).op == .mul {
571 // Return type is a pointer but the expression dereferences a pointer
572 // (e.g., interface smartcast: *(T*)(obj._object)). Strip the deref
573 // to return the pointer directly.
574 deref := expr.expr as ast.PrefixExpr
575 g.expr(deref.expr)
576 } else if vector_elem_type_for_name(g.cur_fn_ret_type) != '' && expr is ast.InitExpr
577 && g.gen_simd_vector_init_expr(g.cur_fn_ret_type, expr.fields) {
578 } else if g.gen_auto_deref_value_param_arg(g.cur_fn_ret_type, expr) {
579 } else {
580 g.expr(expr)
581 }
582 } else if g.cur_fn_name == 'main' {
583 g.sb.write_string(' 0')
584 }
585 g.sb.writeln(';')
586 }
587 ast.ForStmt {
588 g.gen_for_stmt(node)
589 }
590 ast.FlowControlStmt {
591 g.write_indent()
592 if node.op == .key_break {
593 g.sb.writeln('break;')
594 } else if node.op == .key_continue {
595 if g.comptime_continue_label != '' {
596 g.sb.writeln('goto ${g.comptime_continue_label};')
597 } else {
598 g.sb.writeln('continue;')
599 }
600 } else if node.op == .key_goto {
601 g.sb.writeln('goto ${node.label};')
602 }
603 }
604 ast.ModuleStmt {
605 g.cur_module = node.name.replace('.', '_')
606 }
607 ast.ImportStmt {}
608 ast.ConstDecl {
609 g.gen_const_decl(node)
610 }
611 ast.StructDecl {
612 g.gen_struct_decl(node)
613 }
614 ast.EnumDecl {
615 g.gen_enum_decl(node)
616 }
617 ast.TypeDecl {
618 if node.variants.len > 0 {
619 g.gen_sum_type_decl(node)
620 } else if node.base_type !is ast.EmptyExpr {
621 g.gen_type_alias(node)
622 }
623 }
624 ast.InterfaceDecl {
625 g.gen_interface_decl(node)
626 }
627 ast.GlobalDecl {
628 g.gen_global_decl(node)
629 }
630 ast.Directive {
631 // C directives are collected and emitted in the preamble.
632 _ = node
633 }
634 ast.ForInStmt {
635 panic('bug in v2 compiler: ForInStmt should have been lowered in v2.transformer')
636 }
637 ast.DeferStmt {
638 panic('bug in v2 compiler: DeferStmt should have been lowered in v2.transformer (${g.cur_file_name}:${g.cur_fn_name})')
639 }
640 ast.AssertStmt {
641 panic('bug in v2 compiler: AssertStmt should have been lowered in v2.transformer')
642 }
643 ast.ComptimeStmt {
644 g.gen_comptime_stmt(node)
645 }
646 ast.BlockStmt {
647 for bs in node.stmts {
648 g.gen_stmt(bs)
649 }
650 }
651 ast.LabelStmt {
652 g.write_indent()
653 g.sb.writeln('${node.name}:;')
654 if node.stmt !is ast.EmptyStmt {
655 g.gen_stmt(node.stmt)
656 }
657 }
658 ast.AsmStmt {
659 g.write_indent()
660 g.sb.writeln('/* [TODO] AsmStmt */')
661 }
662 []ast.Attribute {}
663 ast.EmptyStmt {}
664 // else {}
665 }
666}
667
668fn (mut g Gen) gen_comptime_stmt(node ast.ComptimeStmt) {
669 inner := node.stmt
670 if inner is ast.ForStmt {
671 g.gen_comptime_for(inner)
672 return
673 }
674 if inner is ast.ExprStmt {
675 if inner.expr is ast.ComptimeExpr {
676 if inner.expr.expr is ast.IfExpr {
677 g.gen_comptime_if_stmt(inner.expr.expr)
678 return
679 }
680 }
681 g.write_indent()
682 g.expr(inner.expr)
683 g.sb.writeln(';')
684 return
685 }
686 g.write_indent()
687 g.sb.writeln('/* [TODO] ComptimeStmt */')
688}
689
690fn (mut g Gen) gen_comptime_if_stmt(node ast.IfExpr) {
691 result := g.eval_comptime_cond(node.cond)
692 if result {
693 g.gen_stmts(node.stmts)
694 return
695 }
696 if node.else_expr is ast.IfExpr {
697 else_if := node.else_expr as ast.IfExpr
698 if else_if.cond is ast.EmptyExpr {
699 g.gen_stmts(else_if.stmts)
700 } else {
701 g.gen_comptime_if_stmt(else_if)
702 }
703 }
704}
705
706fn (mut g Gen) gen_comptime_for(node ast.ForStmt) {
707 // Extract ForInStmt from the ForStmt's init field
708 if node.init !is ast.ForInStmt {
709 g.write_indent()
710 g.sb.writeln('/* [TODO] ComptimeFor non-for-in */')
711 return
712 }
713 for_in := node.init as ast.ForInStmt
714 // The expression should be T.fields (a SelectorExpr)
715 if for_in.expr !is ast.SelectorExpr {
716 g.write_indent()
717 g.sb.writeln('/* [TODO] ComptimeFor non-selector */')
718 return
719 }
720 sel := for_in.expr as ast.SelectorExpr
721 kind := sel.rhs.name
722 if kind !in ['fields', 'methods'] {
723 g.write_indent()
724 g.sb.writeln('/* [TODO] ComptimeFor ${kind} */')
725 return
726 }
727 type_name := sel.lhs.name()
728 concrete := g.active_generic_types[type_name] or {
729 c_name := g.expr_type_to_c(sel.lhs).trim_space()
730 g.concrete_type_from_c_name(c_name) or {
731 g.write_indent()
732 g.sb.writeln('/* [TODO] ComptimeFor unknown type ${type_name} */')
733 return
734 }
735 }
736 if concrete !is types.Struct {
737 g.write_indent()
738 g.sb.writeln('/* ComptimeFor: ${type_name} is not a struct */')
739 return
740 }
741 struct_type := g.comptime_for_struct_type(concrete, concrete as types.Struct)
742 if kind == 'methods' {
743 g.gen_comptime_for_methods(node, for_in, type_name, struct_type)
744 return
745 }
746 field_var := for_in.value.name()
747 // Save comptime state
748 prev_field_var := g.comptime_field_var
749 prev_field_name := g.comptime_field_name
750 prev_field_type := g.comptime_field_type
751 prev_field_raw_type := g.comptime_field_raw_type
752 prev_field_attrs := g.comptime_field_attrs
753 prev_field_idx := g.comptime_field_idx
754 prev_field_is_embed := g.comptime_field_is_embed
755 prev_continue_label := g.comptime_continue_label
756 g.comptime_field_var = field_var
757 g.write_indent()
758 g.sb.writeln('{ /* comptime for ${field_var} in ${type_name}.fields */')
759 g.indent++
760 for i, field in struct_type.fields {
761 g.comptime_field_name = field.name
762 g.comptime_field_type = g.types_type_to_c(field.typ)
763 g.comptime_field_raw_type = field.typ
764 g.comptime_field_attrs = g.comptime_field_attribute_strings(struct_type.name, field)
765 g.comptime_field_idx = i
766 g.comptime_field_is_embed = g.comptime_field_is_embedded(struct_type, field)
767 g.tmp_counter++
768 g.comptime_continue_label = '__v_ctf_continue_${g.tmp_counter}_${i}'
769 g.write_indent()
770 g.sb.writeln('{ /* field ${i}: ${field.name} */')
771 g.indent++
772 // Use ForStmt.stmts for the loop body
773 g.gen_stmts(node.stmts)
774 g.write_indent()
775 g.sb.writeln('${g.comptime_continue_label}:;')
776 g.indent--
777 g.write_indent()
778 g.sb.writeln('}')
779 }
780 g.indent--
781 g.write_indent()
782 g.sb.writeln('}')
783 // Restore comptime state
784 g.comptime_field_var = prev_field_var
785 g.comptime_field_name = prev_field_name
786 g.comptime_field_type = prev_field_type
787 g.comptime_field_raw_type = prev_field_raw_type
788 g.comptime_field_attrs = prev_field_attrs
789 g.comptime_field_idx = prev_field_idx
790 g.comptime_field_is_embed = prev_field_is_embed
791 g.comptime_continue_label = prev_continue_label
792}
793
794fn (g &Gen) comptime_field_is_embedded(struct_type types.Struct, field types.Field) bool {
795 for embedded in struct_type.embedded {
796 embedded_name := embedded.name.all_after_last('__')
797 if field.name == embedded.name || field.name == embedded_name {
798 return true
799 }
800 }
801 return false
802}
803
804fn (mut g Gen) comptime_for_struct_type(concrete types.Type, fallback types.Struct) types.Struct {
805 struct_c_name := g.types_type_to_c(concrete).trim_space().trim_right('*')
806 if struct_c_name == '' {
807 return fallback
808 }
809 full_struct_type := g.lookup_struct_type_by_c_name(struct_c_name)
810 if full_struct_type.fields.len > 0
811 && !type_contains_generic_placeholder(types.Type(full_struct_type)) {
812 return full_struct_type
813 }
814 return fallback
815}
816
817// gen_comptime_for_methods emits the body of `$for method in T.methods` by
818// looping over the AST FnDecls whose receiver C-type matches T, setting the
819// `g.comptime_method_*` state per iteration, and recursively generating the
820// loop body so per-method selectors (method.name, method.attrs, etc.) and
821// `app.$method(...)` dispatch can be resolved by expr.v.
822fn (mut g Gen) gen_comptime_for_methods(node ast.ForStmt, for_in ast.ForInStmt, type_name string, struct_type types.Struct) {
823 concrete_struct_type := types.Type(struct_type)
824 struct_c_name := g.types_type_to_c(concrete_struct_type).trim_space().trim_right('*')
825 method_decls := g.collect_method_fndecls(struct_type.name, struct_c_name)
826 method_var := for_in.value.name()
827 prev_method_var := g.comptime_method_var
828 prev_method_name := g.comptime_method_name
829 prev_method_attrs := g.comptime_method_attrs
830 prev_method_return_type := g.comptime_method_return_type
831 prev_method_args := g.comptime_method_args
832 prev_method_idx := g.comptime_method_idx
833 prev_method_receiver_type := g.comptime_method_receiver_type
834 prev_method_struct_name := g.comptime_method_struct_name
835 prev_continue_label := g.comptime_continue_label
836 g.comptime_method_var = method_var
837 g.comptime_method_receiver_type = struct_c_name
838 g.comptime_method_struct_name = struct_type.name
839 g.write_indent()
840 g.sb.writeln('{ /* comptime for ${method_var} in ${type_name}.methods */')
841 g.indent++
842 for i, decl in method_decls {
843 g.comptime_method_name = decl.name
844 g.comptime_method_attrs = comptime_attribute_strings(decl.attributes)
845 g.comptime_method_return_type = decl.typ.return_type
846 g.comptime_method_args = decl.typ.params.clone()
847 g.comptime_method_idx = i
848 g.tmp_counter++
849 g.comptime_continue_label = '__v_ctm_continue_${g.tmp_counter}_${i}'
850 g.write_indent()
851 g.sb.writeln('{ /* method ${i}: ${decl.name} */')
852 g.indent++
853 g.gen_stmts(node.stmts)
854 g.write_indent()
855 g.sb.writeln('${g.comptime_continue_label}:;')
856 g.indent--
857 g.write_indent()
858 g.sb.writeln('}')
859 }
860 g.indent--
861 g.write_indent()
862 g.sb.writeln('}')
863 g.comptime_method_var = prev_method_var
864 g.comptime_method_name = prev_method_name
865 g.comptime_method_attrs = prev_method_attrs
866 g.comptime_method_return_type = prev_method_return_type
867 g.comptime_method_args = prev_method_args
868 g.comptime_method_idx = prev_method_idx
869 g.comptime_method_receiver_type = prev_method_receiver_type
870 g.comptime_method_struct_name = prev_method_struct_name
871 g.comptime_continue_label = prev_continue_label
872}
873
874// collect_method_fndecls walks all loaded files and returns FnDecls whose
875// receiver type matches either the struct V-name or its C-mangled name.
876fn (mut g Gen) collect_method_fndecls(struct_v_name string, struct_c_name string) []ast.FnDecl {
877 mut out := []ast.FnDecl{}
878 if g.has_flat() {
879 for i in 0 .. g.flat.files.len {
880 fc := g.flat.file_cursor(i)
881 g.set_file_cursor_module(fc)
882 stmts := fc.stmts()
883 for j in 0 .. stmts.len() {
884 stmt := stmts.at(j)
885 if stmt.kind() != .stmt_fn_decl {
886 continue
887 }
888 decl := stmt.fn_decl_signature()
889 if !decl.is_method {
890 continue
891 }
892 if decl.receiver.typ is ast.EmptyExpr {
893 continue
894 }
895 receiver_v_name := decl.receiver.typ.name()
896 if receiver_v_name == struct_v_name || receiver_v_name == struct_c_name
897 || short_type_name(struct_c_name) == receiver_v_name {
898 out << decl
899 continue
900 }
901 receiver_c_name := g.expr_type_to_c(decl.receiver.typ).trim_space().trim_right('*')
902 if receiver_c_name == struct_c_name {
903 out << decl
904 }
905 }
906 }
907 return out
908 }
909 for file in g.files {
910 for stmt in file.stmts {
911 if stmt is ast.FnDecl {
912 if !stmt.is_method {
913 continue
914 }
915 if stmt.receiver.typ is ast.EmptyExpr {
916 continue
917 }
918 receiver_v_name := stmt.receiver.typ.name()
919 if receiver_v_name == struct_v_name || receiver_v_name == struct_c_name
920 || short_type_name(struct_c_name) == receiver_v_name {
921 out << stmt
922 continue
923 }
924 // Compare resolved C name as fallback (handles module-qualified receivers).
925 receiver_c_name := g.expr_type_to_c(stmt.receiver.typ).trim_space().trim_right('*')
926 if receiver_c_name == struct_c_name {
927 out << stmt
928 }
929 }
930 }
931 }
932 return out
933}
934
935fn comptime_attribute_strings(attrs []ast.Attribute) []string {
936 mut out := []string{cap: attrs.len}
937 for attr in attrs {
938 if attr.name != '' {
939 if attr.value is ast.EmptyExpr {
940 out << attr.name
941 } else {
942 out << '${attr.name}: ${attr.value.name().trim("'")}'
943 }
944 } else if attr.value !is ast.EmptyExpr {
945 out << attr.value.name().trim("'")
946 }
947 }
948 return out
949}
950
951fn (mut g Gen) gen_return_mut_param_value(expr ast.Expr) bool {
952 if expr !is ast.Ident || g.cur_fn_ret_type.ends_with('*') {
953 return false
954 }
955 ident := expr as ast.Ident
956 if ident.name !in g.cur_fn_mut_params {
957 return false
958 }
959 local_type := (g.get_local_var_c_type(ident.name) or { '' }).trim_space()
960 if !local_type.ends_with('*') {
961 return false
962 }
963 local_base := local_type.trim_right('*')
964 ret_base := g.cur_fn_ret_type.trim_right('*')
965 if local_base == '' || ret_base == '' {
966 return false
967 }
968 if local_base != ret_base && short_type_name(local_base) != short_type_name(ret_base) {
969 return false
970 }
971 g.sb.write_string('(*')
972 g.expr(expr)
973 g.sb.write_string(')')
974 return true
975}
976
977fn (mut g Gen) comptime_field_attribute_strings(struct_name string, field types.Field) []string {
978 if g.has_flat() {
979 for i in 0 .. g.flat.files.len {
980 stmts := g.flat.file_cursor(i).stmts()
981 for j in 0 .. stmts.len() {
982 stmt := stmts.at(j)
983 if stmt.kind() != .stmt_struct_decl {
984 continue
985 }
986 decl := stmt.struct_decl()
987 if decl.name != struct_name && !struct_name.ends_with('__${decl.name}') {
988 continue
989 }
990 for ast_field in decl.fields {
991 if ast_field.name == field.name {
992 return comptime_attribute_strings(ast_field.attributes)
993 }
994 }
995 }
996 }
997 return comptime_attribute_strings(field.attributes)
998 }
999 for file in g.files {
1000 for stmt in file.stmts {
1001 if stmt is ast.StructDecl {
1002 if stmt.name != struct_name && !struct_name.ends_with('__${stmt.name}') {
1003 continue
1004 }
1005 for ast_field in stmt.fields {
1006 if ast_field.name == field.name {
1007 return comptime_attribute_strings(ast_field.attributes)
1008 }
1009 }
1010 }
1011 }
1012 }
1013 return comptime_attribute_strings(field.attributes)
1014}
1015