v / vlib / v2 / transformer / fn.v
7318 lines · 7094 sloc · 219.68 KB
Raw
1// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module transformer
5
6import v2.ast
7import v2.token
8import v2.types
9
10struct ConcreteSumtypeWrapInfo {
11 name string
12 variants []string
13}
14
15// get_fn_return_type gets the return type for a function
16fn (t &Transformer) get_fn_return_type(fn_name string) ?types.Type {
17 if fn_name.contains('__') {
18 module_name := fn_name.all_before_last('__')
19 short_name := fn_name.all_after_last('__')
20 mut method_lookup_names := []string{}
21 t.append_method_lookup_type_name(mut method_lookup_names, module_name)
22 if ret_type := t.lookup_method_return_type(method_lookup_names, short_name) {
23 return ret_type
24 }
25 if ret_type := t.cached_fn_return_type_index['${module_name}#${short_name}'] {
26 return ret_type
27 }
28 short_module := short_module_name(module_name)
29 if short_module != module_name {
30 if ret_type := t.cached_fn_return_type_index['${short_module}#${short_name}'] {
31 return ret_type
32 }
33 }
34 }
35 // First try the current module scope.
36 if ret_type := t.cached_fn_return_type_index['${t.cur_module}#${fn_name}'] {
37 return ret_type
38 }
39 // Try builtin scope directly (many common functions are here).
40 if t.cur_module != 'builtin' {
41 if ret_type := t.cached_fn_return_type_index['builtin#${fn_name}'] {
42 return ret_type
43 }
44 }
45 // Fallback: scan all module scopes for local/private functions.
46 if ret_type := t.cached_fn_return_type_index['*#${fn_name}'] {
47 return ret_type
48 }
49 return none
50}
51
52fn (t &Transformer) type_from_param_type_expr(expr ast.Expr, generic_params []string) ?types.Type {
53 generic_name := direct_generic_param_name_from_expr(expr, generic_params)
54 if generic_name != '' {
55 return types.Type(types.NamedType(generic_name))
56 }
57 if typ := t.generic_aware_type_from_param_type_expr(expr, generic_params) {
58 return typ
59 }
60 // Resolve from the AST directly so nested type shapes like `&map[K]V`
61 // don't lose structure round-tripping through C-style name strings
62 // (which are ambiguous for pointer-of-map vs. map-of-pointer).
63 if typ := t.lookup_type_from_expr(expr) {
64 return typ
65 }
66 type_name := t.expr_to_type_name(expr)
67 if type_name == '' {
68 return none
69 }
70 if typ := t.c_name_to_type(type_name) {
71 return typ
72 }
73 c_name := t.v_type_name_to_c_name(type_name)
74 if c_name != '' && c_name != type_name {
75 if typ := t.c_name_to_type(c_name) {
76 return typ
77 }
78 }
79 if typ := t.concrete_generic_sumtype_base_type(type_name) {
80 return typ
81 }
82 if c_name != '' && c_name != type_name {
83 if typ := t.concrete_generic_sumtype_base_type(c_name) {
84 return typ
85 }
86 }
87 return none
88}
89
90fn (t &Transformer) concrete_generic_sumtype_base_type(type_name string) ?types.Type {
91 generic_idx := type_name.index('_T_') or { return none }
92 base_name := type_name[..generic_idx]
93 if base_name == '' {
94 return none
95 }
96 typ := t.lookup_concrete_generic_sumtype_base_name(base_name) or { return none }
97 if typ is types.SumType {
98 return typ
99 }
100 return none
101}
102
103fn (t &Transformer) lookup_concrete_generic_sumtype_base_name(base_name string) ?types.Type {
104 last_dunder := base_name.last_index('__') or { return t.lookup_sumtype_by_name(base_name) }
105 module_part := base_name[..last_dunder]
106 short_name := base_name[last_dunder + 2..]
107 if module_part == '' || short_name == '' {
108 return none
109 }
110 if module_part.contains('__') {
111 if typ := t.lookup_sumtype_in_nested_module_path(module_part, short_name) {
112 return typ
113 }
114 if typ := t.lookup_sumtype_by_name('${module_call_c_prefix(module_part)}__${short_name}') {
115 return typ
116 }
117 if typ := t.lookup_sumtype_by_name(base_name) {
118 return typ
119 }
120 return none
121 }
122 if typ := t.lookup_sumtype_by_name(base_name) {
123 return typ
124 }
125 if typ := t.lookup_sumtype_in_nested_module_path(module_part, short_name) {
126 return typ
127 }
128 return none
129}
130
131fn (t &Transformer) lookup_sumtype_by_name(name string) ?types.Type {
132 typ := t.lookup_type(name) or { return none }
133 if typ is types.SumType {
134 return typ
135 }
136 return none
137}
138
139fn (t &Transformer) lookup_sumtype_in_module(module_name string, short_name string) ?types.Type {
140 scope := t.get_module_scope(module_name) or { return none }
141 typ := scope.lookup_type(short_name) or { return none }
142 if typ is types.SumType {
143 return typ
144 }
145 return none
146}
147
148fn (t &Transformer) lookup_sumtype_in_nested_module_path(module_part string, short_name string) ?types.Type {
149 if typ := t.lookup_sumtype_in_module(module_part, short_name) {
150 return typ
151 }
152 dotted_module := module_part.replace('__', '.')
153 if dotted_module != module_part {
154 if typ := t.lookup_sumtype_in_module(dotted_module, short_name) {
155 return typ
156 }
157 }
158 return none
159}
160
161fn fixed_array_len_from_type_expr(expr ast.Expr) int {
162 if expr is ast.BasicLiteral {
163 return expr.value.int()
164 }
165 if expr is ast.Ident {
166 return expr.name.int()
167 }
168 return 0
169}
170
171fn (t &Transformer) generic_aware_type_from_param_type_expr(expr ast.Expr, generic_params []string) ?types.Type {
172 if expr is ast.Ident {
173 if expr.name in generic_params {
174 return types.Type(types.NamedType(expr.name))
175 }
176 return none
177 }
178 if expr is ast.ModifierExpr {
179 return t.generic_aware_type_from_param_type_expr(expr.expr, generic_params)
180 }
181 if expr is ast.PrefixExpr && expr.op == .amp {
182 base := t.type_from_param_type_expr(expr.expr, generic_params) or { return none }
183 return types.Type(types.Pointer{
184 base_type: base
185 })
186 }
187 if expr is ast.Type {
188 match expr {
189 ast.ArrayType {
190 elem := t.type_from_param_type_expr(expr.elem_type, generic_params) or {
191 return none
192 }
193 return types.Type(types.Array{
194 elem_type: elem
195 })
196 }
197 ast.ArrayFixedType {
198 elem := t.type_from_param_type_expr(expr.elem_type, generic_params) or {
199 return none
200 }
201 return types.Type(types.ArrayFixed{
202 len: fixed_array_len_from_type_expr(expr.len)
203 elem_type: elem
204 })
205 }
206 ast.MapType {
207 key := t.type_from_param_type_expr(expr.key_type, generic_params) or { return none }
208 value := t.type_from_param_type_expr(expr.value_type, generic_params) or {
209 return none
210 }
211 return types.Type(types.Map{
212 key_type: key
213 value_type: value
214 })
215 }
216 ast.PointerType {
217 base := t.type_from_param_type_expr(expr.base_type, generic_params) or {
218 return none
219 }
220 return types.Type(types.Pointer{
221 base_type: base
222 })
223 }
224 ast.OptionType {
225 base := t.type_from_param_type_expr(expr.base_type, generic_params) or {
226 return none
227 }
228 return types.Type(types.OptionType{
229 base_type: base
230 })
231 }
232 ast.ResultType {
233 base := t.type_from_param_type_expr(expr.base_type, generic_params) or {
234 return none
235 }
236 return types.Type(types.ResultType{
237 base_type: base
238 })
239 }
240 else {}
241 }
242 }
243 return none
244}
245
246fn (mut t Transformer) seed_fallback_fn_param_scope(params []ast.Parameter, generic_params []string) {
247 for param in params {
248 if param.name == '' || param.name == '_' {
249 continue
250 }
251 if typ := t.type_from_param_type_expr(param.typ, generic_params) {
252 t.remember_local_decl_type(param.name, typ)
253 t.register_local_var_type(param.name, typ)
254 }
255 }
256}
257
258fn (mut t Transformer) seed_fn_param_decl_types(params []ast.Parameter, generic_params []string) {
259 for param in params {
260 if param.name == '' || param.name == '_' {
261 continue
262 }
263 if typ := t.type_from_param_type_expr(param.typ, generic_params) {
264 t.remember_local_decl_type(param.name, typ)
265 continue
266 }
267 if typ := t.lookup_var_type(param.name) {
268 t.remember_local_decl_type(param.name, typ)
269 }
270 }
271}
272
273fn (mut t Transformer) seed_fn_pointer_param_return_types(params []ast.Parameter, generic_params []string) {
274 for param in params {
275 if param.name == '' || param.name == '_' {
276 continue
277 }
278 if ret_type := t.fn_type_expr_return_type(param.typ, generic_params) {
279 t.local_fn_pointer_return_types[param.name] = ret_type
280 }
281 }
282}
283
284fn (t &Transformer) fn_type_expr_return_type(expr ast.Expr, generic_params []string) ?types.Type {
285 mut fn_type := ast.FnType{}
286 mut ok := false
287 if expr is ast.Type && expr is ast.FnType {
288 fn_type = expr as ast.FnType
289 ok = true
290 }
291 if !ok || fn_type.return_type is ast.EmptyExpr {
292 return none
293 }
294 return t.type_from_param_type_expr(fn_type.return_type, generic_params)
295}
296
297// fn_returns_result checks if a function returns a Result type
298fn (t &Transformer) fn_returns_result(fn_name string) bool {
299 ret_type := t.get_fn_return_type(fn_name) or { return false }
300 return ret_type is types.ResultType
301}
302
303// fn_returns_option checks if a function returns an Option type
304fn (t &Transformer) fn_returns_option(fn_name string) bool {
305 ret_type := t.get_fn_return_type(fn_name) or { return false }
306 return ret_type is types.OptionType
307}
308
309// get_fn_return_base_type gets the base type name for a function returning Result/Option
310fn (t &Transformer) get_fn_return_base_type(fn_name string) string {
311 ret_type := t.get_fn_return_type(fn_name) or { return '' }
312 match ret_type {
313 types.ResultType {
314 return ret_type.base_type.name()
315 }
316 types.OptionType {
317 return ret_type.base_type.name()
318 }
319 else {
320 return ''
321 }
322 }
323}
324
325fn (t &Transformer) channel_receive_wrapper_type(expr ast.Expr) ?types.Type {
326 if expr !is ast.PrefixExpr {
327 return none
328 }
329 prefix_expr := expr as ast.PrefixExpr
330 if prefix_expr.op != .arrow {
331 return none
332 }
333 mut recv_type := types.Type(types.void_)
334 if typ := t.get_expr_type(prefix_expr.expr) {
335 recv_type = typ
336 } else if prefix_expr.expr is ast.SelectorExpr {
337 recv_type = t.get_struct_field_type(prefix_expr.expr) or { return none }
338 } else {
339 return none
340 }
341 if elem_type := recv_type.channel_elem_type() {
342 return types.Type(types.OptionType{
343 base_type: elem_type
344 })
345 }
346 return none
347}
348
349fn (t &Transformer) expr_wrapper_type_for_or(expr ast.Expr) ?types.Type {
350 if !expr_has_valid_data(expr) {
351 return none
352 }
353 if typ := t.get_expr_type(expr) {
354 if typ is types.OptionType || typ is types.ResultType {
355 return typ
356 }
357 }
358 if typ := t.fn_pointer_call_return_type(expr) {
359 if typ is types.OptionType || typ is types.ResultType {
360 return typ
361 }
362 }
363 if typ := t.resolve_call_return_type(expr) {
364 if typ is types.OptionType || typ is types.ResultType {
365 return typ
366 }
367 }
368 if wrapper_type := t.channel_receive_wrapper_type(expr) {
369 return wrapper_type
370 }
371 return none
372}
373
374// extract_return_sumtype_name extracts the base sumtype name from a return type AST node.
375// For ?SumType (OptionType) or !SumType (ResultType), returns the base type name.
376fn (t &Transformer) extract_return_sumtype_name(return_type ast.Expr) string {
377 if concrete_name := t.extract_concrete_generic_return_sumtype_name(return_type) {
378 return concrete_name
379 }
380 if return_type is ast.Type {
381 return t.extract_base_type_name_from_type(return_type)
382 }
383 return ''
384}
385
386fn (t &Transformer) extract_concrete_generic_return_sumtype_name(return_type ast.Expr) ?string {
387 match return_type {
388 ast.GenericArgs {
389 return t.concrete_generic_return_sumtype_name_from_parts(return_type.lhs,
390 return_type.args)
391 }
392 ast.GenericArgOrIndexExpr {
393 return t.concrete_generic_return_sumtype_name_from_parts(return_type.lhs, [
394 return_type.expr,
395 ])
396 }
397 ast.Type {
398 match return_type {
399 ast.GenericType {
400 return t.concrete_generic_return_sumtype_name_from_parts(return_type.name,
401 return_type.params)
402 }
403 ast.OptionType {
404 return t.extract_concrete_generic_return_sumtype_name(return_type.base_type)
405 }
406 ast.ResultType {
407 return t.extract_concrete_generic_return_sumtype_name(return_type.base_type)
408 }
409 else {}
410 }
411 }
412 else {}
413 }
414
415 return none
416}
417
418fn (t &Transformer) concrete_generic_return_sumtype_name_from_parts(lhs ast.Expr, args []ast.Expr) ?string {
419 base := t.lookup_type_from_expr(lhs) or {
420 base_full := t.type_expr_name_full(lhs)
421 if base_full != '' {
422 t.lookup_type(base_full) or {
423 base_name := t.type_expr_name(lhs)
424 if base_name == '' {
425 return none
426 }
427 t.lookup_type(base_name) or { return none }
428 }
429 } else {
430 base_name := t.type_expr_name(lhs)
431 if base_name == '' {
432 return none
433 }
434 t.lookup_type(base_name) or { return none }
435 }
436 }
437 if base !is types.SumType {
438 return none
439 }
440 generic_params := generic_template_type_param_names_from_type(base)
441 bindings := t.generic_type_arg_bindings(generic_params, args) or { return none }
442 suffix := t.generic_specialization_suffix_from_bindings(generic_params, bindings)
443 if suffix == '' {
444 return none
445 }
446 return t.type_to_c_name(base) + suffix
447}
448
449fn (t &Transformer) extract_base_type_name_from_type(typ ast.Type) string {
450 if typ is ast.OptionType {
451 return t.extract_type_name_from_expr(typ.base_type)
452 }
453 if typ is ast.ResultType {
454 return t.extract_type_name_from_expr(typ.base_type)
455 }
456 return ''
457}
458
459fn (t &Transformer) extract_type_name_from_expr(expr ast.Expr) string {
460 if expr is ast.Ident {
461 return t.return_type_context_name(expr.name)
462 }
463 if expr is ast.SelectorExpr {
464 if expr.lhs is ast.Ident {
465 lhs_ident := expr.lhs as ast.Ident
466 qualified := '${lhs_ident.name}__${expr.rhs.name}'
467 if _ := t.lookup_type(qualified) {
468 return qualified
469 }
470 }
471 return expr.rhs.name
472 }
473 return ''
474}
475
476fn (t &Transformer) return_type_context_name(name string) string {
477 if name == '' || name.contains('__') {
478 return name
479 }
480 if t.cur_module != '' && t.cur_module != 'main' && t.cur_module != 'builtin' {
481 qualified := '${t.cur_module}__${name}'
482 if _ := t.lookup_type(qualified) {
483 return qualified
484 }
485 }
486 return name
487}
488
489// seed_scope_with_fn_params inserts the function's receiver (for methods)
490// and parameters into the current scope by name → resolved type. Used when
491// the checker did not cache a scope for this function (e.g. generic functions
492// whose only callsite is inside another function body).
493fn (mut t Transformer) seed_scope_with_fn_params(decl ast.FnDecl) {
494 if t.scope == unsafe { nil } {
495 return
496 }
497 if decl.is_method {
498 recv_name := decl.receiver.name
499 if recv_name != '' && t.scope.lookup_var_type(recv_name) == none {
500 if recv_type := t.lookup_type_from_expr(decl.receiver.typ) {
501 mut typ := recv_type
502 if decl.receiver.is_mut {
503 typ = types.Type(types.Pointer{
504 base_type: recv_type
505 })
506 }
507 t.scope.insert(recv_name, typ)
508 }
509 }
510 }
511 for param in decl.typ.params {
512 if param.name == '' {
513 continue
514 }
515 if t.scope.lookup_var_type(param.name) != none {
516 continue
517 }
518 if param_type := t.lookup_type_from_expr(param.typ) {
519 mut typ := param_type
520 if param.is_mut {
521 typ = types.Type(types.Pointer{
522 base_type: param_type
523 })
524 }
525 t.scope.insert(param.name, typ)
526 }
527 }
528}
529
530// lookup_type_from_expr resolves a type-expression AST node (Ident, SelectorExpr,
531// or ast.Type wrapping PointerType/OptionType/ResultType/etc.) to a `types.Type`.
532// Tries module-qualified names before short names so `http.Request` resolves to
533// `http__Request`.
534fn (t &Transformer) lookup_type_from_expr(expr ast.Expr) ?types.Type {
535 if expr is ast.EmptyExpr || !expr_has_valid_data(expr) {
536 return none
537 }
538 if typ := t.get_synth_type(expr.pos()) {
539 return typ
540 }
541 if expr is ast.Ident {
542 if typ := t.lookup_type(expr.name) {
543 return typ
544 }
545 if typ := t.c_name_to_type(expr.name) {
546 return typ
547 }
548 c_name := t.v_type_name_to_c_name(expr.name)
549 if c_name != '' && c_name != expr.name {
550 if typ := t.c_name_to_type(c_name) {
551 return typ
552 }
553 }
554 return none
555 }
556 if expr is ast.SelectorExpr {
557 if expr.lhs is ast.Ident {
558 lhs_ident := expr.lhs as ast.Ident
559 mut module_names := []string{}
560 if full_name := t.cur_import_aliases[lhs_ident.name] {
561 module_names << module_call_c_prefix(full_name)
562 module_names << full_name.replace('.', '__')
563 }
564 if prefix := t.resolve_module_call_prefix(lhs_ident.name) {
565 module_names << prefix
566 }
567 module_names << lhs_ident.name.replace('.', '__')
568 mut seen := map[string]bool{}
569 for module_name in module_names {
570 if module_name == '' || module_name in seen {
571 continue
572 }
573 seen[module_name] = true
574 qualified := '${module_name}__${expr.rhs.name}'
575 if typ := t.lookup_type(qualified) {
576 return typ
577 }
578 }
579 }
580 return t.lookup_type(expr.rhs.name)
581 }
582 if expr is ast.PrefixExpr && expr.op == .amp {
583 base := t.lookup_type_from_expr(expr.expr) or { return none }
584 return types.Type(types.Pointer{
585 base_type: base
586 })
587 }
588 if expr is ast.ModifierExpr {
589 return t.lookup_type_from_expr(expr.expr)
590 }
591 if expr is ast.GenericArgs {
592 return t.lookup_generic_type_from_expr(expr.lhs, expr.args)
593 }
594 if expr is ast.GenericArgOrIndexExpr {
595 return t.lookup_generic_type_from_expr(expr.lhs, [expr.expr])
596 }
597 if expr is ast.Type {
598 return t.lookup_type_from_ast_type(expr)
599 }
600 return none
601}
602
603fn (t &Transformer) lookup_type_from_ast_type(typ ast.Type) ?types.Type {
604 if typ is ast.PointerType {
605 base := t.lookup_type_from_expr(typ.base_type) or { return none }
606 return types.Type(types.Pointer{
607 base_type: base
608 })
609 }
610 if typ is ast.OptionType {
611 base := t.lookup_type_from_expr(typ.base_type) or { return none }
612 return types.Type(types.OptionType{
613 base_type: base
614 })
615 }
616 if typ is ast.ResultType {
617 base := t.lookup_type_from_expr(typ.base_type) or { return none }
618 return types.Type(types.ResultType{
619 base_type: base
620 })
621 }
622 if typ is ast.MapType {
623 key := t.lookup_type_from_expr(typ.key_type) or { return none }
624 value := t.lookup_type_from_expr(typ.value_type) or { return none }
625 return types.Type(types.Map{
626 key_type: key
627 value_type: value
628 })
629 }
630 if typ is ast.ArrayType {
631 elem := t.lookup_type_from_expr(typ.elem_type) or { return none }
632 return types.Type(types.Array{
633 elem_type: elem
634 })
635 }
636 if typ is ast.GenericType {
637 return t.lookup_generic_type_from_expr(typ.name, typ.params)
638 }
639 // For other ast.Type variants (ArrayFixedType, FnType, etc.), the
640 // caller's name extraction logic may not need them. Returning none lets
641 // callers fall back to other resolution paths.
642 return none
643}
644
645fn (t &Transformer) lookup_generic_type_from_expr(lhs ast.Expr, args []ast.Expr) ?types.Type {
646 base := t.lookup_type_from_expr(lhs) or { return none }
647 return t.instantiate_generic_type(base, args)
648}
649
650fn (t &Transformer) instantiate_generic_type(base types.Type, args []ast.Expr) ?types.Type {
651 if base is types.Struct {
652 bindings := t.generic_type_arg_bindings(base.generic_params, args) or { return base }
653 return substitute_type(base, bindings)
654 }
655 return base
656}
657
658fn (t &Transformer) instantiate_generic_sumtype_variant(variant types.Type, bindings map[string]types.Type) types.Type {
659 variant_params := generic_template_type_param_names_from_type(variant)
660 substituted := substitute_type(variant, bindings)
661 suffix := t.generic_specialization_suffix_from_bindings(variant_params, bindings)
662 if suffix == '' {
663 return substituted
664 }
665 return specialize_generic_sumtype_variant_type(variant, substituted, suffix)
666}
667
668fn specialize_generic_sumtype_variant_type(template types.Type, substituted types.Type, suffix string) types.Type {
669 if template is types.Struct && substituted is types.Struct {
670 return types.Type(types.Struct{
671 name: template.name + suffix
672 generic_params: substituted.generic_params
673 implements: substituted.implements
674 embedded: substituted.embedded
675 fields: substituted.fields
676 is_soa: substituted.is_soa
677 })
678 }
679 if template is types.Pointer && substituted is types.Pointer {
680 return types.Type(types.Pointer{
681 lifetime: substituted.lifetime
682 base_type: specialize_generic_sumtype_variant_type(template.base_type,
683 substituted.base_type, suffix)
684 })
685 }
686 if template is types.Array && substituted is types.Array {
687 return types.Type(types.Array{
688 elem_type: specialize_generic_sumtype_variant_type(template.elem_type,
689 substituted.elem_type, suffix)
690 })
691 }
692 if template is types.ArrayFixed && substituted is types.ArrayFixed {
693 return types.Type(types.ArrayFixed{
694 len: substituted.len
695 elem_type: specialize_generic_sumtype_variant_type(template.elem_type,
696 substituted.elem_type, suffix)
697 })
698 }
699 if template is types.Map && substituted is types.Map {
700 return types.Type(types.Map{
701 key_type: specialize_generic_sumtype_variant_type(template.key_type,
702 substituted.key_type, suffix)
703 value_type: specialize_generic_sumtype_variant_type(template.value_type,
704 substituted.value_type, suffix)
705 })
706 }
707 if template is types.OptionType && substituted is types.OptionType {
708 return types.Type(types.OptionType{
709 base_type: specialize_generic_sumtype_variant_type(template.base_type,
710 substituted.base_type, suffix)
711 })
712 }
713 if template is types.ResultType && substituted is types.ResultType {
714 return types.Type(types.ResultType{
715 base_type: specialize_generic_sumtype_variant_type(template.base_type,
716 substituted.base_type, suffix)
717 })
718 }
719 return substituted
720}
721
722fn (t &Transformer) concrete_sumtype_wrap_info_from_lhs(lhs ast.Expr) ?ConcreteSumtypeWrapInfo {
723 match lhs {
724 ast.GenericArgs {
725 return t.concrete_sumtype_wrap_info_from_generic_parts(lhs.lhs, lhs.args)
726 }
727 ast.GenericArgOrIndexExpr {
728 return t.concrete_sumtype_wrap_info_from_generic_parts(lhs.lhs, [lhs.expr])
729 }
730 else {}
731 }
732
733 return none
734}
735
736fn (t &Transformer) concrete_sumtype_wrap_info_from_return_type(return_type ast.Expr) ?ConcreteSumtypeWrapInfo {
737 match return_type {
738 ast.GenericArgs {
739 return t.concrete_sumtype_wrap_info_from_generic_parts(return_type.lhs,
740 return_type.args)
741 }
742 ast.GenericArgOrIndexExpr {
743 return t.concrete_sumtype_wrap_info_from_generic_parts(return_type.lhs, [
744 return_type.expr,
745 ])
746 }
747 ast.Type {
748 match return_type {
749 ast.GenericType {
750 return t.concrete_sumtype_wrap_info_from_generic_parts(return_type.name,
751 return_type.params)
752 }
753 ast.OptionType {
754 return t.concrete_sumtype_wrap_info_from_return_type(return_type.base_type)
755 }
756 ast.ResultType {
757 return t.concrete_sumtype_wrap_info_from_return_type(return_type.base_type)
758 }
759 else {}
760 }
761 }
762 else {}
763 }
764
765 return none
766}
767
768fn (t &Transformer) concrete_sumtype_wrap_info_from_generic_parts(lhs ast.Expr, args []ast.Expr) ?ConcreteSumtypeWrapInfo {
769 base := t.lookup_type_from_expr(lhs) or { return none }
770 if base !is types.SumType {
771 return none
772 }
773 generic_params := generic_template_type_param_names_from_type(types.Type(base))
774 bindings := t.generic_type_arg_bindings(generic_params, args) or { return none }
775 suffix := t.generic_specialization_suffix_from_bindings(generic_params, bindings)
776 if suffix == '' {
777 return none
778 }
779 mut variants := []string{cap: base.variants.len}
780 for variant in base.variants {
781 concrete_variant := t.instantiate_generic_sumtype_variant(variant, bindings)
782 variant_name := t.type_to_c_name(concrete_variant)
783 variants << if variant_name != '' { variant_name } else { concrete_variant.name() }
784 }
785 return ConcreteSumtypeWrapInfo{
786 name: t.type_to_c_name(types.Type(base)) + suffix
787 variants: variants
788 }
789}
790
791fn (t &Transformer) sumtype_wrap_info_for_name(type_name string) ?ConcreteSumtypeWrapInfo {
792 if type_name == '' {
793 return none
794 }
795 if t.is_sum_type(type_name) {
796 return ConcreteSumtypeWrapInfo{
797 name: type_name
798 variants: t.get_sum_type_variants(type_name)
799 }
800 }
801 base := t.concrete_generic_sumtype_base_type(type_name) or { return none }
802 if base !is types.SumType {
803 return none
804 }
805 generic_params := generic_template_type_param_names_from_type(base)
806 if generic_params.len == 0 || t.cur_monomorphized_fn_bindings.len == 0 {
807 return none
808 }
809 mut bindings := map[string]types.Type{}
810 for param in generic_params {
811 concrete := t.cur_monomorphized_fn_bindings[param] or { return none }
812 if clone_type_contains_generic_placeholder(concrete) {
813 return none
814 }
815 bindings[param] = concrete
816 }
817 suffix := t.generic_specialization_suffix_from_bindings(generic_params, bindings)
818 if suffix == '' {
819 return none
820 }
821 expected_name := t.type_to_c_name(base) + suffix
822 if !generic_concrete_type_names_match(expected_name, type_name) {
823 return none
824 }
825 mut variants := []string{cap: base.variants.len}
826 for variant in base.variants {
827 concrete_variant := t.instantiate_generic_sumtype_variant(variant, bindings)
828 variant_name := t.type_to_c_name(concrete_variant)
829 variants << if variant_name != '' { variant_name } else { concrete_variant.name() }
830 }
831 return ConcreteSumtypeWrapInfo{
832 name: type_name
833 variants: variants
834 }
835}
836
837fn (t &Transformer) current_return_sumtype_wrap_info() ?ConcreteSumtypeWrapInfo {
838 if t.cur_fn_return_sumtype_info.name != '' {
839 return t.cur_fn_return_sumtype_info
840 }
841 return t.sumtype_wrap_info_for_name(t.cur_fn_ret_type_name)
842}
843
844fn generic_concrete_type_names_match(expected string, actual string) bool {
845 if expected == actual {
846 return true
847 }
848 if expected == '' || actual == '' {
849 return false
850 }
851 expected_short := if expected.contains('__') { expected.all_after_last('__') } else { expected }
852 actual_short := if actual.contains('__') { actual.all_after_last('__') } else { actual }
853 return expected_short == actual_short
854}
855
856fn (t &Transformer) generic_type_arg_bindings(generic_params []string, args []ast.Expr) ?map[string]types.Type {
857 if generic_params.len == 0 || args.len == 0 {
858 return none
859 }
860 mut bindings := map[string]types.Type{}
861 for i, param_name in generic_params {
862 if i >= args.len {
863 break
864 }
865 arg := args[i]
866 if arg is ast.Ident {
867 if concrete := t.cur_monomorphized_fn_bindings[arg.name] {
868 bindings[param_name] = t.qualify_generic_concrete_type_from_expr(concrete, arg)
869 continue
870 }
871 }
872 if concrete := t.get_synth_type(arg.pos()) {
873 bindings[param_name] = t.qualify_generic_concrete_type_from_expr(concrete, arg)
874 continue
875 }
876 if concrete := t.lookup_type_from_expr(arg) {
877 bindings[param_name] = t.qualify_generic_concrete_type_from_expr(concrete, arg)
878 continue
879 }
880 if concrete := t.get_expr_type(arg) {
881 bindings[param_name] = t.qualify_generic_concrete_type_from_expr(concrete, arg)
882 continue
883 }
884 }
885 if bindings.len == 0 {
886 return none
887 }
888 return bindings
889}
890
891fn (t &Transformer) qualify_generic_concrete_type_from_expr(concrete types.Type, arg ast.Expr) types.Type {
892 match concrete {
893 types.Struct {
894 if name := t.generic_concrete_type_arg_c_name(types.Type(concrete), arg) {
895 return types.Type(types.Struct{
896 name: name
897 generic_params: concrete.generic_params
898 implements: concrete.implements
899 embedded: concrete.embedded
900 fields: concrete.fields
901 is_soa: concrete.is_soa
902 })
903 }
904 }
905 types.Enum {
906 if name := t.generic_concrete_type_arg_c_name(types.Type(concrete), arg) {
907 return types.Type(types.Enum{
908 is_flag: concrete.is_flag
909 name: name
910 fields: concrete.fields
911 })
912 }
913 }
914 types.Interface {
915 if name := t.generic_concrete_type_arg_c_name(types.Type(concrete), arg) {
916 return types.Type(types.Interface{
917 name: name
918 fields: concrete.fields
919 })
920 }
921 }
922 types.SumType {
923 if name := t.generic_concrete_type_arg_c_name(types.Type(concrete), arg) {
924 return types.Type(types.SumType{
925 name: name
926 generic_params: concrete.generic_params
927 variants: concrete.variants
928 })
929 }
930 }
931 types.Alias {
932 if name := t.generic_concrete_type_arg_c_name(types.Type(concrete), arg) {
933 return types.Type(types.Alias{
934 name: name
935 base_type: concrete.base_type
936 })
937 }
938 }
939 types.NamedType {
940 if name := t.generic_concrete_type_arg_c_name(types.Type(concrete), arg) {
941 return types.Type(types.NamedType(name))
942 }
943 }
944 types.Pointer {
945 if base_arg := pointer_generic_type_arg_base(arg) {
946 return types.Type(types.Pointer{
947 lifetime: concrete.lifetime
948 base_type: t.qualify_generic_concrete_type_from_expr(concrete.base_type,
949 base_arg)
950 })
951 }
952 }
953 types.Array {
954 if elem_arg := array_generic_type_arg_elem(arg) {
955 return types.Type(types.Array{
956 elem_type: t.qualify_generic_concrete_type_from_expr(concrete.elem_type,
957 elem_arg)
958 })
959 }
960 }
961 types.ArrayFixed {
962 if elem_arg := array_fixed_generic_type_arg_elem(arg) {
963 return types.Type(types.ArrayFixed{
964 len: concrete.len
965 elem_type: t.qualify_generic_concrete_type_from_expr(concrete.elem_type,
966 elem_arg)
967 })
968 }
969 }
970 types.Map {
971 if parts := map_generic_type_arg_parts(arg) {
972 return types.Type(types.Map{
973 key_type: t.qualify_generic_concrete_type_from_expr(concrete.key_type,
974 parts.key)
975 value_type: t.qualify_generic_concrete_type_from_expr(concrete.value_type,
976 parts.value)
977 })
978 }
979 }
980 types.OptionType {
981 if base_arg := option_generic_type_arg_base(arg) {
982 return types.Type(types.OptionType{
983 base_type: t.qualify_generic_concrete_type_from_expr(concrete.base_type,
984 base_arg)
985 })
986 }
987 }
988 types.ResultType {
989 if base_arg := result_generic_type_arg_base(arg) {
990 return types.Type(types.ResultType{
991 base_type: t.qualify_generic_concrete_type_from_expr(concrete.base_type,
992 base_arg)
993 })
994 }
995 }
996 else {}
997 }
998
999 return concrete
1000}
1001
1002fn (t &Transformer) generic_concrete_type_arg_c_name(concrete types.Type, arg ast.Expr) ?string {
1003 if arg is ast.Ident {
1004 if bound_type := t.cur_monomorphized_fn_bindings[arg.name] {
1005 name := t.type_to_c_name(bound_type)
1006 if name != '' {
1007 return name
1008 }
1009 }
1010 if arg.name.contains('__') {
1011 return arg.name
1012 }
1013 if concrete.name() == arg.name {
1014 return t.generic_ident_type_arg_c_name(arg.name)
1015 }
1016 }
1017 if arg is ast.Type {
1018 match arg {
1019 ast.GenericType {
1020 base_name := t.expr_to_type_name(arg.name)
1021 suffix := t.generic_specialization_suffix(arg.params)
1022 if base_name != '' && suffix != '' {
1023 return base_name + suffix
1024 }
1025 }
1026 else {}
1027 }
1028 }
1029 if name := t.generic_init_type_name(arg) {
1030 return name
1031 }
1032 name := t.expr_to_type_name(arg)
1033 if name == '' {
1034 return none
1035 }
1036 return name
1037}
1038
1039fn (t &Transformer) generic_ident_type_arg_c_name(name string) string {
1040 if name == '' || name.contains('__') || t.is_builtin_type_name(name) {
1041 return name
1042 }
1043 if t.cur_module != '' && t.cur_module != 'main' && t.cur_module != 'builtin' {
1044 if builtin_scope := t.cached_scopes['builtin'] {
1045 if obj := builtin_scope.objects[name] {
1046 if _ := transformer_object_type(obj) {
1047 return name
1048 }
1049 }
1050 }
1051 if module_scope := t.get_module_scope(t.cur_module) {
1052 if obj := module_scope.objects[name] {
1053 if _ := transformer_object_type(obj) {
1054 return '${t.cur_module}__${name}'
1055 }
1056 }
1057 }
1058 }
1059 return name
1060}
1061
1062fn pointer_generic_type_arg_base(arg ast.Expr) ?ast.Expr {
1063 match arg {
1064 ast.PrefixExpr {
1065 if arg.op in [.amp, .mul] {
1066 return arg.expr
1067 }
1068 }
1069 ast.Type {
1070 if arg is ast.PointerType {
1071 return arg.base_type
1072 }
1073 }
1074 else {}
1075 }
1076
1077 return none
1078}
1079
1080fn array_generic_type_arg_elem(arg ast.Expr) ?ast.Expr {
1081 if arg is ast.Type {
1082 match arg {
1083 ast.ArrayType {
1084 return arg.elem_type
1085 }
1086 else {}
1087 }
1088 }
1089 return none
1090}
1091
1092fn array_fixed_generic_type_arg_elem(arg ast.Expr) ?ast.Expr {
1093 if arg is ast.Type {
1094 match arg {
1095 ast.ArrayFixedType {
1096 return arg.elem_type
1097 }
1098 else {}
1099 }
1100 }
1101 return none
1102}
1103
1104struct GenericMapTypeArgParts {
1105 key ast.Expr
1106 value ast.Expr
1107}
1108
1109fn map_generic_type_arg_parts(arg ast.Expr) ?GenericMapTypeArgParts {
1110 if arg is ast.Type {
1111 match arg {
1112 ast.MapType {
1113 return GenericMapTypeArgParts{
1114 key: arg.key_type
1115 value: arg.value_type
1116 }
1117 }
1118 else {}
1119 }
1120 }
1121 return none
1122}
1123
1124fn option_generic_type_arg_base(arg ast.Expr) ?ast.Expr {
1125 if arg is ast.Type {
1126 match arg {
1127 ast.OptionType {
1128 return arg.base_type
1129 }
1130 else {}
1131 }
1132 }
1133 return none
1134}
1135
1136fn result_generic_type_arg_base(arg ast.Expr) ?ast.Expr {
1137 if arg is ast.Type {
1138 match arg {
1139 ast.ResultType {
1140 return arg.base_type
1141 }
1142 else {}
1143 }
1144 }
1145 return none
1146}
1147
1148// get_method_return_type tries to get the return type for a method call.
1149// Returns the return type if found, none otherwise.
1150fn (t &Transformer) get_method_return_type(expr ast.Expr) ?types.Type {
1151 // Check if this is a method call (CallExpr or CallOrCastExpr with SelectorExpr lhs)
1152 mut sel_expr := ast.SelectorExpr{}
1153 mut has_sel := false
1154 if expr is ast.CallExpr {
1155 call_lhs := t.unwrap_call_target_lhs(expr.lhs)
1156 if call_lhs is ast.SelectorExpr {
1157 sel_expr = call_lhs as ast.SelectorExpr
1158 has_sel = true
1159 }
1160 } else if expr is ast.CallOrCastExpr {
1161 call_lhs := t.unwrap_call_target_lhs(expr.lhs)
1162 if call_lhs is ast.SelectorExpr {
1163 sel_expr = call_lhs as ast.SelectorExpr
1164 has_sel = true
1165 }
1166 }
1167 if has_sel {
1168 method_name := sel_expr.rhs.name
1169 mut lookup_type_names := []string{}
1170 // Get the receiver type from the checker's stored types
1171 if receiver_type := t.resolve_expr_type(sel_expr.lhs) {
1172 t.append_method_lookup_type_name(mut lookup_type_names, receiver_type.name())
1173 base_type := t.unwrap_alias_and_pointer_type(receiver_type)
1174 t.append_method_lookup_type_name(mut lookup_type_names, base_type.name())
1175 }
1176 if receiver_type := t.get_expr_type(sel_expr.lhs) {
1177 t.append_method_lookup_type_name(mut lookup_type_names, receiver_type.name())
1178 base_type := t.unwrap_alias_and_pointer_type(receiver_type)
1179 t.append_method_lookup_type_name(mut lookup_type_names, base_type.name())
1180 }
1181 if t.is_string_expr(sel_expr.lhs) {
1182 if ret_type := builtin_string_method_return_type(method_name) {
1183 return ret_type
1184 }
1185 }
1186 if sel_expr.lhs is ast.SelectorExpr {
1187 selector_type_name := t.get_selector_type_name(sel_expr.lhs as ast.SelectorExpr)
1188 if selector_type_name != '' {
1189 t.append_method_lookup_type_name(mut lookup_type_names, selector_type_name)
1190 }
1191 } else if sel_expr.lhs is ast.Ident {
1192 var_type_name := t.get_var_type_name(sel_expr.lhs.name)
1193 t.append_method_lookup_type_name(mut lookup_type_names, var_type_name)
1194 }
1195 if receiver_type := t.resolve_expr_type(sel_expr.lhs) {
1196 if ret_type := t.interface_method_return_type(receiver_type, method_name) {
1197 return ret_type
1198 }
1199 }
1200 if receiver_type := t.get_expr_type(sel_expr.lhs) {
1201 if ret_type := t.interface_method_return_type(receiver_type, method_name) {
1202 return ret_type
1203 }
1204 }
1205 if ret_type := t.lookup_method_return_type(lookup_type_names, method_name) {
1206 return ret_type
1207 }
1208 if ret_type := t.unique_cached_method_return_type(method_name) {
1209 return ret_type
1210 }
1211 if ret_type := t.unique_scope_method_return_type(method_name) {
1212 return ret_type
1213 }
1214 }
1215 return none
1216}
1217
1218fn builtin_string_method_return_type(method_name string) ?types.Type {
1219 match method_name {
1220 'index', 'last_index', 'index_after' {
1221 return types.Type(types.OptionType{
1222 base_type: types.Type(types.int_)
1223 })
1224 }
1225 else {}
1226 }
1227
1228 return none
1229}
1230
1231fn (t &Transformer) interface_method_return_type(receiver_type types.Type, method_name string) ?types.Type {
1232 base_type := t.unwrap_alias_and_pointer_type(receiver_type)
1233 if base_type !is types.Interface {
1234 return none
1235 }
1236 fields := t.resolved_interface_fields(base_type as types.Interface)
1237 for field in fields {
1238 if field.name != method_name || !field.is_interface_method {
1239 continue
1240 }
1241 field_type := t.unwrap_alias_type(field.typ)
1242 if field_type is types.FnType {
1243 return field_type.get_return_type()
1244 }
1245 }
1246 return none
1247}
1248
1249fn (t &Transformer) resolved_interface_fields(iface types.Interface) []types.Field {
1250 if iface.fields.len > 0 {
1251 return iface.fields
1252 }
1253 if iface.name != '' {
1254 if live_type := t.lookup_type(iface.name) {
1255 if live_type is types.Interface && live_type.fields.len > 0 {
1256 return live_type.fields
1257 }
1258 }
1259 short_name := iface.name.all_after_last('__')
1260 if short_name != iface.name {
1261 if live_type := t.lookup_type(short_name) {
1262 if live_type is types.Interface && live_type.fields.len > 0 {
1263 return live_type.fields
1264 }
1265 }
1266 }
1267 }
1268 return iface.fields
1269}
1270
1271fn (t &Transformer) expr_can_be_call_target(expr ast.Expr) bool {
1272 if lhs_type := t.resolve_expr_type(expr) {
1273 return t.is_callable_type(lhs_type)
1274 }
1275 match expr {
1276 ast.Ident {
1277 if t.scope != unsafe { nil } {
1278 if obj := t.scope.lookup_parent(expr.name, 0) {
1279 return obj is types.Fn
1280 }
1281 }
1282 return t.get_fn_return_type(expr.name) != none
1283 }
1284 ast.SelectorExpr {
1285 if expr.lhs is ast.Ident {
1286 return t.get_module_scope((expr.lhs as ast.Ident).name) != none
1287 }
1288 return false
1289 }
1290 else {
1291 return false
1292 }
1293 }
1294}
1295
1296fn (t &Transformer) unwrap_call_target_lhs(lhs ast.Expr) ast.Expr {
1297 match lhs {
1298 ast.GenericArgs {
1299 if t.expr_can_be_call_target(lhs.lhs) {
1300 return t.unwrap_call_target_lhs(lhs.lhs)
1301 }
1302 return lhs
1303 }
1304 ast.GenericArgOrIndexExpr {
1305 if t.expr_can_be_call_target(lhs.lhs) {
1306 return t.unwrap_call_target_lhs(lhs.lhs)
1307 }
1308 return lhs
1309 }
1310 else {
1311 return lhs
1312 }
1313 }
1314}
1315
1316fn module_call_c_prefix(module_name string) string {
1317 if module_name.contains('.') {
1318 return module_name.all_after_last('.')
1319 }
1320 if module_name.contains('__') {
1321 return module_name.all_after_last('__')
1322 }
1323 return module_name
1324}
1325
1326fn (t &Transformer) resolve_module_call_prefix(module_ident string) ?string {
1327 if module_ident == '' {
1328 return none
1329 }
1330 if resolved_mod := t.resolve_module_name(module_ident) {
1331 return module_call_c_prefix(resolved_mod)
1332 }
1333 if t.get_module_scope(module_ident) != none {
1334 return module_call_c_prefix(module_ident)
1335 }
1336 return none
1337}
1338
1339fn (mut t Transformer) transform_generic_module_call_from_parts(lhs ast.Expr, raw_args []ast.Expr, pos token.Pos) ?ast.Expr {
1340 mut sel := ast.SelectorExpr{}
1341 mut type_args := []ast.Expr{}
1342 match lhs {
1343 ast.GenericArgOrIndexExpr {
1344 if lhs.lhs !is ast.SelectorExpr {
1345 return none
1346 }
1347 sel = lhs.lhs as ast.SelectorExpr
1348 type_args << lhs.expr
1349 }
1350 ast.GenericArgs {
1351 if lhs.lhs !is ast.SelectorExpr {
1352 return none
1353 }
1354 sel = lhs.lhs as ast.SelectorExpr
1355 type_args = lhs.args.clone()
1356 }
1357 else {
1358 return none
1359 }
1360 }
1361
1362 if sel.lhs !is ast.Ident {
1363 return none
1364 }
1365 module_ident := (sel.lhs as ast.Ident).name
1366 call_prefix := t.resolve_module_call_prefix(module_ident) or { return none }
1367 suffix := t.generic_specialization_suffix(type_args)
1368 if suffix == '' {
1369 return none
1370 }
1371 call_name := '${call_prefix}__${sel.rhs.name}${suffix}'
1372 args := t.transform_call_args_for_lhs(ast.Expr(ast.SelectorExpr{
1373 lhs: sel.lhs
1374 rhs: sel.rhs
1375 pos: sel.pos
1376 }), raw_args)
1377 return ast.Expr(ast.CallExpr{
1378 lhs: ast.Expr(ast.Ident{
1379 name: call_name
1380 pos: pos
1381 })
1382 args: args
1383 pos: pos
1384 })
1385}
1386
1387fn (mut t Transformer) transform_generic_module_call(expr ast.CallExpr) ?ast.Expr {
1388 return t.transform_generic_module_call_from_parts(expr.lhs, expr.args, expr.pos)
1389}
1390
1391fn (mut t Transformer) transform_generic_selector_method_call(sel ast.SelectorExpr, type_args []ast.Expr, raw_args []ast.Expr, pos token.Pos) ?ast.Expr {
1392 is_module_call := sel.lhs is ast.Ident && t.lookup_var_type(sel.lhs.name) == none
1393 && (t.is_module_ident(sel.lhs.name) || t.get_module_scope(sel.lhs.name) != none)
1394 if is_module_call {
1395 return none
1396 }
1397 suffix := t.generic_specialization_suffix(type_args)
1398 if suffix == '' {
1399 return none
1400 }
1401 generic_lhs := ast.Expr(ast.GenericArgs{
1402 lhs: ast.Expr(sel)
1403 args: type_args
1404 pos: pos
1405 })
1406 call_args := t.lower_missing_call_args(generic_lhs, raw_args)
1407 fn_info := t.lookup_call_fn_info(generic_lhs)
1408 mut args := []ast.Expr{cap: call_args.len + 1}
1409 args << t.transform_expr(sel.lhs)
1410 for i, arg in call_args {
1411 args << t.transform_call_arg_with_sumtype_check(arg, fn_info, i)
1412 }
1413 recv_is_self := t.cur_fn_recv_param != '' && sel.lhs is ast.Ident
1414 && (sel.lhs as ast.Ident).name == t.cur_fn_recv_param && t.get_expr_type(sel.lhs) == none
1415 if recv_is_self && t.cur_fn_recv_prefix != '' {
1416 return ast.Expr(ast.CallExpr{
1417 lhs: ast.Ident{
1418 name: '${t.cur_fn_recv_prefix}__${sel.rhs.name}${suffix}'
1419 }
1420 args: args
1421 pos: pos
1422 })
1423 }
1424 method_name := sel.rhs.name + suffix
1425 if !t.resolves_to_embedded_method(sel.lhs, method_name) {
1426 if resolved := t.resolve_method_call_name(sel.lhs, method_name) {
1427 return ast.Expr(ast.CallExpr{
1428 lhs: ast.Ident{
1429 name: resolved
1430 }
1431 args: args
1432 pos: pos
1433 })
1434 }
1435 }
1436 if !t.resolves_to_embedded_method(sel.lhs, sel.rhs.name) {
1437 if resolved := t.resolve_method_call_name(sel.lhs, sel.rhs.name) {
1438 return ast.Expr(ast.CallExpr{
1439 lhs: ast.Ident{
1440 name: resolved + suffix
1441 }
1442 args: args
1443 pos: pos
1444 })
1445 }
1446 }
1447 return none
1448}
1449
1450fn (t &Transformer) resolve_call_return_type(expr ast.Expr) ?types.Type {
1451 if ret_type := t.generic_call_concrete_return_type(expr) {
1452 return ret_type
1453 }
1454 mut call_lhs := ast.empty_expr
1455 if expr is ast.CallExpr {
1456 call_lhs = t.unwrap_call_target_lhs(expr.lhs)
1457 } else if expr is ast.CallOrCastExpr {
1458 call_lhs = t.unwrap_call_target_lhs(expr.lhs)
1459 } else {
1460 return none
1461 }
1462 match call_lhs {
1463 ast.Ident {
1464 ident := call_lhs as ast.Ident
1465 return t.get_fn_return_type(ident.name)
1466 }
1467 ast.SelectorExpr {
1468 sel := call_lhs as ast.SelectorExpr
1469 if sel.lhs is ast.Ident {
1470 mod_name := (sel.lhs as ast.Ident).name
1471 mut module_names := []string{cap: 4}
1472 module_names << mod_name
1473 if resolved_mod := t.resolve_module_name(mod_name) {
1474 if resolved_mod !in module_names {
1475 module_names << resolved_mod
1476 }
1477 short_mod := if resolved_mod.contains('.') {
1478 resolved_mod.all_after_last('.')
1479 } else if resolved_mod.contains('__') {
1480 resolved_mod.all_after_last('__')
1481 } else {
1482 resolved_mod
1483 }
1484 if short_mod !in module_names {
1485 module_names << short_mod
1486 }
1487 }
1488 for module_name in module_names {
1489 if fn_type := t.lookup_fn_cached(module_name, sel.rhs.name) {
1490 if ret_type := fn_type.get_return_type() {
1491 return ret_type
1492 }
1493 }
1494 }
1495 }
1496 return t.get_method_return_type(expr)
1497 }
1498 else {
1499 return none
1500 }
1501 }
1502}
1503
1504fn (t &Transformer) generic_call_concrete_return_type(expr ast.Expr) ?types.Type {
1505 mut lhs := ast.empty_expr
1506 mut args := []ast.Expr{}
1507 match expr {
1508 ast.CallExpr {
1509 lhs = expr.lhs
1510 args = expr.args.clone()
1511 }
1512 ast.CallOrCastExpr {
1513 lhs = expr.lhs
1514 if expr.expr !is ast.EmptyExpr {
1515 args << expr.expr
1516 }
1517 }
1518 else {
1519 return none
1520 }
1521 }
1522
1523 base_name := t.generic_call_base_name(lhs) or { return none }
1524 decl := t.generic_fn_decl_for_call_info(base_name) or { return none }
1525 generic_params := decl_generic_param_names(decl)
1526 if generic_params.len == 0 || decl.typ.return_type is ast.EmptyExpr {
1527 return none
1528 }
1529 info := t.generic_aware_call_fn_info(lhs, base_name) or { return none }
1530 bindings := t.generic_bindings_from_call_args(info, args) or { return none }
1531 ret_type := t.type_from_param_type_expr(decl.typ.return_type, generic_params) or { return none }
1532 return substitute_type(ret_type, bindings)
1533}
1534
1535fn (t &Transformer) append_method_lookup_type_name(mut names []string, raw_name string) {
1536 normalized := normalized_method_lookup_type_name(raw_name)
1537 if normalized == '' {
1538 return
1539 }
1540 names << normalized
1541 dunder := last_double_underscore(normalized)
1542 if dunder >= 0 {
1543 short_name := normalized[dunder + 2..]
1544 if short_name != '' && short_name != normalized {
1545 names << short_name
1546 }
1547 }
1548}
1549
1550fn normalized_method_lookup_type_name(raw_name string) string {
1551 if raw_name.len == 0 || !transformer_string_has_valid_data(raw_name) {
1552 return ''
1553 }
1554 mut normalized := if raw_name.index_u8(`.`) >= 0 {
1555 raw_name.replace('.', '__')
1556 } else {
1557 raw_name
1558 }
1559 if normalized.starts_with('&') {
1560 normalized = normalized[1..]
1561 }
1562 if normalized.ends_with('*') {
1563 normalized = normalized[..normalized.len - 1]
1564 }
1565 return normalized
1566}
1567
1568fn transformer_string_has_valid_data(s string) bool {
1569 if s.len == 0 {
1570 return true
1571 }
1572 if s.len < 0 || s.len > 1024 {
1573 return false
1574 }
1575 ptr := unsafe { u64(s.str) }
1576 return transformer_data_ptr_has_valid_address(ptr)
1577}
1578
1579fn (t &Transformer) method_key_matches_type_name(method_key string, type_name string) bool {
1580 if method_key.len == 0 || type_name.len == 0 || method_key.len > 1024 || type_name.len > 1024
1581 || !transformer_string_has_valid_data(method_key)
1582 || !transformer_string_has_valid_data(type_name) {
1583 return false
1584 }
1585 // Avoid .replace/.contains here: replace always allocates and contains builds
1586 // a KMP failure table per call. This runs inside O(method_keys) fallback loops
1587 // per call site, so those per-call allocations were a large transform cost.
1588 // Only normalize when a '.' is actually present (index_u8 does not allocate),
1589 // and locate `__` with a hand-rolled scan.
1590 normalized_key := if method_key.index_u8(`.`) >= 0 {
1591 method_key.replace('.', '__')
1592 } else {
1593 method_key
1594 }
1595 normalized_type := if type_name.index_u8(`.`) >= 0 {
1596 type_name.replace('.', '__')
1597 } else {
1598 type_name
1599 }
1600 if normalized_key == normalized_type {
1601 return true
1602 }
1603 key_dunder := last_double_underscore(normalized_key)
1604 type_dunder := last_double_underscore(normalized_type)
1605 if key_dunder >= 0 && type_dunder >= 0 {
1606 return false
1607 }
1608 short_type := if type_dunder >= 0 { normalized_type[type_dunder + 2..] } else { normalized_type }
1609 short_key := if key_dunder >= 0 { normalized_key[key_dunder + 2..] } else { normalized_key }
1610 if short_key == short_type {
1611 return true
1612 }
1613 if normalized_key.len > short_type.len + 2
1614 && normalized_key[normalized_key.len - short_type.len - 2] == `_`
1615 && normalized_key[normalized_key.len - short_type.len - 1] == `_`
1616 && normalized_key.ends_with(short_type) {
1617 return true
1618 }
1619 if normalized_type.len > short_key.len + 2
1620 && normalized_type[normalized_type.len - short_key.len - 2] == `_`
1621 && normalized_type[normalized_type.len - short_key.len - 1] == `_`
1622 && normalized_type.ends_with(short_key) {
1623 return true
1624 }
1625 return false
1626}
1627
1628// candidate_method_keys returns the cached method keys that could fuzzy-match any
1629// of `names` — i.e. those sharing a receiver short name. A method_key_matches_type_name
1630// match always implies equal short names, so the fuzzy fallback loops can scan
1631// these candidates instead of every method key (O(all_keys) per call site).
1632fn (t &Transformer) candidate_method_keys(names []string) []string {
1633 mut cand := []string{}
1634 mut shorts_done := []string{}
1635 for name in names {
1636 if name == '' {
1637 continue
1638 }
1639 sh := method_short_name(name)
1640 if sh in shorts_done {
1641 continue
1642 }
1643 shorts_done << sh
1644 keys := t.cached_method_keys_by_short[sh] or { continue }
1645 cand << keys
1646 }
1647 return cand
1648}
1649
1650fn (t &Transformer) lookup_method_return_type(type_names []string, method_name string) ?types.Type {
1651 if method_name == '' {
1652 return none
1653 }
1654 mut seen := []string{}
1655 for raw_name in type_names {
1656 if raw_name == '' {
1657 continue
1658 }
1659 if raw_name in seen {
1660 continue
1661 }
1662 seen << raw_name
1663 if fn_type := t.lookup_method_cached(raw_name, method_name) {
1664 if ret_type := fn_type.get_return_type() {
1665 return ret_type
1666 }
1667 }
1668 }
1669 for key in t.candidate_method_keys(seen) {
1670 mut matches_receiver := false
1671 for type_name in seen {
1672 if t.method_key_matches_type_name(key, type_name) {
1673 matches_receiver = true
1674 break
1675 }
1676 }
1677 if !matches_receiver {
1678 continue
1679 }
1680 methods_for_type := t.cached_methods[key] or { continue }
1681 for method in methods_for_type {
1682 if method.get_name() != method_name {
1683 continue
1684 }
1685 method_typ := method.get_typ()
1686 if method_typ is types.FnType {
1687 if ret_type := method_typ.get_return_type() {
1688 return ret_type
1689 }
1690 }
1691 }
1692 }
1693 return none
1694}
1695
1696fn (t &Transformer) unique_cached_method_return_type(method_name string) ?types.Type {
1697 if method_name == '' {
1698 return none
1699 }
1700 mut found := types.Type(types.void_)
1701 mut found_any := false
1702 for key in t.cached_method_keys {
1703 methods_for_type := t.cached_methods[key] or { continue }
1704 for method in methods_for_type {
1705 if method.get_name() != method_name {
1706 continue
1707 }
1708 method_typ := method.get_typ()
1709 if method_typ is types.FnType {
1710 ret_type := method_typ.get_return_type() or { continue }
1711 if found_any && ret_type.name() != found.name() {
1712 return none
1713 }
1714 found = ret_type
1715 found_any = true
1716 }
1717 }
1718 }
1719 if found_any {
1720 return found
1721 }
1722 return none
1723}
1724
1725fn (t &Transformer) unique_scope_method_return_type(method_name string) ?types.Type {
1726 if method_name == '' {
1727 return none
1728 }
1729 mut found := types.Type(types.void_)
1730 mut found_any := false
1731 for _, scope in t.cached_scopes {
1732 for key, obj in scope.objects {
1733 if key != method_name && !key.ends_with('__${method_name}')
1734 && !key.ends_with('___${method_name}') && !key.ends_with('.${method_name}') {
1735 continue
1736 }
1737 if obj is types.Fn {
1738 fn_typ := obj.get_typ()
1739 if fn_typ is types.FnType {
1740 ret_type := fn_typ.get_return_type() or { continue }
1741 if found_any && ret_type.name() != found.name() {
1742 return none
1743 }
1744 found = ret_type
1745 found_any = true
1746 }
1747 }
1748 }
1749 }
1750 if found_any {
1751 return found
1752 }
1753 return none
1754}
1755
1756fn (t &Transformer) lookup_method_exists(type_names []string, method_name string) bool {
1757 if method_name == '' {
1758 return false
1759 }
1760 mut seen := []string{}
1761 for raw_name in type_names {
1762 if raw_name == '' || raw_name in seen {
1763 continue
1764 }
1765 seen << raw_name
1766 if _ := t.lookup_method_cached(raw_name, method_name) {
1767 return true
1768 }
1769 }
1770 for key in t.candidate_method_keys(seen) {
1771 mut matches_receiver := false
1772 for type_name in seen {
1773 if t.method_key_matches_type_name(key, type_name) {
1774 matches_receiver = true
1775 break
1776 }
1777 }
1778 if !matches_receiver {
1779 continue
1780 }
1781 methods_for_type := t.cached_methods[key] or { continue }
1782 for method in methods_for_type {
1783 if method.get_name() == method_name {
1784 return true
1785 }
1786 }
1787 }
1788 return false
1789}
1790
1791fn (t &Transformer) type_has_cached_method(typ types.Type, method_name string) bool {
1792 if t.type_name_has_cached_method(typ.name(), method_name) {
1793 return true
1794 }
1795 base_type := t.unwrap_alias_and_pointer_type(typ)
1796 return t.type_name_has_cached_method(base_type.name(), method_name)
1797}
1798
1799fn (t &Transformer) type_name_has_cached_method(raw_name string, method_name string) bool {
1800 normalized := normalized_method_lookup_type_name(raw_name)
1801 if normalized == '' {
1802 return false
1803 }
1804 if _ := t.lookup_method_cached(normalized, method_name) {
1805 return true
1806 }
1807 dunder := last_double_underscore(normalized)
1808 if dunder >= 0 {
1809 short_name := normalized[dunder + 2..]
1810 if short_name != '' && short_name != normalized {
1811 return t.lookup_method_cached(short_name, method_name) != none
1812 }
1813 }
1814 return false
1815}
1816
1817fn (t &Transformer) receiver_has_cached_method(receiver ast.Expr, method_name string) bool {
1818 if typ := t.get_expr_type(receiver) {
1819 return t.type_has_cached_method(typ, method_name)
1820 }
1821 if receiver is ast.SelectorExpr {
1822 selector_type_name := t.get_selector_type_name(receiver)
1823 if t.type_name_has_cached_method(selector_type_name, method_name) {
1824 return true
1825 }
1826 mut lookup_names := []string{}
1827 t.append_method_lookup_type_name(mut lookup_names, selector_type_name)
1828 return t.lookup_method_exists(lookup_names, method_name)
1829 } else if receiver is ast.Ident {
1830 var_type_name := t.get_var_type_name(receiver.name)
1831 if t.type_name_has_cached_method(var_type_name, method_name) {
1832 return true
1833 }
1834 mut lookup_names := []string{}
1835 t.append_method_lookup_type_name(mut lookup_names, var_type_name)
1836 return t.lookup_method_exists(lookup_names, method_name)
1837 }
1838 return false
1839}
1840
1841fn (t &Transformer) smartcast_source_has_cached_method(ctx SmartcastContext, method_name string) bool {
1842 if ctx.sumtype == '' {
1843 return false
1844 }
1845 if typ := t.c_name_to_type(ctx.sumtype) {
1846 return t.type_has_cached_method(typ, method_name)
1847 }
1848 if t.type_name_has_cached_method(ctx.sumtype, method_name) {
1849 return true
1850 }
1851 mut lookup_names := []string{}
1852 t.append_method_lookup_type_name(mut lookup_names, ctx.sumtype)
1853 return t.lookup_method_exists(lookup_names, method_name)
1854}
1855
1856fn (t &Transformer) smartcast_variant_method_name(ctx SmartcastContext, method_name string) ?string {
1857 mut lookup_names := []string{cap: 4}
1858 if ctx.variant_full != '' {
1859 lookup_names << ctx.variant_full
1860 }
1861 if ctx.variant != '' && ctx.variant != ctx.variant_full {
1862 lookup_names << ctx.variant
1863 }
1864 if ctx.variant_full != '' && !ctx.variant_full.contains('__') && t.cur_module != ''
1865 && t.cur_module != 'main' && t.cur_module != 'builtin'
1866 && ctx.variant_full !in ['int', 'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64', 'byte', 'rune', 'f32', 'f64', 'usize', 'isize', 'bool', 'string', 'voidptr', 'charptr', 'byteptr'] {
1867 lookup_names << '${t.cur_module.replace('.', '__')}__${ctx.variant_full}'
1868 }
1869 for lookup_name in lookup_names {
1870 if t.lookup_method_cached(lookup_name, method_name) != none {
1871 return '${lookup_name}__${method_name}'
1872 }
1873 }
1874 return none
1875}
1876
1877fn (mut t Transformer) smartcast_method_receiver(receiver ast.Expr, ctx SmartcastContext) ast.Expr {
1878 is_interface_ctx := ctx.sumtype.starts_with('__iface__')
1879 if is_interface_ctx || t.is_interface_type(ctx.sumtype) || t.is_interface_receiver(receiver) {
1880 iface_object := t.synth_selector(receiver, '_object', types.Type(types.voidptr_))
1881 mut concrete_type := ctx.variant_full
1882 if concrete_type == '' {
1883 concrete_type = ctx.variant
1884 }
1885 if concrete_type != '' {
1886 return ast.CastExpr{
1887 typ: ast.Ident{
1888 name: '${concrete_type}*'
1889 }
1890 expr: iface_object
1891 }
1892 }
1893 }
1894 return t.apply_smartcast_receiver_ctx(receiver, ctx)
1895}
1896
1897struct SmartcastMethodReceiver {
1898 ctx SmartcastContext
1899 receiver ast.Expr
1900}
1901
1902fn (t &Transformer) explicit_cast_inner_expr(expr ast.Expr) ?ast.Expr {
1903 if expr is ast.AsCastExpr {
1904 return expr.expr
1905 }
1906 if expr is ast.CastExpr {
1907 return expr.expr
1908 }
1909 if expr is ast.ParenExpr {
1910 return t.explicit_cast_inner_expr(expr.expr)
1911 }
1912 return none
1913}
1914
1915fn (t &Transformer) method_receiver_without_lhs_explicit_cast(receiver ast.Expr) ?ast.Expr {
1916 if receiver is ast.SelectorExpr {
1917 uncasted_lhs := t.explicit_cast_inner_expr(receiver.lhs) or { return none }
1918 return ast.SelectorExpr{
1919 lhs: uncasted_lhs
1920 rhs: receiver.rhs
1921 pos: receiver.pos
1922 }
1923 }
1924 return none
1925}
1926
1927fn (t &Transformer) smartcast_method_receiver_context(receiver ast.Expr) ?SmartcastMethodReceiver {
1928 receiver_str := t.expr_to_string(receiver)
1929 if receiver_str != '' {
1930 if ctx := t.find_smartcast_for_expr(receiver_str) {
1931 return SmartcastMethodReceiver{
1932 ctx: ctx
1933 receiver: receiver
1934 }
1935 }
1936 }
1937 normalized_receiver := t.method_receiver_without_lhs_explicit_cast(receiver) or { return none }
1938 normalized_str := t.expr_to_string(normalized_receiver)
1939 if normalized_str == '' {
1940 return none
1941 }
1942 ctx := t.find_smartcast_for_expr(normalized_str) or { return none }
1943 return SmartcastMethodReceiver{
1944 ctx: ctx
1945 receiver: normalized_receiver
1946 }
1947}
1948
1949// resolve_expr_type resolves the type of an expression, falling back to scope
1950// lookup when the checker didn't store a type at the expression's position.
1951fn (t &Transformer) resolve_expr_type(expr ast.Expr) ?types.Type {
1952 if !expr_has_valid_data(expr) {
1953 return none
1954 }
1955 // First try the environment (checker stored type)
1956 pos := expr.pos()
1957 if pos.is_valid() {
1958 if typ := t.env.get_expr_type(pos.id) {
1959 return typ
1960 }
1961 }
1962 // Fallback: resolve based on expression structure
1963 if expr is ast.Ident {
1964 if resolved_type := t.lookup_var_type(expr.name) {
1965 return resolved_type
1966 }
1967 return none
1968 }
1969 if expr is ast.IndexExpr {
1970 // For slices (a[x..y]), the result type is the same as the container
1971 if expr.expr is ast.RangeExpr {
1972 if resolved_type := t.resolve_expr_type(expr.lhs) {
1973 return resolved_type
1974 }
1975 }
1976 }
1977 return none
1978}
1979
1980fn (t &Transformer) fn_pointer_call_return_type(expr ast.Expr) ?types.Type {
1981 lhs := match expr {
1982 ast.CallExpr { expr.lhs }
1983 ast.CallOrCastExpr { expr.lhs }
1984 else { return none }
1985 }
1986
1987 if lhs is ast.Ident {
1988 if lhs.name in t.local_fn_pointer_return_types {
1989 ret := t.local_fn_pointer_return_types[lhs.name] or { return none }
1990 return ret
1991 }
1992 fn_type := t.lookup_fn_pointer_var_type(lhs.name) or { return none }
1993 if ret := fn_type.get_return_type() {
1994 return ret
1995 }
1996 }
1997 return none
1998}
1999
2000fn (t &Transformer) lookup_fn_pointer_var_type(name string) ?types.FnType {
2001 if typ := t.lookup_var_type(name) {
2002 return fn_type_from_transformer_type(typ)
2003 }
2004 if t.scope == unsafe { nil } {
2005 return none
2006 }
2007 mut scope := unsafe { t.scope }
2008 for ; scope != unsafe { nil }; scope = scope.parent {
2009 obj := scope.objects[name] or { continue }
2010 return fn_type_from_transformer_type(obj.typ())
2011 }
2012 return none
2013}
2014
2015fn fn_type_from_transformer_type(typ types.Type) ?types.FnType {
2016 if !types.type_has_valid_payload(typ) {
2017 return none
2018 }
2019 match typ {
2020 types.FnType {
2021 return typ
2022 }
2023 types.Alias {
2024 if types.type_has_valid_payload(typ.base_type) && typ.base_type is types.FnType {
2025 return typ.base_type as types.FnType
2026 }
2027 }
2028 types.Pointer {
2029 if !types.type_has_valid_payload(typ.base_type) {
2030 return none
2031 }
2032 if typ.base_type is types.FnType {
2033 return typ.base_type as types.FnType
2034 }
2035 if typ.base_type is types.Alias && types.type_has_valid_payload(typ.base_type.base_type)
2036 && typ.base_type.base_type is types.FnType {
2037 return typ.base_type.base_type as types.FnType
2038 }
2039 }
2040 else {}
2041 }
2042
2043 return none
2044}
2045
2046// expr_returns_option checks if an expression returns an Option type by looking up
2047// its type from the checker's environment. Works for both function and method calls.
2048fn (t &Transformer) expr_returns_option(expr ast.Expr) bool {
2049 if !expr_has_valid_data(expr) {
2050 return false
2051 }
2052 if wrapper_type := t.expr_wrapper_type_for_or(expr) {
2053 return wrapper_type is types.OptionType
2054 }
2055 if typ := t.get_expr_type(expr) {
2056 if typ is types.OptionType || typ.name().starts_with('?') {
2057 return true
2058 }
2059 }
2060 if ret := t.fn_pointer_call_return_type(expr) {
2061 return ret is types.OptionType || ret.name().starts_with('?')
2062 }
2063 if ret := t.get_method_return_type(expr) {
2064 return ret is types.OptionType
2065 }
2066 // Fallback: check if the call target is a function pointer variable with Option return type.
2067 if expr is ast.CallExpr || expr is ast.CallOrCastExpr {
2068 mut call_lhs := ast.empty_expr
2069 if expr is ast.CallExpr {
2070 call_lhs = expr.lhs
2071 } else if expr is ast.CallOrCastExpr {
2072 call_lhs = expr.lhs
2073 }
2074 if call_lhs is ast.Ident {
2075 lhs_ident := call_lhs as ast.Ident
2076 if var_type := t.lookup_var_type(lhs_ident.name) {
2077 return t.fn_type_returns_option(var_type)
2078 }
2079 }
2080 }
2081 return false
2082}
2083
2084// expr_returns_result checks if an expression returns a Result type by looking up
2085// its type from the checker's environment. Works for both function and method calls.
2086fn (t &Transformer) expr_returns_result(expr ast.Expr) bool {
2087 if !expr_has_valid_data(expr) {
2088 return false
2089 }
2090 if typ := t.get_expr_type(expr) {
2091 if typ is types.ResultType || typ.name().starts_with('!') {
2092 return true
2093 }
2094 }
2095 if ret := t.fn_pointer_call_return_type(expr) {
2096 return ret is types.ResultType || ret.name().starts_with('!')
2097 }
2098 if ret := t.get_method_return_type(expr) {
2099 return ret is types.ResultType
2100 }
2101 // Fallback: check if the call target is a function pointer variable with Result return type.
2102 // This handles cases like `if r := fn_ptr_var(args)` where the type checker didn't
2103 // annotate the call expression but the variable's FnType has the return type info.
2104 if expr is ast.CallExpr || expr is ast.CallOrCastExpr {
2105 mut call_lhs := ast.empty_expr
2106 if expr is ast.CallExpr {
2107 call_lhs = expr.lhs
2108 } else if expr is ast.CallOrCastExpr {
2109 call_lhs = expr.lhs
2110 }
2111 if call_lhs is ast.Ident {
2112 lhs_ident := call_lhs as ast.Ident
2113 if var_type := t.lookup_var_type(lhs_ident.name) {
2114 return t.fn_type_returns_result(var_type)
2115 }
2116 }
2117 }
2118 return false
2119}
2120
2121fn (t &Transformer) fn_type_returns_result(typ types.Type) bool {
2122 match typ {
2123 types.FnType {
2124 if ret := typ.get_return_type() {
2125 return ret is types.ResultType
2126 }
2127 }
2128 types.Alias {
2129 return t.fn_type_returns_result(typ.base_type)
2130 }
2131 types.Pointer {
2132 return t.fn_type_returns_result(typ.base_type)
2133 }
2134 else {}
2135 }
2136
2137 return false
2138}
2139
2140fn (t &Transformer) fn_type_returns_option(typ types.Type) bool {
2141 match typ {
2142 types.FnType {
2143 if ret := typ.get_return_type() {
2144 return ret is types.OptionType
2145 }
2146 }
2147 types.Alias {
2148 return t.fn_type_returns_option(typ.base_type)
2149 }
2150 types.Pointer {
2151 return t.fn_type_returns_option(typ.base_type)
2152 }
2153 else {}
2154 }
2155
2156 return false
2157}
2158
2159// get_expr_base_type gets the base type name for an expression returning Result/Option
2160fn (t &Transformer) get_expr_base_type(expr ast.Expr) string {
2161 if !expr_has_valid_data(expr) {
2162 return ''
2163 }
2164 if wrapper_type := t.expr_wrapper_type_for_or(expr) {
2165 match wrapper_type {
2166 types.ResultType {
2167 return wrapper_type.base_type.name()
2168 }
2169 types.OptionType {
2170 return wrapper_type.base_type.name()
2171 }
2172 else {}
2173 }
2174 }
2175 return ''
2176}
2177
2178fn (t &Transformer) contains_call_expr(expr ast.Expr) bool {
2179 return t.contains_call_expr_depth(0, expr)
2180}
2181
2182fn (t &Transformer) contains_call_expr_depth(depth int, expr ast.Expr) bool {
2183 if depth > max_runtime_const_dep_expr_depth || !expr_has_valid_data(expr) {
2184 return false
2185 }
2186 return match expr {
2187 ast.CallExpr {
2188 true
2189 }
2190 ast.CastExpr {
2191 t.contains_call_expr_depth(depth + 1, expr.expr)
2192 }
2193 ast.ParenExpr {
2194 t.contains_call_expr_depth(depth + 1, expr.expr)
2195 }
2196 ast.CallOrCastExpr {
2197 t.contains_call_expr_depth(depth + 1, expr.expr)
2198 }
2199 ast.PrefixExpr {
2200 t.contains_call_expr_depth(depth + 1, expr.expr)
2201 }
2202 ast.PostfixExpr {
2203 t.contains_call_expr_depth(depth + 1, expr.expr)
2204 }
2205 ast.InfixExpr {
2206 t.contains_call_expr_depth(depth + 1, expr.lhs)
2207 || t.contains_call_expr_depth(depth + 1, expr.rhs)
2208 }
2209 ast.ArrayInitExpr {
2210 mut has_call := false
2211 for e in expr.exprs {
2212 if t.contains_call_expr_depth(depth + 1, e) {
2213 has_call = true
2214 break
2215 }
2216 }
2217 has_call = has_call
2218 || (expr.init !is ast.EmptyExpr && t.contains_call_expr_depth(depth + 1, expr.init))
2219 has_call = has_call
2220 || (expr.len !is ast.EmptyExpr && t.contains_call_expr_depth(depth + 1, expr.len))
2221 has_call = has_call
2222 || (expr.cap !is ast.EmptyExpr && t.contains_call_expr_depth(depth + 1, expr.cap))
2223 has_call
2224 }
2225 ast.InitExpr {
2226 mut has_call := false
2227 for field in expr.fields {
2228 if t.contains_call_expr_depth(depth + 1, field.value) {
2229 has_call = true
2230 break
2231 }
2232 }
2233 has_call
2234 }
2235 ast.MapInitExpr {
2236 mut has_call := false
2237 for key in expr.keys {
2238 if t.contains_call_expr_depth(depth + 1, key) {
2239 has_call = true
2240 break
2241 }
2242 }
2243 if !has_call {
2244 for val in expr.vals {
2245 if t.contains_call_expr_depth(depth + 1, val) {
2246 has_call = true
2247 break
2248 }
2249 }
2250 }
2251 has_call
2252 }
2253 ast.SelectorExpr {
2254 t.contains_call_expr_depth(depth + 1, expr.lhs)
2255 }
2256 ast.IndexExpr {
2257 t.contains_call_expr_depth(depth + 1, expr.lhs)
2258 || t.contains_call_expr_depth(depth + 1, expr.expr)
2259 }
2260 else {
2261 false
2262 }
2263 }
2264}
2265
2266// get_call_fn_name extracts the function name from a call expression
2267fn (t &Transformer) get_call_fn_name(expr ast.Expr) string {
2268 if expr is ast.CallExpr {
2269 return t.call_lhs_name(expr.lhs)
2270 }
2271 if expr is ast.CallOrCastExpr {
2272 return t.call_lhs_name(expr.lhs)
2273 }
2274 return ''
2275}
2276
2277// is_method_call_expr returns true when `expr` is a call whose lhs is a method
2278// receiver (`recv.method(...)`), as opposed to a module function call
2279// (`mod.fn(...)`) or a direct identifier call (`fn(...)`).
2280fn (t &Transformer) is_method_call_expr(expr ast.Expr) bool {
2281 mut lhs := ast.empty_expr
2282 if expr is ast.CallExpr {
2283 lhs = t.unwrap_call_target_lhs(expr.lhs)
2284 } else if expr is ast.CallOrCastExpr {
2285 lhs = t.unwrap_call_target_lhs(expr.lhs)
2286 } else {
2287 return false
2288 }
2289 if lhs !is ast.SelectorExpr {
2290 return false
2291 }
2292 sel := lhs as ast.SelectorExpr
2293 // If sel.lhs is an Ident that resolves to a module, treat as module call.
2294 if sel.lhs is ast.Ident {
2295 mod_ident := (sel.lhs as ast.Ident).name
2296 if t.get_module_scope(mod_ident) != none {
2297 return false
2298 }
2299 if t.resolve_module_name(mod_ident) != none {
2300 return false
2301 }
2302 }
2303 return true
2304}
2305
2306fn (t &Transformer) call_lhs_name(lhs ast.Expr) string {
2307 unwrapped_lhs := t.unwrap_call_target_lhs(lhs)
2308 match unwrapped_lhs {
2309 ast.Ident {
2310 ident := unwrapped_lhs as ast.Ident
2311 return ident.name
2312 }
2313 ast.SelectorExpr {
2314 sel := unwrapped_lhs as ast.SelectorExpr
2315 return sel.rhs.name
2316 }
2317 ast.GenericArgs {
2318 ga := unwrapped_lhs as ast.GenericArgs
2319 return t.call_lhs_name(ga.lhs)
2320 }
2321 ast.GenericArgOrIndexExpr {
2322 gai := unwrapped_lhs as ast.GenericArgOrIndexExpr
2323 return t.call_lhs_name(gai.lhs)
2324 }
2325 else {
2326 return ''
2327 }
2328 }
2329}
2330
2331// is_void_call_expr checks if an expression is a function call that returns void.
2332// Used to detect or-blocks that end with a void call (e.g. error_with_pos()).
2333fn (mut t Transformer) is_void_call_expr(expr ast.Expr) bool {
2334 fn_name := t.get_call_fn_name(expr)
2335 if fn_name == '' {
2336 return false
2337 }
2338 if ret := t.get_expr_type(expr) {
2339 ret_name := ret.name()
2340 return ret_name == '' || ret_name == 'void' || ret_name == 'Void'
2341 }
2342 // Check using method return type lookup
2343 if ret := t.get_method_return_type(expr) {
2344 // Check if the return type is actually void (Void, Nil, or Primitive with empty name)
2345 ret_name := ret.name()
2346 if ret_name != '' && ret_name != 'void' && ret_name != 'Void' {
2347 return false // Has a non-void return type
2348 }
2349 return true // Return type is void
2350 }
2351 // For method calls (SelectorExpr LHS), don't fall back to fn_return_type lookup
2352 // since the short method name may conflict with a builtin function.
2353 // e.g. `logger.error(...)` has fn_name='error' which matches builtin `error()`.
2354 mut is_method_call := false
2355 if expr is ast.CallExpr && expr.lhs is ast.SelectorExpr {
2356 is_method_call = true
2357 } else if expr is ast.CallOrCastExpr && expr.lhs is ast.SelectorExpr {
2358 is_method_call = true
2359 }
2360 if !is_method_call {
2361 // Check using fn return type lookup
2362 if ret := t.get_fn_return_type(fn_name) {
2363 ret_name := ret.name()
2364 if ret_name != '' && ret_name != 'void' && ret_name != 'Void' {
2365 return false
2366 }
2367 return true
2368 }
2369 // Fallback for well-known noreturn / void builtins that may not have a
2370 // registered Fn entry (e.g. when the transformer runs against synthetic
2371 // AST in unit tests). Real code with a registered signature still hits
2372 // the get_fn_return_type branch above.
2373 short_name := fn_name.all_after_last('__')
2374 if short_name in ['panic', 'exit', 'eprintln_exit', 'unreachable'] {
2375 return true
2376 }
2377 }
2378 return false
2379}
2380
2381fn (mut t Transformer) transform_fn_decl(decl ast.FnDecl) ast.FnDecl {
2382 lowered_decl := t.fn_decl_with_implicit_veb_context_param(decl)
2383 attrs, stmts := t.transform_fn_decl_parts(lowered_decl)
2384 return ast.FnDecl{
2385 attributes: attrs
2386 is_public: lowered_decl.is_public
2387 is_method: lowered_decl.is_method
2388 is_static: lowered_decl.is_static
2389 receiver: lowered_decl.receiver
2390 language: lowered_decl.language
2391 name: lowered_decl.name
2392 typ: lowered_decl.typ
2393 stmts: stmts
2394 pos: lowered_decl.pos
2395 }
2396}
2397
2398fn (mut t Transformer) fn_decl_with_implicit_veb_context_param(decl ast.FnDecl) ast.FnDecl {
2399 if decl.receiver.name == 'ctx' {
2400 return decl
2401 }
2402 for param in decl.typ.params {
2403 if param.name == 'ctx' {
2404 return decl
2405 }
2406 }
2407 scope_fn_name, fn_scope_key := t.fn_scope_names_for_decl(decl)
2408 if !t.fn_decl_returns_veb_result(decl, scope_fn_name, fn_scope_key) {
2409 return decl
2410 }
2411 ctx_type := t.implicit_veb_context_type(scope_fn_name, fn_scope_key) or { return decl }
2412 ctx_base_type := if ctx_type is types.Pointer { ctx_type.base_type } else { ctx_type }
2413 ctx_param := ast.Parameter{
2414 name: 'ctx'
2415 typ: t.type_to_ast_expr(ctx_base_type, decl.pos)
2416 is_mut: true
2417 pos: decl.pos
2418 }
2419 mut params := []ast.Parameter{cap: decl.typ.params.len + 1}
2420 params << ctx_param
2421 for param in decl.typ.params {
2422 params << param
2423 }
2424 return ast.FnDecl{
2425 attributes: decl.attributes
2426 is_public: decl.is_public
2427 is_method: decl.is_method
2428 is_static: decl.is_static
2429 receiver: decl.receiver
2430 language: decl.language
2431 name: decl.name
2432 typ: ast.FnType{
2433 generic_params: decl.typ.generic_params
2434 params: params
2435 return_type: decl.typ.return_type
2436 }
2437 stmts: decl.stmts
2438 pos: decl.pos
2439 }
2440}
2441
2442fn (mut t Transformer) implicit_veb_context_type(scope_fn_name string, fn_scope_key string) ?types.Type {
2443 if fn_scope := t.cached_fn_scopes[fn_scope_key] {
2444 if typ := fn_scope.lookup_var_type('ctx') {
2445 return typ
2446 }
2447 }
2448 if fn_scope := t.env.get_fn_scope(t.cur_module, scope_fn_name) {
2449 if typ := fn_scope.lookup_var_type('ctx') {
2450 return typ
2451 }
2452 }
2453 return none
2454}
2455
2456fn (mut t Transformer) fn_decl_returns_veb_result(decl ast.FnDecl, scope_fn_name string, fn_scope_key string) bool {
2457 if t.return_expr_is_veb_result(decl.typ.return_type) {
2458 return true
2459 }
2460 if ret := t.get_fn_return_type(scope_fn_name) {
2461 if ret.name() == 'veb__Result' || ret.name() == 'veb.Result' {
2462 return true
2463 }
2464 }
2465 if ret := t.get_fn_return_type(fn_scope_key) {
2466 if ret.name() == 'veb__Result' || ret.name() == 'veb.Result' {
2467 return true
2468 }
2469 }
2470 return false
2471}
2472
2473fn (t &Transformer) return_expr_is_veb_result(expr ast.Expr) bool {
2474 match expr {
2475 ast.Ident {
2476 return expr.name == 'veb__Result' || expr.name == 'veb.Result'
2477 || (expr.name == 'Result' && t.cur_module == 'veb')
2478 }
2479 ast.SelectorExpr {
2480 if expr.lhs is ast.Ident {
2481 lhs := expr.lhs as ast.Ident
2482 return lhs.name == 'veb' && expr.rhs.name == 'Result'
2483 }
2484 }
2485 else {}
2486 }
2487
2488 return false
2489}
2490
2491fn (t &Transformer) fn_scope_names_for_decl(decl ast.FnDecl) (string, string) {
2492 scope_fn_name := if decl.is_method {
2493 // Match checker scope keys: receiver base type name WITHOUT current module prefix,
2494 // then `__method_name`. The module name is already part of env.get_fn_scope key.
2495 mut recv_name := t.get_receiver_type_name(decl.receiver.typ)
2496 if t.cur_module != '' {
2497 prefix := '${t.cur_module}__'
2498 if recv_name.starts_with(prefix) {
2499 recv_name = recv_name[prefix.len..]
2500 }
2501 }
2502 '${recv_name}__${decl.name}'
2503 } else {
2504 decl.name
2505 }
2506 fn_scope_key := if t.cur_module == '' {
2507 scope_fn_name
2508 } else {
2509 '${t.cur_module}__${scope_fn_name}'
2510 }
2511 return scope_fn_name, fn_scope_key
2512}
2513
2514// transform_fn_decl_parts_to_flat is the flat-builder mirror of
2515// `transform_fn_decl_parts`. Returns the final attribute list and the body
2516// stmts already encoded as FlatNodeIds in `out`, ready for the FnDecl arm to
2517// wrap via `emit_fn_decl_by_ids`. Currently a literal pass-through (delegates
2518// to the legacy `_parts` helper, then leaf-encodes each stmt via
2519// `out.emit_stmt`) — same observable behavior as the previous FnDecl arm's
2520// explicit per-stmt loop, just moved behind a named seam. The point of the
2521// seam is to enable progressive porting of `transform_stmts` body-stmt
2522// expansion sites in follow-up sessions: each future session can replace
2523// part of the pass-through with direct-emit logic that skips intermediate
2524// `ast.Stmt` wrappers. Bit-equal scaffolding — zero memory savings on its
2525// own; the wins materialise in the per-site ports.
2526fn (mut t Transformer) transform_fn_decl_parts_to_flat(decl ast.FnDecl, mut out ast.FlatBuilder) ([]ast.Attribute, []ast.FlatNodeId) {
2527 attrs, stmts := t.transform_fn_decl_parts(decl)
2528 mut stmt_ids := []ast.FlatNodeId{cap: stmts.len}
2529 for s in stmts {
2530 stmt_ids << out.emit_stmt(s)
2531 }
2532 return attrs, stmt_ids
2533}
2534
2535// transform_fn_decl_parts is the body-work driver behind `transform_fn_decl`.
2536// It returns the two variable parts of the lowered FnDecl — final attribute
2537// list (possibly augmented with `noinline` for `@[live]`) and the final
2538// transformed + defer-lowered stmt list — leaving the immutable
2539// is_public/is_method/is_static/receiver/language/name/typ/pos fields to be
2540// re-attached by the caller. The flat-write port's FnDecl arm calls
2541// `transform_fn_decl_parts_to_flat` (above) which delegates here and then
2542// leaf-encodes. Future sessions fork the body work into direct-emit paths
2543// inside the `_to_flat` seam without touching this legacy helper or its
2544// non-flat callers.
2545struct FnBodyTransformCtx {
2546mut:
2547 live_fn_detected bool
2548 scope_fn_name string
2549 fn_scope_key string
2550 has_return_type bool
2551 fn_return_type types.Type
2552 old_scope &types.Scope = unsafe { nil }
2553 old_fn_root_scope &types.Scope = unsafe { nil }
2554 old_local_decl_types map[string]types.Type
2555 old_fn_ret_type_name string
2556 old_fn_return_sumtype_info ConcreteSumtypeWrapInfo
2557 old_fn_returns_option bool
2558 old_fn_returns_result bool
2559 old_fn_name_str string
2560 old_fn_recv_prefix string
2561 old_fn_recv_param string
2562 old_fn_recv_is_ptr bool
2563 old_monomorphized_bindings map[string]types.Type
2564 old_fn_generic_params []string
2565 old_local_fn_pointer_return_types map[string]types.Type
2566 old_local_receiver_generic_bindings map[string]map[string]types.Type
2567 old_generic_var_type_params map[string]string
2568 old_smartcast_stack []SmartcastContext
2569 old_smartcast_expr_counts map[string]int
2570}
2571
2572fn (mut t Transformer) enter_fn_body_transform(decl ast.FnDecl) ?FnBodyTransformCtx {
2573 mut ctx := FnBodyTransformCtx{}
2574 // and they will never be called, so emit an empty body.
2575 if has_non_lifetime_generic_params(decl.typ.generic_params) {
2576 mut has_generic_types := decl.is_static || decl.name in t.env.generic_types
2577 if !has_generic_types {
2578 for key, _ in t.env.generic_types {
2579 if key.starts_with('${decl.name}[') || key.contains('.${decl.name}[')
2580 || key.ends_with('.${decl.name}') || key.contains('__${decl.name}[')
2581 || key.ends_with('__${decl.name}') {
2582 has_generic_types = true
2583 break
2584 }
2585 }
2586 }
2587 // Generic function values (`handler[T]`) are specialized later by cgen, not
2588 // through the normal checked call path, so keep their bodies for that pass.
2589 if !has_generic_types && decl.name !in t.generic_fn_value_names {
2590 return none
2591 }
2592 }
2593
2594 // Check for conditional compilation attributes (e.g., @[if verbose ?])
2595 // Skip functions whose conditions evaluate to false, and mark them for call elision
2596 for attr in decl.attributes {
2597 if attr.comptime_cond !is ast.EmptyExpr {
2598 if !t.eval_comptime_cond(attr.comptime_cond) {
2599 t.elided_fns[decl.name] = true
2600 return none
2601 }
2602 }
2603 }
2604
2605 // Detect @[live] functions for hot code reloading
2606 if t.pref != unsafe { nil }
2607 && (t.pref.backend == .arm64 || t.pref.backend == .x64 || t.pref.backend == .cleanc) {
2608 if decl.attributes.has('live') {
2609 mangled := if decl.is_method {
2610 recv_name := t.get_receiver_type_name(decl.receiver.typ)
2611 '${recv_name}__${decl.name}'
2612 } else {
2613 decl.name
2614 }
2615 recv_type := if decl.is_method {
2616 t.get_receiver_type_name(decl.receiver.typ)
2617 } else {
2618 ''
2619 }
2620 t.live_fns << LiveFn{
2621 decl_name: decl.name
2622 mangled_name: mangled
2623 is_method: decl.is_method
2624 recv_type: recv_type
2625 }
2626 if t.cur_file_name.len > 0 {
2627 t.live_source_file = t.cur_file_name
2628 }
2629 ctx.live_fn_detected = true
2630 }
2631 }
2632 // Save current scope and fn_root_scope
2633 ctx.old_scope = t.scope
2634 ctx.old_fn_root_scope = t.fn_root_scope
2635
2636 // Get the function's scope from the environment (populated by checker)
2637 // This contains parameter types, receiver type, and local variables
2638 // For methods, include receiver type in the key (e.g., "SortedMap__set").
2639 // The key must match how the checker generates it (using resolved/base type).
2640 scope_fn_name, fn_scope_key := t.fn_scope_names_for_decl(decl)
2641 ctx.fn_scope_key = fn_scope_key
2642 fn_generic_params := generic_param_names(decl.typ.generic_params)
2643 ctx.old_local_decl_types = t.local_decl_types.move()
2644 t.local_decl_types = map[string]types.Type{}
2645 if fn_scope := t.cached_fn_scopes[fn_scope_key] {
2646 t.scope = types.new_scope(fn_scope)
2647 t.fn_root_scope = t.scope
2648 } else {
2649 // Fallback: create a new scope if function scope not found.
2650 // Generic function bodies are skipped by the checker when no
2651 // specialization is recorded at body-check time, so no scope is
2652 // cached. Seed the scope from the AST params/receiver so method
2653 // return-type lookups in or-expr lowering keep working.
2654 t.open_scope()
2655 t.fn_root_scope = t.scope
2656 t.seed_fallback_fn_param_scope(decl.typ.params, fn_generic_params)
2657 // Cache the seeded scope locally and also publish directly to env.fn_scopes
2658 // (lock-protected). Worker→main merge of cached_fn_scopes can silently drop
2659 // keys inserted into a worker's map after clone, so we publish the entry
2660 // through the shared environment as well — that's what cleanc reads at
2661 // emit time via env.get_fn_scope(cur_module, fn_name).
2662 t.cached_fn_scopes[fn_scope_key] = t.fn_root_scope
2663 t.env.set_fn_scope(t.cur_module, scope_fn_name, t.fn_root_scope)
2664 }
2665 if decl.is_method && decl.receiver.name != '' && decl.receiver.name != '_' {
2666 if typ := t.type_from_param_type_expr(decl.receiver.typ, fn_generic_params) {
2667 t.remember_local_decl_type(decl.receiver.name, typ)
2668 t.register_local_var_type(decl.receiver.name, typ)
2669 }
2670 }
2671 t.seed_fn_param_decl_types(decl.typ.params, fn_generic_params)
2672 t.seed_fn_pointer_param_return_types(decl.typ.params, fn_generic_params)
2673 // Ensure params/receiver are present in scope. The checker may cache an
2674 // empty scope for generic function bodies (no specialization recorded),
2675 // so seed missing entries from the AST so method-return-type lookups in
2676 // or-expr lowering (e.g. `req.header.get(.host) or { ... }`) work.
2677 t.seed_scope_with_fn_params(decl)
2678 ctx.old_monomorphized_bindings = t.cur_monomorphized_fn_bindings.move()
2679 t.cur_monomorphized_fn_bindings = t.lookup_monomorphized_fn_bindings(t.cur_module,
2680 scope_fn_name) or {
2681 t.lookup_monomorphized_fn_bindings(t.cur_module, decl.name) or {
2682 map[string]types.Type{}
2683 }
2684 }
2685
2686 // Set current function return type for sum type wrapping in returns
2687 // and enum shorthand resolution
2688 ctx.old_fn_ret_type_name = t.cur_fn_ret_type_name
2689 ctx.old_fn_return_sumtype_info = t.cur_fn_return_sumtype_info
2690 ctx.old_fn_returns_option = t.cur_fn_returns_option
2691 ctx.old_fn_returns_result = t.cur_fn_returns_result
2692 t.cur_fn_return_sumtype_info = ConcreteSumtypeWrapInfo{}
2693 t.cur_fn_returns_option = false
2694 t.cur_fn_returns_result = false
2695 if decl.typ.return_type is ast.Type {
2696 t.cur_fn_returns_option = decl.typ.return_type is ast.OptionType
2697 t.cur_fn_returns_result = decl.typ.return_type is ast.ResultType
2698 }
2699 if decl.typ.return_type is ast.Ident {
2700 ret_name := decl.typ.return_type.name
2701 // Qualify with module prefix for enum shorthand resolution
2702 // (e.g., Token → token__Token so resolve_enum_shorthand produces token__Token__member)
2703 // Skip qualification if the type is a builtin type (e.g., ChanState is defined in
2704 // vlib/builtin, so functions in the sync module returning ChanState should NOT
2705 // produce sync__ChanState__member — just ChanState__member).
2706 mut is_builtin_ret_type := false
2707 if !ret_name.contains('__') {
2708 if scope := t.get_module_scope('builtin') {
2709 if obj := scope.lookup_parent(ret_name, 0) {
2710 is_builtin_ret_type = obj is types.Type
2711 }
2712 }
2713 // Fallback: check if module-qualified name does NOT exist as a type.
2714 // If `sync__ChanState` is not a real type but `ChanState` is (builtin),
2715 // then don't add the module prefix.
2716 if !is_builtin_ret_type && t.cur_module != '' {
2717 qualified := '${t.cur_module}__${ret_name}'
2718 qualified_exists := t.lookup_type(qualified) != none
2719 if !qualified_exists {
2720 is_builtin_ret_type = true
2721 }
2722 }
2723 }
2724 if t.cur_module != '' && t.cur_module != 'main' && t.cur_module != 'builtin'
2725 && !ret_name.contains('__') && !is_builtin_ret_type {
2726 t.cur_fn_ret_type_name = '${t.cur_module}__${ret_name}'
2727 } else {
2728 t.cur_fn_ret_type_name = ret_name
2729 }
2730 } else if decl.typ.return_type is ast.SelectorExpr {
2731 // Handle module-qualified return types like token.Token
2732 sel := decl.typ.return_type as ast.SelectorExpr
2733 if sel.lhs is ast.Ident {
2734 t.cur_fn_ret_type_name = '${sel.lhs.name}__${sel.rhs.name}'
2735 }
2736 } else {
2737 t.cur_fn_ret_type_name = t.extract_return_sumtype_name(decl.typ.return_type)
2738 }
2739 t.cur_fn_return_sumtype_info = t.concrete_sumtype_wrap_info_from_return_type(decl.typ.return_type) or {
2740 ConcreteSumtypeWrapInfo{}
2741 }
2742
2743 // Transform function body
2744 // Clear per-function state: array_elem_type_overrides tracks .map() result types
2745 // and must not leak across function boundaries (e.g., variable 'a' in one function
2746 // must not affect variable 'a' in another function).
2747 t.array_elem_type_overrides = map[string]string{}
2748 t.interface_concrete_types = map[string]string{}
2749 ctx.old_fn_name_str = t.cur_fn_name_str
2750 ctx.old_fn_recv_prefix = t.cur_fn_recv_prefix
2751 ctx.old_fn_recv_param = t.cur_fn_recv_param
2752 ctx.old_fn_recv_is_ptr = t.cur_fn_recv_is_ptr
2753 t.cur_fn_name_str = decl.name
2754 if decl.is_method {
2755 recv_name := t.get_receiver_type_name(decl.receiver.typ)
2756 if t.cur_module != '' && t.cur_module != 'main' && t.cur_module != 'builtin'
2757 && !recv_name.contains('__') {
2758 t.cur_fn_recv_prefix = '${t.cur_module}__${recv_name}'
2759 } else {
2760 t.cur_fn_recv_prefix = recv_name
2761 }
2762 t.cur_fn_recv_param = decl.receiver.name
2763 mut recv_is_ptr := decl.receiver.is_mut
2764 if recv_type := t.type_from_param_type_expr(decl.receiver.typ, fn_generic_params) {
2765 recv_is_ptr = recv_is_ptr || t.is_pointer_type(recv_type)
2766 }
2767 t.cur_fn_recv_is_ptr = recv_is_ptr
2768 } else {
2769 t.cur_fn_recv_prefix = ''
2770 t.cur_fn_recv_param = ''
2771 t.cur_fn_recv_is_ptr = false
2772 }
2773 ctx.old_fn_generic_params = t.cur_fn_generic_params
2774 ctx.old_local_fn_pointer_return_types = t.local_fn_pointer_return_types.move()
2775 ctx.old_local_receiver_generic_bindings = t.local_receiver_generic_bindings.move()
2776 ctx.old_generic_var_type_params = t.generic_var_type_params.move()
2777 t.cur_fn_generic_params = fn_generic_params
2778 t.local_fn_pointer_return_types = map[string]types.Type{}
2779 t.local_receiver_generic_bindings = map[string]map[string]types.Type{}
2780 t.seed_fn_pointer_param_return_types(decl.typ.params, fn_generic_params)
2781 if t.generic_var_type_params.len == 0 {
2782 t.generic_var_type_params = map[string]string{}
2783 }
2784 if t.cur_fn_generic_params.len > 0 {
2785 for param in decl.typ.params {
2786 if placeholder := t.generic_placeholder_from_type_expr(param.typ) {
2787 t.generic_var_type_params[param.name] = placeholder
2788 }
2789 }
2790 }
2791 ctx.old_smartcast_stack = t.smartcast_stack
2792 ctx.old_smartcast_expr_counts = t.smartcast_expr_counts.move()
2793 t.smartcast_stack = []SmartcastContext{cap: 4}
2794 t.smartcast_expr_counts = map[string]int{}
2795 ctx.has_return_type = decl.typ.return_type !is ast.EmptyExpr
2796 ctx.fn_return_type = t.get_fn_return_type(scope_fn_name) or {
2797 t.get_fn_return_type(fn_scope_key) or { types.Type(types.void_) }
2798 }
2799 return ctx
2800}
2801
2802fn (mut t Transformer) restore_fn_body_transform_state(mut ctx FnBodyTransformCtx) {
2803 t.smartcast_stack = ctx.old_smartcast_stack
2804 t.smartcast_expr_counts = ctx.old_smartcast_expr_counts.move()
2805 t.cur_fn_generic_params = ctx.old_fn_generic_params
2806 t.local_fn_pointer_return_types = ctx.old_local_fn_pointer_return_types.move()
2807 t.local_receiver_generic_bindings = ctx.old_local_receiver_generic_bindings.move()
2808 t.generic_var_type_params = ctx.old_generic_var_type_params.move()
2809 t.cur_fn_name_str = ctx.old_fn_name_str
2810 t.cur_fn_recv_prefix = ctx.old_fn_recv_prefix
2811 t.cur_fn_recv_param = ctx.old_fn_recv_param
2812 t.cur_fn_recv_is_ptr = ctx.old_fn_recv_is_ptr
2813 t.cur_monomorphized_fn_bindings = ctx.old_monomorphized_bindings.move()
2814 t.cur_fn_ret_type_name = ctx.old_fn_ret_type_name
2815 t.cur_fn_return_sumtype_info = ctx.old_fn_return_sumtype_info
2816 t.cur_fn_returns_option = ctx.old_fn_returns_option
2817 t.cur_fn_returns_result = ctx.old_fn_returns_result
2818}
2819
2820fn (mut t Transformer) finish_fn_body_transform(decl ast.FnDecl, mut ctx FnBodyTransformCtx) []ast.Attribute {
2821 if t.fn_root_scope != unsafe { nil } {
2822 t.cached_fn_scopes[ctx.fn_scope_key] = t.fn_root_scope
2823 }
2824 t.local_decl_types = ctx.old_local_decl_types.move()
2825
2826 // Restore previous scope and fn_root_scope
2827 t.scope = ctx.old_scope
2828 t.fn_root_scope = ctx.old_fn_root_scope
2829
2830 // For @[live] functions, force @[noinline] so the function gets its own
2831 // symbol in the binary (required for -hot-fn extraction).
2832 if ctx.live_fn_detected && !decl.attributes.has('noinline') {
2833 mut new_attrs := []ast.Attribute{cap: decl.attributes.len + 1}
2834 new_attrs << ast.Attribute{
2835 value: ast.Expr(ast.Ident{
2836 name: 'noinline'
2837 })
2838 }
2839 for a in decl.attributes {
2840 new_attrs << a
2841 }
2842 return new_attrs
2843 }
2844
2845 return decl.attributes
2846}
2847
2848fn (mut t Transformer) transform_fn_decl_parts(decl ast.FnDecl) ([]ast.Attribute, []ast.Stmt) {
2849 mut ctx := t.enter_fn_body_transform(decl) or { return decl.attributes, []ast.Stmt{} }
2850 transformed_stmts := t.transform_stmts(decl.stmts)
2851 t.restore_fn_body_transform_state(mut ctx)
2852 final_stmts := t.lower_defer_stmts(transformed_stmts, ctx.has_return_type, ctx.fn_return_type)
2853 attrs := t.finish_fn_body_transform(decl, mut ctx)
2854 return attrs, final_stmts
2855}
2856
2857fn has_non_lifetime_generic_params(params []ast.Expr) bool {
2858 for param in params {
2859 if param !is ast.LifetimeExpr {
2860 return true
2861 }
2862 }
2863 return false
2864}
2865
2866fn generic_param_names(params []ast.Expr) []string {
2867 mut names := []string{cap: params.len}
2868 for param in params {
2869 match param {
2870 ast.Ident {
2871 names << param.name
2872 }
2873 ast.LifetimeExpr {
2874 continue
2875 }
2876 ast.Type {
2877 name := param.name()
2878 if name != '' {
2879 names << name
2880 }
2881 }
2882 else {}
2883 }
2884 }
2885 return names
2886}
2887
2888fn (t &Transformer) generic_call_lhs_from_index_expr(lhs ast.Expr) ?ast.Expr {
2889 if lhs !is ast.IndexExpr {
2890 return none
2891 }
2892 index_expr := lhs as ast.IndexExpr
2893 if index_expr.is_gated || !t.expr_looks_like_type_arg(index_expr.expr) {
2894 return none
2895 }
2896 if index_expr.lhs !is ast.Ident && index_expr.lhs !is ast.SelectorExpr {
2897 return none
2898 }
2899 return ast.Expr(ast.GenericArgOrIndexExpr{
2900 lhs: index_expr.lhs
2901 expr: index_expr.expr
2902 pos: index_expr.pos
2903 })
2904}
2905
2906fn (t &Transformer) expr_looks_like_type_arg(expr ast.Expr) bool {
2907 match expr {
2908 ast.Ident {
2909 if expr.name in ['int', 'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64', 'f32',
2910 'f64', 'bool', 'rune', 'byte', 'string', 'isize', 'usize', 'voidptr', 'charptr',
2911 'byteptr', 'T', 'U', 'V', 'K', 'W'] {
2912 return true
2913 }
2914 if expr.name.len > 0 && expr.name[0] >= `A` && expr.name[0] <= `Z` {
2915 return true
2916 }
2917 return t.lookup_type(expr.name) != none
2918 }
2919 ast.SelectorExpr {
2920 if expr.rhs.name.len > 0 && expr.rhs.name[0] >= `A` && expr.rhs.name[0] <= `Z` {
2921 return true
2922 }
2923 return t.lookup_type(expr.name().replace('.', '__')) != none
2924 }
2925 ast.PrefixExpr {
2926 return expr.op == .amp && t.expr_looks_like_type_arg(expr.expr)
2927 }
2928 ast.Type, ast.GenericArgs {
2929 return true
2930 }
2931 else {
2932 return false
2933 }
2934 }
2935}
2936
2937fn (mut t Transformer) transform_call_expr(expr ast.CallExpr) ast.Expr {
2938 // Resolve $d('key', default) comptime define calls to their default value.
2939 // $d reads from compile-time environment; we just use the default.
2940 if expr.lhs is ast.Ident && expr.lhs.name == 'd' && expr.args.len == 2 {
2941 return t.transform_expr(expr.args[1])
2942 }
2943 if transformed_embed := t.transform_embed_file_chain_lhs(ast.Expr(expr), expr.pos) {
2944 return t.transform_expr(transformed_embed)
2945 }
2946 // Inline generic math functions (abs[T], min[T], max[T], maxof[T], minof[T]).
2947 // Generic function declarations are not instantiated by the compiler, so these
2948 // become unresolved symbols unless inlined here.
2949 if inlined := t.try_inline_generic_math_call(expr) {
2950 return inlined
2951 }
2952 // Expand .filter() / .map() calls to hoisted statements + temp variable
2953 if expanded := t.try_expand_filter_or_map_expr(expr) {
2954 return expanded
2955 }
2956 if generic_lhs := t.generic_call_lhs_from_index_expr(expr.lhs) {
2957 return t.transform_call_expr(ast.CallExpr{
2958 lhs: generic_lhs
2959 args: expr.args
2960 pos: expr.pos
2961 })
2962 }
2963 // Array literal lowering already builds:
2964 // builtin__new_array_from_c_array_noscan(len, cap, sizeof(T), [values...])
2965 // Re-transforming that 4th ArrayInitExpr argument causes nested lowering and
2966 // corrupt AST payloads in later cleanc/codegen stages.
2967 if expr.lhs is ast.Ident && expr.lhs.name == 'builtin__new_array_from_c_array_noscan'
2968 && expr.args.len == 4 && expr.args[3] is ast.ArrayInitExpr {
2969 mut args := []ast.Expr{cap: expr.args.len}
2970 for i, arg in expr.args {
2971 if i == 3 {
2972 args << arg
2973 } else {
2974 args << t.transform_expr(arg)
2975 }
2976 }
2977 return ast.CallExpr{
2978 lhs: ast.Expr(expr.lhs)
2979 args: args
2980 pos: expr.pos
2981 }
2982 }
2983
2984 // Some enum flag calls are lowered early to array__has/array__all.
2985 // Recover them here and rewrite back to bitwise flag checks.
2986 if expr.lhs is ast.Ident && expr.args.len == 2 {
2987 if expr.lhs.name in ['array__has', 'array__all'] {
2988 method_name := if expr.lhs.name == 'array__has' { 'has' } else { 'all' }
2989 receiver_type := t.get_enum_type(expr.args[0])
2990 mut should_rewrite := t.is_flag_enum_receiver(expr.args[0], receiver_type)
2991 if !should_rewrite {
2992 if recv_type := t.get_expr_type(expr.args[0]) {
2993 base := t.unwrap_alias_and_pointer_type(recv_type)
2994 if base is types.Enum {
2995 should_rewrite = base.is_flag
2996 } else if base !is types.Array && base !is types.ArrayFixed {
2997 // array__has/array__all are array-only helpers. If lowering produced
2998 // them for a non-array receiver, this is the enum-flag path.
2999 should_rewrite = true
3000 }
3001 }
3002 }
3003 if should_rewrite {
3004 return t.transform_flag_enum_method(expr.args[0], method_name, [expr.args[1]],
3005 receiver_type)
3006 }
3007 }
3008 }
3009 if expr.lhs is ast.Ident && expr.args.len == 2 {
3010 method_name := match expr.lhs.name {
3011 'array__contains' { 'contains' }
3012 'array__index' { 'index' }
3013 'array__last_index' { 'last_index' }
3014 else { '' }
3015 }
3016
3017 if method_name != '' {
3018 if info := t.get_array_method_info(expr.args[0]) {
3019 fn_name := t.register_needed_array_method(info, method_name)
3020 mut value_arg := expr.args[1]
3021 if value_arg is ast.PrefixExpr {
3022 prefix_arg := value_arg as ast.PrefixExpr
3023 if prefix_arg.op == .amp {
3024 value_arg = prefix_arg.expr
3025 }
3026 }
3027 return ast.CallExpr{
3028 lhs: ast.Ident{
3029 name: fn_name
3030 }
3031 args: [
3032 t.transform_array_receiver_expr(expr.args[0]),
3033 t.transform_expr(value_arg),
3034 ]
3035 pos: expr.pos
3036 }
3037 }
3038 }
3039 }
3040 // Check if this is a flag enum method call: receiver.has(arg) or receiver.all(arg)
3041 if expr.lhs is ast.SelectorExpr {
3042 sel := expr.lhs as ast.SelectorExpr
3043 if t.is_native_be {
3044 if concrete := t.get_native_default_interface_concrete_type(sel.lhs, sel.rhs.name) {
3045 call_args := t.lower_missing_call_args(expr.lhs, expr.args)
3046 mut native_args := []ast.Expr{cap: call_args.len + 1}
3047 native_args << t.transform_expr(sel.lhs)
3048 for arg in call_args {
3049 native_args << t.transform_expr(arg)
3050 }
3051 return ast.CallExpr{
3052 lhs: ast.Ident{
3053 name: '${concrete}__${sel.rhs.name}'
3054 }
3055 args: native_args
3056 pos: expr.pos
3057 }
3058 }
3059 }
3060 // Skip calls to conditionally compiled functions (e.g., @[if verbose ?])
3061 if sel.rhs.name in t.elided_fns {
3062 return ast.Expr(ast.BasicLiteral{
3063 kind: .number
3064 value: '0'
3065 })
3066 }
3067 if receiver_type := t.resolve_expr_type(sel.lhs) {
3068 if is_embed_file_helper_type(receiver_type) {
3069 mut args := []ast.Expr{cap: expr.args.len}
3070 for arg in expr.args {
3071 args << t.transform_expr(arg)
3072 }
3073 return ast.CallExpr{
3074 lhs: ast.Expr(ast.SelectorExpr{
3075 lhs: t.transform_expr(sel.lhs)
3076 rhs: sel.rhs
3077 pos: expr.pos
3078 })
3079 args: args
3080 pos: expr.pos
3081 }
3082 }
3083 }
3084 // arr.sort() or arr.sort(a < b) - generate comparator and use sort_with_compare
3085 if sel.rhs.name in ['sort', 'sorted'] {
3086 if expr.args.len == 0 {
3087 // .sort() with no args: default ascending
3088 if result := t.transform_sort_call(sel.lhs, sel.rhs.name, [], expr.pos) {
3089 return result
3090 }
3091 } else if expr.args.len == 1 && t.is_sort_compare_lambda_expr(expr.args[0]) {
3092 // .sort(a < b) with lambda comparator
3093 if result := t.transform_sort_call(sel.lhs, sel.rhs.name, [expr.args[0]], expr.pos) {
3094 return result
3095 }
3096 }
3097 }
3098 method_name := sel.rhs.name
3099 if method_name == 'zero' && expr.args.len == 0 {
3100 receiver_type := t.get_enum_type(sel.lhs)
3101 if t.is_flag_enum(receiver_type) {
3102 return ast.CastExpr{
3103 typ: ast.Ident{
3104 name: receiver_type
3105 }
3106 expr: ast.BasicLiteral{
3107 kind: .number
3108 value: '0'
3109 }
3110 pos: expr.pos
3111 }
3112 }
3113 }
3114 if method_name in ['has', 'all'] {
3115 if expr.args.len == 1 {
3116 arg0 := expr.args[0]
3117 is_string_arg := arg0 is ast.StringLiteral
3118 || (arg0 is ast.BasicLiteral && arg0.kind == .string)
3119 if !is_string_arg {
3120 // Try to detect if receiver is a flag enum
3121 receiver_type := t.get_enum_type(sel.lhs)
3122 if t.is_flag_enum_receiver(sel.lhs, receiver_type) {
3123 // Transform the method call
3124 return t.transform_flag_enum_method(sel.lhs, method_name, expr.args,
3125 receiver_type)
3126 }
3127 }
3128 }
3129 }
3130 if method_name in ['contains', 'index', 'last_index'] && expr.args.len == 1
3131 && t.specific_array_method_c_name(sel.lhs, method_name) == none {
3132 if info := t.get_array_method_info(sel.lhs) {
3133 fn_name := t.register_needed_array_method(info, method_name)
3134 return ast.CallExpr{
3135 lhs: ast.Ident{
3136 name: fn_name
3137 }
3138 args: [
3139 t.transform_array_receiver_expr(sel.lhs),
3140 t.transform_expr(expr.args[0]),
3141 ]
3142 pos: expr.pos
3143 }
3144 }
3145 }
3146 // Transform direct .str() calls on arrays/maps to specialized function calls
3147 // e.g., a.str() where a is []int -> Array_int_str(a)
3148 if method_name == 'str' && expr.args.len == 0 {
3149 // Keep explicit user-defined/declared str() methods (e.g. strings.Builder).
3150 // Only lower to helper calls when there is no real method on the receiver type.
3151 mut should_lower_str := !t.receiver_has_cached_method(sel.lhs, method_name)
3152 if !should_lower_str {
3153 if recv_type := t.get_expr_type(sel.lhs) {
3154 base_type := t.unwrap_alias_and_pointer_type(recv_type)
3155 if base_type is types.Array || base_type is types.ArrayFixed
3156 || base_type is types.Map {
3157 has_alias_str := if recv_type is types.Alias {
3158 t.resolve_alias_receiver_method_name(recv_type, method_name) != none
3159 } else {
3160 false
3161 }
3162 should_lower_str = !has_alias_str
3163 }
3164 }
3165 }
3166 if _ := t.specific_array_method_c_name(sel.lhs, method_name) {
3167 should_lower_str = false
3168 }
3169 if should_lower_str {
3170 str_fn_info := t.get_str_fn_info_for_expr(sel.lhs)
3171 // Skip Array_u8_str: []u8 = strings.Builder which has its own .str() method
3172 // with different semantics (finalizes builder vs formatting array contents).
3173 if str_fn_info.str_fn_name != '' && str_fn_info.str_fn_name != 'Array_u8_str' {
3174 t.needed_str_fns[str_fn_info.str_fn_name] = str_fn_info.elem_type
3175 // Also register enum types so the generator produces
3176 // the proper if-else variant chain instead of a struct stub.
3177 if typ := t.get_expr_type(sel.lhs) {
3178 if typ is types.Enum {
3179 t.needed_enum_str_fns[str_fn_info.str_fn_name] = typ
3180 }
3181 }
3182 return ast.CallExpr{
3183 lhs: ast.Ident{
3184 name: str_fn_info.str_fn_name
3185 }
3186 args: [
3187 t.transform_expr(sel.lhs),
3188 ]
3189 pos: expr.pos
3190 }
3191 }
3192 }
3193 }
3194 // Sum type .type_name() - lower to match on _tag
3195 if method_name == 'type_name' && expr.args.len == 0 {
3196 if lowered := t.transform_sumtype_type_name(sel.lhs) {
3197 return lowered
3198 }
3199 }
3200 // Check for smart-casted method call: se.lhs.method() when se.lhs is smartcast to Type
3201 if t.has_active_smartcast() {
3202 if smartcast_receiver := t.smartcast_method_receiver_context(sel.lhs) {
3203 if resolved_fn := t.smartcast_variant_method_name(smartcast_receiver.ctx,
3204 sel.rhs.name)
3205 {
3206 casted_receiver := t.smartcast_method_receiver(smartcast_receiver.receiver,
3207 smartcast_receiver.ctx)
3208 mut args := []ast.Expr{cap: expr.args.len + 1}
3209 args << casted_receiver
3210 for arg in expr.args {
3211 args << t.transform_expr(arg)
3212 }
3213 return ast.CallExpr{
3214 lhs: ast.Ident{
3215 name: resolved_fn
3216 }
3217 args: args
3218 pos: expr.pos
3219 }
3220 }
3221 }
3222 }
3223 // Check for interface method call: iface.method(args...)
3224 if native_call := t.try_transform_native_interface_concrete_call(sel, expr.args, expr.pos,
3225 expr.lhs)
3226 {
3227 return native_call
3228 }
3229 if t.is_interface_receiver(sel.lhs) {
3230 call_args := t.lower_missing_call_args(expr.lhs, expr.args)
3231 iface_fn_info := t.lookup_call_fn_info(expr.lhs)
3232 mut transformed_iface_args := []ast.Expr{cap: call_args.len}
3233 for i, arg in call_args {
3234 transformed_iface_args << t.transform_call_arg_with_sumtype_check(arg,
3235 iface_fn_info, i)
3236 }
3237 transformed_iface_args = t.lower_variadic_args(expr.lhs, transformed_iface_args)
3238 // Native backends (arm64/x64): resolve to direct concrete method call.
3239 // `iface.method(args...)` → `ConcreteType__method(iface, args...)`
3240 if t.is_native_be {
3241 if concrete := t.get_interface_concrete_type_for_expr(sel.lhs) {
3242 resolved_method := '${concrete}__${sel.rhs.name}'
3243 mut native_args := []ast.Expr{cap: transformed_iface_args.len + 1}
3244 native_receiver := t.native_interface_receiver_arg(sel.lhs, concrete)
3245 native_args << t.transform_expr(native_receiver)
3246 native_args << transformed_iface_args
3247 return ast.CallExpr{
3248 lhs: ast.Ident{
3249 name: resolved_method
3250 }
3251 args: native_args
3252 pos: expr.pos
3253 }
3254 }
3255 if concrete := t.get_native_default_interface_concrete_type(sel.lhs, sel.rhs.name) {
3256 resolved_method := '${concrete}__${sel.rhs.name}'
3257 mut native_args := []ast.Expr{cap: transformed_iface_args.len + 1}
3258 native_args << t.transform_expr(sel.lhs)
3259 native_args << transformed_iface_args
3260 return ast.CallExpr{
3261 lhs: ast.Ident{
3262 name: resolved_method
3263 }
3264 args: native_args
3265 pos: expr.pos
3266 }
3267 }
3268 }
3269 // C/cleanc backends: Transform to vtable dispatch
3270 // Prepend iface._object to the args list
3271 mut new_args := []ast.Expr{cap: transformed_iface_args.len + 1}
3272 new_args << t.synth_selector(sel.lhs, '_object', types.Type(types.voidptr_))
3273 new_args << transformed_iface_args
3274 return ast.CallExpr{
3275 lhs: ast.Expr(expr.lhs) // Keep the selector: iface.method
3276 args: new_args
3277 pos: expr.pos
3278 }
3279 }
3280 }
3281 // Check for println/eprintln with non-string argument
3282 // Transform: println(arr) -> println(Array_int_str(arr))
3283 if expr.lhs is ast.Ident {
3284 fn_name := expr.lhs.name
3285 if fn_name in ['println', 'eprintln', 'print', 'eprint'] && expr.args.len == 1 {
3286 arg := expr.args[0]
3287 if arg is ast.StringInterLiteral {
3288 return ast.CallExpr{
3289 lhs: ast.Expr(expr.lhs)
3290 args: [t.transform_expr(arg)]
3291 pos: expr.pos
3292 }
3293 }
3294 if !t.is_string_expr(arg) {
3295 // Get the str function name and record it for generation
3296 str_fn_info := t.get_str_fn_info_for_expr(arg)
3297 if str_fn_info.str_fn_name != '' {
3298 t.needed_str_fns[str_fn_info.str_fn_name] = str_fn_info.elem_type
3299 if typ := t.get_expr_type(arg) {
3300 if typ is types.Enum {
3301 t.needed_enum_str_fns[str_fn_info.str_fn_name] = typ
3302 }
3303 }
3304 mut str_call_args := []ast.Expr{cap: 1}
3305 str_call_args << t.transform_expr(arg)
3306 // Transform to println(Type_str(arg))
3307 return ast.CallExpr{
3308 lhs: ast.Expr(expr.lhs)
3309 args: [
3310 ast.Expr(ast.CallExpr{
3311 lhs: ast.Ident{
3312 name: str_fn_info.str_fn_name
3313 }
3314 args: str_call_args
3315 pos: expr.pos
3316 }),
3317 ]
3318 pos: expr.pos
3319 }
3320 }
3321 }
3322 }
3323 }
3324 if generic_module_call := t.transform_generic_module_call(expr) {
3325 return generic_module_call
3326 }
3327 if expr.lhs is ast.GenericArgs {
3328 ga := expr.lhs as ast.GenericArgs
3329 if ga.lhs is ast.Ident {
3330 lhs := t.specialize_generic_callable_expr(ga.lhs, ga.args, ga.pos)
3331 return ast.CallExpr{
3332 lhs: lhs
3333 args: t.transform_call_args_for_lhs(lhs, expr.args)
3334 pos: expr.pos
3335 }
3336 }
3337 }
3338 if expr.lhs is ast.GenericArgOrIndexExpr {
3339 gai := expr.lhs as ast.GenericArgOrIndexExpr
3340 if gai.lhs is ast.Ident {
3341 lhs := t.specialize_generic_callable_expr(gai.lhs, [gai.expr], gai.pos)
3342 return ast.CallExpr{
3343 lhs: lhs
3344 args: t.transform_call_args_for_lhs(lhs, expr.args)
3345 pos: expr.pos
3346 }
3347 }
3348 }
3349 if expr.lhs is ast.GenericArgs {
3350 ga := expr.lhs as ast.GenericArgs
3351 if ga.lhs is ast.SelectorExpr {
3352 if transformed := t.transform_generic_selector_method_call(ga.lhs as ast.SelectorExpr,
3353 ga.args, expr.args, expr.pos)
3354 {
3355 return transformed
3356 }
3357 }
3358 }
3359 if expr.lhs is ast.IndexExpr {
3360 idx := expr.lhs as ast.IndexExpr
3361 if idx.lhs is ast.SelectorExpr {
3362 if transformed := t.transform_generic_selector_method_call(idx.lhs as ast.SelectorExpr, [
3363 idx.expr,
3364 ], expr.args, expr.pos)
3365 {
3366 return transformed
3367 }
3368 }
3369 }
3370 // Method call resolution: rewrite receiver.method(args) -> Type__method(receiver, args)
3371 if expr.lhs is ast.SelectorExpr {
3372 sel := expr.lhs as ast.SelectorExpr
3373 if resolved_static := t.resolve_static_type_method_call(sel.lhs, sel.rhs.name) {
3374 call_args := t.lower_missing_call_args(expr.lhs, expr.args)
3375 fn_info := t.generic_aware_call_fn_info(expr.lhs, resolved_static)
3376 mut args := []ast.Expr{cap: call_args.len}
3377 for i, arg in call_args {
3378 args << t.transform_call_arg_with_sumtype_check(arg, fn_info, i)
3379 }
3380 args = t.lower_variadic_args(expr.lhs, args)
3381 mut call_name := resolved_static
3382 if info := fn_info {
3383 if inferred := t.inferred_generic_call_name(call_name, info, call_args) {
3384 call_name = inferred
3385 }
3386 }
3387 return ast.CallExpr{
3388 lhs: ast.Ident{
3389 name: call_name
3390 }
3391 args: args
3392 pos: expr.pos
3393 }
3394 }
3395 // Nested module call: rand.seed.time_seed_array() -> seed__time_seed_array()
3396 if sel.lhs is ast.SelectorExpr {
3397 inner := sel.lhs as ast.SelectorExpr
3398 if inner.lhs is ast.Ident && t.is_module_ident(inner.lhs.name) {
3399 sub_mod := inner.rhs.name
3400 fn_name := sel.rhs.name
3401 // Check if sub_mod is actually a module scope (not a variable like os.args)
3402 mut resolved_name := ''
3403 if t.get_module_scope(sub_mod) != none {
3404 resolved_name = '${sub_mod}__${fn_name}'
3405 } else {
3406 full_mod := '${inner.lhs.name}__${sub_mod}'
3407 if t.get_module_scope(full_mod) != none {
3408 resolved_name = '${full_mod}__${fn_name}'
3409 }
3410 }
3411 if resolved_name != '' {
3412 args := t.transform_call_args_for_lhs(ast.Expr(ast.Ident{
3413 name: resolved_name
3414 }), expr.args)
3415 return ast.CallExpr{
3416 lhs: ast.Ident{
3417 name: resolved_name
3418 }
3419 args: args
3420 pos: expr.pos
3421 }
3422 }
3423 }
3424 }
3425 mut resolved_module_call_name := ''
3426 if sel.lhs is ast.Ident {
3427 lhs_name := (sel.lhs as ast.Ident).name
3428 if t.lookup_var_type(lhs_name) == none {
3429 if resolved_mod := t.resolve_module_name(lhs_name) {
3430 mut module_names := []string{cap: 2}
3431 module_names << resolved_mod
3432 if lhs_name != resolved_mod {
3433 module_names << lhs_name
3434 }
3435 for mod_name in module_names {
3436 if _ := t.lookup_fn_cached(mod_name, sel.rhs.name) {
3437 call_mod := if mod_name.contains('.') {
3438 mod_name.all_after_last('.')
3439 } else if mod_name.contains('__') {
3440 mod_name.all_after_last('__')
3441 } else {
3442 mod_name
3443 }
3444 resolved_module_call_name = '${call_mod}__${sel.rhs.name}'
3445 break
3446 }
3447 }
3448 }
3449 if resolved_module_call_name == '' {
3450 if call_prefix := t.resolve_module_call_prefix(lhs_name) {
3451 if _ := t.lookup_fn_cached(call_prefix, sel.rhs.name) {
3452 resolved_module_call_name = '${call_prefix}__${sel.rhs.name}'
3453 }
3454 }
3455 }
3456 }
3457 }
3458 is_module_call := resolved_module_call_name != ''
3459 if is_module_call {
3460 call_args := t.lower_missing_call_args(expr.lhs, expr.args)
3461 fn_info := t.generic_aware_call_fn_info(expr.lhs, resolved_module_call_name)
3462 mut args := []ast.Expr{cap: call_args.len}
3463 for i, arg in call_args {
3464 args << t.transform_call_arg_with_sumtype_check(arg, fn_info, i)
3465 }
3466 args = t.lower_variadic_args(expr.lhs, args)
3467 mut call_name := resolved_module_call_name
3468 if info := fn_info {
3469 if inferred := t.inferred_generic_call_name(call_name, info, call_args) {
3470 call_name = inferred
3471 }
3472 }
3473 return ast.CallExpr{
3474 lhs: ast.Ident{
3475 name: call_name
3476 }
3477 args: args
3478 pos: expr.pos
3479 }
3480 }
3481 if embedded_call := t.transform_promoted_embedded_method_call(sel, expr.args, expr.pos) {
3482 return embedded_call
3483 }
3484 if !is_module_call && !t.resolves_to_embedded_method(sel.lhs, sel.rhs.name) {
3485 if resolved := t.resolve_method_call_name(sel.lhs, sel.rhs.name) {
3486 // Guard against misresolution: if the receiver is known to be a string
3487 // (e.g., tos2() returns string), ensure string methods aren't resolved to
3488 // array methods. This can happen when the checker's type store is unreliable
3489 // (e.g., in ARM64-compiled binaries with chained-access issues).
3490 if resolved.starts_with('array__') && t.is_string_expr(sel.lhs) {
3491 call_args := t.lower_missing_call_args(expr.lhs, expr.args)
3492 mut args := []ast.Expr{cap: call_args.len + 1}
3493 args << t.transform_expr(sel.lhs)
3494 for arg in call_args {
3495 args << t.transform_expr(arg)
3496 }
3497 return ast.CallExpr{
3498 lhs: ast.Ident{
3499 name: 'string__${sel.rhs.name}'
3500 }
3501 args: args
3502 pos: expr.pos
3503 }
3504 }
3505 // For nested array .clone(), use clone_to_depth with the correct depth
3506 // so inner arrays are deeply cloned instead of shallow-copied.
3507 if resolved == 'array__clone' && expr.args.len == 0 {
3508 if recv_type := t.get_expr_type(sel.lhs) {
3509 depth := t.get_array_nesting_depth(recv_type)
3510 if depth > 1 {
3511 return ast.CallExpr{
3512 lhs: ast.Ident{
3513 name: 'array__clone_to_depth'
3514 }
3515 args: [
3516 t.transform_expr(sel.lhs),
3517 ast.Expr(ast.BasicLiteral{
3518 kind: .number
3519 value: '${depth - 1}'
3520 }),
3521 ]
3522 pos: expr.pos
3523 }
3524 }
3525 }
3526 }
3527 if sel.rhs.name == 'clone' && expr.args.len == 0 {
3528 if recv_type := t.get_expr_type(sel.lhs) {
3529 _ = t.auto_clone_fn_name_for_type(recv_type)
3530 }
3531 }
3532 // insert(i, arr) → insert_many(i, arr.data, arr.len)
3533 if resolved.ends_with('__insert') && expr.args.len == 2 {
3534 if arg_type := t.get_expr_type(expr.args[1]) {
3535 arg_base := t.unwrap_alias_and_pointer_type(arg_type)
3536 if arg_base is types.Array {
3537 arr_arg := t.transform_expr(expr.args[1])
3538 return ast.CallExpr{
3539 lhs: ast.Ident{
3540 name: resolved.replace('__insert', '__insert_many')
3541 }
3542 args: [
3543 t.transform_expr(sel.lhs),
3544 t.transform_expr(expr.args[0]),
3545 ast.Expr(ast.SelectorExpr{
3546 lhs: arr_arg
3547 rhs: ast.Ident{
3548 name: 'data'
3549 }
3550 }),
3551 ast.Expr(ast.SelectorExpr{
3552 lhs: arr_arg
3553 rhs: ast.Ident{
3554 name: 'len'
3555 }
3556 }),
3557 ]
3558 pos: expr.pos
3559 }
3560 }
3561 }
3562 }
3563 // prepend(arr) → prepend_many(arr.data, arr.len)
3564 if resolved.ends_with('__prepend') && expr.args.len == 1 {
3565 if arg_type := t.get_expr_type(expr.args[0]) {
3566 arg_base := t.unwrap_alias_and_pointer_type(arg_type)
3567 if arg_base is types.Array {
3568 arr_arg := t.transform_expr(expr.args[0])
3569 return ast.CallExpr{
3570 lhs: ast.Ident{
3571 name: resolved.replace('__prepend', '__prepend_many')
3572 }
3573 args: [
3574 t.transform_expr(sel.lhs),
3575 ast.Expr(ast.SelectorExpr{
3576 lhs: arr_arg
3577 rhs: ast.Ident{
3578 name: 'data'
3579 }
3580 }),
3581 ast.Expr(ast.SelectorExpr{
3582 lhs: arr_arg
3583 rhs: ast.Ident{
3584 name: 'len'
3585 }
3586 }),
3587 ]
3588 pos: expr.pos
3589 }
3590 }
3591 }
3592 }
3593 call_args := t.lower_missing_call_args(expr.lhs, expr.args)
3594 is_static := t.is_static_method_call(sel.lhs)
3595 fn_info := t.generic_aware_call_fn_info(expr.lhs, resolved)
3596 mut transformed_call_args := []ast.Expr{cap: call_args.len}
3597 for i, arg in call_args {
3598 transformed_call_args << t.transform_call_arg_with_sumtype_check(arg, fn_info, i)
3599 }
3600 transformed_call_args = t.lower_variadic_args(expr.lhs, transformed_call_args)
3601 mut args := []ast.Expr{cap: transformed_call_args.len + 1}
3602 if !is_static {
3603 args << t.transform_method_receiver_arg(sel.lhs, sel.rhs.name, resolved)
3604 }
3605 args << transformed_call_args
3606 mut call_name := resolved
3607 if info := fn_info {
3608 if receiver_inferred := t.receiver_generic_method_call_name(call_name, sel.lhs,
3609 info, call_args)
3610 {
3611 call_name = receiver_inferred
3612 } else if inferred := t.inferred_generic_call_name(call_name, info, call_args) {
3613 call_name = inferred
3614 }
3615 } else if receiver_inferred := t.receiver_generic_method_call_name(call_name,
3616 sel.lhs, CallFnInfo{}, call_args)
3617 {
3618 call_name = receiver_inferred
3619 }
3620 if call_name == resolved && !is_static {
3621 if full_info := t.generic_call_info_for_decl(resolved) {
3622 mut full_call_args := []ast.Expr{cap: call_args.len + 1}
3623 full_call_args << sel.lhs
3624 for arg in call_args {
3625 full_call_args << arg
3626 }
3627 if inferred := t.inferred_generic_call_name(resolved, full_info,
3628 full_call_args)
3629 {
3630 call_name = inferred
3631 }
3632 if full_bindings := t.generic_bindings_from_call_args(full_info,
3633 full_call_args)
3634 {
3635 t.register_generic_bindings(resolved, full_bindings)
3636 }
3637 }
3638 }
3639 return ast.CallExpr{
3640 lhs: ast.Ident{
3641 name: call_name
3642 }
3643 args: args
3644 pos: expr.pos
3645 }
3646 }
3647 }
3648 }
3649 // Generic method call: w.get[T](args) where LHS is GenericArgOrIndexExpr
3650 // wrapping a SelectorExpr. Resolve the method call and append the generic
3651 // specialization suffix so cleanc can later substitute concrete types.
3652 if expr.lhs is ast.GenericArgOrIndexExpr {
3653 gai := expr.lhs as ast.GenericArgOrIndexExpr
3654 if gai.lhs is ast.SelectorExpr {
3655 sel := gai.lhs as ast.SelectorExpr
3656 is_module_call := sel.lhs is ast.Ident && t.lookup_var_type(sel.lhs.name) == none
3657 && (t.is_module_ident(sel.lhs.name) || t.get_module_scope(sel.lhs.name) != none)
3658 if !is_module_call {
3659 // Compute generic specialization suffix from the type arg
3660 suffix := '_T_' + t.generic_specialization_token(gai.expr)
3661 // When receiver matches the current method's receiver parameter
3662 // and get_expr_type would fail (generic body), use the known prefix
3663 recv_is_self := t.cur_fn_recv_param != '' && sel.lhs is ast.Ident
3664 && (sel.lhs as ast.Ident).name == t.cur_fn_recv_param && t.get_expr_type(sel.lhs) == none
3665 if recv_is_self && t.cur_fn_recv_prefix != '' {
3666 call_args2 := t.lower_missing_call_args(expr.lhs, expr.args)
3667 fn_info2 := t.lookup_call_fn_info(expr.lhs)
3668 mut args2 := []ast.Expr{cap: call_args2.len + 1}
3669 args2 << t.transform_expr(sel.lhs)
3670 for i, arg in call_args2 {
3671 args2 << t.transform_call_arg_with_sumtype_check(arg, fn_info2, i)
3672 }
3673 return ast.CallExpr{
3674 lhs: ast.Ident{
3675 name: '${t.cur_fn_recv_prefix}__${sel.rhs.name}${suffix}'
3676 }
3677 args: args2
3678 pos: expr.pos
3679 }
3680 }
3681 method_name := sel.rhs.name + suffix
3682 if !t.resolves_to_embedded_method(sel.lhs, method_name) {
3683 if resolved := t.resolve_method_call_name(sel.lhs, method_name) {
3684 call_args2 := t.lower_missing_call_args(expr.lhs, expr.args)
3685 fn_info2 := t.lookup_call_fn_info(expr.lhs)
3686 mut args2 := []ast.Expr{cap: call_args2.len + 1}
3687 args2 << t.transform_expr(sel.lhs)
3688 for i, arg in call_args2 {
3689 args2 << t.transform_call_arg_with_sumtype_check(arg, fn_info2, i)
3690 }
3691 return ast.CallExpr{
3692 lhs: ast.Ident{
3693 name: resolved
3694 }
3695 args: args2
3696 pos: expr.pos
3697 }
3698 }
3699 }
3700 // Fallback: resolve without suffix and append suffix to the resolved name
3701 if !t.resolves_to_embedded_method(sel.lhs, sel.rhs.name) {
3702 if resolved := t.resolve_method_call_name(sel.lhs, sel.rhs.name) {
3703 call_args2 := t.lower_missing_call_args(expr.lhs, expr.args)
3704 fn_info2 := t.lookup_call_fn_info(expr.lhs)
3705 mut args2 := []ast.Expr{cap: call_args2.len + 1}
3706 args2 << t.transform_expr(sel.lhs)
3707 for i, arg in call_args2 {
3708 args2 << t.transform_call_arg_with_sumtype_check(arg, fn_info2, i)
3709 }
3710 return ast.CallExpr{
3711 lhs: ast.Ident{
3712 name: resolved + suffix
3713 }
3714 args: args2
3715 pos: expr.pos
3716 }
3717 }
3718 }
3719 }
3720 }
3721 }
3722 // Default: transform arguments.
3723 // This is important for smart cast propagation through method chains
3724 // e.g., stmt.name.replace() when stmt is smartcast
3725 call_args := t.lower_missing_call_args(expr.lhs, expr.args)
3726 // Look up function parameter types for sumtype re-wrapping
3727 fn_info := t.call_fn_info_for_lhs(expr.lhs)
3728 mut args := []ast.Expr{cap: call_args.len}
3729 for i, arg in call_args {
3730 // When an argument has an active smartcast but the function parameter
3731 // expects the original sumtype, temporarily disable the smartcast so the
3732 // original sumtype value is passed through without being unwrapped.
3733 args << t.transform_call_arg_with_sumtype_check(arg, fn_info, i)
3734 }
3735 args = t.lower_variadic_args(expr.lhs, args)
3736 mut call_lhs := t.transform_expr(expr.lhs)
3737 if expr.lhs is ast.Ident {
3738 if info := fn_info {
3739 if inferred := t.inferred_generic_call_name(expr.lhs.name, info, call_args) {
3740 t.register_generic_call_return_type(expr.lhs.name, info, call_args, expr.pos)
3741 call_lhs = ast.Expr(ast.Ident{
3742 name: inferred
3743 pos: expr.lhs.pos
3744 })
3745 }
3746 }
3747 }
3748 return ast.CallExpr{
3749 // The fallback path keeps unresolved calls in call form, but the lhs can
3750 // still contain value expressions such as `buf[..n].bytestr`; lower those
3751 // receivers here so backend codegen never sees raw slice syntax.
3752 lhs: call_lhs
3753 args: args
3754 pos: expr.pos
37