vxx / vlib / v3 / gen / c / fn.v
5208 lines · 4991 sloc · 157.52 KB · 2b81918e62db9ab38787518603a7f93bee80868c
Raw
1module c
2
3import strings
4import v3.flat
5import v3.types
6
7struct TestHarnessFn {
8 name string
9 c_name string
10 ret types.Type
11}
12
13struct TestHarnessHooks {
14mut:
15 testsuite_begin string
16 testsuite_end string
17 before_each string
18 after_each string
19}
20
21struct TopLevelStmt {
22 id flat.NodeId
23 file string
24 module string
25}
26
27// gen_fns emits fns output for c.
28fn (mut g FlatGen) gen_fns() {
29 preferred_fns := g.preferred_c_backend_fn_nodes()
30 mut cur_module := ''
31 mut cur_file := ''
32 for i in 0 .. g.a.nodes.len {
33 node := g.a.nodes[i]
34 kind_id := node_kind_id(node)
35 if kind_id == 77 {
36 cur_file = node.value
37 g.tc.cur_file = cur_file
38 cur_module = ''
39 g.tc.cur_module = cur_module
40 continue
41 }
42 if kind_id == 73 {
43 cur_module = node.value
44 g.tc.cur_file = cur_file
45 g.tc.cur_module = cur_module
46 continue
47 }
48
49 if kind_id == 61 {
50 if !g.should_emit_fn_node_in_module(node, i, cur_module, cur_file) {
51 continue
52 }
53 qfn := g.fn_c_name_in_module(cur_module, node.value)
54 if preferred_idx := preferred_fns[qfn] {
55 if preferred_idx != i {
56 continue
57 }
58 }
59 if g.emitted_fn_contains(qfn) {
60 continue
61 }
62 g.emitted_fns[qfn] = true
63 g.tc.cur_file = cur_file
64 g.tc.cur_module = cur_module
65 g.gen_fn_in_module(node, cur_module)
66 }
67 }
68}
69
70fn (g &FlatGen) preferred_c_backend_fn_nodes() map[string]int {
71 mut preferred := map[string]int{}
72 mut ranks := map[string]int{}
73 mut cur_module := ''
74 mut cur_file := ''
75 for i in 0 .. g.a.nodes.len {
76 node := g.a.nodes[i]
77 kind_id := node_kind_id(node)
78 if kind_id == 77 {
79 cur_file = node.value
80 cur_module = ''
81 continue
82 }
83 if kind_id == 73 {
84 cur_module = node.value
85 continue
86 }
87 if kind_id != 61 {
88 continue
89 }
90 qfn := qualified_fn_name_in_module(cur_module, node.value)
91 rank := c_backend_fn_file_rank(cur_file)
92 if qfn !in preferred || rank > ranks[qfn] {
93 preferred[qfn] = i
94 ranks[qfn] = rank
95 }
96 }
97 return preferred
98}
99
100fn c_backend_fn_file_rank(file string) int {
101 if file.ends_with('.c.v') {
102 return 1
103 }
104 return 0
105}
106
107fn (mut g FlatGen) gen_synthetic_main_after_fns() {
108 if g.test_files.len > 0 {
109 g.gen_test_main()
110 return
111 }
112 if g.has_entry_main() {
113 return
114 }
115 top_level_stmts := g.top_level_stmts()
116 if top_level_stmts.len > 0 {
117 g.gen_top_level_main(top_level_stmts)
118 }
119}
120
121fn (g &FlatGen) has_entry_main() bool {
122 mut cur_module := ''
123 for node in g.a.nodes {
124 kind_id := node_kind_id(node)
125 if kind_id == 77 {
126 cur_module = ''
127 continue
128 }
129 if kind_id == 73 {
130 cur_module = node.value
131 continue
132 }
133 if kind_id == 61 && node.value == 'main' && (cur_module.len == 0 || cur_module == 'main') {
134 return true
135 }
136 }
137 return false
138}
139
140fn (g &FlatGen) top_level_stmts() []TopLevelStmt {
141 mut stmts := []TopLevelStmt{}
142 for file_idx, file_node in g.a.nodes {
143 if !g.should_emit_top_level_file(file_idx, file_node) {
144 continue
145 }
146 module_name := g.top_level_file_module_name(file_node)
147 for i in 0 .. file_node.children_count {
148 child_id := g.a.child(&file_node, i)
149 if int(child_id) < g.a.user_code_start {
150 continue
151 }
152 if g.cgen_is_top_level_stmt(child_id) {
153 stmts << TopLevelStmt{
154 id: child_id
155 file: file_node.value
156 module: if module_name.len == 0 { 'main' } else { module_name }
157 }
158 }
159 }
160 }
161 return stmts
162}
163
164fn (g &FlatGen) should_emit_top_level_file(file_idx int, file_node flat.Node) bool {
165 if file_idx < g.a.user_code_start || file_node.kind != .file || file_node.children_count == 0 {
166 return false
167 }
168 module_name := g.top_level_file_module_name(file_node)
169 return module_name.len == 0 || module_name == 'main'
170}
171
172fn (g &FlatGen) top_level_file_module_name(file_node flat.Node) string {
173 for i in 0 .. file_node.children_count {
174 child := g.a.child_node(&file_node, i)
175 if child.kind == .module_decl {
176 return child.value
177 }
178 }
179 return ''
180}
181
182fn (g &FlatGen) cgen_is_top_level_stmt(id flat.NodeId) bool {
183 if int(id) < 0 {
184 return false
185 }
186 node := g.a.nodes[int(id)]
187 return match node.kind {
188 .expr_stmt, .assign, .decl_assign, .selector_assign, .index_assign, .for_stmt,
189 .for_in_stmt, .if_expr, .assert_stmt, .defer_stmt {
190 true
191 }
192 .block, .comptime_if {
193 for i in 0 .. node.children_count {
194 if g.cgen_is_top_level_stmt(g.a.child(&node, i)) {
195 return true
196 }
197 }
198 false
199 }
200 else {
201 false
202 }
203 }
204}
205
206// should_emit_fn_node reports whether should emit fn node applies in c.
207fn (mut g FlatGen) should_emit_fn_node(node flat.Node, node_index int) bool {
208 return g.should_emit_fn_node_in_module(node, node_index, g.tc.cur_module, g.tc.cur_file)
209}
210
211// should_emit_fn_node_in_module reports whether should emit fn node in module applies in c.
212fn (mut g FlatGen) should_emit_fn_node_in_module(node flat.Node, node_index int, module_name string, file_name string) bool {
213 _ = node_index
214 qfn := qualified_fn_name_in_module(module_name, node.value)
215 if g.should_rename_user_main_for_tests(module_name, node.value) {
216 return true
217 }
218 if module_name == 'builtin' && node.value == 'exit' {
219 return true
220 }
221 // `array.pointers` is emitted as an intrinsic at call sites; the raw
222 // builtin body has an erased `array` receiver and generates invalid C.
223 if module_name == 'builtin' && node.value == 'array.pointers' {
224 return false
225 }
226 if node.value.starts_with('__anon_fn_') || qfn.contains('__anon_fn_') {
227 return true
228 }
229 if node.generic_params.len == 0 && cgen_is_operator_overload_fn(node.value)
230 && g.test_files[file_name] {
231 return true
232 }
233 if g.has_used_fn_filter() && !g.used_fn_contains_in_module(node.value, module_name) {
234 return false
235 }
236 return true
237}
238
239fn cgen_is_operator_overload_fn(name string) bool {
240 if !name.contains('.') {
241 return false
242 }
243 method := name.all_after_last('.')
244 return method in ['+', '-', '*', '/', '%', '==', '!=', '<', '>', '<=', '>=']
245}
246
247fn is_generated_fn_after_markused(name string) bool {
248 return name.starts_with('__anon_fn_')
249}
250
251// used_fn_contains reports whether used fn contains applies in c.
252fn (g &FlatGen) used_fn_contains(name string) bool {
253 if name.len == 0 {
254 return false
255 }
256 return g.used_fns[name]
257}
258
259fn (g &FlatGen) used_fn_contains_in_module(name string, module_name string) bool {
260 if module_name.len > 0 && module_name != 'main' && module_name != 'builtin'
261 && name.starts_with('${module_name}.') {
262 if g.used_fn_contains(name) {
263 return true
264 }
265 cfn := c_name(name)
266 if cfn != name && g.used_fn_contains(cfn) {
267 return true
268 }
269 }
270 dfn := dotted_fn_name_in_module(module_name, name)
271 qfn := qualified_fn_name_in_module(module_name, name)
272 if g.used_fn_contains(dfn) || g.used_fn_contains(qfn) {
273 return true
274 }
275 if g.used_fn_contains(c_name(dfn)) || g.used_fn_contains(c_name(qfn)) {
276 return true
277 }
278 if module_name.len == 0 || module_name == 'main' || module_name == 'builtin' {
279 cfn := c_name(name)
280 return g.used_fn_contains(name) || g.used_fn_contains(cfn)
281 }
282 return false
283}
284
285// has_used_fn_filter reports whether has used fn filter applies in c.
286fn (g &FlatGen) has_used_fn_filter() bool {
287 return g.used_fns.len > 0 && g.used_fn_contains('main')
288}
289
290// emitted_fn_contains reports whether emitted fn contains applies in c.
291fn (g &FlatGen) emitted_fn_contains(name string) bool {
292 return name.len > 0 && g.emitted_fns[name]
293}
294
295// qualified_fn_name supports qualified fn name handling for FlatGen.
296fn (g &FlatGen) qualified_fn_name(name string) string {
297 return qualified_fn_name_in_module(g.tc.cur_module, name)
298}
299
300fn (g &FlatGen) export_fn_name_in_module(module_name string, name string) ?string {
301 qname := dotted_fn_name_in_module(module_name, name)
302 if export_name := g.a.export_fn_names[qname] {
303 return export_name
304 }
305 if (module_name.len == 0 || module_name == 'main' || module_name == 'builtin')
306 && name in g.a.export_fn_names {
307 return g.a.export_fn_names[name]
308 }
309 return none
310}
311
312// qualified_fn_name_in_module supports qualified fn name in module handling for c.
313fn qualified_fn_name_in_module(module_name string, name string) string {
314 if module_name == 'builtin' && name == 'free' {
315 return 'v_free'
316 }
317 if module_name.len > 0 && module_name != 'main' && module_name != 'builtin' {
318 return c_name('${module_name}.${name}')
319 }
320 if name == 'free' {
321 return 'v_free'
322 }
323 return c_name(name)
324}
325
326fn is_main_fn_in_main_module(module_name string, name string) bool {
327 return name == 'main' && (module_name.len == 0 || module_name == 'main')
328}
329
330fn (g &FlatGen) should_rename_user_main_for_tests(module_name string, name string) bool {
331 return g.test_files.len > 0 && is_main_fn_in_main_module(module_name, name)
332}
333
334fn (g &FlatGen) fn_c_name_in_module(module_name string, name string) string {
335 if g.should_rename_user_main_for_tests(module_name, name) {
336 return g.test_user_main_c_name()
337 }
338 return qualified_fn_name_in_module(module_name, name)
339}
340
341fn (g &FlatGen) test_user_main_c_name() string {
342 base := 'main__user_main'
343 if !g.c_fn_symbol_exists(base) {
344 return base
345 }
346 limit := g.a.nodes.len + 2
347 for idx in 1 .. limit {
348 candidate := '${base}_${idx}'
349 if !g.c_fn_symbol_exists(candidate) {
350 return candidate
351 }
352 }
353 return '${base}_${limit}'
354}
355
356fn (g &FlatGen) c_fn_symbol_exists(candidate string) bool {
357 mut cur_module := ''
358 for node in g.a.nodes {
359 kind_id := node_kind_id(node)
360 if kind_id == 77 {
361 cur_module = ''
362 continue
363 }
364 if kind_id == 73 {
365 cur_module = node.value
366 continue
367 }
368 if kind_id != 61 {
369 continue
370 }
371 if qualified_fn_name_in_module(cur_module, node.value) == candidate {
372 return true
373 }
374 if export_name := g.export_fn_name_in_module(cur_module, node.value) {
375 if export_name == candidate {
376 return true
377 }
378 }
379 }
380 return false
381}
382
383// direct_call_name supports direct call name handling for FlatGen.
384fn (mut g FlatGen) direct_call_name(name string) string {
385 if compat_name := g.libc_compat_call_name(name) {
386 return compat_name
387 }
388 if g.test_files.len > 0 && (name == 'main' || name == 'main.main') {
389 return g.test_user_main_c_name()
390 }
391 if name == 'free' {
392 return 'v_free'
393 }
394 if name == 'int_str' {
395 return 'int__str'
396 }
397 if name == 'bool_str' {
398 return 'bool__str'
399 }
400 if name == 'char.vstring' {
401 return 'charptr__vstring'
402 }
403 if name == 'char.vstring_with_len' {
404 return 'charptr__vstring_with_len'
405 }
406 return c_name(name)
407}
408
409fn (mut g FlatGen) direct_call_name_for_call(id flat.NodeId, name string) string {
410 if g.test_files.len > 0 && (name == 'main' || name == 'main.main') {
411 if resolved := g.tc.resolved_call_name(id) {
412 if resolved == 'main' || resolved == 'main.main' {
413 return g.test_user_main_c_name()
414 }
415 }
416 return c_name(name)
417 }
418 if specialized := g.specialized_generic_method_name_for_call_with_arg_count(id, name, -1) {
419 return c_name(specialized)
420 }
421 return g.direct_call_name(name)
422}
423
424fn (mut g FlatGen) libc_compat_call_name(name string) ?string {
425 if name.starts_with('C.') {
426 cfn := c_name(name)
427 wide_cfn := c_winapi_wide_export_name(cfn)
428 if wide_cfn != cfn {
429 return wide_cfn
430 }
431 }
432 // `builtin.v_gettid()` reaches libc through `C.gettid()` on Linux/glibc, but
433 // that symbol is not declared by all usable C header sets. Route it through a
434 // tiny runtime compatibility helper instead of emitting an undeclared call.
435 if name == 'C.gettid' {
436 g.libc_compat_fns['gettid'] = true
437 return 'v3_gettid'
438 }
439 if name in ['C.v_filelock_lock', 'C.v_filelock_unlock'] {
440 g.libc_compat_fns['filelock'] = true
441 return c_name(name)
442 }
443 return none
444}
445
446fn (g &FlatGen) test_user_main_fn_value_c_name(id flat.NodeId, node flat.Node) ?string {
447 if g.test_files.len == 0 || node.kind != .ident || node.value != 'main' {
448 return none
449 }
450 looked_up := g.tc.cur_scope.lookup(node.value) or { types.Type(types.void_) }
451 if looked_up !is types.Void {
452 return none
453 }
454 if resolved := g.tc.resolved_fn_value_name(id) {
455 if resolved == 'main' || resolved == 'main.main' {
456 return g.test_user_main_c_name()
457 }
458 return none
459 }
460 if g.usable_expr_type(id) is types.FnType {
461 return g.test_user_main_c_name()
462 }
463 return none
464}
465
466fn (g &FlatGen) import_alias_module(alias string) ?string {
467 if alias.len == 0 {
468 return none
469 }
470 if g.tc != unsafe { nil } && g.tc.cur_file.len > 0 {
471 key := g.tc.cur_file + '\n' + alias
472 if mod := g.tc.file_imports[key] {
473 return mod
474 }
475 }
476 if mod := g.modules[alias] {
477 return mod
478 }
479 return none
480}
481
482fn (g &FlatGen) has_import_alias(alias string) bool {
483 if _ := g.import_alias_module(alias) {
484 return true
485 }
486 return false
487}
488
489// dotted_fn_name supports dotted fn name handling for FlatGen.
490fn (g &FlatGen) dotted_fn_name(name string) string {
491 return dotted_fn_name_in_module(g.tc.cur_module, name)
492}
493
494// dotted_fn_name_in_module supports dotted fn name in module handling for c.
495fn dotted_fn_name_in_module(module_name string, name string) string {
496 if module_name.len > 0 && module_name != 'main' && module_name != 'builtin' {
497 return '${module_name}.${name}'
498 }
499 return name
500}
501
502// qualify_name_in_module supports qualify name in module handling for c.
503fn qualify_name_in_module(module_name string, name string) string {
504 if module_name.len == 0 || module_name == 'main' || module_name == 'builtin' {
505 return name
506 }
507 if name.contains('.') {
508 return name
509 }
510 return '${module_name}.${name}'
511}
512
513// gen_fn emits fn output for c.
514fn (mut g FlatGen) gen_fn(node flat.Node) {
515 g.gen_fn_in_module(node, g.tc.cur_module)
516}
517
518fn (mut g FlatGen) write_method_c_name(id flat.NodeId, node flat.Node, method_name string) {
519 if specialized := g.specialized_generic_method_name_for_call_with_arg_count(id, method_name,
520 int(node.children_count))
521 {
522 g.write(c_name(specialized))
523 return
524 }
525 g.write(c_name(method_name))
526}
527
528fn (g &FlatGen) specialized_generic_method_name_for_call_with_arg_count(id flat.NodeId, method_name string, arg_count int) ?string {
529 if !method_name.contains('.') {
530 return none
531 }
532 receiver := method_name.all_before_last('.')
533 method := method_name.all_after_last('.')
534 if receiver.len == 0 || method.len == 0 {
535 return none
536 }
537 call_ret := g.call_default_return_type(id)
538 if type_arg := generic_method_type_arg_from_return(call_ret) {
539 for arg in [type_arg, type_arg.all_after_last('.')] {
540 for candidate in ['${receiver}[${arg}].${method}',
541 '${receiver.all_after_last('.')}[${arg}].${method}'] {
542 if candidate in g.tc.fn_param_types || candidate in g.tc.fn_ret_types {
543 return candidate
544 }
545 }
546 }
547 }
548 for allow_short_receiver in [false, true] {
549 for candidate, ret in g.tc.fn_ret_types {
550 if !candidate.contains('[') || candidate.all_after_last('.') != method {
551 continue
552 }
553 candidate_receiver := candidate.all_before_last('.')
554 base_receiver := candidate_receiver.all_before('[')
555 receiver_matches := base_receiver == receiver || (allow_short_receiver
556 && base_receiver.all_after_last('.') == receiver.all_after_last('.'))
557 if !receiver_matches {
558 continue
559 }
560 params := g.tc.fn_param_types[candidate] or { []types.Type{} }
561 if arg_count >= 0 && params.len != arg_count {
562 continue
563 }
564 if g.type_names_match(call_ret, ret) || call_ret.name() == ret.name() {
565 return candidate
566 }
567 }
568 }
569 return none
570}
571
572fn generic_method_type_arg_from_return(ret types.Type) ?string {
573 if ret is types.ResultType {
574 name := ret.base_type.name()
575 if name.len > 0 && name != 'void' {
576 return name
577 }
578 }
579 if ret is types.OptionType {
580 name := ret.base_type.name()
581 if name.len > 0 && name != 'void' {
582 return name
583 }
584 }
585 return none
586}
587
588// static_method_fn_name resolves `Type.method(...)` static calls where `Type` is a
589// named type, struct, enum, sum type or type alias (e.g. `SimdFloat4.new` for
590// `type SimdFloat4 = vec.Vec4[f32]`). Returns the fn key, or none if `type_ident`
591// is not a type or has no such static method.
592fn (g &FlatGen) static_method_fn_name(type_ident string, method string) ?string {
593 qtype := g.tc.qualify_name(type_ident)
594 is_type := type_ident in g.tc.type_aliases || qtype in g.tc.type_aliases
595 || type_ident in g.tc.structs || qtype in g.tc.structs || type_ident in g.tc.enum_names
596 || qtype in g.tc.enum_names || type_ident in g.tc.sum_types || qtype in g.tc.sum_types
597 if !is_type {
598 return none
599 }
600 // Prefer the module-qualified key: a static method defined in the current (or the
601 // type's) module is emitted under its qualified C name (`game__Animation__load`),
602 // so the call must resolve to the same qualified key even though an unqualified
603 // alias (`Animation.load`) may also be registered.
604 qdirect := '${qtype}.${method}'
605 if qtype != type_ident && (qdirect in g.tc.fn_ret_types || qdirect in g.tc.fn_param_types) {
606 return qdirect
607 }
608 direct := '${type_ident}.${method}'
609 if direct in g.tc.fn_ret_types || direct in g.tc.fn_param_types {
610 return direct
611 }
612 if qdirect in g.tc.fn_ret_types || qdirect in g.tc.fn_param_types {
613 return qdirect
614 }
615 return none
616}
617
618fn (g &FlatGen) resolve_method_name(type_name string, method string) string {
619 direct := '${type_name}.${method}'
620 if direct in g.tc.fn_param_types || direct in g.tc.fn_ret_types {
621 return direct
622 }
623 if g.tc.cur_module.len > 0 && g.tc.cur_module != 'main' && g.tc.cur_module != 'builtin'
624 && !type_name.contains('.') {
625 qualified := '${g.tc.cur_module}.${type_name}.${method}'
626 if qualified in g.tc.fn_param_types || qualified in g.tc.fn_ret_types {
627 return qualified
628 }
629 }
630 return ''
631}
632
633fn c_string_literal_pointer_arg(arg_node flat.Node, expected types.Type) bool {
634 if arg_node.kind != .char_literal || !arg_node.value.starts_with('c:') {
635 return false
636 }
637 if expected is types.Pointer {
638 base := types.unwrap_pointer(expected)
639 return base is types.Char
640 }
641 return false
642}
643
644fn (mut g FlatGen) gen_pointer_builtin_method_call(node flat.Node, fn_node &flat.Node, base_type types.Type) bool {
645 receiver := pointer_builtin_receiver_name_for_c(base_type)
646 if receiver.len == 0 {
647 return false
648 }
649 method := fn_node.value
650 if receiver in ['charptr', 'byteptr'] && method in ['vstring', 'vstring_with_len'] {
651 g.write(c_name('${receiver}.${method}'))
652 g.write('(')
653 g.gen_expr(g.a.child(fn_node, 0))
654 for i in 1 .. node.children_count {
655 g.write(', ')
656 g.gen_expr(g.a.child(&node, i))
657 }
658 g.write(')')
659 return true
660 }
661 if receiver in ['byteptr', 'voidptr'] && method == 'vbytes' {
662 g.write(c_name('${receiver}.${method}'))
663 g.write('(')
664 g.gen_expr(g.a.child(fn_node, 0))
665 for i in 1 .. node.children_count {
666 g.write(', ')
667 g.gen_expr(g.a.child(&node, i))
668 }
669 g.write(')')
670 return true
671 }
672 return false
673}
674
675fn pointer_builtin_receiver_name_for_c(typ types.Type) string {
676 if typ is types.Alias {
677 if typ.name in ['charptr', 'byteptr', 'voidptr'] {
678 return typ.name
679 }
680 return pointer_builtin_receiver_name_for_c(typ.base_type)
681 }
682 if typ is types.Pointer {
683 base := typ.base_type
684 if base is types.Alias {
685 if base.name == 'byte' {
686 return 'byteptr'
687 }
688 return pointer_builtin_receiver_name_for_c(base)
689 }
690 if base is types.Char {
691 return 'charptr'
692 }
693 if base is types.Void {
694 return 'voidptr'
695 }
696 if base is types.Primitive && base.name() == 'u8' {
697 return 'byteptr'
698 }
699 }
700 if typ is types.Char {
701 return 'charptr'
702 }
703 if typ is types.Void {
704 return 'voidptr'
705 }
706 if typ is types.Primitive && typ.name() == 'u8' {
707 return 'byteptr'
708 }
709 return ''
710}
711
712fn (mut g FlatGen) gen_special_c_callback_arg(fn_name string, arg_idx int, arg_id flat.NodeId, expected_param types.Type) bool {
713 clean_name := fn_name.trim_string_left('C.').all_after_last('.')
714 if clean_name == 'mbedtls_ssl_conf_sni' && arg_idx == 1 {
715 g.write('(int (*)(void *, mbedtls_ssl_context *, const unsigned char *, size_t))')
716 g.gen_expr(arg_id)
717 return true
718 }
719 // Only convert to `(void*)` for an actual C-callback slot. A V `fn (...) ...`
720 // parameter is generated as a `_fn_ptr_*` typedef and must receive the function
721 // pointer directly: `(void*)foo` is an object-pointer-to-function-pointer cast that
722 // strict C rejects and that is not portable across ABIs. C functions (and loosely
723 // typed `voidptr` slots) still need it, because C uses the header prototype.
724 // `fn_type_from` is alias-aware, so a `type Cb = fn ()` parameter is recognised too.
725 if _ := fn_type_from(expected_param) {
726 return false
727 }
728 // A V function passed by name to a C function (a callback) must be cast: the V
729 // declaration's parameter type (often `voidptr`) is ignored by C in favour of the
730 // real header prototype, so the bare name trips -Wincompatible-function-pointer-types.
731 // `(void*)` converts cleanly to any function-pointer parameter.
732 if int(arg_id) >= 0 {
733 arg_node := g.a.nodes[int(arg_id)]
734 if arg_node.kind == .ident {
735 is_local := (g.tc.cur_scope.lookup(arg_node.value) or { types.Type(types.void_) }) !is types.Void
736 if !is_local {
737 call_name := g.call_key(arg_id, arg_node.value)
738 fn_key := if call_name in g.tc.fn_ret_types {
739 call_name
740 } else if arg_node.value in g.tc.fn_ret_types {
741 arg_node.value
742 } else {
743 ''
744 }
745 if fn_key.len > 0 && !fn_key.starts_with('C.') {
746 g.write('(void*)')
747 g.write(c_name(fn_key))
748 return true
749 }
750 }
751 }
752 }
753 return false
754}
755
756fn (mut g FlatGen) spawn_wrapper_decls() {
757 for def in g.spawn_wrapper_defs {
758 g.writeln(def)
759 }
760 if g.spawn_wrapper_defs.len > 0 {
761 g.writeln('')
762 }
763}
764
765// expr_is_addressable reports whether an expression denotes a stable lvalue whose address
766// outlives the enclosing statement expression — a variable, a field/index access reaching one,
767// or a dereference. Rvalues (struct literals, calls, ...) only have temporary storage, so their
768// address must not be captured in a method value's static receiver slot.
769fn (g &FlatGen) expr_is_addressable(id flat.NodeId) bool {
770 if int(id) < 0 {
771 return false
772 }
773 node := g.a.nodes[int(id)]
774 return match node.kind {
775 .ident {
776 true
777 }
778 .index {
779 node.value != 'range' && node.children_count > 0
780 && g.expr_is_addressable(g.a.child(&node, 0))
781 }
782 .selector {
783 node.children_count > 0 && g.expr_is_addressable(g.a.child(&node, 0))
784 }
785 .prefix {
786 node.op == .mul
787 }
788 .paren {
789 node.children_count > 0 && g.expr_is_addressable(g.a.child(&node, 0))
790 }
791 else {
792 false
793 }
794 }
795}
796
797// gen_method_value_closure handles a method used as a *value* (e.g. `game.draw`
798// passed where a `fn ()` callback is expected) rather than called. A plain struct
799// field access can't represent the bound receiver, so it binds the receiver into a
800// per-site context global and yields a wrapper function that invokes the method on
801// it. Returns false when the selector is an ordinary field access (handled normally).
802//
803// LIMITATION: the captured receiver lives in a single per-site global, so it is
804// only valid while no later evaluation of the *same* selector site overwrites it.
805// That covers the supported cases — passing/storing a method value and invoking it
806// before the site is re-evaluated (immediate callbacks, a single stored callback).
807// It does NOT support keeping several live method values from one site with
808// different receivers (e.g. building an array of `obj.method` in a loop): they all
809// share the global and would observe the last-bound receiver. A correct general
810// form needs a real per-closure captured environment, which the v3 backend has no
811// ABI for yet (anonymous fns are lifted without true capture). Such escaping method
812// values should be reworked (e.g. capture into an explicit struct + free fn) until
813// closure support lands.
814fn (mut g FlatGen) gen_method_value_closure(base_id flat.NodeId, base_type types.Type, method string) bool {
815 clean := types.unwrap_pointer(base_type)
816 if clean !is types.Struct {
817 return false
818 }
819 struct_name := (clean as types.Struct).name
820 // A real field shadows any same-named method: that's a field access, not a value.
821 if _ := g.struct_field_type(struct_name, method) {
822 return false
823 }
824 method_key := g.resolve_method_name(struct_name, method)
825 mut params := []types.Type{}
826 mut ret := types.Type(types.void_)
827 mut cname := ''
828 if method_key.len > 0 {
829 params = g.tc.fn_param_types[method_key] or { return false }
830 ret = g.tc.fn_ret_types[method_key] or { types.Type(types.void_) }
831 cname = c_name(method_key)
832 } else if ci := g.tc.generic_method_value_info['${struct_name}.${method}'] {
833 // Generic receiver (`Box[int]`): the open `Box[T].get` registration is gone by
834 // cgen, so use the substituted params/return the checker stashed, plus the
835 // monomorphised C name `c_name('Box[int].get')` == `Box_int__get`.
836 params = ci.params.clone()
837 // The receiver param is the un-substituted open `Box[T]`; replace it with the
838 // concrete receiver type (keeping the method's pointer-ness) so its C name
839 // resolves to `Box_int` rather than the template `Box`.
840 if params.len > 0 {
841 recv_concrete := types.unwrap_pointer(base_type)
842 params[0] = if params[0] is types.Pointer {
843 types.Type(types.Pointer{
844 base_type: recv_concrete
845 })
846 } else {
847 recv_concrete
848 }
849 }
850 ret = ci.return_type
851 cname = c_name('${struct_name}.${method}')
852 } else {
853 return false
854 }
855 if params.len == 0 {
856 return false
857 }
858 recv_is_ptr := params[0] is types.Pointer
859 recv_ct := g.tc.c_type(params[0])
860 // Use the method's ABI return type (matching `cname`'s signature and the callback
861 // fn-pointer typedef): an option/result is `Optional_T`, a fixed array its
862 // `_v_ret_*` wrapper — not the bare `c_type` (`Optional`/`Array_fixed_*`).
863 ret_ct := g.fn_return_type_name(ret)
864 idx := g.tmp_count
865 g.tmp_count++
866 ctx_name := '_mvctx_${idx}'
867 wrap_name := '_mvwrap_${idx}'
868 mut wparams := []string{}
869 mut call_args := [ctx_name]
870 for i in 1 .. params.len {
871 pt := g.tc.c_type(params[i])
872 wparams << '${pt} a${i}'
873 call_args << 'a${i}'
874 }
875 wparam_str := if wparams.len == 0 { 'void' } else { wparams.join(', ') }
876 g.spawn_wrapper_defs << 'static ${recv_ct} ${ctx_name};'
877 ret_prefix := if ret_ct == 'void' { '' } else { 'return ' }
878 g.spawn_wrapper_defs << 'static ${ret_ct} ${wrap_name}(${wparam_str}) { ${ret_prefix}${cname}(${call_args.join(', ')}); }'
879 base_is_ptr := base_type is types.Pointer
880 // Set the context global to the receiver, then yield the wrapper as the value.
881 if recv_is_ptr && !base_is_ptr && !g.expr_is_addressable(base_id) {
882 // Pointer receiver bound to an rvalue base (`Foo{..}.tick`, `make_foo().tick`): taking
883 // `&(rvalue)` captures a temporary that dies with the enclosing statement expression,
884 // before the callback runs. Copy the receiver into a durable static slot and point at it.
885 val_ct := g.tc.c_type(types.unwrap_pointer(params[0]))
886 g.spawn_wrapper_defs << 'static ${val_ct} ${ctx_name}_recv;'
887 g.write('({ ${ctx_name}_recv = ')
888 g.gen_expr(base_id)
889 g.write('; ${ctx_name} = &${ctx_name}_recv')
890 } else {
891 g.write('({ ${ctx_name} = ')
892 if recv_is_ptr && !base_is_ptr {
893 g.write('&(')
894 g.gen_expr(base_id)
895 g.write(')')
896 } else if !recv_is_ptr && base_is_ptr {
897 g.write('*(')
898 g.gen_expr(base_id)
899 g.write(')')
900 } else {
901 g.gen_expr(base_id)
902 }
903 }
904 // Yield the wrapper as the callback value. A V `fn (...) ...` parameter is a
905 // `_fn_ptr_*` typedef and needs the wrapper cast to that function-pointer type; only
906 // a C / `voidptr` callback slot gets the object-pointer `(void*)` cast (which strict
907 // C rejects for function pointers). `fn_type_from` is alias-aware, so a method value
908 // passed through a `type Cb = fn ()` parameter (an `Alias`, not a bare `FnType`) is
909 // recognised too and cast to the function-pointer typedef rather than `(void*)`.
910 if fnt := fn_type_from(g.expected_expr_type) {
911 fnptr_ct := g.value_c_type(fnt)
912 g.write('; (${fnptr_ct})${wrap_name}; })')
913 } else {
914 g.write('; (void*)${wrap_name}; })')
915 }
916 return true
917}
918
919fn (mut g FlatGen) callback_wrapper_decls() {
920 for def in g.callback_wrapper_defs {
921 g.writeln(def)
922 }
923 if g.callback_wrapper_defs.len > 0 {
924 g.writeln('')
925 }
926}
927
928fn (mut g FlatGen) gen_spawn_expr(node flat.Node) {
929 if node.children_count == 0 {
930 g.write('(void*)0')
931 return
932 }
933 call_id := g.a.child(&node, 0)
934 call_node := g.a.nodes[int(call_id)]
935 if call_node.kind != .call || call_node.children_count == 0 {
936 g.write('(void*)0')
937 return
938 }
939 fn_node := g.a.child_node(&call_node, 0)
940 // The spawned call's return type: heap-captured by the wrapper so a later
941 // `[]thread T .wait()` can recover the value (void callees just return NULL).
942 // Use the callee's ABI return type, not the bare value type: an option/result
943 // return is `Optional_T` (not the generic `Optional`, whose payload is `int`) and a
944 // fixed-array return is its `_v_ret_*` wrapper struct. The wrapper mallocs and
945 // assigns this type, and `[]thread T.wait()` must read back the same layout.
946 ret_ct := g.fn_return_type_name(g.tc.resolve_type(call_id))
947 mut wrapper := ''
948 mut arg_expr := 'NULL'
949 if fn_node.kind == .ident {
950 call_key := g.call_key(call_id, fn_node.value)
951 looked_up := g.tc.cur_scope.lookup(fn_node.value) or { types.Type(types.void_) }
952 cfn := if looked_up !is types.Void && fn_type_from(looked_up) != none {
953 c_name(fn_node.value)
954 } else if call_key in g.tc.fn_ret_types || call_key in g.tc.fn_param_types {
955 g.direct_call_name_for_call(call_id, call_key)
956 } else {
957 c_name(fn_node.value)
958 }
959 if call_node.children_count == 1 {
960 wrapper = g.ensure_noarg_spawn_wrapper(cfn, ret_ct)
961 } else {
962 // `spawn work(a, b)` packs the arguments into a heap struct so the
963 // spawned thread receives them, instead of silently dropping them.
964 param_types := g.param_types_for(call_key, fn_node.value)
965 if param_types.len > 0 && param_types.len == int(call_node.children_count) - 1 {
966 mut param_cts := []string{}
967 mut arg_exprs := []string{}
968 for i, pt in param_types {
969 param_cts << g.tc.c_type(pt)
970 arg_exprs << g.expr_to_string(g.a.child(&call_node, i + 1))
971 }
972 g.emit_args_spawn_expr(cfn, param_cts, arg_exprs, ret_ct)
973 return
974 }
975 }
976 } else if fn_node.kind == .selector && fn_node.children_count > 0 {
977 base_id := g.a.child(fn_node, 0)
978 base_type := g.receiver_base_type(base_id)
979 clean_type := concrete_receiver_type(base_type)
980 method_name := g.resolved_method_name_for_spawn(clean_type, fn_node.value)
981 if method_name.len > 0 {
982 param_types := g.param_types_for(method_name, fn_node.value)
983 if param_types.len > 0 {
984 receiver_type := param_types[0]
985 receiver_ct := g.tc.c_type(receiver_type)
986 base_expr := g.expr_to_string(base_id)
987 if call_node.children_count == 1 {
988 if receiver_type is types.Pointer {
989 wrapper = g.ensure_receiver_spawn_wrapper(c_name(method_name), receiver_ct,
990 ret_ct)
991 if base_type is types.Pointer {
992 arg_expr = '(${receiver_ct})(${base_expr})'
993 } else {
994 arg_expr = '(${receiver_ct})(&(${base_expr}))'
995 }
996 } else {
997 // Casting the void* thread argument straight to a struct type
998 // is invalid C, so copy the value receiver into the heap arg
999 // struct and dispatch through the argument-packing path.
1000 receiver_value := if base_type is types.Pointer {
1001 '*(${base_expr})'
1002 } else {
1003 base_expr
1004 }
1005 g.emit_args_spawn_expr(c_name(method_name), [receiver_ct], [
1006 receiver_value,
1007 ], ret_ct)
1008 return
1009 }
1010 } else if param_types.len == int(call_node.children_count) {
1011 // `spawn recv.method(a, b)` packs the receiver and arguments
1012 // into a heap struct rather than dropping the call.
1013 receiver_expr := if receiver_type is types.Pointer {
1014 if base_type is types.Pointer {
1015 '(${receiver_ct})(${base_expr})'
1016 } else {
1017 '(${receiver_ct})(&(${base_expr}))'
1018 }
1019 } else if base_type is types.Pointer {
1020 '*(${base_expr})'
1021 } else {
1022 base_expr
1023 }
1024 mut param_cts := [receiver_ct]
1025 mut arg_exprs := [receiver_expr]
1026 for i in 1 .. param_types.len {
1027 param_cts << g.tc.c_type(param_types[i])
1028 arg_exprs << g.expr_to_string(g.a.child(&call_node, i))
1029 }
1030 g.emit_args_spawn_expr(c_name(method_name), param_cts, arg_exprs, ret_ct)
1031 return
1032 }
1033 }
1034 }
1035 }
1036 if wrapper.len == 0 {
1037 g.write('(void*)0')
1038 return
1039 }
1040 tmp := g.tmp_count
1041 g.tmp_count++
1042 g.write('({ pthread_t _t${tmp}; pthread_attr_t _a${tmp}; pthread_attr_init(&_a${tmp}); ')
1043 g.write('pthread_attr_setstacksize(&_a${tmp}, 8388608); ')
1044 g.write('int _r${tmp} = pthread_create(&_t${tmp}, &_a${tmp}, ${wrapper}, (void*)(${arg_expr})); ')
1045 g.write('pthread_attr_destroy(&_a${tmp}); (void)_r${tmp}; (void*)_t${tmp}; })')
1046}
1047
1048// spawn_wrapper_body builds the thread-wrapper statement that invokes the spawned
1049// call and returns its result as a `void*`. When the callee returns a value, the
1050// result is heap-copied so `[]thread T .wait()` can recover it (the wait fn frees
1051// it); a void callee returns NULL. `post` runs after the call (e.g. `free(p);`).
1052fn spawn_wrapper_body(call_expr string, ret_ct string, post string) string {
1053 if ret_ct == 'void' || ret_ct.len == 0 {
1054 return '${call_expr}; ${post}return NULL;'
1055 }
1056 return '${ret_ct}* __tr = (${ret_ct}*)malloc(sizeof(${ret_ct})); *__tr = ${call_expr}; ${post}return (void*)__tr;'
1057}
1058
1059fn (mut g FlatGen) ensure_noarg_spawn_wrapper(cfn string, ret_ct string) string {
1060 key := 'noarg|${cfn}'
1061 if name := g.spawn_wrapper_names[key] {
1062 return name
1063 }
1064 name := c_name('${cfn}_thread_wrapper')
1065 g.spawn_wrapper_names[key] = name
1066 body := spawn_wrapper_body('${cfn}()', ret_ct, '')
1067 g.spawn_wrapper_defs << 'static void* ${name}(void* arg) { (void)arg; ${body} }'
1068 return name
1069}
1070
1071fn (mut g FlatGen) ensure_receiver_spawn_wrapper(cfn string, receiver_ct string, ret_ct string) string {
1072 key := 'receiver|${cfn}|${receiver_ct}'
1073 if name := g.spawn_wrapper_names[key] {
1074 return name
1075 }
1076 name := c_name('${cfn}_thread_wrapper')
1077 g.spawn_wrapper_names[key] = name
1078 body := spawn_wrapper_body('${cfn}((${receiver_ct})arg)', ret_ct, '')
1079 g.spawn_wrapper_defs << 'static void* ${name}(void* arg) { ${body} }'
1080 return name
1081}
1082
1083// ensure_args_spawn_wrapper registers (once per callee) a heap-arg struct plus a
1084// thread wrapper that unpacks the struct, calls the function with all arguments,
1085// and frees the struct. Returns the wrapper name and the arg struct name.
1086fn (mut g FlatGen) ensure_args_spawn_wrapper(cfn string, param_cts []string, ret_ct string) (string, string) {
1087 struct_name := c_name('${cfn}_thread_args')
1088 key := 'args|${cfn}'
1089 if name := g.spawn_wrapper_names[key] {
1090 return name, struct_name
1091 }
1092 name := c_name('${cfn}_args_thread_wrapper')
1093 g.spawn_wrapper_names[key] = name
1094 mut fields := ''
1095 mut call_args := []string{}
1096 for i, ct in param_cts {
1097 fields += '${ct} a${i}; '
1098 call_args << 'p->a${i}'
1099 }
1100 g.spawn_wrapper_defs << 'typedef struct { ${fields}} ${struct_name};'
1101 body := spawn_wrapper_body('${cfn}(${call_args.join(', ')})', ret_ct, 'free(p); ')
1102 g.spawn_wrapper_defs << 'static void* ${name}(void* arg) { ${struct_name}* p = (${struct_name}*)arg; ${body} }'
1103 return name, struct_name
1104}
1105
1106// emit_args_spawn_expr writes a statement-expression that heap-allocates the arg
1107// struct, populates it, and starts the thread on the packing wrapper.
1108fn (mut g FlatGen) emit_args_spawn_expr(cfn string, param_cts []string, arg_exprs []string, ret_ct string) {
1109 wrapper, struct_name := g.ensure_args_spawn_wrapper(cfn, param_cts, ret_ct)
1110 tmp := g.tmp_count
1111 g.tmp_count++
1112 g.write('({ ${struct_name}* _sa${tmp} = (${struct_name}*)malloc(sizeof(${struct_name})); ')
1113 for i, e in arg_exprs {
1114 g.write('_sa${tmp}->a${i} = ${e}; ')
1115 }
1116 g.write('pthread_t _t${tmp}; pthread_attr_t _at${tmp}; pthread_attr_init(&_at${tmp}); ')
1117 g.write('pthread_attr_setstacksize(&_at${tmp}, 8388608); ')
1118 g.write('int _r${tmp} = pthread_create(&_t${tmp}, &_at${tmp}, ${wrapper}, (void*)_sa${tmp}); ')
1119 g.write('pthread_attr_destroy(&_at${tmp}); (void)_r${tmp}; (void*)_t${tmp}; })')
1120}
1121
1122fn (g &FlatGen) resolved_method_name_for_spawn(clean_type types.Type, method string) string {
1123 mut type_name := clean_type.name()
1124 if clean_type is types.Struct {
1125 type_name = clean_type.name
1126 }
1127 method_name := '${type_name}.${method}'
1128 if method_name in g.tc.fn_param_types {
1129 return method_name
1130 }
1131 for alias, target in g.tc.type_aliases {
1132 if target == type_name {
1133 alias_method := '${alias}.${method}'
1134 if alias_method in g.tc.fn_param_types {
1135 return alias_method
1136 }
1137 }
1138 }
1139 return ''
1140}
1141
1142// gen_fn_in_module emits fn in module output for c.
1143fn (mut g FlatGen) gen_fn_in_module(node flat.Node, module_name string) {
1144 g.tc.cur_module = module_name
1145 g.cur_fn_name = node.value
1146 g.tc.push_scope()
1147 g.defers = []flat.NodeId{}
1148 g.fn_defers = []flat.NodeId{}
1149 g.fn_defer_counts = map[int]string{}
1150 g.defer_capture_names = []string{}
1151 g.defer_capture_types = map[string]types.Type{}
1152 g.set_cur_fn_ret(types.Type(types.void_))
1153 old_param_names := g.cur_param_names.clone()
1154 old_param_type_values := g.cur_param_type_values.clone()
1155 old_param_types := g.cur_param_types.clone()
1156 old_mut_params := g.cur_mut_params.clone()
1157 g.cur_param_names = []string{}
1158 g.cur_param_type_values = []types.Type{}
1159 g.cur_param_types = map[string]types.Type{}
1160 g.cur_mut_params = map[string]bool{}
1161 for i in 0 .. node.children_count {
1162 param_id := g.a.child(&node, i)
1163 p := g.a.node(param_id)
1164 if node_kind_id(p) == 75 && p.value.len > 0 {
1165 param_type := g.tc.parse_type(p.typ)
1166 g.cur_param_names << p.value
1167 g.cur_param_type_values << param_type
1168 g.cur_param_types[p.value] = param_type
1169 if p.op == .amp {
1170 g.cur_mut_params[p.value] = true
1171 }
1172 g.tc.cur_scope.insert(p.value, param_type)
1173 }
1174 }
1175 g.insert_cur_implicit_veb_ctx_param(node)
1176 fn_defer_ids := g.collect_function_defer_ids(node)
1177 g.prepare_function_defers(fn_defer_ids)
1178 is_entry_main := is_main_fn_in_main_module(module_name, node.value) && g.test_files.len == 0
1179 if is_entry_main {
1180 g.writeln('int main(int argc, char** argv) {')
1181 if g.has_builtins {
1182 g.writeln('\tg_main_argc = argc;')
1183 g.writeln('\tg_main_argv = argv;')
1184 }
1185 g.gen_compiler_vexe_env_setup()
1186 if g.const_runtime_inits.len > 0 || g.runtime_inits.len > 0 || g.module_init_fns.len > 0
1187 || g.global_inits.len > 0 {
1188 g.writeln('\t_vinit();')
1189 }
1190 } else {
1191 ret_type := g.fn_node_return_type(node, module_name)
1192 g.set_cur_fn_ret(ret_type)
1193 g.write(g.fn_return_type_name(ret_type))
1194 g.write(' ')
1195 g.write(g.fn_c_name_in_module(module_name, node.value))
1196 g.write('(')
1197 g.write_fn_node_params(node)
1198 g.writeln(') {')
1199 }
1200 g.indent++
1201 g.gen_function_defer_prelude()
1202
1203 for i in 0 .. node.children_count {
1204 id := g.a.child(&node, i)
1205 child := g.a.node(id)
1206 if child.kind != .param {
1207 g.tc.cur_module = module_name
1208 g.gen_node(id)
1209 }
1210 }
1211 g.gen_all_defers()
1212 if is_entry_main {
1213 g.writeln('return 0;')
1214 } else if g.cur_fn_ret_is_optional {
1215 ct := g.optional_type_name(g.cur_fn_ret)
1216 g.writeln('return (${ct}){.ok = true};')
1217 }
1218 g.indent--
1219 g.writeln('}')
1220 g.writeln('')
1221 if !is_entry_main {
1222 g.gen_export_wrapper_for_fn(node, module_name)
1223 }
1224 g.cur_param_names = old_param_names.clone()
1225 g.cur_param_type_values = old_param_type_values.clone()
1226 g.cur_param_types = old_param_types.clone()
1227 g.cur_mut_params = old_mut_params.clone()
1228 g.tc.pop_scope()
1229}
1230
1231fn (mut g FlatGen) gen_export_wrapper_for_fn(node flat.Node, module_name string) {
1232 export_name := g.export_fn_name_in_module(module_name, node.value) or { return }
1233 canonical_name := g.fn_c_name_in_module(module_name, node.value)
1234 ret_type := g.fn_node_return_type(node, module_name)
1235 ret_ct := g.fn_return_type_name(ret_type)
1236 g.write(ret_ct)
1237 g.write(' ')
1238 g.write(export_name)
1239 g.write('(')
1240 g.write_fn_node_params(node)
1241 g.writeln(') {')
1242 g.indent++
1243 args := g.export_wrapper_arg_names(node)
1244 call := '${canonical_name}(${args.join(', ')})'
1245 if ret_type is types.Void {
1246 g.writeln('${call};')
1247 } else {
1248 g.writeln('return ${call};')
1249 }
1250 g.indent--
1251 g.writeln('}')
1252 g.writeln('')
1253}
1254
1255fn (mut g FlatGen) export_wrapper_arg_names(node flat.Node) []string {
1256 mut args := []string{}
1257 needs_implicit_ctx := g.fn_needs_implicit_veb_ctx(node)
1258 insert_implicit_ctx_after_first := needs_implicit_ctx && g.fn_has_receiver_param(node)
1259 mut written := 0
1260 mut implicit_ctx_written := false
1261 for i in 0 .. node.children_count {
1262 param_id := g.a.child(&node, i)
1263 p := g.a.node(param_id)
1264 if p.kind != .param {
1265 continue
1266 }
1267 param_name := if p.value == '_' { '_${written}' } else { c_name(p.value) }
1268 args << param_name
1269 written++
1270 if insert_implicit_ctx_after_first && !implicit_ctx_written {
1271 args << 'ctx'
1272 written++
1273 implicit_ctx_written = true
1274 }
1275 }
1276 if needs_implicit_ctx && !implicit_ctx_written {
1277 args << 'ctx'
1278 }
1279 return args
1280}
1281
1282fn (mut g FlatGen) gen_top_level_main(stmts []TopLevelStmt) {
1283 old_tc_file := g.tc.cur_file
1284 old_tc_module := g.tc.cur_module
1285 g.tc.cur_module = 'main'
1286 old_fn_name := g.cur_fn_name
1287 g.cur_fn_name = 'main'
1288 g.tc.push_scope()
1289 g.defers = []flat.NodeId{}
1290 g.fn_defers = []flat.NodeId{}
1291 g.fn_defer_counts = map[int]string{}
1292 g.defer_capture_names = []string{}
1293 g.defer_capture_types = map[string]types.Type{}
1294 g.set_cur_fn_ret(types.Type(types.void_))
1295 old_param_names := g.cur_param_names.clone()
1296 old_param_type_values := g.cur_param_type_values.clone()
1297 old_param_types := g.cur_param_types.clone()
1298 old_mut_params := g.cur_mut_params.clone()
1299 g.cur_param_names = []string{}
1300 g.cur_param_type_values = []types.Type{}
1301 g.cur_param_types = map[string]types.Type{}
1302 g.cur_mut_params = map[string]bool{}
1303 mut fn_defer_ids := []flat.NodeId{}
1304 for stmt in stmts {
1305 g.collect_function_defer_ids_from(stmt.id, mut fn_defer_ids)
1306 }
1307 g.prepare_function_defers(fn_defer_ids)
1308 g.writeln('int main(int argc, char** argv) {')
1309 if g.has_builtins {
1310 g.writeln('\tg_main_argc = argc;')
1311 g.writeln('\tg_main_argv = argv;')
1312 }
1313 g.gen_compiler_vexe_env_setup()
1314 if g.const_runtime_inits.len > 0 || g.runtime_inits.len > 0 || g.module_init_fns.len > 0
1315 || g.global_inits.len > 0 {
1316 g.writeln('\t_vinit();')
1317 }
1318 g.indent++
1319 g.gen_function_defer_prelude()
1320 for stmt in stmts {
1321 g.tc.cur_file = stmt.file
1322 g.tc.cur_module = stmt.module
1323 g.gen_top_level_main_stmt(stmt.id)
1324 }
1325 g.gen_all_defers()
1326 g.writeln('return 0;')
1327 g.indent--
1328 g.writeln('}')
1329 g.writeln('')
1330 g.cur_param_names = old_param_names.clone()
1331 g.cur_param_type_values = old_param_type_values.clone()
1332 g.cur_param_types = old_param_types.clone()
1333 g.cur_mut_params = old_mut_params.clone()
1334 g.cur_fn_name = old_fn_name
1335 g.tc.cur_file = old_tc_file
1336 g.tc.cur_module = old_tc_module
1337 g.tc.pop_scope()
1338}
1339
1340fn (mut g FlatGen) gen_top_level_main_stmt(id flat.NodeId) {
1341 if int(id) < 0 || int(id) >= g.a.nodes.len {
1342 return
1343 }
1344 node := g.a.nodes[int(id)]
1345 if node.kind in [.block, .comptime_if] {
1346 for i in 0 .. node.children_count {
1347 child_id := g.a.child(&node, i)
1348 if g.cgen_is_top_level_stmt(child_id) {
1349 g.gen_top_level_main_stmt(child_id)
1350 }
1351 }
1352 return
1353 }
1354 g.gen_node(id)
1355}
1356
1357fn (mut g FlatGen) gen_test_main() {
1358 tests, hooks := g.test_harness_fns()
1359 g.tc.cur_module = 'main'
1360 g.writeln('int main(int argc, char** argv) {')
1361 if g.has_builtins {
1362 g.writeln('\tg_main_argc = argc;')
1363 g.writeln('\tg_main_argv = argv;')
1364 }
1365 g.gen_compiler_vexe_env_setup()
1366 if g.const_runtime_inits.len > 0 || g.runtime_inits.len > 0 || g.module_init_fns.len > 0
1367 || g.global_inits.len > 0 {
1368 g.writeln('\t_vinit();')
1369 }
1370 g.indent++
1371 if hooks.testsuite_begin.len > 0 {
1372 g.writeln('${hooks.testsuite_begin}();')
1373 }
1374 for idx, test_fn in tests {
1375 if hooks.before_each.len > 0 {
1376 g.writeln('${hooks.before_each}();')
1377 }
1378 g.gen_test_fn_call(test_fn, hooks, idx)
1379 if hooks.after_each.len > 0 {
1380 g.writeln('${hooks.after_each}();')
1381 }
1382 }
1383 if hooks.testsuite_end.len > 0 {
1384 g.writeln('${hooks.testsuite_end}();')
1385 }
1386 g.writeln('return 0;')
1387 g.indent--
1388 g.writeln('}')
1389 g.writeln('')
1390}
1391
1392fn (mut g FlatGen) gen_test_fn_call(test_fn TestHarnessFn, hooks TestHarnessHooks, idx int) {
1393 if test_fn.ret is types.OptionType || test_fn.ret is types.ResultType {
1394 ct := g.optional_type_name(test_fn.ret)
1395 tmp_name := '__test_opt_${idx}'
1396 g.writeln('${ct} ${tmp_name} = ${test_fn.c_name}();')
1397 g.writeln('if (!${tmp_name}.ok) {')
1398 g.indent++
1399 g.writeln('fprintf(stderr, "test failed: ${c_escape(test_fn.name)}\\n");')
1400 if hooks.after_each.len > 0 {
1401 g.writeln('${hooks.after_each}();')
1402 }
1403 if hooks.testsuite_end.len > 0 {
1404 g.writeln('${hooks.testsuite_end}();')
1405 }
1406 g.writeln('return 1;')
1407 g.indent--
1408 g.writeln('}')
1409 return
1410 }
1411 g.writeln('${test_fn.c_name}();')
1412}
1413
1414fn (g &FlatGen) test_harness_fns() ([]TestHarnessFn, TestHarnessHooks) {
1415 mut tests := []TestHarnessFn{}
1416 mut hooks := TestHarnessHooks{}
1417 for file_idx, file_node in g.a.nodes {
1418 if !g.is_user_test_file_node(file_idx, file_node) {
1419 continue
1420 }
1421 module_name := g.test_file_module_name(file_node)
1422 mut decl_ids := []flat.NodeId{}
1423 g.collect_test_harness_decl_ids(file_node, mut decl_ids)
1424 for child_id in decl_ids {
1425 child := g.a.nodes[int(child_id)]
1426 cname := qualified_fn_name_in_module(module_name, child.value)
1427 match child.value {
1428 'testsuite_begin' {
1429 if hooks.testsuite_begin.len == 0 && g.is_supported_test_hook_decl(child) {
1430 hooks.testsuite_begin = cname
1431 }
1432 }
1433 'testsuite_end' {
1434 if hooks.testsuite_end.len == 0 && g.is_supported_test_hook_decl(child) {
1435 hooks.testsuite_end = cname
1436 }
1437 }
1438 'before_each' {
1439 if hooks.before_each.len == 0 && g.is_supported_test_hook_decl(child) {
1440 hooks.before_each = cname
1441 }
1442 }
1443 'after_each' {
1444 if hooks.after_each.len == 0 && g.is_supported_test_hook_decl(child) {
1445 hooks.after_each = cname
1446 }
1447 }
1448 else {
1449 if child.value.starts_with('test_') && g.is_supported_test_fn_decl(child) {
1450 tests << TestHarnessFn{
1451 name: child.value
1452 c_name: cname
1453 ret: g.tc.parse_type(child.typ)
1454 }
1455 }
1456 }
1457 }
1458 }
1459 }
1460 return tests, hooks
1461}
1462
1463fn (g &FlatGen) collect_test_harness_decl_ids(node flat.Node, mut ids []flat.NodeId) {
1464 if node.kind != .file && node.kind != .block {
1465 return
1466 }
1467 for i in 0 .. node.children_count {
1468 child_id := g.a.child(&node, i)
1469 if int(child_id) < g.a.user_code_start {
1470 continue
1471 }
1472 child := g.a.nodes[int(child_id)]
1473 if child.kind == .fn_decl {
1474 ids << child_id
1475 } else if child.kind == .block {
1476 g.collect_test_harness_decl_ids(child, mut ids)
1477 }
1478 }
1479}
1480
1481fn (g &FlatGen) is_supported_test_fn_decl(node flat.Node) bool {
1482 if node.generic_params.len > 0 {
1483 return false
1484 }
1485 if g.test_fn_param_count(node) != 0 {
1486 return false
1487 }
1488 return test_harness_fn_return_supported(g.tc.parse_type(node.typ))
1489}
1490
1491fn (g &FlatGen) is_supported_test_hook_decl(node flat.Node) bool {
1492 if node.generic_params.len > 0 {
1493 return false
1494 }
1495 return g.test_fn_param_count(node) == 0 && g.tc.parse_type(node.typ) is types.Void
1496}
1497
1498fn (g &FlatGen) test_fn_param_count(node flat.Node) int {
1499 mut count := 0
1500 for i in 0 .. node.children_count {
1501 child := g.a.child_node(&node, i)
1502 if child.kind == .param {
1503 count++
1504 }
1505 }
1506 return count
1507}
1508
1509fn test_harness_fn_return_supported(ret types.Type) bool {
1510 return ret is types.Void || ret is types.OptionType || ret is types.ResultType
1511}
1512
1513fn (g &FlatGen) is_user_test_file_node(file_idx int, file_node flat.Node) bool {
1514 if file_idx < g.a.user_code_start || file_node.kind != .file || file_node.children_count == 0 {
1515 return false
1516 }
1517 return g.test_files[file_node.value]
1518}
1519
1520fn (g &FlatGen) test_file_module_name(file_node flat.Node) string {
1521 for i in 0 .. file_node.children_count {
1522 child := g.a.child_node(&file_node, i)
1523 if child.kind == .module_decl {
1524 return child.value
1525 }
1526 }
1527 return ''
1528}
1529
1530// collect_function_defer_ids updates collect function defer ids state for c.
1531fn (mut g FlatGen) collect_function_defer_ids(node flat.Node) []flat.NodeId {
1532 mut ids := []flat.NodeId{}
1533 for i in 0 .. node.children_count {
1534 g.collect_function_defer_ids_from(g.a.child(&node, i), mut ids)
1535 }
1536 return ids
1537}
1538
1539// collect_function_defer_ids_from updates collect function defer ids from state for c.
1540fn (mut g FlatGen) collect_function_defer_ids_from(id flat.NodeId, mut ids []flat.NodeId) {
1541 if !g.valid_node_id(id) {
1542 return
1543 }
1544 node := g.a.nodes[int(id)]
1545 if node.kind == .fn_decl || node.kind == .c_fn_decl || node.kind == .fn_literal {
1546 return
1547 }
1548 if node.kind == .defer_stmt && node.value == 'function' {
1549 ids << id
1550 return
1551 }
1552 for i in 0 .. node.children_count {
1553 g.collect_function_defer_ids_from(g.a.child(&node, i), mut ids)
1554 }
1555}
1556
1557// prepare_function_defers supports prepare function defers handling for FlatGen.
1558fn (mut g FlatGen) prepare_function_defers(fn_defer_ids []flat.NodeId) {
1559 for idx, defer_id in fn_defer_ids {
1560 g.fn_defer_counts[int(defer_id)] = '${c_name(g.cur_fn_name)}_defer_${idx}_count'
1561 defer_node := g.a.nodes[int(defer_id)]
1562 if defer_node.children_count > 0 {
1563 g.collect_function_defer_captures(g.a.child(&defer_node, 0))
1564 }
1565 }
1566}
1567
1568// collect_function_defer_captures updates collect function defer captures state for c.
1569fn (mut g FlatGen) collect_function_defer_captures(id flat.NodeId) {
1570 if !g.valid_node_id(id) {
1571 return
1572 }
1573 node := g.a.nodes[int(id)]
1574 if node.kind == .fn_decl || node.kind == .c_fn_decl || node.kind == .fn_literal {
1575 return
1576 }
1577 if node.kind == .ident {
1578 g.add_function_defer_capture(id, node.value)
1579 }
1580 for i in 0 .. node.children_count {
1581 g.collect_function_defer_captures(g.a.child(&node, i))
1582 }
1583}
1584
1585// add_function_defer_capture updates add function defer capture state for FlatGen.
1586fn (mut g FlatGen) add_function_defer_capture(id flat.NodeId, name string) {
1587 if name.len == 0 || name == '_' || name in g.cur_param_names || g.has_import_alias(name)
1588 || name in g.global_modules || name in g.defer_capture_types {
1589 return
1590 }
1591 typ := g.usable_expr_type(id)
1592 if typ is types.Void || typ is types.Unknown || typ is types.FnType {
1593 return
1594 }
1595 ct := g.tc.c_type(typ)
1596 if ct.len == 0 || ct == 'void' || ct.starts_with('fn_ptr:') {
1597 return
1598 }
1599 g.defer_capture_names << name
1600 g.defer_capture_types[name] = typ
1601}
1602
1603// gen_function_defer_prelude emits function defer prelude output for c.
1604fn (mut g FlatGen) gen_function_defer_prelude() {
1605 for _, count_name in g.fn_defer_counts {
1606 g.writeln('int ${count_name} = 0;')
1607 }
1608 for name in g.defer_capture_names {
1609 typ := g.defer_capture_types[name] or { continue }
1610 ct := g.tc.c_type(typ)
1611 g.write('${ct} ${c_name(name)} = ')
1612 g.gen_default_value_for_type(typ)
1613 g.writeln(';')
1614 g.tc.cur_scope.insert(name, typ)
1615 }
1616}
1617
1618// set_cur_fn_ret updates set cur fn ret state for c.
1619fn (mut g FlatGen) set_cur_fn_ret(ret_type types.Type) {
1620 g.cur_fn_ret = ret_type
1621 g.cur_fn_ret_is_optional = false
1622 g.cur_fn_ret_base = types.Type(types.void_)
1623 if ret_type is types.OptionType {
1624 g.cur_fn_ret_is_optional = true
1625 g.cur_fn_ret_base = ret_type.base_type
1626 } else if ret_type is types.ResultType {
1627 g.cur_fn_ret_is_optional = true
1628 g.cur_fn_ret_base = ret_type.base_type
1629 }
1630}
1631
1632// gen_compiler_vexe_env_setup emits compiler vexe env setup output for c.
1633fn (mut g FlatGen) gen_compiler_vexe_env_setup() {
1634 if g.compiler_vroot.len == 0 {
1635 return
1636 }
1637 root := c_escape(g.compiler_vroot)
1638 g.writeln('\tif (getenv("VEXE") == NULL || getenv("VEXE")[0] == 0) {')
1639 g.writeln('\t\tconst char* v3_arg0 = argc > 0 ? argv[0] : "v";')
1640 g.writeln("\t\tconst char* v3_base = strrchr(v3_arg0, '/');")
1641 g.writeln('\t\tv3_base = v3_base == NULL ? v3_arg0 : v3_base + 1;')
1642 g.writeln('\t\tif (v3_base[0] == 0) v3_base = "v";')
1643 g.writeln('\t\tconst char* v3_checkout_root = "${root}";')
1644 g.writeln('\t\tchar v3_checkout_vexe[4096];')
1645 g.writeln('\t\tsnprintf(v3_checkout_vexe, sizeof(v3_checkout_vexe), "%s/%s", v3_checkout_root, v3_base);')
1646 g.writeln('\t\tif (access(v3_checkout_vexe, F_OK) != 0) snprintf(v3_checkout_vexe, sizeof(v3_checkout_vexe), "%s/v", v3_checkout_root);')
1647 g.writeln('\t\tchar v3_src_real[4096];')
1648 g.writeln('\t\tchar* v3_src_real_result = realpath(v3_arg0, v3_src_real);')
1649 g.writeln('\t\tconst char* v3_vexe = v3_src_real_result != NULL ? v3_src_real : v3_arg0;')
1650 g.writeln('\t\tif (access(v3_checkout_vexe, F_OK) == 0) v3_vexe = v3_checkout_vexe;')
1651 g.writeln('\t\tif (v3_vexe[0] != 0) {')
1652 g.writeln('#ifdef _WIN32')
1653 g.writeln('\t\t\t_putenv_s("VEXE", v3_vexe);')
1654 g.writeln('#else')
1655 g.writeln('\t\t\tsetenv("VEXE", v3_vexe, 1);')
1656 g.writeln('#endif')
1657 g.writeln('\t\t}')
1658 g.writeln('\t}')
1659}
1660
1661// gen_defers emits defers output for c.
1662fn (mut g FlatGen) gen_defers() {
1663 g.gen_defers_from(0)
1664}
1665
1666// gen_all_defers emits all defers output for c.
1667fn (mut g FlatGen) gen_all_defers() {
1668 g.gen_defers()
1669 g.gen_fn_defers()
1670}
1671
1672// gen_defers_from emits defers from output for c.
1673fn (mut g FlatGen) gen_defers_from(start int) {
1674 if g.defers.len == 0 {
1675 return
1676 }
1677 mut i := g.defers.len
1678 for i > start {
1679 i--
1680 defer_body := g.a.nodes[int(g.defers[i])]
1681 g.writeln('{')
1682 g.indent++
1683 for j in 0 .. defer_body.children_count {
1684 g.gen_node(g.a.child(&defer_body, j))
1685 }
1686 g.indent--
1687 g.writeln('}')
1688 }
1689}
1690
1691// gen_fn_defers emits fn defers output for c.
1692fn (mut g FlatGen) gen_fn_defers() {
1693 if g.fn_defers.len == 0 {
1694 return
1695 }
1696 mut i := g.fn_defers.len
1697 for i > 0 {
1698 i--
1699 defer_id := g.fn_defers[i]
1700 defer_node := g.a.nodes[int(defer_id)]
1701 defer_body := g.a.nodes[int(g.a.child(&defer_node, 0))]
1702 count_name := g.fn_defer_counts[int(defer_id)] or { '0' }
1703 iter_name := '${count_name}_i'
1704 g.writeln('for (int ${iter_name} = 0; ${iter_name} < ${count_name}; ${iter_name}++) {')
1705 g.indent++
1706 for j in 0 .. defer_body.children_count {
1707 g.gen_node(g.a.child(&defer_body, j))
1708 }
1709 g.indent--
1710 g.writeln('}')
1711 }
1712}
1713
1714// trim_defers transforms trim defers data for c.
1715fn (mut g FlatGen) trim_defers(start int) {
1716 if start >= g.defers.len {
1717 return
1718 }
1719 g.defers = g.defers[..start].clone()
1720}
1721
1722// gen_ierror_from_error_call converts gen ierror from error call data for c.
1723fn (mut g FlatGen) gen_ierror_from_error_call(node flat.Node) {
1724 fn_node := g.a.child_node(&node, 0)
1725 g.write('(IError){._typ = 0, ._object = NULL, .message = ')
1726 if node.children_count > 1 {
1727 g.gen_expr(g.a.child(&node, 1))
1728 } else {
1729 g.write('_S("")')
1730 }
1731 g.write(', .code = ')
1732 if fn_node.value == 'error_with_code' && node.children_count > 2 {
1733 g.gen_expr(g.a.child(&node, 2))
1734 } else {
1735 g.write('0')
1736 }
1737 g.write('}')
1738}
1739
1740// gen_optional_error_from_call converts gen optional error from call data for c.
1741fn (mut g FlatGen) gen_optional_error_from_call(ct string, node flat.Node) {
1742 g.write('(${ct}){.ok = false, .err = ')
1743 g.gen_ierror_from_error_call(node)
1744 g.write('}')
1745}
1746
1747// gen_call emits call output for c.
1748fn (mut g FlatGen) gen_call(id flat.NodeId, node flat.Node) {
1749 fn_node := g.a.child_node(&node, 0)
1750 fn_name := fn_node.value
1751 target_name := g.call_target_name(g.a.child(&node, 0))
1752 if target_name == 'array.pointers' || fn_name == 'array.pointers' {
1753 if node.children_count > 1 {
1754 arg_id := g.a.child(&node, 1)
1755 g.gen_array_pointers_expr(arg_id, g.tc.resolve_type(arg_id) is types.Pointer)
1756 } else {
1757 g.write('array_new(sizeof(voidptr), 0, 0)')
1758 }
1759 return
1760 }
1761 if target_name in ['json.decode', 'json2.decode'] {
1762 ret_type := g.json_decode_result_type(g.a.child(&node, 0)) or {
1763 g.call_default_return_type(id)
1764 }
1765 g.gen_default_value_for_type(ret_type)
1766 return
1767 }
1768 if target_name in ['json.encode', 'json2.encode']
1769 || (g.tc.cur_module == 'json2' && target_name == 'encode') {
1770 g.gen_default_value_for_type(g.call_default_return_type(id))
1771 return
1772 }
1773 if g.is_veb_json_result_call(fn_node) {
1774 g.gen_default_value_for_type(g.call_default_return_type(id))
1775 return
1776 }
1777 if target_name == 'veb.run_at' {
1778 g.gen_default_value_for_type(g.call_default_return_type(id))
1779 return
1780 }
1781 if g.is_missing_middleware_use_call(fn_node) {
1782 g.write('0')
1783 return
1784 }
1785 if fn_node.kind == .selector && fn_node.value == 'str' {
1786 base_type := g.tc.resolve_type(g.a.child(fn_node, 0))
1787 clean_type := concrete_receiver_type(base_type)
1788 if clean_type is types.Enum {
1789 if _ := g.enum_receiver_method_name(clean_type, fn_node.value) {
1790 // Let normal method call generation handle custom enum str methods.
1791 } else {
1792 g.gen_enum_str_call(fn_node, clean_type)
1793 return
1794 }
1795 }
1796 }
1797 if fn_node.kind == .selector && fn_node.value == 'close' {
1798 base_id := g.a.child(fn_node, 0)
1799 base_type := g.tc.resolve_type(base_id)
1800 if base_type is types.Channel {
1801 panic('channel close should be lowered by v3 transform')
1802 }
1803 }
1804 match fn_name {
1805 'array_new' {
1806 g.write('array_new(')
1807 for i in 1 .. node.children_count {
1808 if i > 1 {
1809 g.write(', ')
1810 }
1811 arg_id := g.a.child(&node, i)
1812 arg_node := g.a.nodes[int(arg_id)]
1813 if i == 1 {
1814 arg_expr := g.expr_to_string(arg_id)
1815 if raw_sizeof := raw_sizeof_arg_value(arg_expr) {
1816 if raw_sizeof_needs_normalization(raw_sizeof) {
1817 g.write('sizeof(${g.sizeof_target(raw_sizeof)})')
1818 } else {
1819 g.write(arg_expr)
1820 }
1821 } else {
1822 g.write(arg_expr)
1823 }
1824 } else if arg_node.kind == .sizeof_expr {
1825 g.write('sizeof(${g.sizeof_target(arg_node.value)})')
1826 } else if raw_sizeof := raw_sizeof_arg_value(arg_node.value) {
1827 if raw_sizeof_needs_normalization(raw_sizeof) {
1828 g.write('sizeof(${g.sizeof_target(raw_sizeof)})')
1829 } else {
1830 g.gen_expr(arg_id)
1831 }
1832 } else {
1833 g.gen_expr(arg_id)
1834 }
1835 }
1836 g.write(')')
1837 return
1838 }
1839 'new_map' {
1840 if node.typ.starts_with('map[') {
1841 map_type := g.tc.parse_type(node.typ)
1842 if map_type is types.Map {
1843 g.write_new_map(map_type.key_type, map_type.value_type)
1844 return
1845 }
1846 }
1847 g.write('new_map(')
1848 g.gen_call_args(fn_name, node, 1)
1849 g.write(')')
1850 return
1851 }
1852 'panic' {
1853 g.write('panic(')
1854 if node.children_count > 1 {
1855 arg_id := g.a.child(&node, 1)
1856 arg_type := g.tc.resolve_type(arg_id)
1857 if arg_type is types.Struct && arg_type.name == 'IError' {
1858 g.gen_expr(arg_id)
1859 g.write('.message')
1860 } else {
1861 g.gen_expr(arg_id)
1862 }
1863 }
1864 g.write(')')
1865 return
1866 }
1867 'error' {
1868 if g.cur_fn_ret_is_optional {
1869 ct := g.optional_type_name(g.cur_fn_ret)
1870 g.gen_optional_error_from_call(ct, node)
1871 } else {
1872 g.gen_ierror_from_error_call(node)
1873 }
1874 return
1875 }
1876 'error_with_code' {
1877 if g.cur_fn_ret_is_optional {
1878 ct := g.optional_type_name(g.cur_fn_ret)
1879 g.gen_optional_error_from_call(ct, node)
1880 } else {
1881 g.gen_ierror_from_error_call(node)
1882 }
1883 return
1884 }
1885 else {
1886 mut is_method := false
1887 mut is_c_call := false
1888 mut method_name := ''
1889 mut base_id := flat.NodeId(0)
1890 if fn_node.kind == .selector {
1891 base := g.a.child_node(fn_node, 0)
1892 base_is_local := if base.kind == .ident {
1893 (g.tc.cur_scope.lookup(base.value) or { types.Type(types.void_) }) !is types.Void
1894 } else {
1895 false
1896 }
1897 if base.kind == .ident && base.value == 'C' {
1898 g.write(g.direct_call_name('C.${fn_node.value}'))
1899 is_c_call = true
1900 } else if g.is_flag_enum_method(fn_node) {
1901 g.gen_flag_enum_call(node)
1902 return
1903 } else if base.kind == .ident && !base_is_local && g.has_import_alias(base.value) {
1904 mod := g.import_alias_module(base.value) or { '' }
1905 full_name := '${mod}.${fn_node.value}'
1906 if full_name in g.tc.type_aliases || full_name in g.tc.structs
1907 || full_name in g.tc.enum_names || full_name in g.tc.sum_types {
1908 target_type := g.tc.parse_type(full_name)
1909 ct := g.tc.c_type(target_type)
1910 if target_type is types.SumType && node.children_count > 1 {
1911 g.gen_sum_cast_expr(target_type, g.a.child(&node, 1))
1912 } else {
1913 g.write('(${ct})(')
1914 for i in 1 .. node.children_count {
1915 if i > 1 {
1916 g.write(', ')
1917 }
1918 g.gen_expr(g.a.child(&node, i))
1919 }
1920 g.write(')')
1921 }
1922 return
1923 }
1924 g.write(c_name(full_name))
1925 g.write('(')
1926 g.gen_call_args(full_name, node, 1)
1927 g.write(')')
1928 return
1929 } else if base.kind == .ident && !base_is_local
1930 && g.static_method_fn_name(base.value, fn_node.value) != none {
1931 // `Type.method(...)` where the base ident names a (possibly
1932 // imported) type, not a value — e.g. `Animation.load(path)` inside
1933 // the type's own module. Resolve to the module-qualified static fn.
1934 static_fn := g.static_method_fn_name(base.value, fn_node.value) or { '' }
1935 g.write(g.direct_call_name(static_fn))
1936 g.write('(')
1937 // Lower the arguments through the ordinary call path so a static method
1938 // parameter that needs coercion — option/result wrapping, fn-pointer alias
1939 // callbacks, sum variants, fixed-array decay, variadic/`@[params]` handling —
1940 // is emitted against its expected type, not as the raw expression.
1941 g.gen_call_args(static_fn, node, 1)
1942 g.write(')')
1943 return
1944 } else if base.kind == .selector {
1945 inner := g.a.child_node(base, 0)
1946 inner_is_local := if inner.kind == .ident {
1947 (g.tc.cur_scope.lookup(inner.value) or { types.Type(types.void_) }) !is types.Void
1948 } else {
1949 false
1950 }
1951 if inner.kind == .ident && !inner_is_local {
1952 if mod := g.import_alias_module(inner.value) {
1953 full_name := '${mod}.${base.value}.${fn_node.value}'
1954 g.write(c_name(full_name))
1955 g.write('(')
1956 g.gen_call_args(full_name, node, 1)
1957 g.write(')')
1958 return
1959 }
1960 }
1961 {
1962 base_type := g.tc.resolve_type(g.a.child(fn_node, 0))
1963 clean_type := concrete_receiver_type(base_type)
1964 if g.gen_interface_method_call(node, fn_node, base_type) {
1965 return
1966 }
1967 if g.gen_fn_field_call(node, fn_node, base_type) {
1968 return
1969 }
1970 if arr := array_like_type(clean_type) {
1971 g.gen_array_method_call(node, fn_node, arr)
1972 return
1973 }
1974 if clean_type is types.ArrayFixed && fn_node.value == 'bytestr' {
1975 g.write('u8__vstring_with_len((u8*)')
1976 g.gen_expr(g.a.child(fn_node, 0))
1977 len_expr := g.fixed_array_len_value(clean_type)
1978 g.write(', ${len_expr})')
1979 return
1980 }
1981 if g.gen_pointer_builtin_method_call(node, fn_node, base_type) {
1982 return
1983 }
1984 if clean_type is types.Map {
1985 if fn_node.value == 'str' {
1986 g.gen_map_str_expr(g.a.child(fn_node, 0), base_type)
1987 return
1988 } else if fn_node.value == 'clone' {
1989 g.write('map__clone(')
1990 g.gen_map_ref_arg(g.a.child(fn_node, 0), base_type)
1991 g.write(')')
1992 return
1993 } else if fn_node.value in ['keys', 'values', 'delete', 'clear', 'free',
1994 'move', 'reserve'] {
1995 panic('map method `${fn_node.value}` should be lowered by v3 transform')
1996 }
1997 }
1998 if clean_type is types.String {
1999 method_name = 'string.${fn_node.value}'
2000 if method_name in g.tc.fn_param_types {
2001 is_method = true
2002 base_id = g.a.child(fn_node, 0)
2003 g.write_method_c_name(id, node, method_name)
2004 } else {
2005 g.write('string__${fn_node.value}(')
2006 g.gen_expr(g.a.child(fn_node, 0))
2007 for i in 1 .. node.children_count {
2008 g.write(', ')
2009 g.gen_expr(g.a.child(&node, i))
2010 }
2011 g.write(')')
2012 return
2013 }
2014 }
2015 if !is_method && (clean_type is types.Primitive
2016 || clean_type is types.ISize || clean_type is types.USize
2017 || clean_type is types.Rune) {
2018 tname := clean_type.name()
2019 prim_method := '${tname}.${fn_node.value}'
2020 if prim_method in g.tc.fn_param_types {
2021 is_method = true
2022 base_id = g.a.child(fn_node, 0)
2023 g.write(c_name(prim_method))
2024 } else {
2025 mut prim_found := false
2026 if alias_method := g.find_alias_method(tname, fn_node.value) {
2027 is_method = true
2028 prim_found = true
2029 base_id = g.a.child(fn_node, 0)
2030 g.write(c_name(alias_method))
2031 }
2032 if !prim_found {
2033 alt_name := g.find_prim_method(fn_node.value)
2034 if alt_name.len > 0 {
2035 is_method = true
2036 base_id = g.a.child(fn_node, 0)
2037 g.write(alt_name)
2038 }
2039 }
2040 }
2041 }
2042 if !is_method {
2043 mut struct_name := clean_type.name()
2044 if clean_type is types.Struct {
2045 struct_name = clean_type.name
2046 }
2047 method_name = g.resolve_method_name(struct_name, fn_node.value)
2048 if method_name.len == 0 {
2049 for alias, target in g.tc.type_aliases {
2050 if target == struct_name {
2051 alias_method := '${alias}.${fn_node.value}'
2052 if alias_method in g.tc.fn_param_types {
2053 method_name = alias_method
2054 break
2055 }
2056 }
2057 }
2058 }
2059 if method_name.len > 0 && method_name in g.tc.fn_param_types {
2060 is_method = true
2061 base_id = g.a.child(fn_node, 0)
2062 g.write_method_c_name(id, node, method_name)
2063 } else {
2064 str_method := 'string.${fn_node.value}'
2065 if str_method in g.tc.fn_param_types {
2066 is_method = true
2067 method_name = str_method
2068 base_id = g.a.child(fn_node, 0)
2069 g.write(c_name(str_method))
2070 } else if embedded_method := g.embedded_method_name_for_type(clean_type,
2071 fn_node.value)
2072 {
2073 is_method = true
2074 method_name = embedded_method
2075 base_id = g.a.child(fn_node, 0)
2076 g.write(c_name(embedded_method))
2077 } else if struct_name.len > 0 {
2078 is_method = true
2079 base_id = g.a.child(fn_node, 0)
2080 fallback_method := '${struct_name}.${fn_node.value}'
2081 g.write_method_c_name(id, node, fallback_method)
2082 } else {
2083 g.gen_expr(g.a.child(&node, 0))
2084 }
2085 }
2086 }
2087 }
2088 } else if base.kind == .ident
2089 && (base.value in g.tc.structs || base.value in g.tc.enum_names || g.tc.qualify_name(base.value) in g.tc.structs
2090 || g.tc.qualify_name(base.value) in g.tc.enum_names) {
2091 qname := if base.value in g.tc.structs || base.value in g.tc.enum_names {
2092 base.value
2093 } else {
2094 g.tc.qualify_name(base.value)
2095 }
2096 static_name := '${qname}.${fn_node.value}'
2097 g.write(c_name(static_name))
2098 g.write('(')
2099 for i in 1 .. node.children_count {
2100 if i > 1 {
2101 g.write(', ')
2102 }
2103 g.gen_expr(g.a.child(&node, i))
2104 }
2105 g.write(')')
2106 return
2107 } else {
2108 base_type := g.tc.resolve_type(g.a.child(fn_node, 0))
2109 clean_type := concrete_receiver_type(base_type)
2110 if g.gen_interface_method_call(node, fn_node, base_type) {
2111 return
2112 }
2113 if g.gen_fn_field_call(node, fn_node, base_type) {
2114 return
2115 }
2116 if arr := array_like_type(clean_type) {
2117 g.gen_array_method_call(node, fn_node, arr)
2118 return
2119 }
2120 if clean_type is types.ArrayFixed && fn_node.value == 'bytestr' {
2121 g.write('u8__vstring_with_len((u8*)')
2122 g.gen_expr(g.a.child(fn_node, 0))
2123 len_expr := g.fixed_array_len_value(clean_type)
2124 g.write(', ${len_expr})')
2125 return
2126 }
2127 if g.gen_pointer_builtin_method_call(node, fn_node, base_type) {
2128 return
2129 }
2130 if clean_type is types.Map {
2131 if fn_node.value == 'str' {
2132 g.gen_map_str_expr(g.a.child(fn_node, 0), base_type)
2133 return
2134 } else if fn_node.value == 'clone' {
2135 g.write('map__clone(')
2136 g.gen_map_ref_arg(g.a.child(fn_node, 0), base_type)
2137 g.write(')')
2138 return
2139 } else if fn_node.value in ['keys', 'values', 'delete', 'clear', 'free',
2140 'move', 'reserve'] {
2141 panic('map method `${fn_node.value}` should be lowered by v3 transform')
2142 }
2143 }
2144 if clean_type is types.String {
2145 method_name = 'string.${fn_node.value}'
2146 if method_name in g.tc.fn_param_types {
2147 is_method = true
2148 base_id = g.a.child(fn_node, 0)
2149 g.write_method_c_name(id, node, method_name)
2150 } else {
2151 g.write('string__${fn_node.value}(')
2152 g.gen_expr(g.a.child(fn_node, 0))
2153 for i in 1 .. node.children_count {
2154 g.write(', ')
2155 g.gen_expr(g.a.child(&node, i))
2156 }
2157 g.write(')')
2158 return
2159 }
2160 }
2161 if !is_method && (clean_type is types.Void || clean_type is types.Primitive)
2162 && fn_node.value in ['vstring', 'vstring_with_len'] {
2163 g.write('u8__${fn_node.value}((u8*)')
2164 g.gen_expr(g.a.child(fn_node, 0))
2165 for i in 1 .. node.children_count {
2166 g.write(', ')
2167 g.gen_expr(g.a.child(&node, i))
2168 }
2169 g.write(')')
2170 return
2171 }
2172 if !is_method && clean_type is types.Struct && clean_type.name == 'IError' {
2173 if fn_node.value == 'msg' {
2174 g.gen_expr(g.a.child(fn_node, 0))
2175 g.write('.message')
2176 return
2177 } else if fn_node.value == 'code' {
2178 g.gen_expr(g.a.child(fn_node, 0))
2179 g.write('.code')
2180 return
2181 }
2182 }
2183 if !is_method && clean_type is types.Struct && clean_type.name == 'array'
2184 && fn_node.value == 'free' {
2185 g.write('array__free(')
2186 if base_type is types.Pointer {
2187 g.gen_expr(g.a.child(fn_node, 0))
2188 } else {
2189 g.write('&')
2190 g.gen_expr(g.a.child(fn_node, 0))
2191 }
2192 g.write(')')
2193 return
2194 }
2195 if !is_method && (clean_type is types.Primitive
2196 || clean_type is types.ISize || clean_type is types.USize
2197 || clean_type is types.Rune) {
2198 tname := clean_type.name()
2199 prim_method := '${tname}.${fn_node.value}'
2200 if prim_method in g.tc.fn_param_types {
2201 is_method = true
2202 base_id = g.a.child(fn_node, 0)
2203 g.write(c_name(prim_method))
2204 } else {
2205 mut prim_found := false
2206 if alias_method := g.find_alias_method(tname, fn_node.value) {
2207 is_method = true
2208 prim_found = true
2209 base_id = g.a.child(fn_node, 0)
2210 g.write(c_name(alias_method))
2211 }
2212 if !prim_found {
2213 alt_name := g.find_prim_method(fn_node.value)
2214 if alt_name.len > 0 {
2215 is_method = true
2216 base_id = g.a.child(fn_node, 0)
2217 g.write(alt_name)
2218 }
2219 }
2220 }
2221 }
2222 if !is_method {
2223 // Static method on a named type / type alias (e.g. `SimdFloat4.new(...)`,
2224 // where `SimdFloat4` names a type, not a value). The base ident resolves to
2225 // no value type, so handle it before the instance-method fallback.
2226 base_node_s := g.a.child_node(fn_node, 0)
2227 if base_node_s.kind == .ident {
2228 if static_fn := g.static_method_fn_name(base_node_s.value,
2229 fn_node.value)
2230 {
2231 g.write(g.direct_call_name(static_fn))
2232 g.write('(')
2233 for i in 1 .. node.children_count {
2234 if i > 1 {
2235 g.write(', ')
2236 }
2237 g.gen_expr(g.a.child(&node, i))
2238 }
2239 g.write(')')
2240 return
2241 }
2242 }
2243 }
2244 if !is_method {
2245 mut struct_name := clean_type.name()
2246 if clean_type is types.Struct {
2247 struct_name = clean_type.name
2248 }
2249 method_name = g.resolve_method_name(struct_name, fn_node.value)
2250 if method_name.len == 0 {
2251 for alias, target in g.tc.type_aliases {
2252 if target == struct_name {
2253 alias_method := '${alias}.${fn_node.value}'
2254 if alias_method in g.tc.fn_param_types {
2255 method_name = alias_method
2256 break
2257 }
2258 }
2259 }
2260 }
2261 if method_name.len > 0 && method_name in g.tc.fn_param_types {
2262 is_method = true
2263 base_id = g.a.child(fn_node, 0)
2264 g.write_method_c_name(id, node, method_name)
2265 } else {
2266 str_method := 'string.${fn_node.value}'
2267 if str_method in g.tc.fn_param_types {
2268 is_method = true
2269 method_name = str_method
2270 base_id = g.a.child(fn_node, 0)
2271 g.write(c_name(str_method))
2272 } else if embedded_method := g.embedded_method_name_for_type(clean_type,
2273 fn_node.value)
2274 {
2275 is_method = true
2276 method_name = embedded_method
2277 base_id = g.a.child(fn_node, 0)
2278 g.write(c_name(embedded_method))
2279 } else if struct_name.len > 0 {
2280 is_method = true
2281 base_id = g.a.child(fn_node, 0)
2282 fallback_method := '${struct_name}.${fn_node.value}'
2283 g.write_method_c_name(id, node, fallback_method)
2284 } else {
2285 g.gen_expr(g.a.child(&node, 0))
2286 }
2287 }
2288 }
2289 // !is_method
2290 }
2291 } else {
2292 fn_id := g.a.child(&node, 0)
2293 fn_ident := g.a.nodes[int(fn_id)]
2294 if fn_ident.kind == .ident {
2295 qname := g.tc.qualify_name(fn_ident.value)
2296 if fn_ident.value in g.tc.type_aliases || qname in g.tc.type_aliases
2297 || fn_ident.value in g.tc.structs || qname in g.tc.structs
2298 || fn_ident.value in g.tc.enum_names || qname in g.tc.enum_names
2299 || fn_ident.value in g.tc.sum_types || qname in g.tc.sum_types {
2300 type_name := if fn_ident.value in g.tc.type_aliases
2301 || fn_ident.value in g.tc.structs || fn_ident.value in g.tc.enum_names
2302 || fn_ident.value in g.tc.sum_types {
2303 fn_ident.value
2304 } else {
2305 qname
2306 }
2307 target_type := g.tc.parse_type(type_name)
2308 ct := g.tc.c_type(target_type)
2309 if target_type is types.SumType && node.children_count > 1 {
2310 g.gen_sum_cast_expr(target_type, g.a.child(&node, 1))
2311 } else {
2312 g.write('(${ct})(')
2313 for i in 1 .. node.children_count {
2314 if i > 1 {
2315 g.write(', ')
2316 }
2317 g.gen_expr(g.a.child(&node, i))
2318 }
2319 g.write(')')
2320 }
2321 return
2322 }
2323 call_key := g.call_key(id, fn_ident.value)
2324 looked_up := g.tc.cur_scope.lookup(fn_ident.value) or {
2325 types.Type(types.void_)
2326 }
2327 if looked_up !is types.Void && fn_type_from(looked_up) != none {
2328 g.write(c_name(fn_ident.value))
2329 } else if call_key in g.tc.fn_ret_types || call_key in g.tc.fn_param_types {
2330 g.write(g.direct_call_name_for_call(id, call_key))
2331 } else if specialized := g.specialized_generic_method_name_for_call_with_arg_count(id,
2332 fn_ident.value, -1)
2333 {
2334 g.write(c_name(specialized))
2335 } else {
2336 g.write(c_name(fn_ident.value))
2337 }
2338 } else {
2339 g.gen_expr(fn_id)
2340 }
2341 }
2342 g.write('(')
2343 actual_fn := if is_method {
2344 method_name
2345 } else if target_name.contains('.') {
2346 g.call_key(id, target_name)
2347 } else {
2348 g.call_key(id, fn_name)
2349 }
2350 mut param_types := g.param_types_for(actual_fn, fn_name)
2351 if param_types.len == 0 && !is_method {
2352 // Calling through a function-typed value (a generic `f F` parameter or a
2353 // local fn variable): the callee is not a named function, so recover the
2354 // parameter types from the value's own function type. This lets `mut` /
2355 // pointer arguments receive their `&` exactly as a direct call would.
2356 if ft := fn_type_from(g.tc.resolve_type(g.a.child(&node, 0))) {
2357 param_types = ft.params.clone()
2358 }
2359 }
2360 mut arg_start := 1
2361 if is_method {
2362 base_type := g.receiver_base_type(base_id)
2363 wants_ptr := param_types.len > 0 && param_types[0] is types.Pointer
2364 receiver_type_name := g.type_lookup_name(base_type)
2365 method_short := method_name.all_after_last('.').all_after_last('__')
2366 atomic_receiver_wants_ptr := method_name.starts_with('stdatomic.AtomicVal_')
2367 || method_name.starts_with('stdatomic.AtomicVal.')
2368 || method_name.starts_with('AtomicVal_')
2369 || method_name.starts_with('AtomicVal.')
2370 || (receiver_type_name.contains('AtomicVal')
2371 && method_short in ['load', 'store', 'add', 'sub', 'swap', 'compare_and_swap'])
2372 if param_types.len > 0
2373 && g.gen_embedded_method_receiver(base_id, base_type, param_types[0], wants_ptr) {
2374 arg_start = 1
2375 } else {
2376 mut is_ptr_base := base_type is types.Pointer
2377 // A `string.*` method always takes its receiver by value. If the
2378 // receiver mis-resolves to a char pointer (e.g. a `const x =
2379 // os.getenv(..)` whose type was inferred as `&char`), the actual
2380 // storage is still a `string`, so do not dereference it.
2381 if is_ptr_base && method_name.starts_with('string.')
2382 && base_type is types.Pointer && base_type.base_type is types.Char {
2383 is_ptr_base = false
2384 }
2385 if (wants_ptr || atomic_receiver_wants_ptr) && !is_ptr_base {
2386 g.write('&')
2387 } else if !wants_ptr && !atomic_receiver_wants_ptr && is_ptr_base {
2388 g.write('*')
2389 }
2390 g.gen_expr(base_id)
2391 arg_start = 1
2392 }
2393 }
2394 num_call_args := node.children_count - arg_start
2395 is_variadic_fn := !is_method && !is_c_call && (g.tc.fn_variadic[actual_fn] or { false })
2396 variadic_idx := if is_variadic_fn && param_types.len > 0
2397 && param_types[param_types.len - 1] is types.Array {
2398 param_types.len - 1
2399 } else {
2400 -1
2401 }
2402 // A veb handler whose hidden `Context` parameter (param index 1, right
2403 // after the receiver) was omitted by the caller forwards the enclosing
2404 // handler's `ctx` in that slot so the remaining explicit arguments still
2405 // line up with their parameters.
2406 expected_non_ctx := if is_method { param_types.len - 1 } else { param_types.len }
2407 forward_ctx := param_types.len > 1 && g.is_implicit_veb_ctx_param(param_types[1])
2408 && num_call_args < expected_non_ctx && g.cur_scope_has_ctx()
2409 if forward_ctx && is_method {
2410 g.write(', ctx')
2411 }
2412 mut emitted_arg_count := 0
2413 for i in arg_start .. node.children_count {
2414 mut arg_idx := if is_method { i } else { i - 1 }
2415 if forward_ctx && (is_method || i - arg_start >= 1) {
2416 arg_idx++
2417 }
2418 arg_id := g.a.child(&node, i)
2419 if int(arg_id) < 0 {
2420 continue
2421 }
2422 arg_node := g.a.nodes[int(arg_id)]
2423 if is_method || emitted_arg_count > 0 {
2424 g.write(', ')
2425 }
2426 emitted_arg_count++
2427 if arg_node.kind == .field_init && variadic_idx >= 0 && arg_idx == variadic_idx {
2428 variadic_type := param_types[variadic_idx]
2429 if variadic_type is types.Array {
2430 if variadic_type.elem_type is types.Struct {
2431 c_elem := g.tc.c_type(variadic_type.elem_type)
2432 g.write('new_array_from_c_array(1, 1, sizeof(${c_elem}), (${c_elem}[]){')
2433 g.gen_params_struct_arg(variadic_type.elem_type, node, i)
2434 g.write('})')
2435 break
2436 }
2437 }
2438 }
2439 if arg_node.kind == .field_init {
2440 // `@[params]` struct argument: trailing `key: value` args form a struct literal
2441 ptyp := if arg_idx < param_types.len {
2442 param_types[arg_idx]
2443 } else {
2444 types.Type(types.void_)
2445 }
2446 g.gen_params_struct_arg(ptyp, node, i)
2447 break
2448 }
2449 if fixed := array_fixed_type(g.tc.resolve_type(arg_id)) {
2450 g.gen_fixed_array_data_arg(arg_id, fixed)
2451 continue
2452 }
2453 if !is_c_call && arg_idx < param_types.len {
2454 if fixed := array_fixed_type(param_types[arg_idx]) {
2455 g.gen_fixed_array_data_arg(arg_id, fixed)
2456 continue
2457 }
2458 }
2459 if !is_c_call && arg_idx < param_types.len
2460 && g.gen_pointer_arg_from_array_literal(arg_node, param_types[arg_idx]) {
2461 continue
2462 }
2463 cb_param := if arg_idx >= 0 && arg_idx < param_types.len {
2464 param_types[arg_idx]
2465 } else {
2466 types.Type(types.void_)
2467 }
2468 if g.gen_special_c_callback_arg(target_name, arg_idx, arg_id, cb_param) {
2469 continue
2470 }
2471 if variadic_idx >= 0 && arg_idx == variadic_idx {
2472 variadic_type := param_types[variadic_idx]
2473 if variadic_type is types.Array {
2474 if spread_id := g.spread_arg_child(arg_node) {
2475 g.gen_expr_with_expected_type(spread_id, variadic_type)
2476 continue
2477 }
2478 if num_call_args > param_types.len {
2479 c_elem := g.tc.c_type(variadic_type.elem_type)
2480 count := num_call_args - variadic_idx
2481 g.write('new_array_from_c_array(${count}, ${count}, sizeof(${c_elem}), (${c_elem}[]){')
2482 for j in i .. node.children_count {
2483 if j > i {
2484 g.write(', ')
2485 }
2486 g.gen_expr_with_expected_type(g.a.child(&node, j),
2487 variadic_type.elem_type)
2488 }
2489 g.write('})')
2490 break
2491 }
2492 arg_type := g.tc.resolve_type(arg_id)
2493 if arg_type !is types.Array {
2494 c_elem := g.tc.c_type(variadic_type.elem_type)
2495 g.write('new_array_from_c_array(1, 1, sizeof(${c_elem}), (${c_elem}[]){')
2496 g.gen_expr_with_expected_type(arg_id, variadic_type.elem_type)
2497 g.write('})')
2498 continue
2499 }
2500 }
2501 }
2502 mut needs_addr := false
2503 if !is_c_call && arg_idx < param_types.len && param_types[arg_idx] is types.Pointer
2504 && !(arg_node.kind == .prefix && arg_node.op == .amp) {
2505 arg_type := g.tc.resolve_type(arg_id)
2506 if arg_type !is types.Pointer
2507 && !c_string_literal_pointer_arg(arg_node, param_types[arg_idx]) {
2508 needs_addr = true
2509 }
2510 }
2511 if !is_c_call && arg_idx < param_types.len {
2512 pt := param_types[arg_idx]
2513 if pt is types.Enum {
2514 g.expected_enum = pt.name
2515 }
2516 }
2517 if !is_c_call && arg_idx < param_types.len {
2518 if child_id := g.addressed_rvalue_arg(arg_node) {
2519 pt := param_types[arg_idx]
2520 if pt is types.Pointer {
2521 ct := g.tc.c_type(types.unwrap_pointer(pt))
2522 g.write('({${ct} _t${g.tmp_count} = ')
2523 g.gen_expr_with_expected_type(child_id, types.unwrap_pointer(pt))
2524 g.write('; &_t${g.tmp_count};})')
2525 g.tmp_count++
2526 g.expected_enum = ''
2527 continue
2528 }
2529 }
2530 }
2531 is_rvalue := arg_node.kind == .call
2532 || (arg_node.kind == .index && arg_node.value == 'range')
2533 || g.arg_is_const_ident(arg_node)
2534 if needs_addr && g.arg_is_const_ident(arg_node) {
2535 pt := param_types[arg_idx]
2536 ct := g.tc.c_type(types.unwrap_pointer(pt))
2537 g.write('(${ct}[]){')
2538 g.gen_expr_with_expected_type(arg_id, types.unwrap_pointer(pt))
2539 g.write('}')
2540 } else if needs_addr && is_rvalue {
2541 pt := param_types[arg_idx]
2542 ct := g.tc.c_type(types.unwrap_pointer(pt))
2543 g.write('({${ct} _t${g.tmp_count} = ')
2544 g.gen_expr_with_expected_type(arg_id, types.unwrap_pointer(pt))
2545 g.write('; &_t${g.tmp_count};})')
2546 g.tmp_count++
2547 } else {
2548 if needs_addr {
2549 g.write('&')
2550 }
2551 emitted_variant := !needs_addr && !is_c_call && arg_idx < param_types.len
2552 && g.gen_sum_variant_arg(arg_id, param_types[arg_idx])
2553 if !emitted_variant {
2554 if !is_c_call && arg_idx < param_types.len
2555 && g.gen_optional_arg(arg_id, param_types[arg_idx]) {
2556 // handled
2557 } else if !is_c_call && arg_idx < param_types.len {
2558 g.gen_expr_with_expected_type(arg_id, param_types[arg_idx])
2559 } else {
2560 g.gen_expr(arg_id)
2561 }
2562 }
2563 }
2564 g.expected_enum = ''
2565 // A no-arg delegation leaves the forwarded ctx as the final argument;
2566 // emit it here, right after the receiver, for the lowered free call.
2567 if forward_ctx && !is_method && i - arg_start == 0 {
2568 g.write(', ctx')
2569 }
2570 }
2571 // Count the forwarded ctx (if any) as already supplied.
2572 actual_args := emitted_arg_count + (if forward_ctx { 1 } else { 0 })
2573 expected_args := if is_method {
2574 param_types.len - 1
2575 } else {
2576 param_types.len
2577 }
2578 if !is_c_call && expected_args > 0 && actual_args < expected_args {
2579 for pi in actual_args .. expected_args {
2580 if is_method || pi > 0 {
2581 g.write(', ')
2582 }
2583 pidx := if is_method { pi + 1 } else { pi }
2584 pt := param_types[pidx]
2585 // The implicit veb `Context` parameter is supplied from the
2586 // enclosing handler's `ctx`, not a zero/default value.
2587 if g.is_implicit_veb_ctx_param(pt) && g.cur_scope_has_ctx() {
2588 g.write('ctx')
2589 } else {
2590 g.gen_default_value_for_type(pt)
2591 }
2592 }
2593 }
2594 g.write(')')
2595 }
2596 }
2597}
2598
2599// receiver_base_type supports receiver base type handling for FlatGen.
2600fn (g &FlatGen) receiver_base_type(base_id flat.NodeId) types.Type {
2601 if int(base_id) < 0 {
2602 return types.Type(types.void_)
2603 }
2604 base := g.a.nodes[int(base_id)]
2605 if base.kind == .ident {
2606 if typ := g.current_param_type(base.value) {
2607 return typ
2608 }
2609 if typ := g.cur_param_types[base.value] {
2610 return typ
2611 }
2612 if typ := g.tc.cur_scope.lookup(base.value) {
2613 return typ
2614 }
2615 }
2616 return g.tc.resolve_type(base_id)
2617}
2618
2619fn concrete_receiver_type(base_type types.Type) types.Type {
2620 clean_type := types.unwrap_pointer(base_type)
2621 if clean_type is types.Alias {
2622 return clean_type.base_type
2623 }
2624 return clean_type
2625}
2626
2627fn (g &FlatGen) receiver_storage_type(base_id flat.NodeId) ?types.Type {
2628 if int(base_id) < 0 || int(base_id) >= g.a.nodes.len {
2629 return none
2630 }
2631 node := g.a.nodes[int(base_id)]
2632 if node.kind == .selector && node.children_count > 0 {
2633 parent_type := g.tc.resolve_type(g.a.child(&node, 0))
2634 return g.field_type(parent_type, node.value)
2635 }
2636 return none
2637}
2638
2639fn (g &FlatGen) receiver_needs_address(base_id flat.NodeId, base_type types.Type) bool {
2640 if int(base_id) >= 0 && int(base_id) < g.a.nodes.len {
2641 node := g.a.nodes[int(base_id)]
2642 if node.kind == .selector {
2643 if storage_type := g.receiver_storage_type(base_id) {
2644 return storage_type !is types.Pointer
2645 }
2646 }
2647 }
2648 return base_type !is types.Pointer
2649}
2650
2651fn (mut g FlatGen) gen_interface_method_call(node flat.Node, fn_node flat.Node, base_type types.Type) bool {
2652 clean0 := types.unwrap_pointer(base_type)
2653 mut clean := clean0
2654 if clean0 is types.Alias {
2655 clean = clean0.base_type
2656 }
2657 if clean !is types.Interface {
2658 return false
2659 }
2660 iface := clean as types.Interface
2661 if c_name(iface.name) == 'IError' {
2662 return false
2663 }
2664 if iface.name !in g.interfaces {
2665 return false
2666 }
2667 method_name := '${iface.name}.${fn_node.value}'
2668 param_types := g.interface_method_param_types(method_name) or { []types.Type{} }
2669 base_id := g.a.child(fn_node, 0)
2670 g.write(c_name(method_name))
2671 g.write('(')
2672 if g.receiver_needs_address(base_id, base_type) {
2673 g.write('&')
2674 }
2675 g.gen_expr(base_id)
2676 mut emitted_arg_count := 0
2677 for i in 1 .. node.children_count {
2678 arg_id := g.a.child(&node, i)
2679 if int(arg_id) < 0 {
2680 continue
2681 }
2682 arg_node := g.a.nodes[int(arg_id)]
2683 g.write(', ')
2684 emitted_arg_count++
2685 param_idx := i
2686 if param_idx < param_types.len {
2687 if fixed := array_fixed_type(param_types[param_idx]) {
2688 g.gen_fixed_array_data_arg(arg_id, fixed)
2689 continue
2690 }
2691 if g.gen_pointer_arg_from_array_literal(arg_node, param_types[param_idx]) {
2692 continue
2693 }
2694 if child_id := g.addressed_rvalue_arg(arg_node) {
2695 pt := param_types[param_idx]
2696 if pt is types.Pointer {
2697 ct := g.tc.c_type(types.unwrap_pointer(pt))
2698 g.write('({${ct} _t${g.tmp_count} = ')
2699 g.gen_expr_with_expected_type(child_id, types.unwrap_pointer(pt))
2700 g.write('; &_t${g.tmp_count};})')
2701 g.tmp_count++
2702 continue
2703 }
2704 }
2705 emitted_variant := g.gen_sum_variant_arg(arg_id, param_types[param_idx])
2706 if !emitted_variant {
2707 if g.gen_optional_arg(arg_id, param_types[param_idx]) {
2708 // handled
2709 } else {
2710 g.gen_expr_with_expected_type(arg_id, param_types[param_idx])
2711 }
2712 }
2713 } else {
2714 g.gen_expr(arg_id)
2715 }
2716 }
2717 expected_args := if param_types.len > 0 { param_types.len - 1 } else { 0 }
2718 if emitted_arg_count < expected_args {
2719 for pi in emitted_arg_count .. expected_args {
2720 g.write(', ')
2721 g.gen_default_value_for_type(param_types[pi + 1])
2722 }
2723 }
2724 g.write(')')
2725 return true
2726}
2727
2728fn (g &FlatGen) call_target_name(id flat.NodeId) string {
2729 if int(id) < 0 || int(id) >= g.a.nodes.len {
2730 return ''
2731 }
2732 node := g.a.nodes[int(id)]
2733 match node.kind {
2734 .ident {
2735 return node.value
2736 }
2737 .selector {
2738 if node.children_count == 0 {
2739 return node.value
2740 }
2741 base := g.a.child_node(&node, 0)
2742 if base.kind == .ident {
2743 if mod := g.import_alias_module(base.value) {
2744 return '${mod}.${node.value}'
2745 }
2746 return '${base.value}.${node.value}'
2747 }
2748 return node.value
2749 }
2750 .index {
2751 if node.children_count > 0 {
2752 return g.call_target_name(g.a.child(&node, 0))
2753 }
2754 return node.value
2755 }
2756 else {
2757 return node.value
2758 }
2759 }
2760}
2761
2762fn (g &FlatGen) call_has_selector_name(id flat.NodeId, name string) bool {
2763 if int(id) < 0 || int(id) >= g.a.nodes.len {
2764 return false
2765 }
2766 node := g.a.nodes[int(id)]
2767 if node.kind == .selector && node.value == name {
2768 return true
2769 }
2770 for i in 0 .. node.children_count {
2771 if g.call_has_selector_name(g.a.child(&node, i), name) {
2772 return true
2773 }
2774 }
2775 return false
2776}
2777
2778fn (g &FlatGen) is_veb_json_result_call(fn_node flat.Node) bool {
2779 if fn_node.kind == .ident {
2780 if fn_node.value in ['veb.Context.json', 'veb.Context.json_pretty'] {
2781 return true
2782 }
2783 if fn_node.value !in ['Context.json', 'Context.json_pretty'] {
2784 return false
2785 }
2786 if fn_node.value in g.tc.fn_param_types || fn_node.value in g.tc.fn_ret_types {
2787 return false
2788 }
2789 return g.type_name_known_in_current_module('Context')
2790 && g.type_embeds_veb_context(g.tc.parse_type('Context'))
2791 }
2792 if fn_node.kind != .selector || fn_node.children_count == 0 {
2793 return false
2794 }
2795 if fn_node.value !in ['json', 'json_pretty'] {
2796 return false
2797 }
2798 receiver_id := g.a.child(fn_node, 0)
2799 receiver_type := types.unwrap_pointer(g.tc.resolve_type(receiver_id))
2800 if receiver_type is types.Struct {
2801 receiver_name := receiver_type.name
2802 if receiver_name != 'veb.Context' {
2803 method_name := '${receiver_name}.${fn_node.value}'
2804 if method_name in g.tc.fn_param_types || method_name in g.tc.fn_ret_types {
2805 return false
2806 }
2807 }
2808 }
2809 if embedded_method := g.embedded_method_name_for_type(receiver_type, fn_node.value) {
2810 return embedded_method in ['veb.Context.json', 'veb.Context.json_pretty']
2811 }
2812 return g.is_veb_context_receiver(receiver_id)
2813}
2814
2815fn (g &FlatGen) is_veb_context_receiver(id flat.NodeId) bool {
2816 typ := g.tc.resolve_type(id)
2817 return g.type_embeds_veb_context(typ)
2818}
2819
2820fn (g &FlatGen) type_embeds_veb_context(typ types.Type) bool {
2821 clean := types.unwrap_pointer(typ)
2822 if clean is types.Alias {
2823 return g.type_embeds_veb_context(clean.base_type)
2824 }
2825 if clean !is types.Struct {
2826 return false
2827 }
2828 struct_name := clean.name()
2829 if struct_name == 'veb.Context' {
2830 return true
2831 }
2832 fields := g.struct_fields_for_type(struct_name) or { return false }
2833 for field in fields {
2834 embedded_type_name := g.embedded_field_type_name(field)
2835 if embedded_type_name == 'veb.Context' {
2836 return true
2837 }
2838 if embedded_type_name.len > 0
2839 && g.type_embeds_veb_context(g.tc.parse_type(embedded_type_name)) {
2840 return true
2841 }
2842 }
2843 return false
2844}
2845
2846fn (g &FlatGen) is_missing_middleware_use_call(fn_node flat.Node) bool {
2847 if fn_node.kind == .ident {
2848 if !fn_node.value.ends_with('.use') {
2849 return false
2850 }
2851 if fn_node.value in g.tc.fn_param_types || fn_node.value in g.tc.fn_ret_types {
2852 return false
2853 }
2854 receiver := fn_node.value.all_before_last('.')
2855 return g.struct_has_middleware_receiver(receiver)
2856 }
2857 if fn_node.kind != .selector || fn_node.value != 'use' || fn_node.children_count == 0 {
2858 return false
2859 }
2860 base_type := g.tc.resolve_type(g.a.child(fn_node, 0))
2861 clean_type := types.unwrap_pointer(base_type)
2862 if clean_type !is types.Struct {
2863 return false
2864 }
2865 method_name := '${clean_type.name()}.use'
2866 if method_name in g.tc.fn_param_types || method_name in g.tc.fn_ret_types {
2867 return false
2868 }
2869 return g.struct_has_middleware_receiver(clean_type.name())
2870}
2871
2872fn (g &FlatGen) struct_has_middleware_receiver(type_name string) bool {
2873 if is_middleware_type_name(type_name) {
2874 return true
2875 }
2876 fields := g.struct_fields_for_type(type_name) or { return false }
2877 for field in fields {
2878 embedded_type_name := g.embedded_field_type_name(field)
2879 if is_middleware_type_name(embedded_type_name) {
2880 return true
2881 }
2882 }
2883 return false
2884}
2885
2886fn is_middleware_type_name(name string) bool {
2887 base := if name.contains('[') { name.all_before('[') } else { name }
2888 return base == 'veb.Middleware'
2889}
2890
2891fn (g &FlatGen) call_default_return_type(id flat.NodeId) types.Type {
2892 if g.expected_expr_type is types.OptionType || g.expected_expr_type is types.ResultType {
2893 return g.expected_expr_type
2894 }
2895 if g.expected_expr_type is types.Struct && g.expected_expr_type.name.starts_with('Optional') {
2896 return g.expected_expr_type
2897 }
2898 if g.expected_expr_type is types.String {
2899 return g.expected_expr_type
2900 }
2901 if typ := g.tc.expr_type(id) {
2902 if typ !is types.Unknown && typ !is types.Void {
2903 return typ
2904 }
2905 }
2906 return g.tc.resolve_type(id)
2907}
2908
2909fn (g &FlatGen) json_decode_result_type(callee_id flat.NodeId) ?types.Type {
2910 if int(callee_id) < 0 || int(callee_id) >= g.a.nodes.len {
2911 return none
2912 }
2913 callee := g.a.nodes[int(callee_id)]
2914 if callee.kind != .index || callee.children_count < 2 {
2915 return none
2916 }
2917 arg := g.a.child_node(&callee, 1)
2918 mut type_name := ''
2919 if arg.typ.len > 0 {
2920 type_name = arg.typ
2921 } else if arg.kind == .array_init && arg.value.len > 0 {
2922 type_name = '[]${arg.value}'
2923 } else if arg.value.len > 0 {
2924 type_name = arg.value
2925 }
2926 if type_name.len == 0 {
2927 return none
2928 }
2929 return types.Type(types.ResultType{
2930 base_type: g.tc.parse_type(type_name)
2931 })
2932}
2933
2934fn (g &FlatGen) embedded_method_name_for_type(base_type types.Type, method string) ?string {
2935 type_name := g.type_lookup_name(base_type)
2936 if type_name.len == 0 || method.len == 0 {
2937 return none
2938 }
2939 return g.embedded_method_name_for_struct(type_name, method)
2940}
2941
2942fn (g &FlatGen) embedded_method_name_for_struct(type_name string, method string) ?string {
2943 fields := g.struct_fields_for_type(type_name) or { return none }
2944 for field in fields {
2945 embedded_type_name := g.embedded_field_type_name(field)
2946 if embedded_type_name.len == 0 {
2947 continue
2948 }
2949 method_name := '${embedded_type_name}.${method}'
2950 if method_name in g.tc.fn_param_types {
2951 return method_name
2952 }
2953 if found := g.embedded_method_name_for_struct(embedded_type_name, method) {
2954 return found
2955 }
2956 }
2957 return none
2958}
2959
2960fn (mut g FlatGen) gen_embedded_method_receiver(base_id flat.NodeId, base_type types.Type, expected_type types.Type, wants_ptr bool) bool {
2961 field := g.embedded_receiver_field_for_expected(base_type, expected_type) or { return false }
2962 field_is_ptr := field.typ is types.Pointer
2963 if wants_ptr && !field_is_ptr {
2964 g.write('&')
2965 } else if !wants_ptr && field_is_ptr {
2966 g.write('*')
2967 }
2968 needs_paren := g.a.nodes[int(base_id)].kind !in [.ident, .selector]
2969 if needs_paren {
2970 g.write('(')
2971 }
2972 g.gen_expr(base_id)
2973 if needs_paren {
2974 g.write(')')
2975 }
2976 first_op := if base_type is types.Pointer { '->' } else { '.' }
2977 g.write('${first_op}${c_name(field.name)}')
2978 return true
2979}
2980
2981fn (g &FlatGen) embedded_receiver_field_for_expected(base_type types.Type, expected_type types.Type) ?types.StructField {
2982 base_name := g.type_lookup_name(base_type)
2983 expected_name := g.type_lookup_name(expected_type)
2984 if base_name.len == 0 || expected_name.len == 0 {
2985 return none
2986 }
2987 fields := g.struct_fields_for_type(base_name) or { return none }
2988 for field in fields {
2989 embedded_type_name := g.embedded_field_type_name(field)
2990 if embedded_type_name == expected_name {
2991 return field
2992 }
2993 }
2994 return none
2995}
2996
2997// current_param_type returns current param type data for FlatGen.
2998fn (g &FlatGen) current_param_type(name string) ?types.Type {
2999 for i in 0 .. g.cur_param_names.len {
3000 if g.cur_param_names[i] == name {
3001 return g.cur_param_type_values[i]
3002 }
3003 }
3004 return none
3005}
3006
3007// current_param_is_mut returns true when a current param originated from `mut name T`.
3008fn (g &FlatGen) current_param_is_mut(name string) bool {
3009 return g.cur_mut_params[name] or { false }
3010}
3011
3012// gen_enum_str_call emits an explicit `enum_val.str()` (with no user-defined `str`)
3013// by routing to the compiler-synthesized `<Enum>__autostr`, so it matches `${enum}`
3014// interpolation exactly — including `[flag]` enums' `Enum{.a | .b}` form, which the
3015// old inline single-value ternary chain could not render.
3016fn (mut g FlatGen) gen_enum_str_call(fn_node &flat.Node, enum_type types.Enum) {
3017 mut name := enum_type.name
3018 if name.starts_with('main.') {
3019 name = name[5..]
3020 }
3021 g.write('${c_name(name)}__autostr(')
3022 g.gen_expr(g.a.child(fn_node, 0))
3023 g.write(')')
3024}
3025
3026// enum_receiver_method_name supports enum receiver method name handling for FlatGen.
3027fn (g &FlatGen) enum_receiver_method_name(enum_type types.Enum, method string) ?string {
3028 name := enum_type.name
3029 direct := '${name}.${method}'
3030 if direct in g.tc.fn_param_types {
3031 return direct
3032 }
3033 if name.contains('.') {
3034 return none
3035 }
3036 for candidate, _ in g.tc.fn_param_types {
3037 if candidate.ends_with('.${direct}') {
3038 return candidate
3039 }
3040 }
3041 return none
3042}
3043
3044// gen_fn_field_call emits fn field call output for c.
3045fn (mut g FlatGen) gen_fn_field_call(node flat.Node, fn_node &flat.Node, base_type types.Type) bool {
3046 fn_type := g.fn_field_type(base_type, fn_node.value) or { return false }
3047 base_id := g.a.child(fn_node, 0)
3048 base := g.a.nodes[int(base_id)]
3049 needs_paren := base.kind !in [.ident, .selector, .call]
3050 if needs_paren {
3051 g.write('(')
3052 }
3053 g.gen_expr(base_id)
3054 if needs_paren {
3055 g.write(')')
3056 }
3057 if base_type is types.Pointer {
3058 g.write('->')
3059 } else {
3060 g.write('.')
3061 }
3062 g.write(c_name(fn_node.value))
3063 g.write('(')
3064 for i in 1 .. node.children_count {
3065 if i > 1 {
3066 g.write(', ')
3067 }
3068 arg_id := g.a.child(&node, i)
3069 arg_idx := i - 1
3070 if arg_idx < fn_type.params.len {
3071 g.gen_arg_for_expected_type(arg_id, fn_type.params[arg_idx])
3072 } else {
3073 g.gen_expr(arg_id)
3074 }
3075 }
3076 g.write(')')
3077 return true
3078}
3079
3080// call_key updates call key state for FlatGen.
3081fn (g &FlatGen) call_key(id flat.NodeId, name string) string {
3082 if name.contains('.') {
3083 normalized := g.normalize_call_key(name)
3084 if normalized in g.tc.fn_param_types || normalized in g.tc.fn_ret_types {
3085 return normalized
3086 }
3087 }
3088 if resolved := g.tc.resolved_call_name(id) {
3089 return g.normalize_call_key(resolved)
3090 }
3091 return g.normalize_call_key(name)
3092}
3093
3094// normalize_call_key transforms normalize call key data for c.
3095fn (g &FlatGen) normalize_call_key(name string) string {
3096 if name.starts_with('main.') {
3097 short_name := name.all_after_last('.')
3098 if short_name in g.tc.fn_param_types || short_name in g.tc.fn_ret_types {
3099 return short_name
3100 }
3101 }
3102 if !name.contains('.') && g.tc.cur_module.len > 0 && g.tc.cur_module != 'main'
3103 && g.tc.cur_module != 'builtin' {
3104 local := '${g.tc.cur_module}.${name}'
3105 if local in g.tc.fn_param_types || local in g.tc.fn_ret_types {
3106 return local
3107 }
3108 }
3109 if name in g.tc.fn_param_types || name in g.tc.fn_ret_types {
3110 return name
3111 }
3112 qname := g.tc.qualify_fn_name(name)
3113 if qname in g.tc.fn_param_types || qname in g.tc.fn_ret_types {
3114 return qname
3115 }
3116 for _, mod_name in g.tc.imports {
3117 imported := '${mod_name}.${name}'
3118 if imported in g.tc.fn_param_types || imported in g.tc.fn_ret_types {
3119 return imported
3120 }
3121 }
3122 return qname
3123}
3124
3125// param_types_for supports param types for handling for FlatGen.
3126// param_types_for resolves the parameter types of a called function. It is invoked once
3127// per call site during codegen, and the slow path below scans every known function, so
3128// results are memoized: without this, generic/monomorphized call names that miss the
3129// direct lookups re-scan (and copy) the whole function table on every call (O(n^2)).
3130fn (mut g FlatGen) param_types_for(name string, fallback string) []types.Type {
3131 cache_key := '${name}\x01${fallback}'
3132 if cached := g.param_types_cache[cache_key] {
3133 return cached
3134 }
3135 result := g.param_types_for_uncached(name, fallback)
3136 g.param_types_cache[cache_key] = result
3137 return result
3138}
3139
3140fn (mut g FlatGen) param_types_for_uncached(name string, fallback string) []types.Type {
3141 if generic_params := g.generic_receiver_method_param_types(name) {
3142 return generic_params
3143 }
3144 if interface_types := g.interface_method_param_types(name) {
3145 return interface_types
3146 }
3147 for candidate in [name, fallback] {
3148 if params := g.tc.fn_param_types[candidate] {
3149 return params
3150 }
3151 if candidate.starts_with('main.') {
3152 short_name := candidate.all_after_last('.')
3153 if params := g.tc.fn_param_types[short_name] {
3154 return params
3155 }
3156 }
3157 }
3158 decl_types := g.param_types_from_decl(name, fallback)
3159 if decl_types.len > 0 {
3160 return decl_types
3161 }
3162 if name.contains('.') {
3163 // O(1) lookup via the precomputed short-name index instead of scanning the whole
3164 // function table on every (cache-missing) call — this fallback ran ~3000× and was
3165 // a top cgen self-time cost (each scan is O(functions)).
3166 short_name := name.all_after_last('.')
3167 if params := g.param_types_by_short[short_name] {
3168 return params
3169 }
3170 }
3171 return []types.Type{}
3172}
3173
3174fn (g &FlatGen) generic_receiver_method_param_types(name string) ?[]types.Type {
3175 if !name.contains('.') || !name.contains('[') || !name.contains(']') {
3176 return none
3177 }
3178 receiver := name.all_before_last('.')
3179 method := name.all_after_last('.')
3180 info := g.tc.resolve_generic_struct_method(receiver, method) or { return none }
3181 return info.params.clone()
3182}
3183
3184// precompute_param_type_index builds short-name -> param-types, preserving the fallback's
3185// priority (fn_decl_param_types first, then the checker's fn_param_types; first match wins).
3186fn (mut g FlatGen) precompute_param_type_index() {
3187 for name, params in g.fn_decl_param_types {
3188 if name.contains('.') {
3189 short := name.all_after_last('.')
3190 if short !in g.param_types_by_short {
3191 g.param_types_by_short[short] = params
3192 }
3193 }
3194 }
3195 for name, params in g.tc.fn_param_types {
3196 if name.contains('.') {
3197 short := name.all_after_last('.')
3198 if short !in g.param_types_by_short {
3199 g.param_types_by_short[short] = params
3200 }
3201 }
3202 }
3203}
3204
3205fn (g &FlatGen) interface_method_param_types(name string) ?[]types.Type {
3206 if !name.contains('.') {
3207 return none
3208 }
3209 iface_name := name.all_before_last('.')
3210 if iface_name !in g.interfaces {
3211 return none
3212 }
3213 method := name.all_after_last('.')
3214 if iface_name == 'IError' && method == 'str' {
3215 return none
3216 }
3217 decl_key := g.interface_method_signature_key(iface_name, method) or { return none }
3218 decl_params := g.tc.fn_param_types[decl_key] or { return none }
3219 mut params := []types.Type{cap: decl_params.len}
3220 params << types.Type(types.Pointer{
3221 base_type: types.Type(types.Interface{
3222 name: iface_name
3223 })
3224 })
3225 if decl_params.len > 1 {
3226 for i in 1 .. decl_params.len {
3227 params << decl_params[i]
3228 }
3229 }
3230 return params
3231}
3232
3233// param_types_from_decl converts param types from decl data for c.
3234fn (mut g FlatGen) param_types_from_decl(name string, fallback string) []types.Type {
3235 if name.contains('.') {
3236 if ptypes := g.fn_decl_param_types[name] {
3237 return ptypes
3238 }
3239 } else {
3240 for candidate in [fallback, name] {
3241 if ptypes := g.fn_decl_param_types[candidate] {
3242 return ptypes
3243 }
3244 }
3245 }
3246 return []types.Type{}
3247}
3248
3249// short_receiver_method_name supports short receiver method name handling for c.
3250fn short_receiver_method_name(name string) string {
3251 if !name.contains('.') {
3252 return ''
3253 }
3254 receiver := name.all_before_last('.')
3255 if !receiver.contains('.') {
3256 return ''
3257 }
3258 return '${receiver.all_after_last('.')}.${name.all_after_last('.')}'
3259}
3260
3261// gen_arg_for_expected_type emits arg for expected type output for c.
3262fn (mut g FlatGen) gen_arg_for_expected_type(arg_id flat.NodeId, expected types.Type) {
3263 arg_node := g.a.nodes[int(arg_id)]
3264 mut needs_addr := false
3265 if expected is types.Pointer && !(arg_node.kind == .prefix && arg_node.op == .amp) {
3266 arg_type := g.tc.resolve_type(arg_id)
3267 if arg_type !is types.Pointer {
3268 needs_addr = true
3269 }
3270 }
3271 if needs_addr {
3272 g.write('&')
3273 }
3274 if !needs_addr && g.gen_sum_variant_arg(arg_id, expected) {
3275 return
3276 }
3277 if !needs_addr && g.gen_optional_arg(arg_id, expected) {
3278 return
3279 }
3280 g.gen_expr_with_expected_type(arg_id, expected)
3281}
3282
3283fn (mut g FlatGen) gen_callback_fn_value_for_expected_type(arg_id flat.NodeId, expected types.Type) bool {
3284 return g.gen_callback_fn_value_for_expected_c_abi(arg_id, expected, '')
3285}
3286
3287fn (mut g FlatGen) gen_callback_fn_value_for_expected_c_abi(arg_id flat.NodeId, expected types.Type, expected_c_abi string) bool {
3288 expected_fn := fn_type_from(expected) or { return false }
3289 actual_name := g.callback_fn_value_name(arg_id, expected) or { return false }
3290 actual_fn := g.callback_fn_value_type(actual_name) or { return false }
3291 wrapper := g.ensure_callback_userdata_wrapper(actual_name, actual_fn, expected_fn,
3292 expected_c_abi) or { return false }
3293 g.write(wrapper)
3294 return true
3295}
3296
3297fn (mut g FlatGen) gen_callback_fn_value_for_field_c_abi(arg_id flat.NodeId, expected types.Type, expected_c_abi string) bool {
3298 if expected_c_abi.len == 0 {
3299 return false
3300 }
3301 if g.gen_callback_fn_value_for_expected_c_abi(arg_id, expected, expected_c_abi) {
3302 return true
3303 }
3304 if call_name := g.callback_direct_fn_value_name_for_c_abi(arg_id, expected, expected_c_abi) {
3305 g.write(g.callback_c_fn_name(call_name))
3306 return true
3307 }
3308 if _ := g.callback_fn_value_name(arg_id, expected) {
3309 g.gen_expr(arg_id)
3310 return true
3311 }
3312 return false
3313}
3314
3315fn (mut g FlatGen) callback_fn_value_name(id flat.NodeId, expected types.Type) ?string {
3316 if name := g.tc.resolved_fn_value_name(id) {
3317 return name
3318 }
3319 if int(id) < 0 || int(id) >= g.a.nodes.len {
3320 return none
3321 }
3322 node := g.a.nodes[int(id)]
3323 if node.kind in [.cast_expr, .paren, .expr_stmt] && node.children_count > 0 {
3324 return g.callback_fn_value_name(g.a.child(&node, 0), expected)
3325 }
3326 return none
3327}
3328
3329fn (mut g FlatGen) callback_direct_fn_value_name(id flat.NodeId, expected types.Type) ?string {
3330 return g.callback_direct_fn_value_name_for_c_abi(id, expected, '')
3331}
3332
3333fn (mut g FlatGen) callback_direct_fn_value_name_for_c_abi(id flat.NodeId, expected types.Type, expected_c_abi string) ?string {
3334 expected_fn := fn_type_from(expected) or { return none }
3335 actual_name := g.callback_fn_value_name(id, expected) or { return none }
3336 actual_fn := g.callback_fn_value_type(actual_name) or { return none }
3337 if !g.callback_fn_types_direct_compatible(actual_fn, expected_fn, expected_c_abi) {
3338 return none
3339 }
3340 return actual_name
3341}
3342
3343fn (mut g FlatGen) callback_fn_value_type(name string) ?types.FnType {
3344 params := g.tc.fn_param_types[name] or { return none }
3345 ret := g.tc.fn_ret_types[name] or { return none }
3346 return types.FnType{
3347 params: params.clone()
3348 return_type: ret
3349 }
3350}
3351
3352fn (mut g FlatGen) callback_fn_types_direct_compatible(actual types.FnType, expected types.FnType, expected_c_abi string) bool {
3353 if actual.params.len != expected.params.len {
3354 return false
3355 }
3356 if g.callback_c_type(actual.return_type) != g.callback_expected_return_c_type(expected.return_type,
3357 expected_c_abi) {
3358 return false
3359 }
3360 for i in 0 .. expected.params.len {
3361 if g.callback_c_type(fn_type_param(actual, i)) != g.callback_expected_param_c_type(expected,
3362 i, expected_c_abi) {
3363 return false
3364 }
3365 }
3366 return true
3367}
3368
3369fn (mut g FlatGen) ensure_callback_userdata_wrapper(actual_name string, actual types.FnType, expected types.FnType, expected_c_abi string) ?string {
3370 if actual.params.len != expected.params.len {
3371 return none
3372 }
3373 actual_ret_ct := g.callback_c_type(actual.return_type)
3374 expected_ret_ct := g.callback_expected_return_c_type(expected.return_type, expected_c_abi)
3375 if actual_ret_ct != expected_ret_ct {
3376 return none
3377 }
3378 mut needs_wrapper := false
3379 mut param_decls := []string{}
3380 mut call_args := []string{}
3381 for i in 0 .. expected.params.len {
3382 expected_param := fn_type_param(expected, i)
3383 actual_param := fn_type_param(actual, i)
3384 expected_ct := g.callback_expected_param_c_type(expected, i, expected_c_abi)
3385 actual_ct := g.callback_c_type(actual_param)
3386 param_decls << '${expected_ct} arg${i}'
3387 if actual_ct == expected_ct {
3388 call_args << 'arg${i}'
3389 continue
3390 }
3391 if g.callback_can_cast_userdata_param(actual_param, expected_param) {
3392 call_args << '(${actual_ct})arg${i}'
3393 needs_wrapper = true
3394 continue
3395 }
3396 if callback_can_cast_const_abi_param(actual_ct, expected_ct) {
3397 call_args << '(${actual_ct})arg${i}'
3398 needs_wrapper = true
3399 continue
3400 }
3401 return none
3402 }
3403 if !needs_wrapper {
3404 return none
3405 }
3406 actual_c_name := g.callback_c_fn_name(actual_name)
3407 expected_key := if expected_c_abi.len > 0 {
3408 expected_c_abi
3409 } else {
3410 g.callback_fn_type_key(expected)
3411 }
3412 key := '${actual_c_name}|${g.callback_fn_type_key(actual)}|${expected_key}'
3413 if name := g.callback_wrapper_names[key] {
3414 return name
3415 }
3416 name := c_name('${actual_c_name}_callback_adapter_${callback_stable_key_hash(key)}')
3417 g.callback_wrapper_names[key] = name
3418 params := if param_decls.len == 0 { 'void' } else { param_decls.join(', ') }
3419 call := '${actual_c_name}(${call_args.join(', ')})'
3420 body := if expected_ret_ct == 'void' {
3421 'static void ${name}(${params}) { ${call}; }'
3422 } else {
3423 'static ${expected_ret_ct} ${name}(${params}) { return ${call}; }'
3424 }
3425 g.callback_wrapper_defs << body
3426 return name
3427}
3428
3429fn (mut g FlatGen) callback_expected_return_c_type(typ types.Type, expected_c_abi string) string {
3430 if expected_c_abi.len > 0 {
3431 ret, _ := fn_ptr_typedef_parts(expected_c_abi)
3432 return ret.trim_space()
3433 }
3434 return g.callback_c_type(typ)
3435}
3436
3437fn (mut g FlatGen) callback_expected_param_c_type(expected types.FnType, idx int, expected_c_abi string) string {
3438 if expected_c_abi.len > 0 {
3439 params := callback_fn_ptr_param_c_types(expected_c_abi)
3440 if idx < params.len {
3441 return params[idx]
3442 }
3443 }
3444 return g.callback_c_type(fn_type_param(expected, idx))
3445}
3446
3447fn callback_fn_ptr_param_c_types(encoded string) []string {
3448 _, params := fn_ptr_typedef_parts(encoded)
3449 clean := params.trim_space()
3450 if clean.len == 0 || clean == 'void' {
3451 return []string{}
3452 }
3453 mut out := []string{}
3454 for param in clean.split(',') {
3455 out << param.trim_space()
3456 }
3457 return out
3458}
3459
3460fn callback_can_cast_const_abi_param(actual_ct string, expected_ct string) bool {
3461 actual := actual_ct.trim_space()
3462 expected := expected_ct.trim_space()
3463 if !expected.starts_with('const ') || !expected.ends_with('*') {
3464 return false
3465 }
3466 return actual == expected['const '.len..].trim_space()
3467}
3468
3469fn (mut g FlatGen) callback_c_type(typ types.Type) string {
3470 if typ is types.Void {
3471 return 'void'
3472 }
3473 mut ct := if typ is types.OptionType || typ is types.ResultType {
3474 g.optional_type_name(typ)
3475 } else {
3476 g.tc.c_type(typ)
3477 }
3478 if ct.starts_with('fn_ptr:') {
3479 ct = g.resolve_fn_ptr_type(ct)
3480 }
3481 return ct
3482}
3483
3484fn (mut g FlatGen) callback_fn_type_key(typ types.FnType) string {
3485 mut parts := []string{}
3486 for i in 0 .. typ.params.len {
3487 parts << g.callback_c_type(fn_type_param(typ, i))
3488 }
3489 return '${g.callback_c_type(typ.return_type)}|${parts.join(',')}'
3490}
3491
3492fn callback_stable_key_hash(key string) string {
3493 mut hash := u64(1469598103934665603)
3494 for b in key.bytes() {
3495 hash ^= u64(b)
3496 hash *= u64(1099511628211)
3497 }
3498 return '${hash}'
3499}
3500
3501fn (g &FlatGen) callback_can_cast_userdata_param(actual types.Type, expected types.Type) bool {
3502 return (callback_is_voidptr_type(expected) && callback_is_nonvoid_pointer_type(actual))
3503 || (callback_is_nonvoid_pointer_type(expected) && callback_is_voidptr_type(actual))
3504}
3505
3506fn callback_is_voidptr_type(typ types.Type) bool {
3507 clean := callback_unalias_type(typ)
3508 if clean is types.Pointer {
3509 base := callback_unalias_type(clean.base_type)
3510 return base is types.Void
3511 }
3512 return false
3513}
3514
3515fn callback_is_nonvoid_pointer_type(typ types.Type) bool {
3516 clean := callback_unalias_type(typ)
3517 if clean is types.Pointer {
3518 base := callback_unalias_type(clean.base_type)
3519 return base !is types.Void
3520 }
3521 return false
3522}
3523
3524fn callback_unalias_type(typ types.Type) types.Type {
3525 if typ is types.Alias {
3526 return callback_unalias_type(typ.base_type)
3527 }
3528 return typ
3529}
3530
3531fn (g &FlatGen) callback_c_fn_name(name string) string {
3532 if name.starts_with('C.') {
3533 return c_name(name.all_after_last('.'))
3534 }
3535 if g.test_files.len > 0 && (name == 'main' || name == 'main.main') {
3536 return g.test_user_main_c_name()
3537 }
3538 if name.starts_with('main.') {
3539 return c_name(name.all_after_last('.'))
3540 }
3541 return c_name(name)
3542}
3543
3544fn fn_type_param(typ types.FnType, idx int) types.Type {
3545 return typ.params[idx]
3546}
3547
3548// gen_optional_arg emits optional arg output for c.
3549fn (mut g FlatGen) gen_optional_arg(arg_id flat.NodeId, expected types.Type) bool {
3550 mut base_type := types.Type(types.void_)
3551 if expected is types.OptionType {
3552 base_type = expected.base_type
3553 } else if expected is types.ResultType {
3554 base_type = expected.base_type
3555 } else {
3556 return false
3557 }
3558 if g.expr_is_optional_literal(arg_id, expected) {
3559 g.gen_expr_with_expected_type(g.collapsed_optional_literal(arg_id, expected), expected)
3560 return true
3561 }
3562 arg_type := g.usable_expr_type(arg_id)
3563 if arg_type is types.OptionType || arg_type is types.ResultType {
3564 if g.type_names_match(arg_type, expected) {
3565 g.gen_expr_with_expected_type(arg_id, expected)
3566 return true
3567 }
3568 arg_node := g.a.nodes[int(arg_id)]
3569 if arg_node.kind == .none_expr || g.expr_really_returns_optional(arg_id) {
3570 g.gen_expr_with_expected_type(arg_id, expected)
3571 return true
3572 }
3573 }
3574 ct := g.optional_type_name(expected)
3575 if base_type is types.Void {
3576 g.write('(${ct}){.ok = true}')
3577 return true
3578 }
3579 g.write('(${ct}){.ok = true, .value = ')
3580 g.gen_expr_with_expected_type(arg_id, base_type)
3581 g.write('}')
3582 return true
3583}
3584
3585// expr_is_optional_literal supports expr is optional literal handling for FlatGen.
3586fn (mut g FlatGen) expr_is_optional_literal(id flat.NodeId, expected types.Type) bool {
3587 if int(id) < 0 {
3588 return false
3589 }
3590 node := g.a.nodes[int(id)]
3591 if node.kind != .struct_init && node.kind != .cast_expr {
3592 return false
3593 }
3594 return node.value.starts_with('?') || node.value.starts_with('!')
3595 || node.value == g.optional_type_name(expected) || node.value.starts_with('Optional')
3596}
3597
3598// collapsed_optional_literal supports collapsed optional literal handling for FlatGen.
3599fn (mut g FlatGen) collapsed_optional_literal(id flat.NodeId, expected types.Type) flat.NodeId {
3600 mut current := id
3601 for _ in 0 .. 4 {
3602 value_id := g.optional_literal_value_id(current) or { break }
3603 if !g.expr_is_optional_literal(value_id, expected) {
3604 break
3605 }
3606 current = value_id
3607 }
3608 return current
3609}
3610
3611// optional_literal_value_id supports optional literal value id handling for FlatGen.
3612fn (g &FlatGen) optional_literal_value_id(id flat.NodeId) ?flat.NodeId {
3613 if int(id) < 0 {
3614 return none
3615 }
3616 node := g.a.nodes[int(id)]
3617 if node.kind != .struct_init && node.kind != .cast_expr {
3618 return none
3619 }
3620 for i in 0 .. node.children_count {
3621 field := g.a.child_node(&node, i)
3622 if field.kind == .field_init && field.value == 'value' && field.children_count > 0 {
3623 return g.a.child(field, 0)
3624 }
3625 }
3626 return none
3627}
3628
3629// fn_field_type supports fn field type handling for FlatGen.
3630fn (g &FlatGen) fn_field_type(base_type types.Type, field_name string) ?types.FnType {
3631 field_type := g.field_type(base_type, field_name) or { return none }
3632 return fn_type_from(field_type)
3633}
3634
3635// field_type supports field type handling for FlatGen.
3636fn (g &FlatGen) field_type(base_type types.Type, field_name string) ?types.Type {
3637 clean0 := types.unwrap_pointer(base_type)
3638 mut clean := clean0
3639 if clean0 is types.Alias {
3640 clean = clean0.base_type
3641 }
3642 mut struct_name := ''
3643 if clean is types.Struct {
3644 struct_name = clean.name
3645 } else if clean is types.Array {
3646 struct_name = 'array'
3647 } else if clean is types.Map {
3648 struct_name = 'map'
3649 } else if clean is types.String {
3650 struct_name = 'string'
3651 }
3652 if struct_name.len == 0 {
3653 return none
3654 }
3655 fields := g.tc.structs[struct_name] or { return none }
3656 for field in fields {
3657 if field.name == field_name {
3658 return field.typ
3659 }
3660 }
3661 return none
3662}
3663
3664// fn_type_from supports fn type from handling for c.
3665fn fn_type_from(t types.Type) ?types.FnType {
3666 if t is types.FnType {
3667 return t
3668 }
3669 if t is types.Alias {
3670 return fn_type_from(t.base_type)
3671 }
3672 return none
3673}
3674
3675// gen_call_args emits call args output for c.
3676fn (mut g FlatGen) gen_call_args(fn_name string, node flat.Node, start int) {
3677 mut param_types := g.param_types_for(fn_name, fn_name.all_after_last('.'))
3678 // Retry with the bare method name only when the qualified name is genuinely unresolved —
3679 // not when it is a known function that simply takes no parameters. The short-name index
3680 // matches any same-named function, so retrying a real 0-param fn (e.g. a static method
3681 // `Type.build()`) would adopt an unrelated `build`'s parameters and append phantom args.
3682 if param_types.len == 0 && fn_name.contains('.') && fn_name !in g.tc.fn_param_types
3683 && c_name(fn_name) !in g.tc.fn_param_types {
3684 param_types = g.param_types_for(fn_name.all_after_last('.'), fn_name.all_after_last('.'))
3685 }
3686 is_c_variadic_fn := g.tc.c_variadic_fns[fn_name] or { false }
3687 is_variadic_fn := !is_c_variadic_fn && (g.tc.fn_variadic[fn_name] or { false })
3688 variadic_idx := if is_variadic_fn && param_types.len > 0
3689 && param_types[param_types.len - 1] is types.Array {
3690 param_types.len - 1
3691 } else {
3692 -1
3693 }
3694 typed_param_count := if is_c_variadic_fn && param_types.len > 0
3695 && param_types[param_types.len - 1] is types.Array {
3696 param_types.len - 1
3697 } else {
3698 param_types.len
3699 }
3700 num_args := node.children_count - start
3701 is_variadic := variadic_idx >= 0 && num_args > param_types.len
3702 for i in start .. node.children_count {
3703 if i > start {
3704 g.write(', ')
3705 }
3706 arg_idx := i - start
3707 arg_id := g.a.child(&node, i)
3708 arg_node := g.a.nodes[int(arg_id)]
3709 if arg_node.kind == .field_init && variadic_idx >= 0 && arg_idx == variadic_idx {
3710 variadic_type := param_types[variadic_idx]
3711 if variadic_type is types.Array {
3712 if variadic_type.elem_type is types.Struct {
3713 c_elem := g.tc.c_type(variadic_type.elem_type)
3714 g.write('new_array_from_c_array(1, 1, sizeof(${c_elem}), (${c_elem}[]){')
3715 g.gen_params_struct_arg(variadic_type.elem_type, node, i)
3716 g.write('})')
3717 break
3718 }
3719 }
3720 }
3721 if arg_node.kind == .field_init {
3722 // `@[params]` struct argument: trailing `key: value` args form a struct literal
3723 ptyp := if arg_idx < typed_param_count {
3724 param_types[arg_idx]
3725 } else {
3726 types.Type(types.void_)
3727 }
3728 g.gen_params_struct_arg(ptyp, node, i)
3729 break
3730 }
3731 if fixed := array_fixed_type(g.tc.resolve_type(arg_id)) {
3732 g.gen_fixed_array_data_arg(arg_id, fixed)