vxx / vlib / v2_toberemoved / 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
3755 }
3756}
3757
3758fn (mut t Transformer) transform_call_args_for_lhs(lhs ast.Expr, raw_args []ast.Expr) []ast.Expr {
3759 call_args := t.lower_missing_call_args(lhs, raw_args)
3760 fn_info := t.call_fn_info_for_lhs(lhs)
3761 mut args := []ast.Expr{cap: call_args.len}
3762 for i, arg in call_args {
3763 args << t.transform_call_arg_with_sumtype_check(arg, fn_info, i)
3764 }
3765 return t.lower_variadic_args(lhs, args)
3766}
3767
3768struct CallFnInfo {
3769mut:
3770 param_types []types.Type
3771 param_names []string
3772 generic_param_names_by_param []string
3773 generic_param_indexes_by_param []int
3774 generic_params []string
3775 is_variadic bool
3776}
3777
3778fn (t &Transformer) call_fn_info_for_lhs(lhs ast.Expr) ?CallFnInfo {
3779 if lhs is ast.Ident && t.has_current_module_concrete_fn(lhs.name) {
3780 return t.lookup_call_fn_info(lhs)
3781 }
3782 if base_name := t.generic_call_base_name(lhs) {
3783 return t.generic_aware_call_fn_info(lhs, base_name)
3784 }
3785 return t.lookup_call_fn_info(lhs)
3786}
3787
3788fn (t &Transformer) generic_aware_call_fn_info(lhs ast.Expr, base_name string) ?CallFnInfo {
3789 mut info := t.lookup_call_fn_info(lhs) or {
3790 decl_info := t.generic_call_info_for_decl(base_name) or { return none }
3791 return t.drop_method_receiver_from_call_info(lhs, decl_info)
3792 }
3793 if decl_info := t.generic_call_info_for_decl(base_name) {
3794 decl_call_info := t.drop_method_receiver_from_call_info(lhs, decl_info)
3795 needs_decl_generic_signature := info.generic_params.len == 0
3796 && decl_call_info.generic_params.len > 0
3797 if info.generic_params.len == 0 {
3798 info.generic_params = decl_call_info.generic_params.clone()
3799 }
3800 if info.generic_param_names_by_param.len == 0 {
3801 info.generic_param_names_by_param = decl_call_info.generic_param_names_by_param.clone()
3802 }
3803 if info.generic_param_indexes_by_param.len == 0 {
3804 info.generic_param_indexes_by_param =
3805 decl_call_info.generic_param_indexes_by_param.clone()
3806 }
3807 if info.param_names.len == 0 {
3808 info.param_names = decl_call_info.param_names.clone()
3809 }
3810 if info.param_types.len == 0 || needs_decl_generic_signature {
3811 info.param_types = decl_call_info.param_types.clone()
3812 }
3813 if !info.is_variadic {
3814 info.is_variadic = decl_call_info.is_variadic
3815 }
3816 }
3817 return info
3818}
3819
3820fn (t &Transformer) generic_call_info_for_decl(base_name string) ?CallFnInfo {
3821 decl := t.generic_fn_decl_for_call_info(base_name) or { return none }
3822 generic_params := decl_generic_param_names(decl)
3823 if generic_params.len == 0 {
3824 return none
3825 }
3826 mut param_types := []types.Type{}
3827 mut param_names := []string{}
3828 mut generic_param_names_by_param := []string{}
3829 mut generic_param_indexes_by_param := []int{}
3830 if decl.is_method {
3831 generic_param_names_by_param << direct_generic_param_name_from_expr(decl.receiver.typ,
3832 generic_params)
3833 generic_param_indexes_by_param << direct_generic_param_index_from_expr(decl.receiver.typ,
3834 generic_params)
3835 if recv_type := t.type_from_param_type_expr(decl.receiver.typ, generic_params) {
3836 param_types << recv_type
3837 param_names << decl.receiver.name
3838 }
3839 }
3840 for param in decl.typ.params {
3841 generic_param_names_by_param << direct_generic_param_name_from_expr(param.typ,
3842 generic_params)
3843 generic_param_indexes_by_param << direct_generic_param_index_from_expr(param.typ,
3844 generic_params)
3845 if typ := t.type_from_param_type_expr(param.typ, generic_params) {
3846 param_types << typ
3847 param_names << param.name
3848 }
3849 }
3850 return CallFnInfo{
3851 param_types: param_types
3852 param_names: param_names
3853 generic_param_names_by_param: generic_param_names_by_param
3854 generic_param_indexes_by_param: generic_param_indexes_by_param
3855 generic_params: generic_params
3856 is_variadic: ast_params_are_variadic(decl.typ.params)
3857 }
3858}
3859
3860fn (t &Transformer) generic_fn_decl_for_call_info(base_name string) ?ast.FnDecl {
3861 mut name := base_name
3862 bracket_pos := name.index_u8(`[`)
3863 if bracket_pos > 0 {
3864 name = name[..bracket_pos]
3865 }
3866 if name.contains('_T_') {
3867 name = name.all_before('_T_')
3868 } else if name.ends_with('_T') {
3869 name = name[..name.len - 2]
3870 }
3871 lookup_key := t.resolve_generic_decl_key_for_call(name, t.generic_fn_decl_index) or {
3872 return none
3873 }
3874 return t.generic_fn_decl_index[lookup_key] or { none }
3875}
3876
3877fn ast_params_are_variadic(params []ast.Parameter) bool {
3878 if params.len == 0 {
3879 return false
3880 }
3881 last_param := params[params.len - 1]
3882 if last_param.typ is ast.PrefixExpr {
3883 return last_param.typ.op == .ellipsis
3884 }
3885 return false
3886}
3887
3888fn direct_generic_param_name_from_expr(expr ast.Expr, generic_params []string) string {
3889 match expr {
3890 ast.Ident {
3891 if expr.name in generic_params {
3892 return expr.name
3893 }
3894 }
3895 ast.ModifierExpr {
3896 return direct_generic_param_name_from_expr(expr.expr, generic_params)
3897 }
3898 else {}
3899 }
3900
3901 return ''
3902}
3903
3904fn direct_generic_param_index_from_expr(expr ast.Expr, generic_params []string) int {
3905 name := direct_generic_param_name_from_expr(expr, generic_params)
3906 if name == '' {
3907 return -1
3908 }
3909 for i, generic_name in generic_params {
3910 if generic_name == name {
3911 return i
3912 }
3913 }
3914 return -1
3915}
3916
3917fn call_fn_info_from_fn_type(fn_type types.FnType) CallFnInfo {
3918 return CallFnInfo{
3919 param_types: fn_type.get_param_types()
3920 param_names: fn_type.get_param_names()
3921 generic_params: fn_type.get_generic_params().filter(!it.starts_with('^'))
3922 is_variadic: fn_type.is_variadic_fn()
3923 }
3924}
3925
3926fn generic_infer_unwrap_alias(t &Transformer, typ types.Type) types.Type {
3927 if !types.type_has_valid_payload(typ) {
3928 return typ
3929 }
3930 match typ {
3931 types.Alias {
3932 base := t.live_alias_base_type(typ) or { return typ }
3933 return generic_infer_unwrap_alias(t, base)
3934 }
3935 else {
3936 return typ
3937 }
3938 }
3939}
3940
3941fn (t &Transformer) infer_generic_type_from_call_arg(param_type types.Type, arg_type types.Type, generic_params []string, mut bindings map[string]types.Type) {
3942 t.infer_generic_type_from_call_arg_with_depth(param_type, arg_type, generic_params, mut
3943 bindings, 0)
3944}
3945
3946fn (t &Transformer) infer_generic_type_from_call_arg_with_depth(param_type types.Type, arg_type types.Type, generic_params []string, mut bindings map[string]types.Type, depth int) {
3947 if !types.type_has_valid_payload(param_type) || !types.type_has_valid_payload(arg_type) {
3948 return
3949 }
3950 if depth > 32 {
3951 return
3952 }
3953 match param_type {
3954 types.Alias {
3955 t.infer_generic_type_from_call_arg_with_depth(param_type.base_type, arg_type,
3956 generic_params, mut bindings, depth + 1)
3957 }
3958 types.Array {
3959 arg_base := generic_infer_unwrap_alias(t, arg_type)
3960 if arg_base is types.Array {
3961 t.infer_generic_type_from_call_arg_with_depth(param_type.elem_type,
3962 arg_base.elem_type, generic_params, mut bindings, depth + 1)
3963 }
3964 }
3965 types.ArrayFixed {
3966 arg_base := generic_infer_unwrap_alias(t, arg_type)
3967 if arg_base is types.ArrayFixed {
3968 t.infer_generic_type_from_call_arg_with_depth(param_type.elem_type,
3969 arg_base.elem_type, generic_params, mut bindings, depth + 1)
3970 } else if arg_base is types.Array {
3971 t.infer_generic_type_from_call_arg_with_depth(param_type.elem_type,
3972 arg_base.elem_type, generic_params, mut bindings, depth + 1)
3973 }
3974 }
3975 types.FnType {
3976 arg_base := generic_infer_unwrap_alias(t, arg_type)
3977 if arg_base is types.FnType {
3978 param_types := param_type.get_param_types()
3979 arg_types := arg_base.get_param_types()
3980 max_params := if param_types.len < arg_types.len {
3981 param_types.len
3982 } else {
3983 arg_types.len
3984 }
3985 for i in 0 .. max_params {
3986 t.infer_generic_type_from_call_arg_with_depth(param_types[i], arg_types[i],
3987 generic_params, mut bindings, depth + 1)
3988 }
3989 if param_ret := param_type.get_return_type() {
3990 if arg_ret := arg_base.get_return_type() {
3991 t.infer_generic_type_from_call_arg_with_depth(param_ret, arg_ret,
3992 generic_params, mut bindings, depth + 1)
3993 }
3994 }
3995 }
3996 }
3997 types.Map {
3998 arg_base := generic_infer_unwrap_alias(t, arg_type)
3999 if arg_base is types.Map {
4000 t.infer_generic_type_from_call_arg_with_depth(param_type.key_type,
4001 arg_base.key_type, generic_params, mut bindings, depth + 1)
4002 t.infer_generic_type_from_call_arg_with_depth(param_type.value_type,
4003 arg_base.value_type, generic_params, mut bindings, depth + 1)
4004 }
4005 }
4006 types.NamedType {
4007 name := string(param_type)
4008 if name in generic_params && name !in bindings {
4009 bindings[name] = normalize_generic_concrete_type(arg_type)
4010 }
4011 }
4012 types.OptionType {
4013 arg_base := generic_infer_unwrap_alias(t, arg_type)
4014 if arg_base is types.OptionType {
4015 t.infer_generic_type_from_call_arg_with_depth(param_type.base_type,
4016 arg_base.base_type, generic_params, mut bindings, depth + 1)
4017 } else {
4018 t.infer_generic_type_from_call_arg_with_depth(param_type.base_type, arg_type,
4019 generic_params, mut bindings, depth + 1)
4020 }
4021 }
4022 types.Pointer {
4023 arg_base := generic_infer_unwrap_alias(t, arg_type)
4024 if arg_base is types.Pointer {
4025 t.infer_generic_type_from_call_arg_with_depth(param_type.base_type,
4026 arg_base.base_type, generic_params, mut bindings, depth + 1)
4027 } else {
4028 t.infer_generic_type_from_call_arg_with_depth(param_type.base_type, arg_type,
4029 generic_params, mut bindings, depth + 1)
4030 }
4031 }
4032 types.ResultType {
4033 arg_base := generic_infer_unwrap_alias(t, arg_type)
4034 if arg_base is types.ResultType {
4035 t.infer_generic_type_from_call_arg_with_depth(param_type.base_type,
4036 arg_base.base_type, generic_params, mut bindings, depth + 1)
4037 } else {
4038 t.infer_generic_type_from_call_arg_with_depth(param_type.base_type, arg_type,
4039 generic_params, mut bindings, depth + 1)
4040 }
4041 }
4042 types.Struct {
4043 arg_base := generic_infer_unwrap_alias(t, arg_type)
4044 if arg_base is types.Struct {
4045 for param_field in param_type.fields {
4046 arg_field := struct_field_by_name(arg_base, param_field.name) or { continue }
4047 t.infer_generic_type_from_call_arg_with_depth(param_field.typ, arg_field.typ,
4048 generic_params, mut bindings, depth + 1)
4049 }
4050 }
4051 }
4052 types.Tuple {
4053 arg_base := generic_infer_unwrap_alias(t, arg_type)
4054 if arg_base is types.Tuple {
4055 param_types := param_type.get_types()
4056 arg_types := arg_base.get_types()
4057 max_types := if param_types.len < arg_types.len {
4058 param_types.len
4059 } else {
4060 arg_types.len
4061 }
4062 for i in 0 .. max_types {
4063 t.infer_generic_type_from_call_arg_with_depth(param_types[i], arg_types[i],
4064 generic_params, mut bindings, depth + 1)
4065 }
4066 }
4067 }
4068 else {}
4069 }
4070}
4071
4072fn (t &Transformer) call_arg_type_for_generic_infer(arg ast.Expr) ?types.Type {
4073 base_arg := if arg is ast.ModifierExpr {
4074 arg.expr
4075 } else {
4076 arg
4077 }
4078 if base_arg is ast.Ident {
4079 if ctx := t.find_smartcast_for_expr(base_arg.name) {
4080 variant_type_name := if ctx.variant_full != '' { ctx.variant_full } else { ctx.variant }
4081 if typ := t.c_name_to_type(variant_type_name) {
4082 return typ
4083 }
4084 }
4085 if typ := t.lookup_local_decl_type(base_arg.name) {
4086 return typ
4087 }
4088 if typ := t.lookup_var_type(base_arg.name) {
4089 return typ
4090 }
4091 }
4092 if base_arg is ast.BasicLiteral {
4093 match base_arg.kind {
4094 .number {
4095 if base_arg.value.contains('.') {
4096 return types.Type(types.f64_)
4097 }
4098 return types.Type(types.int_)
4099 }
4100 .string {
4101 return types.Type(types.string_)
4102 }
4103 .key_true, .key_false {
4104 return types.Type(types.bool_)
4105 }
4106 else {}
4107 }
4108 }
4109 return t.get_expr_type(base_arg)
4110}
4111
4112fn (mut t Transformer) inline_generic_math_result_pos(pos token.Pos, arg ast.Expr) token.Pos {
4113 result_pos := if pos.id != 0 { pos } else { t.next_synth_pos() }
4114 if typ := t.call_arg_type_for_generic_infer(arg) {
4115 t.register_synth_type(result_pos, concrete_literal_array_elem_type(typ))
4116 }
4117 return result_pos
4118}
4119
4120fn (mut t Transformer) make_inline_abs_expr(arg ast.Expr, pos token.Pos) ast.Expr {
4121 abs_cond := t.make_infix_expr(.lt, arg, t.make_number_expr('0'))
4122 return ast.Expr(ast.IfExpr{
4123 cond: abs_cond
4124 stmts: [
4125 ast.Stmt(ast.ExprStmt{
4126 expr: ast.PrefixExpr{
4127 op: .minus
4128 expr: arg
4129 }
4130 }),
4131 ]
4132 else_expr: ast.IfExpr{
4133 stmts: [
4134 ast.Stmt(ast.ExprStmt{
4135 expr: arg
4136 }),
4137 ]
4138 pos: pos
4139 }
4140 pos: pos
4141 })
4142}
4143
4144fn (t &Transformer) inferred_generic_call_name(base_name string, info CallFnInfo, call_args []ast.Expr) ?string {
4145 if base_name == '' || base_name.contains('_T_') || info.generic_params.len == 0 {
4146 return none
4147 }
4148 infer_info := t.generic_inference_info_for_call(base_name, info, call_args.len)
4149 bindings := t.generic_bindings_from_call_args(infer_info, call_args) or { return none }
4150 mut parts := []string{cap: infer_info.generic_params.len}
4151 for param_name in infer_info.generic_params {
4152 concrete := bindings[param_name] or { return none }
4153 parts << t.generic_specialization_token_from_type(concrete)
4154 }
4155 if parts.len == 0 {
4156 return none
4157 }
4158 base := if base_name.contains('_T_') {
4159 base_name.all_before('_T_')
4160 } else if base_name.ends_with('_T') {
4161 base_name[..base_name.len - 2]
4162 } else {
4163 base_name
4164 }
4165 return '${base}_T_${parts.join('_')}'
4166}
4167
4168fn (t &Transformer) generic_inference_info_for_call(base_name string, info CallFnInfo, call_arg_count int) CallFnInfo {
4169 decl_info := t.generic_call_info_for_decl(base_name) or { return info }
4170 if decl_info.generic_params.len == 0 || decl_info.param_types.len != call_arg_count {
4171 return info
4172 }
4173 return decl_info
4174}
4175
4176fn (mut t Transformer) register_generic_call_return_type(base_name string, info CallFnInfo, call_args []ast.Expr, pos token.Pos) {
4177 if !pos.is_valid() || base_name == '' {
4178 return
4179 }
4180 decl := t.generic_fn_decl_for_call_info(base_name) or { return }
4181 generic_params := decl_generic_param_names(decl)
4182 if generic_params.len == 0 || decl.typ.return_type is ast.EmptyExpr {
4183 return
4184 }
4185 infer_info := t.generic_inference_info_for_call(base_name, info, call_args.len)
4186 bindings := t.generic_bindings_from_call_args(infer_info, call_args) or { return }
4187 ret_type := t.type_from_param_type_expr(decl.typ.return_type, generic_params) or { return }
4188 t.register_synth_type(pos, substitute_type(ret_type, bindings))
4189}
4190
4191fn (t &Transformer) generic_bindings_from_call_args(info CallFnInfo, call_args []ast.Expr) ?map[string]types.Type {
4192 if info.generic_params.len == 0 {
4193 return none
4194 }
4195 mut bindings := map[string]types.Type{}
4196 for i, arg in call_args {
4197 param_idx := i
4198 if param_idx >= info.generic_param_indexes_by_param.len {
4199 break
4200 }
4201 generic_idx := info.generic_param_indexes_by_param[param_idx]
4202 if generic_idx < 0 || generic_idx >= info.generic_params.len {
4203 continue
4204 }
4205 generic_name := info.generic_params[generic_idx]
4206 if generic_name in bindings {
4207 continue
4208 }
4209 if !t.call_info_param_is_direct_generic(info, param_idx, generic_name) {
4210 continue
4211 }
4212 arg_type := t.call_arg_type_for_generic_infer(arg) or { continue }
4213 bindings[generic_name] = arg_type
4214 }
4215 t.infer_generic_bindings_from_call_args(info.param_types, call_args, info.generic_params, 0, mut
4216 bindings)
4217 if bindings.len != info.generic_params.len && info.param_types.len > call_args.len {
4218 mut tail_bindings := map[string]types.Type{}
4219 param_offset := info.param_types.len - call_args.len
4220 for i, arg in call_args {
4221 param_idx := i + param_offset
4222 if param_idx >= info.generic_param_indexes_by_param.len {
4223 break
4224 }
4225 generic_idx := info.generic_param_indexes_by_param[param_idx]
4226 if generic_idx < 0 || generic_idx >= info.generic_params.len {
4227 continue
4228 }
4229 generic_name := info.generic_params[generic_idx]
4230 if generic_name in tail_bindings {
4231 continue
4232 }
4233 if !t.call_info_param_is_direct_generic(info, param_idx, generic_name) {
4234 continue
4235 }
4236 arg_type := t.call_arg_type_for_generic_infer(arg) or { continue }
4237 tail_bindings[generic_name] = arg_type
4238 }
4239 t.infer_generic_bindings_from_call_args(info.param_types, call_args, info.generic_params,
4240 param_offset, mut tail_bindings)
4241 if tail_bindings.len > bindings.len {
4242 bindings = tail_bindings.move()
4243 }
4244 }
4245 for param_name in info.generic_params {
4246 if param_name in bindings {
4247 continue
4248 }
4249 if concrete := t.cur_monomorphized_fn_bindings[param_name] {
4250 bindings[param_name] = concrete
4251 }
4252 }
4253 if bindings.len != info.generic_params.len {
4254 return none
4255 }
4256 return bindings
4257}
4258
4259fn (t &Transformer) call_info_param_is_direct_generic(info CallFnInfo, param_idx int, generic_name string) bool {
4260 if param_idx < 0 || generic_name == '' {
4261 return false
4262 }
4263 if param_idx >= info.param_types.len {
4264 return true
4265 }
4266 param_type := generic_infer_unwrap_alias(t, info.param_types[param_idx])
4267 if param_type is types.Void {
4268 return true
4269 }
4270 if param_type is types.NamedType {
4271 return string(param_type) == generic_name
4272 }
4273 return false
4274}
4275
4276fn (t &Transformer) infer_generic_bindings_from_call_args(param_types []types.Type, call_args []ast.Expr, generic_params []string, param_offset int, mut bindings map[string]types.Type) {
4277 if param_offset < 0 {
4278 return
4279 }
4280 for i, arg in call_args {
4281 param_idx := i + param_offset
4282 if param_idx >= param_types.len {
4283 break
4284 }
4285 arg_type := t.call_arg_type_for_generic_infer(arg) or { continue }
4286 t.infer_generic_type_from_call_arg(param_types[param_idx], arg_type, generic_params, mut
4287 bindings)
4288 }
4289}
4290
4291fn (t &Transformer) drop_method_receiver_from_call_info(lhs ast.Expr, info CallFnInfo) CallFnInfo {
4292 mut trimmed := info
4293 if lhs !is ast.SelectorExpr {
4294 return trimmed
4295 }
4296 sel := lhs as ast.SelectorExpr
4297 if t.is_static_method_call(sel.lhs) {
4298 return trimmed
4299 }
4300 if t.resolve_method_call_name(sel.lhs, sel.rhs.name) == none {
4301 return trimmed
4302 }
4303 if trimmed.param_types.len == 0 {
4304 return trimmed
4305 }
4306 first_base := t.unwrap_alias_and_pointer_type(trimmed.param_types[0])
4307 if recv_type := t.get_expr_type(sel.lhs) {
4308 recv_base := t.unwrap_alias_and_pointer_type(recv_type)
4309 recv_name := recv_base.name()
4310 first_name := first_base.name()
4311 if !transformer_string_has_valid_data(recv_name)
4312 || !transformer_string_has_valid_data(first_name) {
4313 return trimmed
4314 }
4315 if recv_name != first_name {
4316 resolved := t.resolve_method_call_name(sel.lhs, sel.rhs.name) or { return trimmed }
4317 recv_key := resolved.all_before_last('__')
4318 if !method_receiver_type_matches_resolved_name(t.type_to_c_name(first_base), recv_key) {
4319 if !method_receiver_type_matches_resolved_name(first_name, recv_key) {
4320 return trimmed
4321 }
4322 }
4323 }
4324 } else {
4325 resolved := t.resolve_method_call_name(sel.lhs, sel.rhs.name) or { return trimmed }
4326 recv_key := resolved.all_before_last('__')
4327 first_name := first_base.name()
4328 if !method_receiver_type_matches_resolved_name(t.type_to_c_name(first_base), recv_key) {
4329 if !transformer_string_has_valid_data(first_name)
4330 || !method_receiver_type_matches_resolved_name(first_name, recv_key) {
4331 return trimmed
4332 }
4333 }
4334 }
4335 trimmed.param_types = trimmed.param_types[1..].clone()
4336 if trimmed.param_names.len > 0 {
4337 trimmed.param_names = trimmed.param_names[1..].clone()
4338 }
4339 if trimmed.generic_param_names_by_param.len > 0 {
4340 trimmed.generic_param_names_by_param = trimmed.generic_param_names_by_param[1..].clone()
4341 }
4342 if trimmed.generic_param_indexes_by_param.len > 0 {
4343 trimmed.generic_param_indexes_by_param = trimmed.generic_param_indexes_by_param[1..].clone()
4344 }
4345 return trimmed
4346}
4347
4348fn call_info_without_first_param(info CallFnInfo) CallFnInfo {
4349 mut trimmed := info
4350 if trimmed.param_types.len > 0 {
4351 trimmed.param_types = trimmed.param_types[1..].clone()
4352 }
4353 if trimmed.param_names.len > 0 {
4354 trimmed.param_names = trimmed.param_names[1..].clone()
4355 }
4356 if trimmed.generic_param_names_by_param.len > 0 {
4357 trimmed.generic_param_names_by_param = trimmed.generic_param_names_by_param[1..].clone()
4358 }
4359 if trimmed.generic_param_indexes_by_param.len > 0 {
4360 trimmed.generic_param_indexes_by_param = trimmed.generic_param_indexes_by_param[1..].clone()
4361 }
4362 return trimmed
4363}
4364
4365fn method_receiver_type_matches_resolved_name(type_name string, resolved_recv string) bool {
4366 mut lhs := type_name.trim_space()
4367 mut rhs := resolved_recv.trim_space()
4368 for lhs.ends_with('*') {
4369 lhs = lhs[..lhs.len - 1].trim_space()
4370 }
4371 for rhs.ends_with('*') {
4372 rhs = rhs[..rhs.len - 1].trim_space()
4373 }
4374 if lhs == '' || rhs == '' {
4375 return false
4376 }
4377 if lhs.contains('_T_') {
4378 lhs = lhs.all_before('_T_')
4379 }
4380 if rhs.contains('_T_') {
4381 rhs = rhs.all_before('_T_')
4382 }
4383 if lhs == rhs {
4384 return true
4385 }
4386 return lhs.all_after_last('__') == rhs.all_after_last('__')
4387}
4388
4389fn (t &Transformer) lookup_call_fn_info(lhs ast.Expr) ?CallFnInfo {
4390 if lhs is ast.SelectorExpr {
4391 if resolved_static := t.resolve_static_type_method_call(lhs.lhs, lhs.rhs.name) {
4392 recv_key := resolved_static.all_before_last('__')
4393 mut lookup_names := []string{cap: 4}
4394 lookup_names << recv_key
4395 if recv_key.contains('__') {
4396 lookup_names << recv_key.all_after_last('__')
4397 }
4398 if lhs.lhs is ast.Ident {
4399 lookup_names << lhs.lhs.name
4400 } else if lhs.lhs is ast.SelectorExpr {
4401 lookup_names << lhs.lhs.rhs.name
4402 }
4403 for name in lookup_names {
4404 if fn_type := t.lookup_method_cached(name, lhs.rhs.name) {
4405 return call_fn_info_from_fn_type(fn_type)
4406 }
4407 }
4408 }
4409 if recv_type := t.get_expr_type(lhs.lhs) {
4410 base_type := t.unwrap_alias_and_pointer_type(recv_type)
4411 if base_type is types.Channel {
4412 if info := t.builtin_channel_call_fn_info(lhs.rhs.name) {
4413 return info
4414 }
4415 if fn_type := t.lookup_method_cached('chan', lhs.rhs.name) {
4416 return call_fn_info_from_fn_type(fn_type)
4417 }
4418 }
4419 }
4420 }
4421 // Prefer checker-resolved callable type for this exact call target.
4422 // This is the most reliable source for method parameter info (names + types).
4423 if lhs_type := t.get_expr_type(lhs) {
4424 callable := t.unwrap_alias_and_pointer_type(lhs_type)
4425 if callable is types.FnType {
4426 mut info := call_fn_info_from_fn_type(callable)
4427 return t.drop_method_receiver_from_call_info(lhs, info)
4428 }
4429 }
4430 if lhs is ast.Ident {
4431 if t.cur_module != '' {
4432 if fn_type := t.lookup_fn_cached(t.cur_module, lhs.name) {
4433 return call_fn_info_from_fn_type(fn_type)
4434 }
4435 }
4436 if fn_type := t.lookup_fn_cached('builtin', lhs.name) {
4437 return call_fn_info_from_fn_type(fn_type)
4438 }
4439 if lhs.name.contains('__') {
4440 module_name := lhs.name.all_before_last('__')
4441 fn_name := lhs.name.all_after_last('__')
4442 if fn_type := t.lookup_fn_cached(module_name, fn_name) {
4443 return call_fn_info_from_fn_type(fn_type)
4444 }
4445 short_module := if module_name.contains('.') {
4446 module_name.all_after_last('.')
4447 } else if module_name.contains('__') {
4448 module_name.all_after_last('__')
4449 } else {
4450 module_name
4451 }
4452 if short_module != module_name {
4453 if fn_type := t.lookup_fn_cached(short_module, fn_name) {
4454 return call_fn_info_from_fn_type(fn_type)
4455 }
4456 }
4457 }
4458 return none
4459 }
4460 if lhs is ast.SelectorExpr {
4461 if lhs.lhs is ast.Ident {
4462 mod_name := (lhs.lhs as ast.Ident).name
4463 mut module_names := []string{cap: 4}
4464 module_names << mod_name
4465 if resolved_mod := t.resolve_module_name(mod_name) {
4466 if resolved_mod !in module_names {
4467 module_names << resolved_mod
4468 }
4469 short_mod := if resolved_mod.contains('.') {
4470 resolved_mod.all_after_last('.')
4471 } else if resolved_mod.contains('__') {
4472 resolved_mod.all_after_last('__')
4473 } else {
4474 resolved_mod
4475 }
4476 if short_mod !in module_names {
4477 module_names << short_mod
4478 }
4479 }
4480 for module_name in module_names {
4481 if fn_type := t.lookup_fn_cached(module_name, lhs.rhs.name) {
4482 return call_fn_info_from_fn_type(fn_type)
4483 }
4484 }
4485 }
4486 if resolved_method := t.resolve_method_call_name(lhs.lhs, lhs.rhs.name) {
4487 recv_key := resolved_method.all_before_last('__')
4488 mut lookup_names := []string{cap: 2}
4489 lookup_names << recv_key
4490 if recv_key.contains('__') {
4491 lookup_names << recv_key.all_after_last('__')
4492 }
4493 for name in lookup_names {
4494 if fn_type := t.lookup_method_cached(name, lhs.rhs.name) {
4495 info := call_fn_info_from_fn_type(fn_type)
4496 return t.drop_method_receiver_from_call_info(lhs, info)
4497 }
4498 }
4499 }
4500 if recv_type := t.get_expr_type(lhs.lhs) {
4501 base_type := t.unwrap_alias_and_pointer_type(recv_type)
4502 type_name := base_type.name()
4503 mut lookup_names := []string{cap: 3}
4504 t.append_method_lookup_type_name(mut lookup_names, type_name)
4505 if lookup_names.len == 0 {
4506 return none
4507 }
4508 if !type_name.contains('__') && t.cur_module != '' && t.cur_module != 'main' {
4509 lookup_names << '${t.cur_module}__${type_name}'
4510 }
4511 for name in lookup_names {
4512 if fn_type := t.lookup_method_cached(name, lhs.rhs.name) {
4513 mut info := call_fn_info_from_fn_type(fn_type)
4514 return t.drop_method_receiver_from_call_info(lhs, info)
4515 }
4516 }
4517 }
4518 }
4519 return none
4520}
4521
4522fn (t &Transformer) builtin_channel_call_fn_info(method_name string) ?CallFnInfo {
4523 if method_name == 'close' {
4524 ierror_type := t.lookup_type('builtin__IError') or {
4525 t.lookup_type('IError') or { types.Type(types.Interface{
4526 name: 'IError'
4527 }) }
4528 }
4529 return CallFnInfo{
4530 param_types: [types.Type(types.Array{
4531 elem_type: ierror_type
4532 })]
4533 param_names: ['err']
4534 is_variadic: true
4535 }
4536 }
4537 return none
4538}
4539
4540fn (t &Transformer) lookup_call_param_types(lhs ast.Expr) []types.Type {
4541 if info := t.call_fn_info_for_lhs(lhs) {
4542 return info.param_types
4543 }
4544 return []types.Type{}
4545}
4546
4547// receiver_type_to_c_prefix maps a receiver's unwrapped type to the C function
4548// name prefix used in method name mangling. For containers (array, map, string)
4549// it returns the generic prefix; for named types it returns the qualified C name.
4550fn (t &Transformer) receiver_type_to_c_prefix(typ types.Type) string {
4551 if !transformer_type_has_safe_payload(typ) {
4552 return ''
4553 }
4554 match typ {
4555 types.Array, types.ArrayFixed {
4556 return 'array'
4557 }
4558 types.Map {
4559 return 'map'
4560 }
4561 types.String {
4562 return 'string'
4563 }
4564 types.Alias {
4565 if !transformer_type_has_safe_payload(typ.base_type) {
4566 return ''
4567 }
4568 // Check if alias over container
4569 match typ.base_type {
4570 types.Array, types.ArrayFixed {
4571 return 'array'
4572 }
4573 types.Map {
4574 return 'map'
4575 }
4576 types.String {
4577 return 'string'
4578 }
4579 else {
4580 return t.type_to_c_name(typ)
4581 }
4582 }
4583 }
4584 types.Struct, types.Enum, types.Interface, types.SumType {
4585 return t.type_to_c_name(typ)
4586 }
4587 types.Channel {
4588 return 'chan'
4589 }
4590 types.Primitive, types.Char, types.Rune {
4591 return t.type_to_c_name(typ)
4592 }
4593 else {
4594 return ''
4595 }
4596 }
4597}
4598
4599// is_static_method_call checks if the receiver of a method call is a type reference
4600// (not a variable/expression), indicating a static method call like EnumType.from_string(s).
4601// In such cases, the receiver should NOT be passed as the first argument.
4602fn (t &Transformer) is_static_method_call(receiver ast.Expr) bool {
4603 if receiver is ast.Ident {
4604 // Uppercase first letter indicates a type name, not a variable
4605 if receiver.name.len > 0 && receiver.name[0] >= `A` && receiver.name[0] <= `Z` {
4606 // Check if scope lookup resolves to a Type definition object (not a
4607 // Const/Fn/Global variable). Type objects represent type definitions
4608 // like enum/struct that are referenced, not value expressions.
4609 if t.scope != unsafe { nil } {
4610 if obj := unsafe { t.scope }.lookup_parent(receiver.name, 0) {
4611 if obj is types.Module {
4612 return false
4613 }
4614 if obj is types.Type || obj is types.TypeObject {
4615 return true
4616 }
4617 return false
4618 }
4619 }
4620 if mut scope := t.get_current_scope() {
4621 if obj := scope.lookup_parent(receiver.name, 0) {
4622 if obj is types.Module {
4623 return false
4624 }
4625 if obj is types.Type || obj is types.TypeObject {
4626 return true
4627 }
4628 return false
4629 }
4630 }
4631 if _ := t.lookup_type(receiver.name) {
4632 return true
4633 }
4634 if _ := t.lookup_var_type(receiver.name) {
4635 return false
4636 }
4637 // Not found in scope — assume type reference
4638 return true
4639 }
4640 }
4641 if receiver is ast.SelectorExpr {
4642 // Module-qualified type: module.TypeName
4643 if receiver.lhs is ast.Ident {
4644 mod_name := (receiver.lhs as ast.Ident).name
4645 type_name := receiver.rhs.name
4646 if t.get_module_scope(mod_name) != none && type_name.len > 0 && type_name[0] >= `A`
4647 && type_name[0] <= `Z` {
4648 return true
4649 }
4650 }
4651 }
4652 return false
4653}
4654
4655fn (t &Transformer) resolve_static_type_method_for_names(c_type_name string, lookup_names []string, method_name string) ?string {
4656 for lookup_name in lookup_names {
4657 if t.lookup_method_cached(lookup_name, method_name) != none {
4658 return '${c_type_name}__${method_name}'
4659 }
4660 }
4661 return none
4662}
4663
4664fn (t &Transformer) resolve_static_type_method_call(receiver ast.Expr, method_name string) ?string {
4665 if receiver is ast.Ident {
4666 type_name := receiver.name
4667 if type_name.len == 0 || type_name[0] < `A` || type_name[0] > `Z` {
4668 return none
4669 }
4670 c_type_name := if t.cur_module != '' && t.cur_module != 'main' {
4671 '${t.cur_module.replace('.', '__')}__${type_name}'
4672 } else {
4673 type_name
4674 }
4675 mut lookup_names := []string{cap: 2}
4676 lookup_names << c_type_name
4677 if c_type_name != type_name {
4678 lookup_names << type_name
4679 }
4680 return t.resolve_static_type_method_for_names(c_type_name, lookup_names, method_name)
4681 }
4682 if receiver is ast.SelectorExpr {
4683 if receiver.lhs !is ast.Ident {
4684 return none
4685 }
4686 type_name := receiver.rhs.name
4687 if type_name.len == 0 || type_name[0] < `A` || type_name[0] > `Z` {
4688 return none
4689 }
4690 mod_ident := (receiver.lhs as ast.Ident).name
4691 mut module_names := []string{cap: 2}
4692 if resolved_mod := t.resolve_module_name(mod_ident) {
4693 module_names << resolved_mod
4694 }
4695 if t.get_module_scope(mod_ident) != none && mod_ident !in module_names {
4696 module_names << mod_ident
4697 }
4698 for mod_name in module_names {
4699 c_type_name := '${mod_name.replace('.', '__')}__${type_name}'
4700 mut lookup_names := []string{cap: 2}
4701 lookup_names << c_type_name
4702 lookup_names << type_name
4703 if resolved := t.resolve_static_type_method_for_names(c_type_name, lookup_names,
4704 method_name)
4705 {
4706 return resolved
4707 }
4708 }
4709 }
4710 return none
4711}
4712
4713fn (t &Transformer) smartcast_selector_field_type(receiver ast.Expr) ?types.Type {
4714 if !t.has_active_smartcast() || receiver !is ast.SelectorExpr {
4715 return none
4716 }
4717 sel := receiver as ast.SelectorExpr
4718 full_str := t.expr_to_string(receiver)
4719 if ctx := t.find_smartcast_for_expr(full_str) {
4720 if typ := t.lookup_type(ctx.variant_full) {
4721 return typ
4722 }
4723 if typ := t.lookup_type(ctx.variant) {
4724 return typ
4725 }
4726 }
4727 lhs_str := t.expr_to_string(sel.lhs)
4728 if ctx := t.find_smartcast_for_expr(lhs_str) {
4729 if field_type := t.lookup_struct_field_type(ctx.variant_full, sel.rhs.name) {
4730 return field_type
4731 }
4732 if ctx.variant != ctx.variant_full {
4733 if field_type := t.lookup_struct_field_type(ctx.variant, sel.rhs.name) {
4734 return field_type
4735 }
4736 }
4737 }
4738 return none
4739}
4740
4741fn (mut t Transformer) transform_method_receiver_expr(receiver ast.Expr) ast.Expr {
4742 if t.has_active_smartcast() && receiver is ast.SelectorExpr {
4743 sel := receiver as ast.SelectorExpr
4744 lhs_str := t.expr_to_string(sel.lhs)
4745 if ctx := t.find_smartcast_for_expr(lhs_str) {
4746 return t.apply_smartcast_field_access_ctx(sel.lhs, sel.rhs.name, ctx)
4747 }
4748 }
4749 return t.transform_expr(receiver)
4750}
4751
4752fn (mut t Transformer) transform_call_lhs_for_smartcast(lhs ast.Expr) ast.Expr {
4753 if !t.has_active_smartcast() || lhs !is ast.SelectorExpr {
4754 return lhs
4755 }
4756 sel := lhs as ast.SelectorExpr
4757 return ast.SelectorExpr{
4758 lhs: t.transform_method_receiver_expr(sel.lhs)
4759 rhs: sel.rhs
4760 pos: sel.pos
4761 }
4762}
4763
4764// resolve_method_call_name resolves a method call on a receiver to its mangled
4765// C function name. Returns e.g. "array__push" or "MyStruct__method". Returns
4766// none when the receiver type is unknown or the method is not registered
4767// (e.g. function pointer field calls), which prevents false lowering.
4768fn (t &Transformer) explicit_cast_receiver_type_name(receiver ast.Expr) ?string {
4769 match receiver {
4770 ast.CastExpr {
4771 return t.explicit_cast_type_expr_name(receiver.typ)
4772 }
4773 ast.CallOrCastExpr {
4774 if receiver.expr is ast.EmptyExpr || !t.call_or_cast_lhs_is_type(receiver.lhs) {
4775 return none
4776 }
4777 return t.explicit_cast_type_expr_name(receiver.lhs)
4778 }
4779 ast.ParenExpr {
4780 return t.explicit_cast_receiver_type_name(receiver.expr)
4781 }
4782 ast.ModifierExpr {
4783 return t.explicit_cast_receiver_type_name(receiver.expr)
4784 }
4785 else {}
4786 }
4787
4788 return none
4789}
4790
4791fn (t &Transformer) explicit_cast_type_expr_name(expr ast.Expr) ?string {
4792 type_name := t.expr_to_type_name(expr)
4793 if type_name == '' {
4794 return none
4795 }
4796 return t.v_type_name_to_c_name(type_name)
4797}
4798
4799fn (t &Transformer) resolve_explicit_cast_method_name(receiver_type_name string, method_name string) ?string {
4800 if receiver_type_name == '' {
4801 return none
4802 }
4803 if t.lookup_method_cached(receiver_type_name, method_name) != none {
4804 return '${receiver_type_name}__${method_name}'
4805 }
4806 dunder := last_double_underscore(receiver_type_name)
4807 if dunder >= 0 {
4808 short_name := receiver_type_name[dunder + 2..]
4809 if t.lookup_method_cached(short_name, method_name) != none {
4810 return '${short_name}__${method_name}'
4811 }
4812 }
4813 if receiver_type_name.starts_with('Array_')
4814 && t.lookup_method_cached('array', method_name) != none {
4815 return 'array__${method_name}'
4816 }
4817 if receiver_type_name.starts_with('Map_') && t.lookup_method_cached('map', method_name) != none {
4818 return 'map__${method_name}'
4819 }
4820 return none
4821}
4822
4823fn (t &Transformer) resolve_alias_receiver_method_name(recv_type types.Type, method_name string) ?string {
4824 mut seen := []string{}
4825 return t.resolve_alias_receiver_method_name_inner(recv_type, method_name, mut seen)
4826}
4827
4828fn (t &Transformer) exact_method_owner_name(raw_name string, method_name string) ?string {
4829 if raw_name == '' {
4830 return none
4831 }
4832 normalized := normalized_method_lookup_type_name(raw_name)
4833 if normalized == '' {
4834 return none
4835 }
4836 if t.lookup_method_cached(normalized, method_name) != none {
4837 return normalized
4838 }
4839 if last_double_underscore(normalized) < 0 && t.cur_module != '' && t.cur_module != 'main'
4840 && t.cur_module != 'builtin' {
4841 qualified_mod := if t.cur_module.index_u8(`.`) >= 0 {
4842 t.cur_module.replace('.', '__')
4843 } else {
4844 t.cur_module
4845 }
4846 qualified := '${qualified_mod}__${normalized}'
4847 if t.lookup_method_cached(qualified, method_name) != none {
4848 return qualified
4849 }
4850 }
4851 return none
4852}
4853
4854fn (t &Transformer) resolve_alias_receiver_method_name_inner(recv_type types.Type, method_name string, mut seen []string) ?string {
4855 match recv_type {
4856 types.Pointer {
4857 return t.resolve_alias_receiver_method_name_inner(recv_type.base_type, method_name, mut
4858 seen)
4859 }
4860 types.NamedType {
4861 key := 'named:${string(recv_type)}'
4862 if key in seen {
4863 return none
4864 }
4865 seen << key
4866 if resolved := t.lookup_type(string(recv_type)) {
4867 return t.resolve_alias_receiver_method_name_inner(resolved, method_name, mut seen)
4868 }
4869 }
4870 types.Alias {
4871 key := 'alias:${recv_type.name}'
4872 if key in seen {
4873 return none
4874 }
4875 seen << key
4876 alias_c_name := t.type_to_c_name(recv_type)
4877 for alias_name in [recv_type.name, alias_c_name] {
4878 if owner := t.exact_method_owner_name(alias_name, method_name) {
4879 return '${owner}__${method_name}'
4880 }
4881 }
4882 }
4883 else {}
4884 }
4885
4886 return none
4887}
4888
4889fn (t &Transformer) resolve_method_call_name(receiver ast.Expr, method_name string) ?string {
4890 if t.is_static_method_call(receiver) {
4891 return t.resolve_static_type_method_call(receiver, method_name)
4892 }
4893 if cast_type_name := t.explicit_cast_receiver_type_name(receiver) {
4894 if resolved := t.resolve_explicit_cast_method_name(cast_type_name, method_name) {
4895 return resolved
4896 }
4897 }
4898 mut recv_type := types.Type(types.void_)
4899 if smartcast_type := t.smartcast_selector_field_type(receiver) {
4900 receiver_str := t.expr_to_string(receiver)
4901 source_has_method := if ctx := t.find_smartcast_for_expr(receiver_str) {
4902 t.smartcast_source_has_cached_method(ctx, method_name)
4903 } else {
4904 false
4905 }
4906 if source_has_method {
4907 ctx := t.find_smartcast_for_expr(receiver_str) or { return none }
4908 recv_type = t.c_name_to_type(ctx.sumtype) or { smartcast_type }
4909 } else if declared_type := t.declared_expr_type_for_method_receiver(receiver) {
4910 recv_type = if t.type_has_cached_method(declared_type, method_name) {
4911 declared_type
4912 } else {
4913 smartcast_type
4914 }
4915 } else {
4916 recv_type = smartcast_type
4917 }
4918 } else if declared_type := t.declared_expr_type_for_method_receiver(receiver) {
4919 recv_type = if t.type_has_cached_method(declared_type, method_name) {
4920 declared_type
4921 } else {
4922 t.get_expr_type(receiver) or { declared_type }
4923 }
4924 } else {
4925 recv_type = t.get_expr_type(receiver) or {
4926 // When type info is unavailable (e.g. methods on typed arrays like []string
4927 // where the scope didn't load), try 'array' as fallback for methods that are
4928 // unique to arrays. Skip methods that also exist on string/map/other types
4929 // since we can't disambiguate without type info.
4930 if method_name !in ['str', 'hex', 'clone', 'free', 'trim', 'bytes', 'bytestr', 'replace',
4931 'contains', 'len', 'index', 'last_index', 'is_blank', 'join', 'to_upper', 'to_lower',
4932 'repeat', 'vbytes', 'plus_two', 'write_u8', 'write_string', 'write_rune'] {
4933 if t.lookup_method_cached('array', method_name) != none {
4934 return 'array__${method_name}'
4935 }
4936 }
4937 // When type lookup fails but receiver is a known string expression, resolve
4938 // as string method. This prevents falling through to SSA builder where
4939 // non-deterministic map iteration could resolve to the wrong overload.
4940 if t.is_string_expr(receiver) {
4941 if t.lookup_method_cached('string', method_name) != none {
4942 return 'string__${method_name}'
4943 }
4944 }
4945 return none
4946 }
4947 }
4948 if method_name == 'clone' {
4949 if _ := t.unwrap_map_type(recv_type) {
4950 return 'map__clone'
4951 }
4952 }
4953 if alias_method := t.resolve_alias_receiver_method_name(recv_type, method_name) {
4954 return alias_method
4955 }
4956 base_type := t.unwrap_alias_and_pointer_type(recv_type)
4957 mut c_prefix := t.receiver_type_to_c_prefix(base_type)
4958 // Guard against type misresolution: when the checker returns array type but
4959 // the receiver is actually a string expression (verified via structural analysis),
4960 // correct the prefix. This can happen in ARM64-compiled binaries where the
4961 // checker's type store may have incorrect entries due to chained-access issues.
4962 if c_prefix == 'array' && t.is_string_expr(receiver) {
4963 c_prefix = 'string'
4964 }
4965 // Self-hosted ARM64 builds can misresolve byte-oriented receivers as `i8`
4966 // even though the builtin helper methods are declared on `u8`.
4967 // Only fall back to u8 when i8 has no method of its own (e.g. bytestr, hex).
4968 if c_prefix == 'i8' && t.lookup_method_cached('i8', method_name) == none
4969 && t.lookup_method_cached('u8', method_name) != none {
4970 c_prefix = 'u8'
4971 }
4972 if c_prefix == '' {
4973 return none
4974 }
4975 if base_type is types.Channel && method_name == 'close' {
4976 return 'chan__close'
4977 }
4978 // Build lookup names for method verification (same pattern as lookup_call_param_types)
4979 type_name := base_type.name()
4980 mut lookup_names := []string{cap: 5}
4981 t.append_method_lookup_type_name(mut lookup_names, type_name)
4982 if lookup_names.len == 0 {
4983 return none
4984 }
4985 if !type_name.contains('__') && t.cur_module != '' && t.cur_module != 'main' {
4986 lookup_names << '${t.cur_module}__${type_name}'
4987 }
4988 // For container types ([]T, map, etc.), also try 'array'/'map'/'string' since
4989 // methods on the generic container are registered under those names.
4990 if c_prefix == 'array' && type_name != 'array' && 'array' !in lookup_names {
4991 lookup_names << 'array'
4992 } else if c_prefix == 'map' && type_name != 'map' && 'map' !in lookup_names {
4993 lookup_names << 'map'
4994 } else if c_prefix == 'string' && type_name != 'string' && 'string' !in lookup_names {
4995 lookup_names << 'string'
4996 }
4997 base_method_name := generic_base_name_without_specialization(method_name)
4998 if base_method_name != method_name {
4999 for name in lookup_names {
5000 if t.lookup_method_cached(name, base_method_name) != none {
5001 return '${c_prefix}__${method_name}'
5002 }
5003 }
5004 }
5005 if specific_array_method := t.specific_array_method_c_name(receiver, method_name) {
5006 return specific_array_method
5007 }
5008 if resolved_declared_method := t.resolve_cached_method_fn_name_for_type(recv_type, method_name,
5009 c_prefix)
5010 {
5011 return resolved_declared_method
5012 }
5013 // Verify method exists via env.lookup_method
5014 for name in lookup_names {
5015 if t.lookup_method_cached(name, method_name) != none {
5016 // For array types: if the method is NOT on generic 'array' but on
5017 // a typed array (e.g., []rune.string()), use the specific C type name
5018 // (e.g., Array_rune) instead of generic 'array'.
5019 if c_prefix == 'array' && t.lookup_method_cached('array', method_name) == none {
5020 mut specific_name := t.type_to_c_name(base_type)
5021 if specific_name == 'Array_i8'
5022 && method_name in ['bytestr', 'byterune', 'hex', 'utf8_to_utf32'] {
5023 specific_name = 'Array_u8'
5024 }
5025 return '${specific_name}__${method_name}'
5026 }
5027 return '${c_prefix}__${method_name}'
5028 }
5029 }
5030 // Fuzzy fallback: iterate method keys to find matching receiver types
5031 for key in t.candidate_method_keys(lookup_names) {
5032 mut matches_receiver := false
5033 for name in lookup_names {
5034 if t.method_key_matches_type_name(key, name) {
5035 matches_receiver = true
5036 break
5037 }
5038 }
5039 if !matches_receiver {
5040 continue
5041 }
5042 methods_for_type := t.cached_methods[key] or { continue }
5043 for method in methods_for_type {
5044 if method.get_name() == method_name {
5045 // When the method was found on a different module-qualified type
5046 // (e.g. key=mbedtls__SSLConn vs c_prefix=ssl__SSLConn), use the
5047 // actual method owner's name. This handles embedded struct method
5048 // promotion where the method body lives on the embedded type.
5049 if key != c_prefix && key.contains('__') && c_prefix.contains('__')
5050 && key.all_after_last('__') == c_prefix.all_after_last('__') {
5051 return '${key}__${method_name}'
5052 }
5053 return '${c_prefix}__${method_name}'
5054 }
5055 }
5056 }
5057 // Embedded struct method fallback: if the type is a struct that embeds
5058 // other structs, check if the method exists on any embedded struct.
5059 // This handles cases like ssl.SSLConn embedding mbedtls.SSLConn.
5060 if base_type is types.Struct {
5061 for embedded in base_type.embedded {
5062 emb_name := embedded.name
5063 if emb_name == '' {
5064 continue
5065 }
5066 if t.lookup_method_cached(emb_name, method_name) != none {
5067 return '${emb_name}__${method_name}'
5068 }
5069 // Also try short name (without module prefix)
5070 short_name := emb_name.all_after_last('__')
5071 if short_name != emb_name && t.lookup_method_cached(short_name, method_name) != none {
5072 return '${emb_name}__${method_name}'
5073 }
5074 }
5075 }
5076 if method_name == 'clone' {
5077 if generated := t.clone_fn_name_for_type(recv_type) {
5078 return generated
5079 }
5080 }
5081 return none
5082}
5083
5084fn (t &Transformer) resolve_cached_method_fn_name_for_type(recv_type types.Type, method_name string, c_prefix string) ?string {
5085 if method_name == '' {
5086 return none
5087 }
5088 base_type := t.unwrap_alias_and_pointer_type(recv_type)
5089 mut prefixes := []string{}
5090 for prefix in [c_prefix, t.type_to_c_name(base_type), base_type.name(),
5091 t.type_to_name(base_type)] {
5092 if prefix != '' && prefix !in prefixes {
5093 prefixes << prefix
5094 }
5095 }
5096 for prefix in prefixes {
5097 candidate := '${prefix}__${method_name}'
5098 if t.cached_function_name_exists(candidate) {
5099 return candidate
5100 }
5101 }
5102 return none
5103}
5104
5105fn (t &Transformer) cached_function_name_exists(name string) bool {
5106 if name == '' {
5107 return false
5108 }
5109 if name in t.declared_method_fns {
5110 return true
5111 }
5112 if name in t.cached_fn_scopes {
5113 return true
5114 }
5115 return false
5116}
5117
5118fn (t &Transformer) resolved_method_uses_declared_receiver(receiver ast.Expr, method_name string, resolved string) bool {
5119 declared_type := t.declared_expr_type_for_method_receiver(receiver) or { return false }
5120 if !t.type_has_cached_method(declared_type, method_name) {
5121 return false
5122 }
5123 base_type := t.unwrap_alias_and_pointer_type(declared_type)
5124 c_prefix := t.receiver_type_to_c_prefix(base_type)
5125 if c_prefix == '' {
5126 return false
5127 }
5128 return resolved == '${c_prefix}__${method_name}'
5129}
5130
5131fn (mut t Transformer) transform_method_receiver_arg(receiver ast.Expr, method_name string, resolved string) ast.Expr {
5132 if t.resolved_method_uses_declared_receiver(receiver, method_name, resolved) {
5133 receiver_key := t.expr_to_string(receiver)
5134 if removed := t.remove_smartcast_for_expr_with_idx(receiver_key) {
5135 transformed := t.transform_expr(receiver)
5136 t.restore_smartcasts([removed])
5137 return transformed
5138 }
5139 }
5140 return t.transform_method_receiver_expr(receiver)
5141}
5142
5143fn (t &Transformer) specific_array_method_c_name(receiver ast.Expr, method_name string) ?string {
5144 recv_type := t.get_expr_type(receiver) or { return none }
5145 base_type := t.unwrap_alias_and_pointer_type(recv_type)
5146 match base_type {
5147 types.Array, types.ArrayFixed {
5148 c_name := t.type_to_c_name(base_type)
5149 if c_name == '' {
5150 return none
5151 }
5152 base_name := base_type.name()
5153 if t.lookup_method_cached(base_name, method_name) != none {
5154 return '${c_name}__${method_name}'
5155 }
5156 if c_name != base_name && t.lookup_method_cached(c_name, method_name) != none {
5157 return '${c_name}__${method_name}'
5158 }
5159 }
5160 else {}
5161 }
5162
5163 return none
5164}
5165
5166fn (t &Transformer) resolves_to_embedded_method(receiver ast.Expr, method_name string) bool {
5167 recv_type := t.get_expr_type(receiver) or { return false }
5168 struct_type := t.live_struct_type_from_type(recv_type) or { return false }
5169 for embedded in struct_type.embedded {
5170 emb_name := embedded.name
5171 if emb_name == '' {
5172 continue
5173 }
5174 if t.lookup_method_cached(emb_name, method_name) != none {
5175 return true
5176 }
5177 short_name := emb_name.all_after_last('__')
5178 if short_name != emb_name && t.lookup_method_cached(short_name, method_name) != none {
5179 return true
5180 }
5181 }
5182 return false
5183}
5184
5185fn (mut t Transformer) transform_promoted_embedded_method_call(sel ast.SelectorExpr, raw_args []ast.Expr, pos token.Pos) ?ast.Expr {
5186 recv_type := t.get_expr_type(sel.lhs) or { return none }
5187 owner_method_name := generic_base_name_without_specialization(sel.rhs.name)
5188 if t.type_has_cached_method(recv_type, owner_method_name) {
5189 return none
5190 }
5191 struct_type := t.live_struct_type_from_type(recv_type) or { return none }
5192 for embedded in struct_type.embedded {
5193 emb_name := embedded.name
5194 if emb_name == '' {
5195 continue
5196 }
5197 mut resolved := ''
5198 if t.lookup_method_cached(emb_name, sel.rhs.name) != none {
5199 resolved = '${emb_name}__${sel.rhs.name}'
5200 } else {
5201 short_name := emb_name.all_after_last('__')
5202 if short_name != emb_name && t.lookup_method_cached(short_name, sel.rhs.name) != none {
5203 resolved = '${emb_name}__${sel.rhs.name}'
5204 }
5205 }
5206 if resolved == '' {
5207 continue
5208 }
5209 base_field_name := embedded_field_name_from_type_name(emb_name)
5210 if base_field_name == '' {
5211 continue
5212 }
5213 parent_struct_name := t.type_to_c_name(types.Type(struct_type))
5214 mut field_name := base_field_name
5215 if concrete_owner := t.concrete_embedded_owner_name(parent_struct_name, base_field_name) {
5216 field_name = concrete_owner
5217 }
5218 embedded_type := t.field_type_from_receiver_type(recv_type, field_name) or {
5219 t.field_type_from_receiver_type(recv_type, base_field_name) or {
5220 types.Type(t.live_embedded_struct_type(embedded))
5221 }
5222 }
5223 embedded_receiver := t.synth_selector(t.transform_method_receiver_expr(sel.lhs),
5224 field_name, embedded_type)
5225 embedded_lhs := ast.Expr(ast.SelectorExpr{
5226 lhs: embedded_receiver
5227 rhs: sel.rhs
5228 pos: sel.pos
5229 })
5230 mut seeded_bindings := t.generic_bindings_for_struct_field(recv_type, field_name) or {
5231 t.generic_bindings_for_struct_field(recv_type, base_field_name) or {
5232 map[string]types.Type{}
5233 }
5234 }
5235 mut call_args := t.lower_missing_call_args(embedded_lhs, raw_args)
5236 fn_info := t.generic_aware_call_fn_info(embedded_lhs, resolved)
5237 mut promoted_info := CallFnInfo{}
5238 mut has_promoted_info := false
5239 if call_args_have_field_init(call_args) {
5240 if decl_info := t.generic_call_info_for_decl(resolved) {
5241 promoted_info = call_info_without_first_param(decl_info)
5242 if promoted_info.param_types.len > 0 {
5243 call_args = t.lower_field_init_call_args(call_args, promoted_info.param_names,
5244 promoted_info.param_types)
5245 has_promoted_info = true
5246 }
5247 }
5248 }
5249 if call_args_have_field_init(call_args) {
5250 if decl := t.generic_fn_decl_for_call_info(resolved) {
5251 if decl.typ.params.len > 0 {
5252 generic_params := decl_generic_param_names(decl)
5253 if param_type := t.type_from_param_type_expr(decl.typ.params[0].typ,
5254 generic_params)
5255 {
5256 concrete_param_type := substitute_type(param_type, seeded_bindings)
5257 promoted_info = CallFnInfo{
5258 param_types: [concrete_param_type]
5259 param_names: ['']
5260 }
5261 call_args = t.lower_field_init_call_args(call_args,
5262 promoted_info.param_names, promoted_info.param_types)
5263 has_promoted_info = true
5264 }
5265 }
5266 }
5267 }
5268 mut transformed_call_args := []ast.Expr{cap: call_args.len}
5269 for i, arg in call_args {
5270 if has_promoted_info {
5271 transformed_call_args << t.transform_call_arg_with_sumtype_check(arg,
5272 promoted_info, i)
5273 } else {
5274 transformed_call_args << t.transform_call_arg_with_sumtype_check(arg, fn_info, i)
5275 }
5276 }
5277 transformed_call_args = t.lower_variadic_args(embedded_lhs, transformed_call_args)
5278 mut args := []ast.Expr{cap: transformed_call_args.len + 1}
5279 args << embedded_receiver
5280 args << transformed_call_args
5281 mut call_name := resolved
5282 if has_promoted_info {
5283 if arg_bindings := t.generic_bindings_from_call_args(promoted_info, call_args) {
5284 for name, typ in arg_bindings {
5285 if name !in seeded_bindings {
5286 seeded_bindings[name] = typ
5287 }
5288 }
5289 }
5290 if seeded := t.generic_method_call_name_from_bindings(call_name, seeded_bindings) {
5291 call_name = seeded
5292 } else if receiver_inferred := t.receiver_generic_method_call_name(call_name,
5293 embedded_receiver, promoted_info, call_args)
5294 {
5295 call_name = receiver_inferred
5296 } else if inferred := t.inferred_generic_call_name(call_name, promoted_info, call_args) {
5297 if !generic_name_contains_placeholder_suffix(inferred) {
5298 call_name = inferred
5299 }
5300 }
5301 } else if info := fn_info {
5302 if arg_bindings := t.generic_bindings_from_call_args(info, call_args) {
5303 for name, typ in arg_bindings {
5304 if name !in seeded_bindings {
5305 seeded_bindings[name] = typ
5306 }
5307 }
5308 }
5309 if seeded := t.generic_method_call_name_from_bindings(call_name, seeded_bindings) {
5310 call_name = seeded
5311 } else if receiver_inferred := t.receiver_generic_method_call_name(call_name,
5312 embedded_receiver, info, call_args)
5313 {
5314 call_name = receiver_inferred
5315 } else if inferred := t.inferred_generic_call_name(call_name, info, call_args) {
5316 if !generic_name_contains_placeholder_suffix(inferred) {
5317 call_name = inferred
5318 }
5319 }
5320 } else if seeded := t.generic_method_call_name_from_bindings(call_name, seeded_bindings) {
5321 call_name = seeded
5322 } else if receiver_inferred := t.receiver_generic_method_call_name(call_name,
5323 embedded_receiver, CallFnInfo{}, call_args)
5324 {
5325 call_name = receiver_inferred
5326 }
5327 if call_name == resolved {
5328 if full_info := t.generic_call_info_for_decl(resolved) {
5329 mut full_call_args := []ast.Expr{cap: call_args.len + 1}
5330 full_call_args << embedded_receiver
5331 for arg in call_args {
5332 full_call_args << arg
5333 }
5334 if full_bindings := t.generic_bindings_from_call_args(full_info, full_call_args) {
5335 if generic_bindings_cover_params(full_bindings, decl_generic_param_names(t.generic_fn_decl_for_call(resolved) or {
5336 return none
5337 }))
5338 {
5339 t.register_generic_bindings(resolved, full_bindings)
5340 if seeded := t.generic_method_call_name_from_bindings(resolved,
5341 full_bindings)
5342 {
5343 call_name = seeded
5344 }
5345 }
5346 }
5347 }
5348 }
5349 return ast.Expr(ast.CallExpr{
5350 lhs: ast.Ident{
5351 name: call_name
5352 }
5353 args: args
5354 pos: pos
5355 })
5356 }
5357 return none
5358}
5359
5360fn (t &Transformer) generic_bindings_for_struct_field(receiver_type types.Type, field_name string) ?map[string]types.Type {
5361 mut cur := receiver_type
5362 for {
5363 if cur is types.Pointer {
5364 cur = cur.base_type
5365 continue
5366 }
5367 if cur is types.Alias {
5368 if bindings := t.lookup_struct_field_generic_decl_bindings(cur.name, field_name) {
5369 return bindings
5370 }
5371 cur = cur.base_type
5372 continue
5373 }
5374 break
5375 }
5376 mut lookup_names := []string{}
5377 type_name := cur.name()
5378 if type_name != '' {
5379 lookup_names << type_name
5380 }
5381 c_name := t.type_to_c_name(cur)
5382 if c_name != '' && c_name !in lookup_names {
5383 lookup_names << c_name
5384 }
5385 short_name := t.type_to_name(cur)
5386 if short_name != '' && short_name !in lookup_names {
5387 lookup_names << short_name
5388 }
5389 for name in lookup_names {
5390 if bindings := t.lookup_struct_field_generic_decl_bindings(name, field_name) {
5391 return bindings
5392 }
5393 }
5394 return none
5395}
5396
5397fn (t &Transformer) generic_method_call_name_from_bindings(base_name string, bindings map[string]types.Type) ?string {
5398 if base_name == '' || bindings.len == 0 {
5399 return none
5400 }
5401 decl := t.generic_fn_decl_for_call(base_name) or { return none }
5402 for param_name in decl_generic_param_names(decl) {
5403 concrete := bindings[param_name] or { return none }
5404 if clone_type_contains_generic_placeholder(concrete) {
5405 return none
5406 }
5407 }
5408 specialized := t.specialized_fn_name(decl, bindings)
5409 if specialized == '' || specialized == decl.name {
5410 return none
5411 }
5412 mut receiver_prefix := base_name.all_before_last('__')
5413 if receiver_prefix == '' {
5414 return none
5415 }
5416 if receiver_prefix.contains('_T_') {
5417 receiver_prefix = receiver_prefix.all_before('_T_')
5418 }
5419 return '${receiver_prefix}__${specialized}'
5420}
5421
5422fn generic_name_contains_placeholder_suffix(name string) bool {
5423 if name.ends_with('_T') {
5424 return true
5425 }
5426 if !name.contains('_T_') {
5427 return false
5428 }
5429 for part in name.all_after('_T_').split('_') {
5430 if part.len == 1 && part[0] >= `A` && part[0] <= `Z` {
5431 return true
5432 }
5433 }
5434 return false
5435}
5436
5437fn (mut t Transformer) lower_missing_call_args(lhs ast.Expr, args []ast.Expr) []ast.Expr {
5438 info := t.call_fn_info_for_lhs(lhs) or { return args }
5439 param_types := info.param_types
5440 if param_types.len == 0 {
5441 return args
5442 }
5443 param_names := info.param_names
5444
5445 mut out := []ast.Expr{cap: args.len}
5446 for arg in args {
5447 out << arg
5448 }
5449 if call_args_have_field_init(out) {
5450 out = t.lower_field_init_call_args(out, param_names, param_types)
5451 }
5452 for i in 0 .. out.len {
5453 if i >= param_types.len {
5454 break
5455 }
5456 out[i] = t.resolve_expr_with_expected_type(out[i], param_types[i])
5457 }
5458 for i in out.len .. param_types.len {
5459 typ := t.unwrap_alias_and_pointer_type(param_types[i])
5460 match typ {
5461 types.Struct {
5462 out << t.empty_struct_arg_expr(param_types[i])
5463 }
5464 else {
5465 break
5466 }
5467 }
5468 }
5469 return out
5470}
5471
5472// lower_variadic_args wraps already-transformed trailing args into an array
5473// literal when a variadic function receives more args than declared params.
5474// Must be called AFTER transform_expr has been applied to all args.
5475fn (t &Transformer) lower_variadic_args(lhs ast.Expr, args []ast.Expr) []ast.Expr {
5476 // Skip C function calls — C variadic functions use native C varargs, not V arrays.
5477 if lhs is ast.SelectorExpr {
5478 if lhs.lhs is ast.Ident && lhs.lhs.name == 'C' {
5479 return args
5480 }
5481 }
5482 info := t.call_fn_info_for_lhs(lhs) or { return args }
5483 if !info.is_variadic || info.param_types.len == 0 {
5484 return args
5485 }
5486 variadic_start := info.param_types.len - 1
5487 if args.len < variadic_start {
5488 return args
5489 }
5490 last_param_type := t.unwrap_alias_and_pointer_type(info.param_types[info.param_types.len - 1])
5491 // When args.len == param_types.len, check if the last arg is already an array
5492 // (e.g., passing []Signal to ...Signal, or using spread ...args). If so, don't wrap.
5493 // If we can't determine the arg type, be conservative and don't wrap.
5494 if args.len == info.param_types.len {
5495 if last_param_type is types.Array {
5496 last_arg := args[args.len - 1]
5497 // Spread operator (...args) passes an array directly, no wrapping needed
5498 if last_arg is ast.PrefixExpr && last_arg.op == .ellipsis {
5499 return args
5500 }
5501 if arg_type := t.get_expr_type(last_arg) {
5502 unwrapped := t.unwrap_alias_and_pointer_type(arg_type)
5503 if unwrapped is types.Array {
5504 return args
5505 }
5506 }
5507 // arg is not an array type, proceed to wrap it
5508 } else {
5509 return args
5510 }
5511 }
5512 if last_param_type is types.Array {
5513 elem_type_name := t.type_to_c_name(last_param_type.elem_type)
5514 if elem_type_name == '' {
5515 return args
5516 }
5517 variadic_count := args.len - variadic_start
5518 mut variadic_exprs := []ast.Expr{cap: variadic_count}
5519 for i in variadic_start .. args.len {
5520 variadic_exprs << args[i]
5521 }
5522 inner_array_typ := ast.Type(ast.ArrayType{
5523 elem_type: ast.Ident{
5524 name: elem_type_name
5525 }
5526 })
5527 array_arg := ast.Expr(ast.CallExpr{
5528 lhs: ast.Ident{
5529 name: 'builtin__new_array_from_c_array_noscan'
5530 }
5531 args: [
5532 ast.Expr(ast.BasicLiteral{
5533 kind: .number
5534 value: '${variadic_count}'
5535 }),
5536 ast.Expr(ast.BasicLiteral{
5537 kind: .number
5538 value: '${variadic_count}'
5539 }),
5540 ast.Expr(ast.KeywordOperator{
5541 op: .key_sizeof
5542 exprs: [ast.Expr(ast.Ident{
5543 name: elem_type_name
5544 })]
5545 }),
5546 ast.Expr(ast.ArrayInitExpr{
5547 typ: ast.Expr(inner_array_typ)
5548 exprs: variadic_exprs
5549 }),
5550 ]
5551 })
5552 mut new_out := []ast.Expr{cap: variadic_start + 1}
5553 for i in 0 .. variadic_start {
5554 new_out << args[i]
5555 }
5556 new_out << array_arg
5557 return new_out
5558 }
5559 return args
5560}
5561
5562fn call_args_have_field_init(args []ast.Expr) bool {
5563 for arg in args {
5564 if arg is ast.FieldInit {
5565 return true
5566 }
5567 }
5568 return false
5569}
5570
5571fn (mut t Transformer) empty_struct_arg_expr(param_type types.Type) ast.Expr {
5572 base := t.unwrap_alias_and_pointer_type(param_type)
5573 if base !is types.Struct {
5574 return ast.empty_expr
5575 }
5576 // For pointer parameters, use `&Type{}` which cleanc lowers to heap allocation.
5577 if t.is_pointer_type(param_type) {
5578 mut cur := param_type
5579 for cur is types.Alias {
5580 alias_t := cur as types.Alias
5581 cur = alias_t.base_type
5582 }
5583 inner := if cur is types.Pointer {
5584 ptr_t := cur as types.Pointer
5585 ptr_t.base_type
5586 } else {
5587 cur
5588 }
5589 if cur is types.Pointer {
5590 init_pos := t.next_synth_pos()
5591 ptr_pos := t.next_synth_pos()
5592 t.register_synth_type(init_pos, inner)
5593 t.register_synth_type(ptr_pos, param_type)
5594 init_expr := ast.Expr(ast.InitExpr{
5595 typ: t.type_to_ast_type_expr(inner)
5596 pos: init_pos
5597 })
5598 return ast.Expr(ast.PrefixExpr{
5599 op: .amp
5600 expr: init_expr
5601 pos: ptr_pos
5602 })
5603 }
5604 // Fallback: nil pointer.
5605 return ast.Expr(ast.Ident{
5606 name: 'nil'
5607 })
5608 }
5609 base_name := t.type_to_c_name(base)
5610 if base_name.ends_with('PRNGConfigStruct') {
5611 init_pos := t.next_synth_pos()
5612 t.register_synth_type(init_pos, param_type)
5613 return ast.Expr(ast.InitExpr{
5614 typ: t.type_to_ast_type_expr(param_type)
5615 fields: [
5616 ast.FieldInit{
5617 name: 'seed_'
5618 value: ast.Expr(ast.CallExpr{
5619 lhs: ast.Ident{
5620 name: 'seed__time_seed_array'
5621 }
5622 args: [
5623 ast.Expr(ast.BasicLiteral{
5624 kind: .number
5625 value: '2'
5626 }),
5627 ]
5628 })
5629 },
5630 ]
5631 pos: init_pos
5632 })
5633 }
5634 init_pos := t.next_synth_pos()
5635 t.register_synth_type(init_pos, param_type)
5636 return ast.Expr(ast.InitExpr{
5637 typ: t.type_to_ast_type_expr(param_type)
5638 pos: init_pos
5639 })
5640}
5641
5642fn (mut t Transformer) lower_field_init_call_args(args []ast.Expr, param_names []string, param_types []types.Type) []ast.Expr {
5643 if args.len == 0 {
5644 return args
5645 }
5646 if param_names.len != param_types.len {
5647 // Keep call args intact when signature info is incomplete.
5648 return args
5649 }
5650 mut name_to_idx := map[string]int{}
5651 for i, name in param_names {
5652 if name.len > 0 {
5653 name_to_idx[name] = i
5654 }
5655 }
5656 // Decide whether this is a named-argument call (FieldInit names match param names),
5657 // or a struct-shorthand literal for the next struct parameter.
5658 mut is_named_args := false
5659 for arg in args {
5660 if arg is ast.FieldInit {
5661 if arg.name in name_to_idx {
5662 is_named_args = true
5663 break
5664 }
5665 }
5666 }
5667 if is_named_args {
5668 return t.lower_named_args_call(args, name_to_idx, param_types)
5669 }
5670 return t.lower_struct_shorthand_call(args, param_types)
5671}
5672
5673fn (mut t Transformer) lower_named_args_call(args []ast.Expr, name_to_idx map[string]int, param_types []types.Type) []ast.Expr {
5674 mut positional := []ast.Expr{}
5675 mut named := map[int]ast.Expr{}
5676 mut seen_named := false
5677 for arg in args {
5678 if arg is ast.FieldInit {
5679 seen_named = true
5680 idx := name_to_idx[arg.name] or {
5681 panic('bug in v2 compiler: unknown named argument `${arg.name}`')
5682 }
5683 if idx < positional.len {
5684 panic('bug in v2 compiler: named argument `${arg.name}` overlaps positional args')
5685 }
5686 if idx in named {
5687 panic('bug in v2 compiler: named argument `${arg.name}` specified more than once')
5688 }
5689 named[idx] = arg.value
5690 continue
5691 }
5692 if seen_named {
5693 panic('bug in v2 compiler: positional arguments after named arguments are not supported')
5694 }
5695 positional << arg
5696 }
5697 if positional.len > param_types.len {
5698 panic('bug in v2 compiler: too many positional arguments (${positional.len})')
5699 }
5700 mut out := []ast.Expr{len: param_types.len, init: ast.empty_expr}
5701 for i, arg in positional {
5702 out[i] = arg
5703 }
5704 for idx, val in named {
5705 if idx >= out.len {
5706 panic('bug in v2 compiler: named argument index out of range (${idx})')
5707 }
5708 if out[idx] !is ast.EmptyExpr {
5709 panic('bug in v2 compiler: named argument overlaps positional argument at index ${idx}')
5710 }
5711 out[idx] = val
5712 }
5713 mut last := -1
5714 for i, val in out {
5715 if val !is ast.EmptyExpr {
5716 last = i
5717 }
5718 }
5719 if last < 0 {
5720 return []ast.Expr{}
5721 }
5722 // Fill gaps up to `last` for struct parameters (default empty init). Other gaps
5723 // indicate missing support for non-struct default arguments.
5724 for i in 0 .. last + 1 {
5725 if out[i] !is ast.EmptyExpr {
5726 continue
5727 }
5728 base := t.unwrap_alias_and_pointer_type(param_types[i])
5729 match base {
5730 types.Struct {
5731 out[i] = t.empty_struct_arg_expr(param_types[i])
5732 }
5733 else {
5734 panic('bug in v2 compiler: missing named argument for parameter ${i}')
5735 }
5736 }
5737 }
5738 return out[..last + 1]
5739}
5740
5741fn (mut t Transformer) lower_struct_shorthand_call(args []ast.Expr, param_types []types.Type) []ast.Expr {
5742 mut positional := []ast.Expr{}
5743 mut fields := []ast.FieldInit{}
5744 mut seen_fields := false
5745 for arg in args {
5746 if arg is ast.FieldInit {
5747 seen_fields = true
5748 fields << ast.FieldInit{
5749 name: arg.name
5750 value: arg.value
5751 }
5752 continue
5753 }
5754 if seen_fields {
5755 panic('bug in v2 compiler: positional arguments after struct-shorthand fields are not supported')
5756 }
5757 positional << arg
5758 }
5759 if fields.len == 0 {
5760 return args
5761 }
5762 param_idx := positional.len
5763 if param_idx >= param_types.len {
5764 panic('bug in v2 compiler: struct-shorthand call has more args than params (${t.cur_file_name}:${t.cur_fn_name_str} positional=${positional.len} params=${param_types.len})')
5765 }
5766 mut param_type := param_types[param_idx]
5767 mut base := t.unwrap_alias_and_pointer_type(param_type)
5768 if base !is types.Struct {
5769 match base {
5770 types.Array {
5771 array_type := base as types.Array
5772 elem_base := t.unwrap_alias_and_pointer_type(array_type.elem_type)
5773 if elem_base is types.Struct {
5774 param_type = array_type.elem_type
5775 base = types.Type(elem_base)
5776 }
5777 }
5778 types.ArrayFixed {
5779 array_fixed_type := base as types.ArrayFixed
5780 elem_base := t.unwrap_alias_and_pointer_type(array_fixed_type.elem_type)
5781 if elem_base is types.Struct {
5782 param_type = array_fixed_type.elem_type
5783 base = types.Type(elem_base)
5784 }
5785 }
5786 else {}
5787 }
5788 }
5789 if base !is types.Struct {
5790 // Some call sites still reach this heuristic with field-init args that are
5791 // not struct shorthands. Keep the original args so later lowering/codegen
5792 // can handle them instead of aborting the whole compilation.
5793 return args
5794 }
5795 mut init_typ := param_type
5796 if t.is_pointer_type(param_type) {
5797 mut cur := param_type
5798 for cur is types.Alias {
5799 alias_t := cur as types.Alias
5800 cur = alias_t.base_type
5801 }
5802 inner := if cur is types.Pointer {
5803 ptr_t := cur as types.Pointer
5804 ptr_t.base_type
5805 } else {
5806 cur
5807 }
5808 if cur is types.Pointer {
5809 init_typ = inner
5810 }
5811 }
5812 init_pos := t.next_synth_pos()
5813 t.register_synth_type(init_pos, init_typ)
5814 mut init_expr := ast.Expr(ast.InitExpr{
5815 typ: t.type_to_ast_type_expr(init_typ)
5816 fields: fields
5817 pos: init_pos
5818 })
5819 if t.is_pointer_type(param_type) {
5820 ptr_pos := t.next_synth_pos()
5821 t.register_synth_type(ptr_pos, param_type)
5822 init_expr = ast.Expr(ast.PrefixExpr{
5823 op: .amp
5824 expr: init_expr
5825 pos: ptr_pos
5826 })
5827 }
5828 mut out := []ast.Expr{cap: positional.len + 1}
5829 out << positional
5830 out << init_expr
5831 return out
5832}
5833
5834fn (t &Transformer) expr_contains_ident_named(expr ast.Expr, name string) bool {
5835 match expr {
5836 ast.Ident {
5837 return expr.name == name
5838 }
5839 ast.SelectorExpr {
5840 return t.expr_contains_ident_named(expr.lhs, name)
5841 }
5842 ast.InfixExpr {
5843 return t.expr_contains_ident_named(expr.lhs, name)
5844 || t.expr_contains_ident_named(expr.rhs, name)
5845 }
5846 ast.ParenExpr {
5847 return t.expr_contains_ident_named(expr.expr, name)
5848 }
5849 ast.PrefixExpr {
5850 return t.expr_contains_ident_named(expr.expr, name)
5851 }
5852 ast.ModifierExpr {
5853 return t.expr_contains_ident_named(expr.expr, name)
5854 }
5855 ast.CastExpr {
5856 return t.expr_contains_ident_named(expr.expr, name)
5857 }
5858 ast.CallExpr {
5859 if t.expr_contains_ident_named(expr.lhs, name) {
5860 return true
5861 }
5862 for arg in expr.args {
5863 if t.expr_contains_ident_named(arg, name) {
5864 return true
5865 }
5866 }
5867 return false
5868 }
5869 ast.CallOrCastExpr {
5870 return t.expr_contains_ident_named(expr.lhs, name)
5871 || t.expr_contains_ident_named(expr.expr, name)
5872 }
5873 ast.IfExpr {
5874 if t.expr_contains_ident_named(expr.cond, name)
5875 || t.expr_contains_ident_named(expr.else_expr, name) {
5876 return true
5877 }
5878 for stmt in expr.stmts {
5879 if t.stmt_uses_ident(stmt, name) {
5880 return true
5881 }
5882 }
5883 return false
5884 }
5885 ast.IndexExpr {
5886 return t.expr_contains_ident_named(expr.lhs, name)
5887 || t.expr_contains_ident_named(expr.expr, name)
5888 }
5889 ast.ArrayInitExpr {
5890 for e in expr.exprs {
5891 if t.expr_contains_ident_named(e, name) {
5892 return true
5893 }
5894 }
5895 return false
5896 }
5897 ast.InitExpr {
5898 for field in expr.fields {
5899 if t.expr_contains_ident_named(field.value, name) {
5900 return true
5901 }
5902 }
5903 return false
5904 }
5905 else {
5906 return false
5907 }
5908 }
5909}
5910
5911fn (t &Transformer) is_sort_compare_lambda_expr(expr ast.Expr) bool {
5912 if expr is ast.InfixExpr {
5913 return t.expr_contains_ident_named(expr, 'a') && t.expr_contains_ident_named(expr, 'b')
5914 }
5915 return false
5916}
5917
5918fn (mut t Transformer) try_transform_native_interface_concrete_call(sel ast.SelectorExpr, args []ast.Expr, pos token.Pos, call_lhs ast.Expr) ?ast.Expr {
5919 if t.pref == unsafe { nil } || (t.pref.backend != .arm64 && t.pref.backend != .x64) {
5920 return none
5921 }
5922 concrete := t.get_interface_concrete_type_for_expr(sel.lhs) or { return none }
5923 call_args := t.lower_missing_call_args(call_lhs, args)
5924 fn_info := t.lookup_call_fn_info(call_lhs)
5925 mut transformed_args := []ast.Expr{cap: call_args.len}
5926 for i, arg in call_args {
5927 transformed_args << t.transform_call_arg_with_sumtype_check(arg, fn_info, i)
5928 }
5929 transformed_args = t.lower_variadic_args(call_lhs, transformed_args)
5930 resolved_method := '${concrete}__${sel.rhs.name}'
5931 mut native_args := []ast.Expr{cap: transformed_args.len + 1}
5932 native_receiver := t.native_interface_receiver_arg(sel.lhs, concrete)
5933 native_args << t.transform_expr(native_receiver)
5934 native_args << transformed_args
5935 return ast.Expr(ast.CallExpr{
5936 lhs: ast.Ident{
5937 name: resolved_method
5938 }
5939 args: native_args
5940 pos: pos
5941 })
5942}
5943
5944fn (mut t Transformer) transform_call_or_cast_expr(expr ast.CallOrCastExpr) ast.Expr {
5945 // Inline generic math functions (abs[T], min[T], max[T]).
5946 if inlined := t.try_inline_generic_math_coce(expr) {
5947 return inlined
5948 }
5949 // Expand .filter() / .map() calls to hoisted statements + temp variable
5950 if expanded := t.try_expand_filter_or_map_expr(expr) {
5951 return expanded
5952 }
5953 if generic_lhs := t.generic_call_lhs_from_index_expr(expr.lhs) {
5954 return t.transform_call_or_cast_expr(ast.CallOrCastExpr{
5955 lhs: generic_lhs
5956 expr: expr.expr
5957 pos: expr.pos
5958 })
5959 }
5960 // Check if this is a flag enum method call: receiver.has(arg) or receiver.all(arg)
5961 if expr.lhs is ast.SelectorExpr {
5962 sel := expr.lhs as ast.SelectorExpr
5963 if t.is_native_be {
5964 if concrete := t.get_native_default_interface_concrete_type(sel.lhs, sel.rhs.name) {
5965 mut call_args := []ast.Expr{}
5966 if expr.expr !is ast.EmptyExpr {
5967 call_args << expr.expr
5968 }
5969 call_args = t.lower_missing_call_args(expr.lhs, call_args)
5970 mut native_args := []ast.Expr{cap: call_args.len + 1}
5971 native_args << t.transform_expr(sel.lhs)
5972 for arg in call_args {
5973 native_args << t.transform_expr(arg)
5974 }
5975 return ast.CallExpr{
5976 lhs: ast.Ident{
5977 name: '${concrete}__${sel.rhs.name}'
5978 }
5979 args: native_args
5980 pos: expr.pos
5981 }
5982 }
5983 }
5984 // Skip calls to conditionally compiled functions (e.g., @[if verbose ?])
5985 if sel.rhs.name in t.elided_fns {
5986 return ast.Expr(ast.BasicLiteral{
5987 kind: .number
5988 value: '0'
5989 })
5990 }
5991 // arr.sort(a < b) may be parsed as CallOrCastExpr in single-arg form.
5992 if sel.rhs.name in ['sort', 'sorted'] && t.is_sort_compare_lambda_expr(expr.expr) {
5993 if result := t.transform_sort_call(sel.lhs, sel.rhs.name, [expr.expr], expr.pos) {
5994 return result
5995 }
5996 }
5997 method_name := sel.rhs.name
5998 if method_name == 'zero' && expr.expr is ast.EmptyExpr {
5999 receiver_type := t.get_enum_type(sel.lhs)
6000 if t.is_flag_enum(receiver_type) {
6001 return ast.CastExpr{
6002 typ: ast.Ident{
6003 name: receiver_type
6004 }
6005 expr: ast.BasicLiteral{
6006 kind: .number
6007 value: '0'
6008 }
6009 pos: expr.pos
6010 }
6011 }
6012 }
6013 if method_name in ['has', 'all'] {
6014 arg0 := expr.expr
6015 is_string_arg := arg0 is ast.StringLiteral
6016 || (arg0 is ast.BasicLiteral && arg0.kind == .string)
6017 if !is_string_arg {
6018 // Try to detect if receiver is a flag enum
6019 receiver_type := t.get_enum_type(sel.lhs)
6020 if t.is_flag_enum_receiver(sel.lhs, receiver_type) {
6021 // Transform the method call
6022 return t.transform_flag_enum_method(sel.lhs, method_name, [expr.expr],
6023 receiver_type)
6024 }
6025 }
6026 }
6027 if method_name in ['contains', 'index', 'last_index']
6028 && t.specific_array_method_c_name(sel.lhs, method_name) == none {
6029 if info := t.get_array_method_info(sel.lhs) {
6030 fn_name := t.register_needed_array_method(info, method_name)
6031 return ast.CallExpr{
6032 lhs: ast.Ident{
6033 name: fn_name
6034 }
6035 args: [
6036 t.transform_array_receiver_expr(sel.lhs),
6037 t.transform_expr(expr.expr),
6038 ]
6039 pos: expr.pos
6040 }
6041 }
6042 }
6043 // Check for smart-casted method call: se.lhs.method() when se.lhs is smartcast to Type
6044 if t.has_active_smartcast() {
6045 if smartcast_receiver := t.smartcast_method_receiver_context(sel.lhs) {
6046 variant_resolved_name := t.smartcast_variant_method_name(smartcast_receiver.ctx,
6047 sel.rhs.name) or { '' }
6048 mut args := []ast.Expr{cap: 1}
6049 if expr.expr !is ast.EmptyExpr {
6050 args << t.transform_expr(expr.expr)
6051 }
6052 // Resolve method name. When the variant type has the method, use the
6053 // variant's name to build the resolved call (e.g. Char__name instead
6054 // of Type__name) to avoid infinite recursion on sum type dispatch.
6055 // Do NOT route through transform_call_expr because it would
6056 // re-transform the already-casted receiver and args.
6057 mut resolved_name := ''
6058 if variant_resolved_name != '' {
6059 resolved_name = variant_resolved_name
6060 } else if rn := t.resolve_method_call_name(sel.lhs, sel.rhs.name) {
6061 resolved_name = rn
6062 }
6063 if resolved_name != '' {
6064 resolved := resolved_name
6065 is_static := t.is_static_method_call(sel.lhs)
6066 mut final_args := []ast.Expr{cap: args.len + 1}
6067 if !is_static {
6068 receiver_arg := if variant_resolved_name != '' {
6069 t.smartcast_method_receiver(smartcast_receiver.receiver,
6070 smartcast_receiver.ctx)
6071 } else {
6072 t.transform_method_receiver_arg(sel.lhs, sel.rhs.name, resolved)
6073 }
6074 final_args << receiver_arg
6075 }
6076 final_args << args
6077 return ast.CallExpr{
6078 lhs: ast.Ident{
6079 name: resolved
6080 }
6081 args: final_args
6082 pos: expr.pos
6083 }
6084 }
6085 // Fallback: keep as method call with casted receiver (let cleanc resolve)
6086 casted_receiver := t.smartcast_method_receiver(smartcast_receiver.receiver,
6087 smartcast_receiver.ctx)
6088 return ast.CallExpr{
6089 lhs: ast.Expr(ast.SelectorExpr{
6090 lhs: casted_receiver
6091 rhs: sel.rhs
6092 pos: sel.pos
6093 })
6094 args: args
6095 pos: expr.pos
6096 }
6097 }
6098 }
6099 // Check for interface method call: iface.method(arg)
6100 mut native_single_args := []ast.Expr{}
6101 if expr.expr !is ast.EmptyExpr {
6102 native_single_args << expr.expr
6103 }
6104 if native_call := t.try_transform_native_interface_concrete_call(sel, native_single_args,
6105 expr.pos, expr.lhs)
6106 {
6107 return native_call
6108 }
6109 if t.is_interface_receiver(sel.lhs) {
6110 mut call_args := []ast.Expr{}
6111 if expr.expr !is ast.EmptyExpr {
6112 call_args << expr.expr
6113 }
6114 call_args = t.lower_missing_call_args(expr.lhs, call_args)
6115 iface_method_fn_info := t.lookup_call_fn_info(expr.lhs)
6116 mut transformed_iface_args := []ast.Expr{cap: call_args.len}
6117 for i, arg in call_args {
6118 transformed_iface_args << t.transform_call_arg_with_sumtype_check(arg,
6119 iface_method_fn_info, i)
6120 }
6121 transformed_iface_args = t.lower_variadic_args(expr.lhs, transformed_iface_args)
6122 // Native backends (arm64/x64): resolve to direct concrete method call.
6123 if t.is_native_be {
6124 if concrete := t.get_interface_concrete_type_for_expr(sel.lhs) {
6125 resolved_iface_method := '${concrete}__${sel.rhs.name}'
6126 mut native_iface_args := []ast.Expr{cap: transformed_iface_args.len + 1}
6127 native_receiver := t.native_interface_receiver_arg(sel.lhs, concrete)
6128 native_iface_args << t.transform_expr(native_receiver)
6129 native_iface_args << transformed_iface_args
6130 return ast.CallExpr{
6131 lhs: ast.Ident{
6132 name: resolved_iface_method
6133 }
6134 args: native_iface_args
6135 pos: expr.pos
6136 }
6137 }
6138 if concrete := t.get_native_default_interface_concrete_type(sel.lhs, sel.rhs.name) {
6139 resolved_iface_method := '${concrete}__${sel.rhs.name}'
6140 mut native_iface_args := []ast.Expr{cap: transformed_iface_args.len + 1}
6141 native_iface_args << t.transform_expr(sel.lhs)
6142 native_iface_args << transformed_iface_args
6143 return ast.CallExpr{
6144 lhs: ast.Ident{
6145 name: resolved_iface_method
6146 }
6147 args: native_iface_args
6148 pos: expr.pos
6149 }
6150 }
6151 }
6152 // C/cleanc backends: Transform to vtable dispatch
6153 mut iface_args := []ast.Expr{cap: transformed_iface_args.len + 1}
6154 iface_args << t.synth_selector(sel.lhs, '_object', types.Type(types.voidptr_))
6155 iface_args << transformed_iface_args
6156 return ast.CallExpr{
6157 lhs: ast.Expr(expr.lhs) // Keep the selector: iface.method
6158 args: iface_args
6159 pos: expr.pos
6160 }
6161 }
6162 }
6163 // Check for println/eprintln with non-string argument
6164 // Transform: println(arr) -> println(Array_int_str(arr))
6165 if expr.lhs is ast.Ident {
6166 fn_name := expr.lhs.name
6167 if fn_name in ['println', 'eprintln', 'print', 'eprint'] {
6168 arg := expr.expr
6169 if arg is ast.StringInterLiteral {
6170 return ast.CallExpr{
6171 lhs: ast.Expr(expr.lhs)
6172 args: [t.transform_expr(arg)]
6173 pos: expr.pos
6174 }
6175 }
6176 if !t.is_string_expr(arg) {
6177 // Get the str function name and record it for generation
6178 str_fn_info := t.get_str_fn_info_for_expr(arg)
6179 if str_fn_info.str_fn_name != '' {
6180 // Record needed str function for later generation
6181 t.needed_str_fns[str_fn_info.str_fn_name] = str_fn_info.elem_type
6182 if typ := t.get_expr_type(arg) {
6183 if typ is types.Enum {
6184 t.needed_enum_str_fns[str_fn_info.str_fn_name] = typ
6185 }
6186 }
6187 mut str_call_args := []ast.Expr{cap: 1}
6188 str_call_args << t.transform_expr(arg)
6189 // Transform to println(Type_str(arg)) - use CallExpr for proper function call syntax
6190 return ast.CallExpr{
6191 lhs: ast.Expr(expr.lhs)
6192 args: [
6193 ast.Expr(ast.CallExpr{
6194 lhs: ast.Ident{
6195 name: str_fn_info.str_fn_name
6196 }
6197 args: str_call_args
6198 pos: expr.pos
6199 }),
6200 ]
6201 pos: expr.pos
6202 }
6203 }
6204 // Fallback: try get_str_fn_name_for_expr for non-array types (int, bool, etc.)
6205 if str_fn_name := t.get_str_fn_name_for_expr(arg) {
6206 mut str_call_args := []ast.Expr{cap: 1}
6207 str_call_args << t.transform_expr(arg)
6208 return ast.CallExpr{
6209 lhs: ast.Expr(expr.lhs)
6210 args: [
6211 ast.Expr(ast.CallExpr{
6212 lhs: ast.Ident{
6213 name: str_fn_name
6214 }
6215 args: str_call_args
6216 pos: expr.pos
6217 }),
6218 ]
6219 pos: expr.pos
6220 }
6221 }
6222 }
6223 }
6224 }
6225 // Check for explicit sum type cast: SumType(value) -> proper wrapping.
6226 // Important: do not transform `value` before variant inference, otherwise casts like
6227 // `Expr(EmptyExpr(0))` turn into `CastExpr` and variant inference can no longer
6228 // recover the variant.
6229 mut sumtype_name := ''
6230 mut sumtype_variants := []string{}
6231 if concrete_info := t.concrete_sumtype_wrap_info_from_lhs(expr.lhs) {
6232 sumtype_name = concrete_info.name
6233 sumtype_variants = concrete_info.variants.clone()
6234 }
6235 if sumtype_variants.len == 0 {
6236 if lhs_typ := t.lookup_type_from_expr(expr.lhs) {
6237 if lhs_typ is types.SumType {
6238 sumtype_name = t.type_to_c_name(lhs_typ)
6239 }
6240 }
6241 }
6242 if sumtype_name == '' {
6243 sumtype_name = t.type_expr_name_full(expr.lhs)
6244 }
6245 if sumtype_variants.len == 0 && (sumtype_name == '' || !t.is_sum_type(sumtype_name)) {
6246 if lhs_typ := t.get_expr_type(expr.lhs) {
6247 if lhs_typ is types.SumType {
6248 sumtype_name = t.type_to_c_name(lhs_typ)
6249 }
6250 }
6251 }
6252 if sumtype_variants.len == 0 && (sumtype_name == '' || !t.is_sum_type(sumtype_name))
6253 && expr.pos.is_valid() {
6254 if expr_typ := t.get_expr_type(ast.Expr(expr)) {
6255 if expr_typ is types.SumType {
6256 sumtype_name = t.type_to_c_name(expr_typ)
6257 }
6258 }
6259 }
6260 if sumtype_variants.len == 0 && sumtype_name != '' && t.is_sum_type(sumtype_name) {
6261 sumtype_variants = t.get_sum_type_variants(sumtype_name)
6262 }
6263 mut lhs_is_type := t.call_or_cast_lhs_is_type(expr.lhs)
6264 if !lhs_is_type && expr.lhs is ast.Ident && t.cur_module != '' {
6265 qualified_lhs := '${t.cur_module}__${expr.lhs.name}'
6266 if _ := t.lookup_type(qualified_lhs) {
6267 lhs_is_type = true
6268 }
6269 }
6270 if sumtype_name != '' && lhs_is_type && sumtype_variants.len > 0 {
6271 if wrapped := t.wrap_sumtype_value_with_variants(expr.expr, sumtype_name, sumtype_variants) {
6272 return wrapped
6273 }
6274 // Fallback for cases where checker typing of `expr.expr` is already widened
6275 // to the target sum type and direct variant inference fails.
6276 transformed_sum_arg := t.transform_expr(expr.expr)
6277 if wrapped := t.wrap_sumtype_value_transformed_with_variants(transformed_sum_arg,
6278 sumtype_name, sumtype_variants)
6279 {
6280 return wrapped
6281 }
6282 }
6283 // If lhs is not a type name, this is a function call - convert to CallExpr
6284 // This ensures downstream code (cleanc) sees a function call, not a cast
6285 if expr.lhs is ast.Ident {
6286 if !t.call_or_cast_lhs_is_type(expr.lhs) {
6287 mut call_args := []ast.Expr{}
6288 if expr.expr !is ast.EmptyExpr {
6289 call_args << expr.expr
6290 }
6291 call_args = t.lower_missing_call_args(expr.lhs, call_args)
6292 coce_fn_info := t.generic_aware_call_fn_info(expr.lhs, expr.lhs.name)
6293 mut args := []ast.Expr{cap: call_args.len}
6294 for i, arg in call_args {
6295 args << t.transform_call_arg_with_sumtype_check(arg, coce_fn_info, i)
6296 }
6297 args = t.lower_variadic_args(expr.lhs, args)
6298 mut call_lhs := t.transform_call_lhs_for_smartcast(expr.lhs)
6299 if info := coce_fn_info {
6300 if inferred := t.inferred_generic_call_name(expr.lhs.name, info, call_args) {
6301 t.register_generic_call_return_type(expr.lhs.name, info, call_args, expr.pos)
6302 call_lhs = ast.Expr(ast.Ident{
6303 name: inferred
6304 pos: expr.lhs.pos
6305 })
6306 }
6307 }
6308 return ast.CallExpr{
6309 lhs: call_lhs
6310 args: args
6311 pos: expr.pos
6312 }
6313 }
6314 }
6315 // Method call resolution: rewrite receiver.method(arg) -> Type__method(receiver, arg)
6316 if expr.lhs is ast.SelectorExpr {
6317 sel := expr.lhs as ast.SelectorExpr
6318 mut resolved_module_call_name := ''
6319 if sel.lhs is ast.Ident {
6320 lhs_name := (sel.lhs as ast.Ident).name
6321 if call_prefix := t.resolve_module_call_prefix(lhs_name) {
6322 if _ := t.lookup_fn_cached(call_prefix, sel.rhs.name) {
6323 resolved_module_call_name = '${call_prefix}__${sel.rhs.name}'
6324 }
6325 }
6326 if resolved_module_call_name == '' && t.lookup_var_type(lhs_name) == none {
6327 if resolved_mod := t.resolve_module_name(lhs_name) {
6328 mut module_names := []string{cap: 2}
6329 module_names << resolved_mod
6330 if lhs_name != resolved_mod {
6331 module_names << lhs_name
6332 }
6333 for mod_name in module_names {
6334 if _ := t.lookup_fn_cached(mod_name, sel.rhs.name) {
6335 call_mod := if mod_name.contains('.') {
6336 mod_name.all_after_last('.')
6337 } else if mod_name.contains('__') {
6338 mod_name.all_after_last('__')
6339 } else {
6340 mod_name
6341 }
6342 resolved_module_call_name = '${call_mod}__${sel.rhs.name}'
6343 break
6344 }
6345 }
6346 }
6347 }
6348 }
6349 if resolved_module_call_name != '' {
6350 mut call_args := []ast.Expr{}
6351 if expr.expr !is ast.EmptyExpr {
6352 call_args << expr.expr
6353 }
6354 call_args = t.lower_missing_call_args(expr.lhs, call_args)
6355 fn_info := t.generic_aware_call_fn_info(expr.lhs, resolved_module_call_name)
6356 mut args := []ast.Expr{cap: call_args.len}
6357 for i, arg in call_args {
6358 args << t.transform_call_arg_with_sumtype_check(arg, fn_info, i)
6359 }
6360 args = t.lower_variadic_args(expr.lhs, args)
6361 mut call_name := resolved_module_call_name
6362 if info := fn_info {
6363 if inferred := t.inferred_generic_call_name(call_name, info, call_args) {
6364 call_name = inferred
6365 }
6366 }
6367 return ast.CallExpr{
6368 lhs: ast.Ident{
6369 name: call_name
6370 }
6371 args: args
6372 pos: expr.pos
6373 }
6374 }
6375 if resolved_static := t.resolve_static_type_method_call(sel.lhs, sel.rhs.name) {
6376 mut call_args := []ast.Expr{}
6377 if expr.expr !is ast.EmptyExpr {
6378 call_args << expr.expr
6379 }
6380 call_args = t.lower_missing_call_args(expr.lhs, call_args)
6381 fn_info := t.generic_aware_call_fn_info(expr.lhs, resolved_static)
6382 mut args := []ast.Expr{cap: call_args.len}
6383 for i, arg in call_args {
6384 args << t.transform_call_arg_with_sumtype_check(arg, fn_info, i)
6385 }
6386 args = t.lower_variadic_args(expr.lhs, args)
6387 mut call_name := resolved_static
6388 if info := fn_info {
6389 if inferred := t.inferred_generic_call_name(call_name, info, call_args) {
6390 call_name = inferred
6391 }
6392 }
6393 return ast.CallExpr{
6394 lhs: ast.Ident{
6395 name: call_name
6396 }
6397 args: args
6398 pos: expr.pos
6399 }
6400 }
6401 is_module_call := sel.lhs is ast.Ident && t.lookup_var_type(sel.lhs.name) == none
6402 && (t.is_module_ident(sel.lhs.name) || t.get_module_scope(sel.lhs.name) != none)
6403 mut promoted_args := []ast.Expr{}
6404 if expr.expr !is ast.EmptyExpr {
6405 promoted_args << expr.expr
6406 }
6407 if embedded_call := t.transform_promoted_embedded_method_call(sel, promoted_args, expr.pos) {
6408 return embedded_call
6409 }
6410 if !is_module_call && !t.resolves_to_embedded_method(sel.lhs, sel.rhs.name) {
6411 if resolved := t.resolve_method_call_name(sel.lhs, sel.rhs.name) {
6412 // prepend(arr) → prepend_many(arr.data, arr.len)
6413 if resolved.ends_with('__prepend') && expr.expr !is ast.EmptyExpr {
6414 if arg_type := t.get_expr_type(expr.expr) {
6415 arg_base := t.unwrap_alias_and_pointer_type(arg_type)
6416 if arg_base is types.Array {
6417 arr_arg := t.transform_expr(expr.expr)
6418 return ast.CallExpr{
6419 lhs: ast.Ident{
6420 name: resolved.replace('__prepend', '__prepend_many')
6421 }
6422 args: [
6423 t.transform_expr(sel.lhs),
6424 ast.Expr(ast.SelectorExpr{
6425 lhs: arr_arg
6426 rhs: ast.Ident{
6427 name: 'data'
6428 }
6429 }),
6430 ast.Expr(ast.SelectorExpr{
6431 lhs: arr_arg
6432 rhs: ast.Ident{
6433 name: 'len'
6434 }
6435 }),
6436 ]
6437 pos: expr.pos
6438 }
6439 }
6440 }
6441 }
6442 // For nested array .repeat(n), use repeat_to_depth with the correct depth
6443 // so inner arrays are deeply cloned instead of shallow-copied.
6444 if resolved == 'array__repeat' {
6445 if recv_type := t.get_expr_type(sel.lhs) {
6446 depth := t.get_array_nesting_depth(recv_type)
6447 if depth > 1 {
6448 return ast.CallExpr{
6449 lhs: ast.Ident{
6450 name: 'array__repeat_to_depth'
6451 }
6452 args: [
6453 t.transform_expr(sel.lhs),
6454 t.transform_expr(expr.expr),
6455 ast.Expr(ast.BasicLiteral{
6456 kind: .number
6457 value: '${depth - 1}'
6458 }),
6459 ]
6460 pos: expr.pos
6461 }
6462 }
6463 }
6464 }
6465 // insert(i, arr) in single-arg form won't happen (needs 2 args)
6466 // but handle it for safety
6467 is_static := t.is_static_method_call(sel.lhs)
6468 mut call_args := []ast.Expr{}
6469 if expr.expr !is ast.EmptyExpr {
6470 call_args << expr.expr
6471 }
6472 call_args = t.lower_missing_call_args(expr.lhs, call_args)
6473 coce_method_fn_info := t.generic_aware_call_fn_info(expr.lhs, resolved)
6474 mut transformed_call_args := []ast.Expr{cap: call_args.len}
6475 for i, arg in call_args {
6476 transformed_call_args << t.transform_call_arg_with_sumtype_check(arg,
6477 coce_method_fn_info, i)
6478 }
6479 transformed_call_args = t.lower_variadic_args(expr.lhs, transformed_call_args)
6480 mut args := []ast.Expr{cap: transformed_call_args.len + 1}
6481 if !is_static {
6482 args << t.transform_method_receiver_arg(sel.lhs, sel.rhs.name, resolved)
6483 }
6484 args << transformed_call_args
6485 mut call_name := resolved
6486 if info := coce_method_fn_info {
6487 if receiver_inferred := t.receiver_generic_method_call_name(call_name, sel.lhs,
6488 info, call_args)
6489 {
6490 call_name = receiver_inferred
6491 } else if inferred := t.inferred_generic_call_name(call_name, info, call_args) {
6492 call_name = inferred
6493 }
6494 } else if receiver_inferred := t.receiver_generic_method_call_name(call_name,
6495 sel.lhs, CallFnInfo{}, call_args)
6496 {
6497 call_name = receiver_inferred
6498 }
6499 return ast.CallExpr{
6500 lhs: ast.Ident{
6501 name: call_name
6502 }
6503 args: args
6504 pos: expr.pos
6505 }
6506 }
6507 }
6508 }
6509 mut generic_module_args := []ast.Expr{}
6510 if expr.expr !is ast.EmptyExpr {
6511 generic_module_args << expr.expr
6512 }
6513 if generic_module_call := t.transform_generic_module_call_from_parts(expr.lhs,
6514 generic_module_args, expr.pos)
6515 {
6516 return generic_module_call
6517 }
6518 if expr.lhs is ast.GenericArgs {
6519 ga := expr.lhs as ast.GenericArgs
6520 if ga.lhs is ast.Ident {
6521 lhs := t.specialize_generic_callable_expr(ga.lhs, ga.args, ga.pos)
6522 args := if expr.expr is ast.EmptyExpr {
6523 []ast.Expr{}
6524 } else {
6525 t.transform_call_args_for_lhs(lhs, [expr.expr])
6526 }
6527 return ast.CallExpr{
6528 lhs: lhs
6529 args: args
6530 pos: expr.pos
6531 }
6532 }
6533 }
6534 if expr.lhs is ast.GenericArgOrIndexExpr {
6535 gai := expr.lhs as ast.GenericArgOrIndexExpr
6536 if gai.lhs is ast.Ident {
6537 lhs := t.specialize_generic_callable_expr(gai.lhs, [gai.expr], gai.pos)
6538 args := if expr.expr is ast.EmptyExpr {
6539 []ast.Expr{}
6540 } else {
6541 t.transform_call_args_for_lhs(lhs, [expr.expr])
6542 }
6543 return ast.CallExpr{
6544 lhs: lhs
6545 args: args
6546 pos: expr.pos
6547 }
6548 }
6549 }
6550 if expr.lhs is ast.GenericArgs {
6551 ga := expr.lhs as ast.GenericArgs
6552 if ga.lhs is ast.SelectorExpr {
6553 raw_args := if expr.expr is ast.EmptyExpr { []ast.Expr{} } else { [expr.expr] }
6554 if transformed := t.transform_generic_selector_method_call(ga.lhs as ast.SelectorExpr,
6555 ga.args, raw_args, expr.pos)
6556 {
6557 return transformed
6558 }
6559 }
6560 }
6561 if expr.lhs is ast.IndexExpr {
6562 idx := expr.lhs as ast.IndexExpr
6563 if idx.lhs is ast.SelectorExpr {
6564 raw_args := if expr.expr is ast.EmptyExpr { []ast.Expr{} } else { [expr.expr] }
6565 if transformed := t.transform_generic_selector_method_call(idx.lhs as ast.SelectorExpr, [
6566 idx.expr,
6567 ], raw_args, expr.pos)
6568 {
6569 return transformed
6570 }
6571 }
6572 }
6573 // Generic method call: w.get[T](arg) where LHS is GenericArgOrIndexExpr
6574 // wrapping a SelectorExpr. Resolve the method call and append the generic
6575 // specialization suffix so cleanc can later substitute concrete types.
6576 if expr.lhs is ast.GenericArgOrIndexExpr {
6577 gai := expr.lhs as ast.GenericArgOrIndexExpr
6578 if gai.lhs is ast.SelectorExpr {
6579 sel := gai.lhs as ast.SelectorExpr
6580 is_module_call := sel.lhs is ast.Ident && t.lookup_var_type(sel.lhs.name) == none
6581 && (t.is_module_ident(sel.lhs.name) || t.get_module_scope(sel.lhs.name) != none)
6582 if !is_module_call {
6583 suffix := '_T_' + t.generic_specialization_token(gai.expr)
6584 // When the receiver matches the current method's receiver parameter
6585 // and get_expr_type would fail (generic body), use the known prefix
6586 // directly to avoid wrong fallback to 'array__' prefix.
6587 recv_is_self := t.cur_fn_recv_param != '' && sel.lhs is ast.Ident
6588 && (sel.lhs as ast.Ident).name == t.cur_fn_recv_param && t.get_expr_type(sel.lhs) == none
6589 if recv_is_self && t.cur_fn_recv_prefix != '' {
6590 mut args2 := []ast.Expr{cap: 2}
6591 args2 << t.transform_expr(sel.lhs)
6592 if expr.expr !is ast.EmptyExpr {
6593 args2 << t.transform_expr(expr.expr)
6594 }
6595 return ast.CallExpr{
6596 lhs: ast.Ident{
6597 name: '${t.cur_fn_recv_prefix}__${sel.rhs.name}${suffix}'
6598 }
6599 args: args2
6600 pos: expr.pos
6601 }
6602 }
6603 method_name := sel.rhs.name + suffix
6604 if !t.resolves_to_embedded_method(sel.lhs, method_name) {
6605 if resolved := t.resolve_method_call_name(sel.lhs, method_name) {
6606 mut args2 := []ast.Expr{cap: 2}
6607 args2 << t.transform_expr(sel.lhs)
6608 if expr.expr !is ast.EmptyExpr {
6609 args2 << t.transform_expr(expr.expr)
6610 }
6611 return ast.CallExpr{
6612 lhs: ast.Ident{
6613 name: resolved
6614 }
6615 args: args2
6616 pos: expr.pos
6617 }
6618 }
6619 }
6620 if !t.resolves_to_embedded_method(sel.lhs, sel.rhs.name) {
6621 if resolved := t.resolve_method_call_name(sel.lhs, sel.rhs.name) {
6622 mut args2 := []ast.Expr{cap: 2}
6623 args2 << t.transform_expr(sel.lhs)
6624 if expr.expr !is ast.EmptyExpr {
6625 args2 << t.transform_expr(expr.expr)
6626 }
6627 return ast.CallExpr{
6628 lhs: ast.Ident{
6629 name: resolved + suffix
6630 }
6631 args: args2
6632 pos: expr.pos
6633 }
6634 }
6635 }
6636 }
6637 }
6638 }
6639 // Unresolved CallOrCastExpr nodes are still ordinary calls when the lhs is not a
6640 // type reference. Route them through the normal call-arg lowering pipeline so
6641 // named args / struct-shorthand args are normalized before codegen.
6642 if !t.call_or_cast_lhs_is_type(expr.lhs) {
6643 mut call_args := []ast.Expr{}
6644 if expr.expr !is ast.EmptyExpr {
6645 call_args << expr.expr
6646 }
6647 call_args = t.lower_missing_call_args(expr.lhs, call_args)
6648 default_fn_info := t.call_fn_info_for_lhs(expr.lhs)
6649 mut args := []ast.Expr{cap: call_args.len}
6650 for i, arg in call_args {
6651 args << t.transform_call_arg_with_sumtype_check(arg, default_fn_info, i)
6652 }
6653 args = t.lower_variadic_args(expr.lhs, args)
6654 mut call_lhs := t.transform_expr(expr.lhs)
6655 if expr.lhs is ast.Ident {
6656 if info := default_fn_info {
6657 if inferred := t.inferred_generic_call_name(expr.lhs.name, info, call_args) {
6658 call_lhs = ast.Expr(ast.Ident{
6659 name: inferred
6660 pos: expr.lhs.pos
6661 })
6662 }
6663 }
6664 }
6665 return ast.CallExpr{
6666 lhs: call_lhs
6667 args: args
6668 pos: expr.pos
6669 }
6670 }
6671 // Default: transform the value expression while preserving the original lhs.
6672 // For cast-like call-or-cast nodes, the lhs is a type expression rather than
6673 // a runtime value.
6674 transformed_arg := t.transform_expr(expr.expr)
6675 return t.lower_call_or_cast_expr(expr.lhs, transformed_arg, expr.pos)
6676}
6677
6678// transform_call_arg_with_sumtype_check transforms a call argument, temporarily
6679// disabling any active smartcast when the function parameter is a sumtype.
6680// This prevents smartcast from unwrapping a sumtype value that should be passed as-is.
6681// It also wraps variant values in sum type init when the parameter expects a
6682// sum type but the argument is a variant (implicit conversion).
6683fn (mut t Transformer) transform_call_arg_with_sumtype_check(arg ast.Expr, fn_info ?CallFnInfo, idx int) ast.Expr {
6684 if info := fn_info {
6685 if idx < info.param_types.len {
6686 param_c_name := t.type_to_c_name(info.param_types[idx])
6687 if param_c_name != '' && t.is_sum_type(param_c_name) {
6688 if transformed := t.transform_declared_sumtype_value(arg, param_c_name) {
6689 return transformed
6690 }
6691 arg_str := t.expr_to_string(arg)
6692 if arg_str != '' {
6693 if ctx := t.find_smartcast_for_expr(arg_str) {
6694 // Only disable smartcast if parameter expects the SAME sumtype
6695 // as the arg's original sumtype. This avoids incorrectly removing
6696 // smartcasts when the parameter type is a DIFFERENT sumtype that
6697 // happens to be the smartcast variant (e.g., ast.Expr smartcast
6698 // to ast.Type, where ast.Type is itself a sumtype).
6699 if t.is_same_sumtype_name(ctx.sumtype, param_c_name) {
6700 if existing := t.remove_smartcast_for_expr(arg_str) {
6701 result := t.transform_expr(arg)
6702 t.push_smartcast_full(existing.expr, existing.variant,
6703 existing.variant_full, existing.sumtype)
6704 return result
6705 }
6706 }
6707 }
6708 }
6709 if wrapped := t.wrap_sumtype_value(arg, param_c_name) {
6710 return wrapped
6711 }
6712 }
6713 }
6714 }
6715 return t.transform_expr(arg)
6716}
6717
6718// get_enum_type get enum type name from an expression
6719fn (t &Transformer) get_enum_type(expr ast.Expr) string {
6720 if recv_type := t.get_expr_type(expr) {
6721 base := t.unwrap_alias_and_pointer_type(recv_type)
6722 if base is types.Enum {
6723 return t.type_to_c_name(base)
6724 }
6725 }
6726 if expr is ast.Ident {
6727 if typ := t.lookup_var_type(expr.name) {
6728 base := t.unwrap_alias_and_pointer_type(typ)
6729 if base is types.Enum {
6730 return t.type_to_c_name(base)
6731 }
6732 }
6733 if typ := t.lookup_type(expr.name) {
6734 if typ is types.Enum {
6735 return t.type_to_c_name(typ)
6736 }
6737 }
6738 for _, scope in t.cached_scopes {
6739 if obj := scope.objects[expr.name] {
6740 if obj is types.Type {
6741 obj_type := obj as types.Type
6742 if obj_type is types.Enum {
6743 return t.type_to_c_name(obj_type)
6744 }
6745 }
6746 }
6747 }
6748 }
6749 // Fallback: for SelectorExpr (struct field access), resolve via struct field type
6750 if expr is ast.SelectorExpr {
6751 if field_type := t.get_struct_field_type(expr) {
6752 base := t.unwrap_alias_and_pointer_type(field_type)
6753 if base is types.Enum {
6754 return t.type_to_c_name(base)
6755 }
6756 }
6757 }
6758 return ''
6759}
6760
6761// is_flag_enum_receiver verifies that a receiver expression is actually a flag
6762// enum value before rewriting `.has/.all` into bitwise operations.
6763fn (t &Transformer) is_flag_enum_receiver(receiver ast.Expr, inferred string) bool {
6764 mut inferred_flag_enum := false
6765 if inferred != '' {
6766 if !t.is_flag_enum(inferred) {
6767 return false
6768 }
6769 inferred_flag_enum = true
6770 }
6771 // Selector receivers (obj.field) are often more reliably typed through field
6772 // lookup than position-based env types.
6773 if receiver is ast.SelectorExpr {
6774 if field_type := t.get_struct_field_type(receiver) {
6775 base := t.unwrap_alias_and_pointer_type(field_type)
6776 if base is types.Enum {
6777 return base.is_flag
6778 }
6779 return false
6780 }
6781 }
6782 if recv_type := t.get_expr_type(receiver) {
6783 base := t.unwrap_alias_and_pointer_type(recv_type)
6784 if base is types.Enum {
6785 return base.is_flag
6786 }
6787 // Type information says this is not an enum (e.g. []Attribute.has()).
6788 return false
6789 }
6790 // If type lookup is missing, keep behavior only when enum inference succeeded.
6791 return inferred_flag_enum
6792}
6793
6794// is_cast_type_name checks if a name is a type name that appears in casts (not a function)
6795fn is_builtin_primitive_cast_name(name string) bool {
6796 if name in ['int', 'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64', 'f32', 'f64', 'bool',
6797 'byte', 'char', 'rune', 'usize', 'isize', 'string', 'byteptr', 'charptr', 'voidptr'] {
6798 return true
6799 }
6800 return false
6801}
6802
6803// is_cast_type_name checks if a name is a type name that appears in casts (not a function)
6804fn (t &Transformer) is_cast_type_name(name string) bool {
6805 if is_builtin_primitive_cast_name(name) {
6806 return true
6807 }
6808 // Type names start with uppercase in V
6809 if name.len > 0 && name[0] >= `A` && name[0] <= `Z` {
6810 return true
6811 }
6812 return false
6813}
6814
6815fn is_c_type_name_for_cast(name string) bool {
6816 // Keep in sync with cleanc `is_c_type_name`.
6817 // This list is only used to disambiguate `C.TYPE(x)` casts from `C.fn(x)` calls
6818 // in `CallOrCastExpr` lowering.
6819 if name in ['FILE', 'DIR', 'va_list', 'pthread_t', 'pthread_mutex_t', 'pthread_cond_t',
6820 'pthread_rwlock_t', 'pthread_attr_t', 'stat', 'tm', 'timespec', 'timeval', 'dirent',
6821 'termios', 'sockaddr', 'sockaddr_in', 'sockaddr_in6', 'sockaddr_un',
6822 'mach_timebase_info_data_t'] {
6823 return true
6824 }
6825 // Module-qualified C type names (e.g. C.log__Logger) are casts, not calls.
6826 if name.contains('__') {
6827 after := name.all_after_last('__')
6828 if after.len > 0 && after[0] >= `A` && after[0] <= `Z` {
6829 return true
6830 }
6831 }
6832 return false
6833}
6834
6835fn (t &Transformer) call_or_cast_lhs_is_type(lhs ast.Expr) bool {
6836 match lhs {
6837 ast.Type {
6838 return true
6839 }
6840 ast.GenericArgs {
6841 return t.call_or_cast_lhs_is_type(lhs.lhs)
6842 }
6843 ast.GenericArgOrIndexExpr {
6844 return t.call_or_cast_lhs_is_type(lhs.lhs)
6845 }
6846 ast.Ident {
6847 // Built-in primitive types are always casts, never function calls.
6848 if is_builtin_primitive_cast_name(lhs.name) {
6849 return true
6850 }
6851 // Prefer functions when names clash (even if uppercase).
6852 if _ := t.get_fn_return_type(lhs.name) {
6853 return false
6854 }
6855 if _ := t.lookup_type(lhs.name) {
6856 return true
6857 }
6858 return t.is_cast_type_name(lhs.name)
6859 }
6860 ast.SelectorExpr {
6861 if lhs.lhs is ast.Ident {
6862 mod := (lhs.lhs as ast.Ident).name
6863 typ_name := lhs.rhs.name
6864 if mod == 'C' {
6865 return is_c_type_name_for_cast(typ_name)
6866 }
6867 qualified := '${mod}__${typ_name}'
6868 if _ := t.get_fn_return_type(qualified) {
6869 return false
6870 }
6871 if resolved_mod := t.resolve_module_name(mod) {
6872 resolved_qualified := '${resolved_mod.replace('.', '__')}__${typ_name}'
6873 if _ := t.get_fn_return_type(resolved_qualified) {
6874 return false
6875 }
6876 }
6877 return t.lookup_type(qualified) != none
6878 }
6879 return false
6880 }
6881 ast.ParenExpr {
6882 return t.call_or_cast_lhs_is_type(lhs.expr)
6883 }
6884 ast.ModifierExpr {
6885 return t.call_or_cast_lhs_is_type(lhs.expr)
6886 }
6887 ast.PrefixExpr {
6888 return t.call_or_cast_lhs_is_type(lhs.expr)
6889 }
6890 else {
6891 return false
6892 }
6893 }
6894}
6895
6896fn (t &Transformer) call_or_cast_lhs_is_type_cursor(lhs ast.Cursor) bool {
6897 if !lhs.is_valid() {
6898 return false
6899 }
6900 match lhs.kind() {
6901 .typ_anon_struct, .typ_array_fixed, .typ_array, .typ_channel, .typ_fn, .typ_generic,
6902 .typ_map, .typ_nil, .typ_none, .typ_option, .typ_pointer, .typ_result, .typ_thread,
6903 .typ_tuple {
6904 return true
6905 }
6906 .expr_ident {
6907 name := lhs.name()
6908 if is_builtin_primitive_cast_name(name) {
6909 return true
6910 }
6911 if _ := t.get_fn_return_type(name) {
6912 return false
6913 }
6914 if _ := t.lookup_type(name) {
6915 return true
6916 }
6917 return t.is_cast_type_name(name)
6918 }
6919 .expr_selector {
6920 base := lhs.edge(0)
6921 if base.kind() != .expr_ident {
6922 return false
6923 }
6924 mod := base.name()
6925 typ_name := selector_rhs_name_cursor(lhs)
6926 if typ_name == '' {
6927 return false
6928 }
6929 if mod == 'C' {
6930 return is_c_type_name_for_cast(typ_name)
6931 }
6932 qualified := '${mod}__${typ_name}'
6933 if _ := t.get_fn_return_type(qualified) {
6934 return false
6935 }
6936 if resolved_mod := t.resolve_module_name(mod) {
6937 resolved_qualified := '${resolved_mod.replace('.', '__')}__${typ_name}'
6938 if _ := t.get_fn_return_type(resolved_qualified) {
6939 return false
6940 }
6941 }
6942 return t.lookup_type(qualified) != none
6943 }
6944 .expr_paren, .expr_modifier, .expr_prefix {
6945 return t.call_or_cast_lhs_is_type_cursor(lhs.edge(0))
6946 }
6947 else {
6948 return false
6949 }
6950 }
6951}
6952
6953fn (t &Transformer) lower_call_or_cast_expr(lhs ast.Expr, arg ast.Expr, pos token.Pos) ast.Expr {
6954 // `CallOrCastExpr` with no argument is always a call (`foo()`), never a cast.
6955 if arg is ast.EmptyExpr {
6956 return ast.CallExpr{
6957 lhs: lhs
6958 args: []ast.Expr{}
6959 pos: pos
6960 }
6961 }
6962 if t.call_or_cast_lhs_is_type(lhs) {
6963 return ast.CastExpr{
6964 typ: lhs
6965 expr: arg
6966 pos: pos
6967 }
6968 }
6969 return ast.CallExpr{
6970 lhs: lhs
6971 args: [arg]
6972 pos: pos
6973 }
6974}
6975
6976// get_call_return_type returns the return type of a function call
6977fn (t &Transformer) get_call_return_type(expr ast.Expr) string {
6978 // Prefer checker-computed expression type when available.
6979 // This is the most reliable source for local/private function returns.
6980 if expr_type := t.resolve_expr_type(expr) {
6981 c_name := t.type_to_c_name(expr_type)
6982 if c_name != '' && c_name != 'void' {
6983 return c_name
6984 }
6985 name := expr_type.name()
6986 if name != '' {
6987 return name
6988 }
6989 }
6990 if ret_type := t.fn_pointer_call_return_type(expr) {
6991 c_name := t.type_to_c_name(ret_type)
6992 if c_name != '' && c_name != 'void' {
6993 return c_name
6994 }
6995 name := ret_type.name()
6996 if name != '' {
6997 return name
6998 }
6999 }
7000
7001 mut fn_name := ''
7002 mut is_method := false
7003 mut is_module_fn := false
7004 mut mod_name := ''
7005 mut selector_expr := ast.SelectorExpr{}
7006 if expr is ast.CallExpr {
7007 if expr.lhs is ast.Ident {
7008 fn_name = expr.lhs.name
7009 } else if expr.lhs is ast.SelectorExpr {
7010 // Method call or module.function call
7011 selector_expr = expr.lhs as ast.SelectorExpr
7012 fn_name = selector_expr.rhs.name
7013 // Check if LHS is a module name (starts with lowercase and no field access)
7014 if selector_expr.lhs is ast.Ident {
7015 lhs_name := (selector_expr.lhs as ast.Ident).name
7016 // Check if it's a module by looking it up
7017 if t.get_module_scope(lhs_name) != none {
7018 is_module_fn = true
7019 mod_name = lhs_name
7020 } else {
7021 is_method = true
7022 }
7023 } else {
7024 is_method = true
7025 }
7026 }
7027 } else if expr is ast.CallOrCastExpr {
7028 if expr.lhs is ast.Ident {
7029 fn_name = expr.lhs.name
7030 } else if expr.lhs is ast.SelectorExpr {
7031 selector_expr = expr.lhs as ast.SelectorExpr
7032 fn_name = selector_expr.rhs.name
7033 // Check if LHS is a module name
7034 if selector_expr.lhs is ast.Ident {
7035 lhs_name := (selector_expr.lhs as ast.Ident).name
7036 if t.get_module_scope(lhs_name) != none {
7037 is_module_fn = true
7038 mod_name = lhs_name
7039 } else {
7040 is_method = true
7041 }
7042 } else {
7043 is_method = true
7044 }
7045 }
7046 }
7047 if fn_name != '' {
7048 // Check function return type using scope lookup
7049 if is_module_fn && mod_name != '' {
7050 // Look up function in the specific module's scope
7051 if mut mod_scope := t.get_module_scope(mod_name) {
7052 if obj := mod_scope.lookup_parent(fn_name, 0) {
7053 if obj is types.Fn {
7054 fn_typ := obj.get_typ()
7055 if fn_typ is types.FnType {
7056 if ret := fn_typ.get_return_type() {
7057 return ret.name()
7058 }
7059 }
7060 }
7061 }
7062 }
7063 } else {
7064 // Look up in current module
7065 if ret_type := t.get_fn_return_type(fn_name) {
7066 return ret_type.name()
7067 }
7068 }
7069 // For method calls, try to look up the method's return type from the receiver
7070 if is_method && selector_expr.lhs !is ast.EmptyExpr {
7071 // Get receiver type and look up method
7072 if recv_type := t.get_expr_type(selector_expr.lhs) {
7073 // Get the base type name for method lookup
7074 base_type := if recv_type is types.Pointer { recv_type.base_type } else { recv_type }
7075 type_name := base_type.name()
7076 // Look up method using environment
7077 if fn_typ := t.lookup_method_cached(type_name, fn_name) {
7078 if ret := fn_typ.get_return_type() {
7079 return ret.name()
7080 }
7081 }
7082 }
7083 }
7084 }
7085 return ''
7086}
7087
7088// --- Generic math function inlining for native backends ---
7089
7090// try_inline_generic_math_call handles CallExpr (multi-arg calls):
7091// - min(a, b) → if a < b { a } else { b }
7092// - max(a, b) → if a > b { a } else { b }
7093// - maxof[T]() / minof[T]() → numeric constant
7094fn (mut t Transformer) try_inline_generic_math_call(expr ast.CallExpr) ?ast.Expr {
7095 // Handle maxof[T]() and minof[T]() - these have GenericArgs lhs
7096 if expr.lhs is ast.GenericArgs && expr.args.len == 0 {
7097 ga := expr.lhs as ast.GenericArgs
7098 if ga.lhs is ast.Ident && ga.args.len == 1 {
7099 fn_name := (ga.lhs as ast.Ident).name
7100 type_name := ga.args[0].name()
7101 if fn_name == 'maxof' {
7102 if val := t.maxof_constant(type_name) {
7103 return val
7104 }
7105 } else if fn_name == 'minof' {
7106 if val := t.minof_constant(type_name) {
7107 return val
7108 }
7109 }
7110 }
7111 }
7112 // Handle maxof[T]() and minof[T]() - parser may produce GenericArgOrIndexExpr for single arg
7113 if expr.lhs is ast.GenericArgOrIndexExpr && expr.args.len == 0 {
7114 gaoi := expr.lhs as ast.GenericArgOrIndexExpr
7115 if gaoi.lhs is ast.Ident {
7116 fn_name := (gaoi.lhs as ast.Ident).name
7117 type_name := gaoi.expr.name()
7118 if fn_name == 'maxof' {
7119 if val := t.maxof_constant(type_name) {
7120 return val
7121 }
7122 } else if fn_name == 'minof' {
7123 if val := t.minof_constant(type_name) {
7124 return val
7125 }
7126 }
7127 }
7128 }
7129 // Handle abs(x) - single-arg call (may be CallExpr within math module)
7130 if expr.lhs is ast.Ident && expr.args.len == 1 {
7131 name := (expr.lhs as ast.Ident).name
7132 if name == 'abs' {
7133 arg := t.transform_expr(expr.args[0])
7134 pos := t.inline_generic_math_result_pos(expr.pos, expr.args[0])
7135 return t.make_inline_abs_expr(arg, pos)
7136 }
7137 }
7138 // Handle math.abs(x) - module-qualified single-arg call
7139 if expr.lhs is ast.SelectorExpr && expr.args.len == 1 {
7140 sel := expr.lhs as ast.SelectorExpr
7141 if sel.lhs is ast.Ident && (sel.lhs as ast.Ident).name == 'math' && sel.rhs.name == 'abs' {
7142 arg := t.transform_expr(expr.args[0])
7143 pos := t.inline_generic_math_result_pos(expr.pos, expr.args[0])
7144 return t.make_inline_abs_expr(arg, pos)
7145 }
7146 }
7147 // Handle min(a, b) and max(a, b) - bare ident calls within math module
7148 if expr.lhs is ast.Ident && expr.args.len == 2 {
7149 name := (expr.lhs as ast.Ident).name
7150 if name == 'min' || name == 'max' {
7151 a := t.transform_expr(expr.args[0])
7152 b := t.transform_expr(expr.args[1])
7153 op := if name == 'min' { token.Token.lt } else { token.Token.gt }
7154 return t.make_inline_if_expr(a, b, op)
7155 }
7156 }
7157 // Handle math.min(a, b) and math.max(a, b) - module-qualified calls
7158 if expr.lhs is ast.SelectorExpr && expr.args.len == 2 {
7159 sel := expr.lhs as ast.SelectorExpr
7160 if sel.lhs is ast.Ident && (sel.lhs as ast.Ident).name == 'math' {
7161 name := sel.rhs.name
7162 if name == 'min' || name == 'max' {
7163 a := t.transform_expr(expr.args[0])
7164 b := t.transform_expr(expr.args[1])
7165 op := if name == 'min' { token.Token.lt } else { token.Token.gt }
7166 return t.make_inline_if_expr(a, b, op)
7167 }
7168 }
7169 }
7170 // Handle already-resolved maxof_T() and minof_T() names (0-arg calls)
7171 if expr.lhs is ast.Ident && expr.args.len == 0 {
7172 name := (expr.lhs as ast.Ident).name
7173 if name.starts_with('maxof_') {
7174 type_name := name[6..]
7175 if val := t.maxof_constant(type_name) {
7176 return val
7177 }
7178 } else if name.starts_with('minof_') {
7179 type_name := name[6..]
7180 if val := t.minof_constant(type_name) {
7181 return val
7182 }
7183 }
7184 }
7185 return none
7186}
7187
7188// try_inline_generic_math_coce handles CallOrCastExpr (single-arg calls):
7189// - abs(x) → if x < 0 { -x } else { x }
7190fn (mut t Transformer) try_inline_generic_math_coce(expr ast.CallOrCastExpr) ?ast.Expr {
7191 // Handle maxof[T]() / minof[T]() when parsed as CallOrCastExpr
7192 if expr.lhs is ast.GenericArgOrIndexExpr && expr.expr is ast.EmptyExpr {
7193 gaoi := expr.lhs as ast.GenericArgOrIndexExpr
7194 if gaoi.lhs is ast.Ident {
7195 fn_name := (gaoi.lhs as ast.Ident).name
7196 type_name := gaoi.expr.name()
7197 if fn_name == 'maxof' {
7198 if val := t.maxof_constant(type_name) {
7199 return val
7200 }
7201 } else if fn_name == 'minof' {
7202 if val := t.minof_constant(type_name) {
7203 return val
7204 }
7205 }
7206 }
7207 }
7208 if expr.lhs is ast.GenericArgs && expr.expr is ast.EmptyExpr {
7209 ga := expr.lhs as ast.GenericArgs
7210 if ga.lhs is ast.Ident && ga.args.len == 1 {
7211 fn_name := (ga.lhs as ast.Ident).name
7212 type_name := ga.args[0].name()
7213 if fn_name == 'maxof' {
7214 if val := t.maxof_constant(type_name) {
7215 return val
7216 }
7217 } else if fn_name == 'minof' {
7218 if val := t.minof_constant(type_name) {
7219 return val
7220 }
7221 }
7222 }
7223 }
7224 if expr.lhs is ast.Ident {
7225 name := (expr.lhs as ast.Ident).name
7226 if name == 'abs' && expr.expr !is ast.EmptyExpr {
7227 arg := t.transform_expr(expr.expr)
7228 pos := t.inline_generic_math_result_pos(expr.pos, expr.expr)
7229 return t.make_inline_abs_expr(arg, pos)
7230 }
7231 }
7232 // Handle math.abs(x)
7233 if expr.lhs is ast.SelectorExpr {
7234 sel := expr.lhs as ast.SelectorExpr
7235 if sel.lhs is ast.Ident
7236 && (sel.lhs as ast.Ident).name == 'math' && sel.rhs.name == 'abs' && expr.expr !is ast.EmptyExpr {
7237 arg := t.transform_expr(expr.expr)
7238 pos := t.inline_generic_math_result_pos(expr.pos, expr.expr)
7239 return t.make_inline_abs_expr(arg, pos)
7240 }
7241 }
7242 return none
7243}
7244
7245// make_inline_if_expr creates: if a OP b { a } else { b }
7246fn (mut t Transformer) make_inline_if_expr(a ast.Expr, b ast.Expr, op token.Token) ast.Expr {
7247 return ast.IfExpr{
7248 cond: t.make_infix_expr(op, a, b)
7249 stmts: [
7250 ast.Stmt(ast.ExprStmt{
7251 expr: a
7252 }),
7253 ]
7254 else_expr: ast.IfExpr{
7255 stmts: [
7256 ast.Stmt(ast.ExprStmt{
7257 expr: b
7258 }),
7259 ]
7260 }
7261 }
7262}
7263
7264fn (t &Transformer) maxof_constant(type_name string) ?ast.Expr {
7265 val := match type_name {
7266 'i8' { '127' }
7267 'i16' { '32767' }
7268 'i32' { '2147483647' }
7269 'int' { '2147483647' }
7270 'i64' { '9223372036854775807' }
7271 'u8' { '255' }
7272 'byte' { '255' }
7273 'u16' { '65535' }
7274 'u32' { '4294967295' }
7275 'u64' { '18446744073709551615' }
7276 'f32' { '3.40282346638528859811704183484516925440e+38' }
7277 'f64' { '1.797693134862315708145274237317043567981e+308' }
7278 else { return none }
7279 }
7280
7281 return ast.Expr(ast.BasicLiteral{
7282 kind: .number
7283 value: val
7284 })
7285}
7286
7287fn (mut t Transformer) minof_constant(type_name string) ?ast.Expr {
7288 // For i64 min, use subtraction expression to avoid C literal overflow:
7289 // -9223372036854775807 - 1 (same pattern as C's LLONG_MIN definition)
7290 if type_name == 'i64' {
7291 return t.make_infix_expr(.minus, ast.Expr(ast.PrefixExpr{
7292 op: .minus
7293 expr: ast.BasicLiteral{
7294 kind: .number
7295 value: '9223372036854775807'
7296 }
7297 }), t.make_number_expr('1'))
7298 }
7299 val := match type_name {
7300 'i8' { '-128' }
7301 'i16' { '-32768' }
7302 'i32' { '-2147483648' }
7303 'int' { '-2147483648' }
7304 'u8' { '0' }
7305 'byte' { '0' }
7306 'u16' { '0' }
7307 'u32' { '0' }
7308 'u64' { '0' }
7309 'f32' { '-3.40282346638528859811704183484516925440e+38' }
7310 'f64' { '-1.797693134862315708145274237317043567981e+308' }
7311 else { return none }
7312 }
7313
7314 return ast.Expr(ast.BasicLiteral{
7315 kind: .number
7316 value: val
7317 })
7318}
7319