v / vlib / v2 / gen / cleanc / expr.v
7293 lines · 7116 sloc · 215.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.
4
5module cleanc
6
7import v2.ast
8import v2.pref as vpref
9import v2.token
10import v2.types
11import strings
12import os
13
14fn is_empty_expr(e ast.Expr) bool {
15 return e is ast.EmptyExpr
16}
17
18fn is_none_expr(expr ast.Expr) bool {
19 match expr {
20 ast.Keyword {
21 return expr.tok == .key_none
22 }
23 ast.Ident {
24 return expr.name == 'none'
25 }
26 else {
27 return false
28 }
29 }
30}
31
32fn is_none_like_expr(expr ast.Expr) bool {
33 if is_none_expr(expr) {
34 return true
35 }
36 if expr is ast.Type && expr is ast.NoneType {
37 return true
38 }
39 return false
40}
41
42fn is_numeric_literal(expr ast.Expr) bool {
43 if expr is ast.BasicLiteral {
44 return expr.kind == .number
45 }
46 if expr is ast.CastExpr {
47 return is_numeric_literal(expr.expr)
48 }
49 if expr is ast.CallOrCastExpr {
50 return is_numeric_literal(expr.expr)
51 }
52 if expr is ast.ParenExpr {
53 return is_numeric_literal(expr.expr)
54 }
55 return false
56}
57
58fn is_nil_like_expr(expr ast.Expr) bool {
59 match expr {
60 ast.Ident {
61 return expr.name == 'nil'
62 }
63 ast.BasicLiteral {
64 return expr.kind == .number && expr.value == '0'
65 }
66 ast.ParenExpr {
67 return is_nil_like_expr(expr.expr)
68 }
69 ast.ModifierExpr {
70 return is_nil_like_expr(expr.expr)
71 }
72 ast.CastExpr {
73 return is_nil_like_expr(expr.expr)
74 }
75 ast.CallOrCastExpr {
76 // CallOrCastExpr can be a type cast wrapping 0
77 return is_nil_like_expr(expr.expr)
78 }
79 ast.Type {
80 return expr is ast.NilType
81 }
82 ast.UnsafeExpr {
83 if expr.stmts.len == 0 {
84 return false
85 }
86 last := expr.stmts[expr.stmts.len - 1]
87 if last is ast.ExprStmt {
88 return is_nil_like_expr(last.expr)
89 }
90 return false
91 }
92 else {
93 return false
94 }
95 }
96}
97
98fn (g &Gen) interface_method_by_name(iface_name string, method_name string) ?InterfaceMethodInfo {
99 mut candidates := []string{}
100 if iface_name != '' {
101 candidates << iface_name
102 short_name := iface_name.all_after_last('__')
103 if short_name != iface_name {
104 candidates << short_name
105 }
106 if g.cur_module != '' && g.cur_module != 'main' {
107 candidates << '${g.cur_module}__${short_name}'
108 }
109 candidates << 'builtin__${short_name}'
110 }
111 for candidate in candidates {
112 if methods := g.interface_methods[candidate] {
113 for method in methods {
114 if method.name == method_name {
115 return method
116 }
117 }
118 }
119 }
120 return none
121}
122
123// find_method_on_any_interface searches all registered interfaces for a method by name.
124// Used for interface-to-interface narrowing where the method is on the target interface.
125fn (g &Gen) find_method_on_any_interface(method_name string) ?InterfaceMethodInfo {
126 for _, methods in g.interface_methods {
127 for method in methods {
128 if method.name == method_name {
129 return method
130 }
131 }
132 }
133 return none
134}
135
136fn (g &Gen) is_interface_type(type_name string) bool {
137 // Array/Map/option/result types are never interfaces, even if their element type is.
138 if type_name.starts_with('Array_') || type_name.starts_with('Map_')
139 || type_name.starts_with('_option_') || type_name.starts_with('_result_') {
140 return false
141 }
142 mut interface_candidates := []string{}
143 if type_name != '' {
144 interface_candidates << type_name
145 short_name := type_name.all_after_last('__')
146 if short_name != type_name {
147 interface_candidates << short_name
148 }
149 if g.cur_module != '' && g.cur_module != 'main' {
150 interface_candidates << '${g.cur_module}__${short_name}'
151 }
152 interface_candidates << 'builtin__${short_name}'
153 }
154 if g.env == unsafe { nil } {
155 for candidate in interface_candidates {
156 if candidate in g.interface_methods {
157 return true
158 }
159 }
160 return false
161 }
162 // Try the type name as-is in the current module scope
163 if mut scope := g.env_scope(g.cur_module) {
164 if obj := scope.lookup_parent(type_name, 0) {
165 if obj is types.Type && obj is types.Interface {
166 return true
167 }
168 }
169 // Also try stripping module prefix: rand__PRNG -> PRNG
170 if type_name.contains('__') {
171 short_name := type_name.all_after_last('__')
172 if obj := scope.lookup_parent(short_name, 0) {
173 if obj is types.Type && obj is types.Interface {
174 return true
175 }
176 }
177 }
178 }
179 // Also check interface_methods registry.
180 for candidate in interface_candidates {
181 if candidate in g.interface_methods {
182 return true
183 }
184 }
185 return false
186}
187
188// is_interface_vtable_method checks if a method is a vtable (abstract) method
189// of an interface, as opposed to a concrete method defined on the interface type.
190fn (g &Gen) is_interface_vtable_method(iface_name string, method_name string) bool {
191 // Every interface value carries a synthetic type_name(void*) callback in its
192 // runtime header, even when not declared in the interface method set.
193 if method_name == 'type_name' {
194 return true
195 }
196 return g.interface_method_by_name(iface_name, method_name) != none
197}
198
199fn (mut g Gen) selector_method_value_name(node ast.SelectorExpr) ?string {
200 if raw := g.get_raw_type(ast.Expr(node)) {
201 mut is_fn_value := false
202 match raw {
203 types.FnType {
204 is_fn_value = true
205 }
206 types.Alias {
207 alias_raw := raw
208 if alias_raw.base_type is types.FnType {
209 is_fn_value = true
210 }
211 }
212 else {}
213 }
214
215 _ = is_fn_value
216 }
217 recv_type := g.get_expr_type(node.lhs)
218 if recv_type == '' {
219 return none
220 }
221 mut base := recv_type
222 if base.ends_with('*') {
223 base = base[..base.len - 1]
224 }
225 mut candidates := []string{}
226 candidates << '${base}__${node.rhs.name}'
227 if base.contains('__') {
228 candidates << '${base.all_after_last('__')}__${node.rhs.name}'
229 }
230 for candidate in candidates {
231 if candidate in g.fn_return_types || candidate in g.fn_param_is_ptr {
232 return candidate
233 }
234 }
235 return none
236}
237
238fn (mut g Gen) gen_bound_method_value_expr(node ast.SelectorExpr, expected_c_type string) bool {
239 method_name := g.selector_method_value_name(node) or { return false }
240 method_params := g.fn_param_types[method_name] or { return false }
241 if method_params.len == 0 {
242 return false
243 }
244 receiver_type := method_params[0]
245 if receiver_type == '' {
246 return false
247 }
248 lhs_type := g.get_expr_type(node.lhs)
249 if lhs_type == '' {
250 return false
251 }
252 lhs_base := lhs_type.trim_right('*')
253 recv_base := receiver_type.trim_right('*')
254 // Only bind true method values where LHS matches receiver.
255 if lhs_base != recv_base && short_type_name(lhs_base) != short_type_name(recv_base) {
256 return false
257 }
258 g.mark_called_fn_name(method_name)
259 callback_param_types := method_params[1..]
260 mut ret_type := g.fn_return_types[method_name] or { 'void' }
261 if ret_type == '' {
262 ret_type = 'void'
263 }
264 bind_id := g.tmp_counter
265 g.tmp_counter++
266 recv_store := '_bound_recv_${bind_id}'
267 wrapper_name := '_bound_method_${bind_id}'
268 mut def := strings.new_builder(256)
269 def.writeln('static ${receiver_type} ${recv_store};')
270 def.write_string('static ${ret_type} ${wrapper_name}(')
271 if callback_param_types.len == 0 {
272 def.write_string('void')
273 } else {
274 for i, ptyp in callback_param_types {
275 if i > 0 {
276 def.write_string(', ')
277 }
278 def.write_string('${ptyp} _arg${i}')
279 }
280 }
281 def.writeln(') {')
282 def.write_string('\t')
283 if ret_type != 'void' {
284 def.write_string('return ')
285 }
286 def.write_string('${method_name}(${recv_store}')
287 for i in 0 .. callback_param_types.len {
288 def.write_string(', _arg${i}')
289 }
290 def.writeln(');')
291 def.writeln('}')
292 def.writeln('')
293 g.anon_fn_defs << def.str()
294 g.sb.write_string('(({ ${recv_store} = ')
295 if receiver_type.ends_with('*') {
296 if lhs_type.ends_with('*') {
297 g.expr(node.lhs)
298 } else {
299 g.sb.write_u8(`&`)
300 g.expr(node.lhs)
301 }
302 } else {
303 if lhs_type.ends_with('*') {
304 g.sb.write_string('(*')
305 g.expr(node.lhs)
306 g.sb.write_string(')')
307 } else {
308 g.expr(node.lhs)
309 }
310 }
311 g.sb.write_string('; ((${expected_c_type})${wrapper_name}); }))')
312 return true
313}
314
315fn (mut g Gen) gen_sum_variant_tag_path_check(expr ast.Expr, path []SumVariantTagStep, positive bool) {
316 if path.len == 0 {
317 g.sb.write_string(if positive { 'false' } else { 'true' })
318 return
319 }
320 if !positive {
321 g.sb.write_string('!')
322 }
323 g.sb.write_string('(')
324 mut access := g.expr_to_string(expr)
325 mut sep := if g.expr_is_pointer(expr) { '->' } else { '.' }
326 for i, step in path {
327 if i > 0 {
328 g.sb.write_string(' && ')
329 }
330 g.sb.write_string('(${access}${sep}_tag == ${step.tag})')
331 access = '(((${step.payload_type}*)(${access}${sep}_data._${step.variant_field})))'
332 sep = '->'
333 }
334 g.sb.write_string(')')
335}
336
337fn (mut g Gen) gen_nested_sum_as_cast(inner_str string, expr ast.Expr, target_type string, path []SumVariantTagStep) bool {
338 if path.len <= 1 {
339 return false
340 }
341 mut access := '(${inner_str})'
342 mut sep := if g.expr_is_pointer(expr) { '->' } else { '.' }
343 for i, step in path {
344 payload_ptr := '(${access}${sep}_data._${step.variant_field})'
345 if i == path.len - 1 {
346 if !g.is_scalar_sum_payload_type(target_type) && target_type != 'string' {
347 g.sb.write_string('(${payload_ptr} ? (*((${target_type}*)${payload_ptr})) : (${target_type}){0})')
348 } else {
349 g.sb.write_string('(*((${target_type}*)${payload_ptr}))')
350 }
351 return true
352 }
353 access = '(*(${step.payload_type}*)${payload_ptr})'
354 sep = '.'
355 }
356 return false
357}
358
359fn (mut g Gen) gen_nested_sum_as_cast_for_selector_source(expr ast.Expr, target_type_name string) bool {
360 unwrapped := g.unwrap_parens(expr)
361 if unwrapped !is ast.SelectorExpr {
362 return false
363 }
364 sel := unwrapped as ast.SelectorExpr
365 mut source_types := []string{}
366 for candidate in [
367 g.selector_storage_field_type(sel).trim_right('*'),
368 g.selector_declared_field_type(sel).trim_right('*'),
369 g.selector_field_type(sel).trim_right('*'),
370 g.get_expr_type(sel).trim_right('*'),
371 ] {
372 if candidate != '' && candidate !in source_types
373 && g.get_sum_type_variants_for(candidate).len > 0 {
374 source_types << candidate
375 }
376 }
377 for source_type in source_types {
378 if g.sum_type_has_direct_variant(source_type, target_type_name) {
379 return false
380 }
381 path := g.sum_variant_tag_path(source_type, target_type_name, []string{}) or { continue }
382 if path.len <= 1 {
383 continue
384 }
385 nested_target_type := path[path.len - 1].payload_type
386 if g.gen_nested_sum_as_cast(g.raw_selector_expr_code(sel), sel, nested_target_type, path) {
387 return true
388 }
389 }
390 return false
391}
392
393fn (mut g Gen) nested_sum_path_from_narrowed_source(source_sum_type string, target_type string) ?[]SumVariantTagStep {
394 if source_sum_type == '' || target_type == '' {
395 return none
396 }
397 for sum_name, _ in g.sum_type_variants {
398 path := g.sum_variant_tag_path(sum_name, target_type, []string{}) or { continue }
399 if path.len <= 1 {
400 continue
401 }
402 first := path[0]
403 if first.payload_type == source_sum_type || first.variant_field == source_sum_type
404 || first.payload_type.all_after_last('__') == source_sum_type.all_after_last('__') {
405 return path
406 }
407 }
408 return none
409}
410
411fn vector_field_index(field string) int {
412 return match field {
413 'x' { 0 }
414 'y' { 1 }
415 'z' { 2 }
416 'w' { 3 }
417 else { -1 }
418 }
419}
420
421fn vector_elem_type_for_name(type_name string) string {
422 base := type_name.trim_right('*')
423 if base.ends_with('SimdFloat4') || base.ends_with('SimdFloat2') {
424 return 'f32'
425 }
426 if base.ends_with('SimdInt4') || base.ends_with('SimdI32_2') {
427 return 'i32'
428 }
429 if base.ends_with('SimdU32_4') || base.ends_with('SimdUint2') {
430 return 'u32'
431 }
432 return ''
433}
434
435fn (mut g Gen) interface_target_cast_inner_expr(type_name string, value_expr ast.Expr) ?ast.Expr {
436 if value_expr is ast.CastExpr
437 && g.expr_type_to_c(value_expr.typ) in [type_name, 'builtin__${type_name}'] {
438 return value_expr.expr
439 }
440 if value_expr is ast.CallOrCastExpr && g.call_or_cast_lhs_is_type(value_expr.lhs)
441 && g.expr_type_to_c(value_expr.lhs) in [type_name, 'builtin__${type_name}'] {
442 return value_expr.expr
443 }
444 if value_expr is ast.ParenExpr {
445 return value_expr.expr
446 }
447 return none
448}
449
450fn (mut g Gen) raw_concrete_type_for_interface_value(type_name string, value_expr ast.Expr) string {
451 if inner := g.interface_target_cast_inner_expr(type_name, value_expr) {
452 return g.raw_concrete_type_for_interface_value(type_name, inner)
453 }
454 mut concrete_type := g.get_expr_type(value_expr)
455 if value_expr is ast.PrefixExpr && value_expr.op == .amp && value_expr.expr is ast.Ident {
456 inner := value_expr.expr as ast.Ident
457 if local_type := g.get_local_var_c_type(inner.name) {
458 local_base := local_type.trim_right('*')
459 if local_type != '' && local_type != 'int' && !g.is_interface_type(local_base) {
460 concrete_type = '${local_type}*'
461 }
462 }
463 }
464 if value_expr is ast.Ident {
465 if local_type := g.get_local_var_c_type(value_expr.name) {
466 local_base := local_type.trim_right('*')
467 current_base := concrete_type.trim_right('*')
468 if local_type != '' && local_type != 'int' && !g.is_interface_type(local_base)
469 && (concrete_type == '' || concrete_type == 'int'
470 || current_base == type_name || local_type.contains('_T_')) {
471 concrete_type = local_type
472 }
473 }
474 }
475 mut raw_concrete := ''
476 if raw := g.get_raw_type(value_expr) {
477 raw_concrete = g.types_type_to_c(raw)
478 }
479 raw_base := raw_concrete.trim_right('*')
480 current_base := concrete_type.trim_right('*')
481 if raw_base != '' && raw_base != 'int'
482 && (current_base == '' || current_base == 'int' || current_base == type_name) {
483 concrete_type = raw_concrete
484 }
485 // For dereference expressions (*ptr), strip one pointer level from the type.
486 // E.g., `*sw` where sw is `SubWindow*` → concrete type is `SubWindow`, not `SubWindow*`.
487 if value_expr is ast.PrefixExpr && value_expr.op == .mul && concrete_type.ends_with('*') {
488 concrete_type = concrete_type[..concrete_type.len - 1]
489 }
490 return concrete_type
491}
492
493fn (mut g Gen) ierror_concrete_base_for_expr(value_expr ast.Expr) string {
494 return g.qualify_ierror_concrete_base(g.raw_concrete_type_for_interface_value('IError',
495 value_expr).trim_right('*'))
496}
497
498// build_ierror_base_index indexes fn_return_types by the `base` of every `X__base__msg` function,
499// storing the smallest matching `X__base` per base (the original scan sorted the keys and took the
500// first). fn_return_types is final after collect_fn_signatures_to_fixed_point, so this builds once.
501fn (mut g Gen) build_ierror_base_index() {
502 mut idx := map[string]string{}
503 for fn_name, _ in g.fn_return_types {
504 if !fn_name.ends_with('__msg') {
505 continue
506 }
507 stripped := fn_name[..fn_name.len - '__msg'.len] // X__base
508 if !stripped.contains('__') {
509 continue // need a real `__base` suffix, matching ends_with('__${base}__msg')
510 }
511 base := stripped.all_after_last('__')
512 if base == '' {
513 continue
514 }
515 if existing := idx[base] {
516 if stripped < existing {
517 idx[base] = stripped
518 }
519 } else {
520 idx[base] = stripped
521 }
522 }
523 g.ierror_base_index = idx.move()
524}
525
526fn (g &Gen) qualify_ierror_concrete_base(base string) string {
527 normalized_base := g.normalize_builtin_qualified_c_type(base)
528 if normalized_base != base {
529 return normalized_base
530 }
531 if base == '' || base == 'int' || base.contains('__') {
532 return base
533 }
534 if g.cur_module != '' && g.cur_module != 'main' && g.cur_module != 'builtin' {
535 qualified := '${g.cur_module}__${base}'
536 if '${qualified}__msg' in g.fn_return_types || 'body_${qualified}' in g.emitted_types {
537 return qualified
538 }
539 }
540 // Index lookup replaces a per-call `fn_return_types.keys().sort()` + scan. The index stores,
541 // per base, the smallest `X__base` (the original returned the first sorted `*__base__msg`
542 // minus the `__msg` suffix).
543 if qualified := g.ierror_base_index[base] {
544 return qualified
545 }
546 if g.ierror_base_index.len == 0 {
547 if qualified := g.scan_ierror_concrete_base(base) {
548 return qualified
549 }
550 }
551 body_suffix := '__${base}'
552 mut body_keys := g.emitted_types.keys()
553 body_keys.sort()
554 for body_key in body_keys {
555 if body_key.starts_with('body_') && body_key.ends_with(body_suffix) {
556 return body_key['body_'.len..]
557 }
558 }
559 mut pending_body_keys := g.pending_late_body_keys.keys()
560 pending_body_keys.sort()
561 for body_key in pending_body_keys {
562 if body_key.starts_with('body_') && body_key.ends_with(body_suffix) {
563 return body_key['body_'.len..]
564 }
565 }
566 return base
567}
568
569fn (g &Gen) scan_ierror_concrete_base(base string) ?string {
570 suffix := '__${base}__msg'
571 mut fn_names := g.fn_return_types.keys()
572 fn_names.sort()
573 for fn_name in fn_names {
574 if !fn_name.ends_with(suffix) {
575 continue
576 }
577 return fn_name[..fn_name.len - '__msg'.len]
578 }
579 return none
580}
581
582fn (mut g Gen) concrete_type_for_interface_value(type_name string, value_expr ast.Expr) string {
583 mut concrete_type := g.raw_concrete_type_for_interface_value(type_name, value_expr)
584 if type_name in ['IError', 'builtin__IError'] {
585 base := g.qualify_ierror_concrete_base(concrete_type.trim_right('*'))
586 if base != '' && base != 'int' && base != concrete_type.trim_right('*') {
587 if concrete_type.ends_with('*') {
588 concrete_type = '${base}*'
589 } else {
590 concrete_type = base
591 }
592 }
593 }
594 return concrete_type
595}
596
597// gen_heap_interface_cast generates a heap-allocated interface struct for &InterfaceType(value) patterns.
598// Returns true if the type is an interface and the cast was generated.
599fn (mut g Gen) gen_heap_interface_cast(type_name string, value_expr ast.Expr) bool {
600 if !g.is_interface_type(type_name) {
601 return false
602 }
603 iface_malloc_call := g.c_heap_malloc_call('sizeof(${type_name})')
604 if is_nil_like_expr(value_expr) || is_none_like_expr(value_expr) {
605 g.sb.write_string('({ ${type_name}* _iface_t = (${type_name}*)${iface_malloc_call}; *_iface_t = ((${type_name}){0}); _iface_t; })')
606 return true
607 }
608 mut concrete_type := g.concrete_type_for_interface_value(type_name, value_expr)
609 if concrete_type == '' || concrete_type == 'int' {
610 return false
611 }
612 base_concrete := if concrete_type.ends_with('*') {
613 concrete_type[..concrete_type.len - 1]
614 } else {
615 concrete_type
616 }
617 if type_name in ['IError', 'builtin__IError'] {
618 g.ierror_wrapper_bases[base_concrete] = true
619 g.needed_ierror_wrapper_bases[base_concrete] = true
620 }
621 if type_name in ['IError', 'builtin__IError'] && base_concrete != type_name
622 && g.concrete_ierror_base_for_c_type(concrete_type) != '' {
623 g.sb.write_string('({ ${type_name}* _iface_t = (${type_name}*)${iface_malloc_call}; *_iface_t = ')
624 g.gen_ierror_from_concrete_expr(value_expr, concrete_type)
625 g.sb.write_string('; _iface_t; })')
626 return true
627 }
628 // IError constants like `none__ = IError(&None__{})` may be emitted before
629 // embedded Error metadata is available. Still use the IError wrapper shape so
630 // the generated C references wrapper functions, not missing direct methods.
631 if type_name in ['IError', 'builtin__IError'] && base_concrete != type_name {
632 base := g.qualify_ierror_concrete_base(base_concrete)
633 if base != '' && base != 'int' {
634 g.sb.write_string('({ ${type_name}* _iface_t = (${type_name}*)${iface_malloc_call}; *_iface_t = ')
635 g.gen_ierror_from_base_expr(base, value_expr, concrete_type)
636 g.sb.write_string('; _iface_t; })')
637 return true
638 }
639 }
640 // Generate: ({ InterfaceType* _iface = malloc(sizeof(InterfaceType));
641 // *_iface = (InterfaceType){._object = (void*)value, ...}; _iface; })
642 g.sb.write_string('({ ${type_name}* _iface_t = (${type_name}*)${iface_malloc_call}; *_iface_t = ((${type_name}){._object = ')
643 if concrete_type.ends_with('*') {
644 g.sb.write_string('(void*)(')
645 g.expr(value_expr)
646 g.sb.write_string(')')
647 } else {
648 g.sb.write_string('(void*)&(')
649 g.expr(value_expr)
650 g.sb.write_string(')')
651 }
652 type_short := if base_concrete.contains('__') {
653 base_concrete.all_after_last('__')
654 } else {
655 base_concrete
656 }
657 type_id := interface_type_id_for_name(type_short)
658 g.sb.write_string(', ._type_id = ${type_id}')
659 if methods := g.interface_methods[type_name] {
660 for method in methods {
661 mut fn_name := '${base_concrete}__${method.name}'
662 mut uses_embedded_method := false
663 if fn_name !in g.fn_param_is_ptr && fn_name !in g.fn_return_types {
664 resolved := g.resolve_embedded_method(base_concrete, method.name)
665 if resolved != '' {
666 fn_name = resolved
667 uses_embedded_method = true
668 }
669 }
670 mut target_name := fn_name
671 if ptr_params := g.fn_param_is_ptr[fn_name] {
672 if uses_embedded_method || (ptr_params.len > 0 && !ptr_params[0]) {
673 target_name = interface_wrapper_name(type_name, base_concrete, method.name)
674 g.needed_interface_wrappers[target_name] = true
675 if target_name !in g.interface_wrapper_specs {
676 g.interface_wrapper_specs[target_name] = InterfaceWrapperSpec{
677 fn_name: fn_name
678 concrete_type: base_concrete
679 method: method
680 }
681 }
682 }
683 }
684 g.sb.write_string(', .${method.name} = (${method.cast_signature})${target_name}')
685 }
686 }
687 g.sb.write_string('}); _iface_t; })')
688 return true
689}
690
691fn (mut g Gen) gen_interface_cast(type_name string, value_expr ast.Expr) bool {
692 if !g.is_interface_type(type_name) {
693 return false
694 }
695 mut cast_value_expr := value_expr
696 for {
697 inner := g.interface_target_cast_inner_expr(type_name, cast_value_expr) or { break }
698 cast_value_expr = inner
699 }
700 if is_nil_like_expr(cast_value_expr) || is_none_like_expr(cast_value_expr) {
701 g.sb.write_string('((${type_name}){0})')
702 return true
703 }
704 // Get the concrete type name
705 mut concrete_type := g.concrete_type_for_interface_value(type_name, cast_value_expr)
706 if concrete_type == '' || concrete_type == 'int' {
707 return false
708 }
709 // Strip pointer suffix for method name construction
710 base_concrete := if concrete_type.ends_with('*') {
711 concrete_type[..concrete_type.len - 1]
712 } else {
713 concrete_type
714 }
715 if type_name in ['IError', 'builtin__IError'] {
716 g.ierror_wrapper_bases[base_concrete] = true
717 g.needed_ierror_wrapper_bases[base_concrete] = true
718 }
719 if base_concrete == type_name {
720 g.expr(value_expr)
721 return true
722 }
723 // Interface-to-interface casting (e.g., Layout -> ScrollableWidget)
724 // requires runtime dispatch: check _type_id to find the concrete type,
725 // then construct the target interface from it.
726 if g.is_interface_type(base_concrete) {
727 return g.gen_iface_to_iface_cast(base_concrete, type_name, cast_value_expr)
728 }
729 // Generate: (InterfaceType){._object = (void*)&expr, .method = ConcreteType__method, ...}
730 // For rvalue expressions (function calls, struct init, etc.), store in a temp
731 // to allow taking the address, and reuse the temp for data field pointers.
732 // When concrete_type is a pointer and the value_expr is a deref (*ptr),
733 // unwrap the deref: for _object we want the pointer, for data fields we use ->.
734 mut effective_value := cast_value_expr
735 if concrete_type.ends_with('*') && cast_value_expr is ast.PrefixExpr
736 && cast_value_expr.op == .mul {
737 effective_value = cast_value_expr.expr
738 }
739 if type_name in ['IError', 'builtin__IError'] {
740 if g.gen_ierror_from_concrete_expr(effective_value, concrete_type) {
741 return true
742 }
743 // See the heap IError cast path above: fallback to wrapper-based IError
744 // construction even when embedded method metadata could not prove it.
745 base := g.qualify_ierror_concrete_base(base_concrete)
746 if base != '' && base != 'int' {
747 if g.gen_ierror_from_base_expr(base, effective_value, concrete_type) {
748 return true
749 }
750 }
751 }
752 is_rvalue := !concrete_type.ends_with('*') && !g.can_take_address(effective_value)
753 // Use a temp variable when there are data fields and the expression is complex
754 // (e.g., a function call returning a pointer). This avoids re-evaluating the
755 // expression for each data field reference, preventing exponential code blowup.
756 data_fields := g.interface_data_fields[type_name]
757 needs_ptr_tmp := concrete_type.ends_with('*') && data_fields.len > 0
758 && !g.is_simple_addressable(effective_value)
759 mut rvalue_tmp := ''
760 if is_rvalue {
761 rvalue_tmp = '_iface_obj${g.tmp_counter}'
762 g.tmp_counter++
763 g.sb.write_string('({ ${base_concrete} ${rvalue_tmp} = ')
764 g.expr(effective_value)
765 g.sb.write_string('; (${type_name}){._object = (void*)&${rvalue_tmp}')
766 } else if needs_ptr_tmp {
767 rvalue_tmp = '_iface_ptr${g.tmp_counter}'
768 g.tmp_counter++
769 g.sb.write_string('({ ${base_concrete}* ${rvalue_tmp} = ')
770 g.expr(effective_value)
771 g.sb.write_string('; (${type_name}){._object = (void*)(${rvalue_tmp})')
772 } else {
773 g.sb.write_string('((${type_name}){._object = ')
774 if concrete_type.ends_with('*') {
775 g.sb.write_string('(void*)(')
776 g.expr(effective_value)
777 g.sb.write_string(')')
778 } else {
779 g.sb.write_string('(void*)&(')
780 g.expr(effective_value)
781 g.sb.write_string(')')
782 }
783 }
784 type_short := if base_concrete.contains('__') {
785 base_concrete.all_after_last('__')
786 } else {
787 base_concrete
788 }
789 type_id := interface_type_id_for_name(type_short)
790 g.sb.write_string(', ._type_id = ${type_id}')
791 // Generate method function pointers from stored interface info
792 if methods := g.interface_methods[type_name] {
793 for method in methods {
794 mut fn_name := '${base_concrete}__${method.name}'
795 mut uses_embedded_method := false
796 // If the method doesn't exist on the concrete type directly,
797 // try resolving through embedded structs (e.g. ssl__SSLConn embeds
798 // openssl__SSLConn, so ssl__SSLConn__addr → openssl__SSLConn__addr).
799 if fn_name !in g.fn_param_is_ptr && fn_name !in g.fn_return_types {
800 resolved := g.resolve_embedded_method(base_concrete, method.name)
801 if resolved != '' {
802 fn_name = resolved
803 uses_embedded_method = true
804 }
805 }
806 mut target_name := fn_name
807 if ptr_params := g.fn_param_is_ptr[fn_name] {
808 if uses_embedded_method || (ptr_params.len > 0 && !ptr_params[0]) {
809 target_name = interface_wrapper_name(type_name, base_concrete, method.name)
810 g.needed_interface_wrappers[target_name] = true
811 if target_name !in g.interface_wrapper_specs {
812 g.interface_wrapper_specs[target_name] = InterfaceWrapperSpec{
813 fn_name: fn_name
814 concrete_type: base_concrete
815 method: method
816 }
817 }
818 }
819 }
820 g.sb.write_string(', .${method.name} = (${method.cast_signature})${target_name}')
821 }
822 }
823 // Generate data field pointers: .field = &(concrete->field)
824 if data_fields.len > 0 {
825 sep := if concrete_type.ends_with('*') { '->' } else { '.' }
826 for df in data_fields {
827 embedded_owner := g.embedded_owner_for(base_concrete, df.name)
828 g.sb.write_string(', .${df.name} = &(')
829 if rvalue_tmp != '' {
830 // Use the temp variable instead of re-evaluating the expression
831 g.sb.write_string(rvalue_tmp)
832 } else {
833 g.expr(effective_value)
834 }
835 if embedded_owner != '' {
836 g.sb.write_string('${sep}${embedded_owner}.${df.name})')
837 } else {
838 g.sb.write_string('${sep}${df.name})')
839 }
840 }
841 }
842 if rvalue_tmp != '' {
843 g.sb.write_string('}; })')
844 } else {
845 g.sb.write_string('})')
846 }
847 return true
848}
849
850fn c_static_v_string_expr(raw string) string {
851 return c_static_v_string_expr_from_c_literal(c_string_literal_content_to_c(raw))
852}
853
854fn c_empty_v_string_expr() string {
855 return c_v_string_expr_from_ptr_len('""', '0', true)
856}
857
858fn expr_to_int_str(e ast.Expr) string {
859 if e is ast.BasicLiteral {
860 return e.value
861 }
862 return '0'
863}
864
865fn (mut g Gen) known_module_runtime_symbol(module_name string, symbol_name string) ?string {
866 if module_name == '' || symbol_name == '' {
867 return none
868 }
869 if scope := g.env_scope(module_name) {
870 if obj := scope.lookup(symbol_name) {
871 if obj is types.Const || obj is types.Global || obj is types.Fn {
872 return module_name
873 }
874 }
875 }
876 return none
877}
878
879fn (mut g Gen) module_selector_storage_c_name(module_name string, symbol_name string) string {
880 c_name := if symbol_name.starts_with('${module_name}__') {
881 symbol_name
882 } else {
883 '${module_name}__${symbol_name}'
884 }
885 if raw_c_name := g.c_extern_module_storage[c_name] {
886 return raw_c_name
887 }
888 return c_name
889}
890
891// expr_to_int_str_with_env resolves fixed-array size expressions, handling
892// both literal numbers and named constants (looked up via type environment).
893fn (g &Gen) expr_to_int_str_with_env(e ast.Expr) string {
894 if e is ast.BasicLiteral {
895 return e.value
896 }
897 if e is ast.Ident {
898 // Try to resolve the constant from the module scope.
899 if g.env != unsafe { nil } {
900 if scope := g.env_scope(g.cur_module) {
901 if obj := scope.lookup_parent(e.name, 0) {
902 if obj is types.Const {
903 if obj.int_val != 0 {
904 return obj.int_val.str()
905 }
906 }
907 }
908 }
909 }
910 }
911 return '0'
912}
913
914fn (mut g Gen) local_var_c_type_for_expr(expr ast.Expr) ?string {
915 unwrapped := strip_expr_wrappers(expr)
916 if unwrapped !is ast.Ident {
917 return none
918 }
919 name := unwrapped.name()
920 if name == '' {
921 return none
922 }
923 return g.get_local_var_c_type(name)
924}
925
926fn (mut g Gen) fn_type_alias_name_for_base_expr(base ast.Expr) ?string {
927 mut candidates := []string{}
928 base_c := g.expr_type_to_c(base).trim_space()
929 if base_c != '' {
930 candidates << base_c
931 }
932 base_name := base.name()
933 if base_name != '' {
934 candidates << base_name
935 if base_name.contains('.') {
936 candidates << base_name.replace('.', '__')
937 } else if g.cur_module != '' && g.cur_module != 'main' && g.cur_module != 'builtin'
938 && !base_name.contains('__') {
939 candidates << '${g.cur_module}__${base_name}'
940 }
941 }
942 for candidate in candidates {
943 if candidate in g.fn_type_aliases {
944 return candidate
945 }
946 }
947 for candidate in candidates {
948 short_name := if candidate.contains('__') {
949 candidate.all_after_last('__')
950 } else {
951 candidate
952 }
953 if short_name == '' {
954 continue
955 }
956 for alias_name, _ in g.fn_type_aliases {
957 if alias_name == short_name || alias_name.ends_with('__${short_name}') {
958 return alias_name
959 }
960 }
961 }
962 return none
963}
964
965fn (mut g Gen) exact_fn_type_alias_cast_target_name(typ ast.Expr) ?string {
966 mut candidates := []string{}
967 base_name := typ.name()
968 if base_name != '' && !base_name.contains('.') && g.cur_module != '' && g.cur_module != 'main'
969 && g.cur_module != 'builtin' && !base_name.contains('__') {
970 candidates << '${g.cur_module}__${base_name}'
971 }
972 base_c := g.expr_type_to_c(typ).trim_space()
973 if base_c != '' {
974 candidates << base_c
975 if base_c.ends_with('*') && !param_type_is_pointer_expr(typ) {
976 candidates << base_c[..base_c.len - 1].trim_space()
977 }
978 }
979 if base_name != '' {
980 if base_name.contains('.') {
981 candidates << base_name.replace('.', '__')
982 } else {
983 candidates << base_name
984 }
985 }
986 for candidate in candidates {
987 if g.c_type_is_fn_pointer_alias(candidate) {
988 return candidate
989 }
990 }
991 return none
992}
993
994fn (mut g Gen) local_non_fn_alias_homonym_cast_target_name(typ ast.Expr, type_name string) ?string {
995 name := type_name.trim_space()
996 mut fn_alias_type := ''
997 if g.c_type_is_fn_pointer_alias(name) {
998 fn_alias_type = name
999 } else if name.len > 1 && name.ends_with('*') {
1000 base_type := name[..name.len - 1].trim_space()
1001 if g.c_type_is_fn_pointer_alias(base_type) {
1002 fn_alias_type = base_type
1003 }
1004 }
1005 if fn_alias_type == '' {
1006 return none
1007 }
1008 base_name := typ.name()
1009 if base_name == '' || base_name.contains('.') || base_name.contains('__') {
1010 return none
1011 }
1012 if g.cur_module == '' || g.cur_module == 'main' || g.cur_module == 'builtin' {
1013 return none
1014 }
1015 local_type := '${g.cur_module}__${base_name}'
1016 if !g.is_type_name(local_type) || g.c_type_is_fn_pointer_alias(local_type) {
1017 return none
1018 }
1019 return local_type
1020}
1021
1022fn (mut g Gen) fn_type_alias_name_from_generic_name(name string) ?string {
1023 mut base_name := name
1024 if name.ends_with('_T') {
1025 base_name = name[..name.len - 2]
1026 } else if name.contains('_T_') {
1027 base_name = name.all_before('_T_')
1028 } else {
1029 return none
1030 }
1031 if g.has_generic_fn_decl_by_base_name(base_name) {
1032 return none
1033 }
1034 return g.fn_type_alias_name_for_base_expr(ast.Ident{
1035 name: base_name
1036 })
1037}
1038
1039fn (mut g Gen) fn_type_alias_cast_type(lhs ast.Expr) ?string {
1040 match lhs {
1041 ast.Ident {
1042 return g.fn_type_alias_name_from_generic_name(lhs.name)
1043 }
1044 ast.SelectorExpr {
1045 if alias_name := g.fn_type_alias_name_for_base_expr(lhs) {
1046 return alias_name
1047 }
1048 return g.fn_type_alias_name_from_generic_name(lhs.rhs.name)
1049 }
1050 ast.GenericArgOrIndexExpr {
1051 return g.fn_type_alias_name_for_base_expr(lhs.lhs)
1052 }
1053 ast.GenericArgs {
1054 return g.fn_type_alias_name_for_base_expr(lhs.lhs)
1055 }
1056 else {
1057 return none
1058 }
1059 }
1060}
1061
1062fn (mut g Gen) call_or_cast_lhs_is_type(lhs ast.Expr) bool {
1063 match lhs {
1064 ast.Type {
1065 return true
1066 }
1067 ast.Ident {
1068 if lhs.name in ['int', 'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64', 'f32',
1069 'f64', 'bool', 'byte', 'char', 'rune', 'usize', 'isize', 'string', 'byteptr',
1070 'charptr', 'voidptr'] {
1071 return true
1072 }
1073 if lhs.name in g.fn_param_is_ptr || lhs.name in g.fn_return_types {
1074 return false
1075 }
1076 if g.is_type_name(lhs.name) || g.is_c_type_name(lhs.name) {
1077 return true
1078 }
1079 for mod in [g.cur_module, 'builtin'] {
1080 if mod == '' {
1081 continue
1082 }
1083 if mut scope := g.env_scope(mod) {
1084 if obj := scope.lookup_parent(lhs.name, 0) {
1085 if obj is types.Fn {
1086 return false
1087 }
1088 if obj is types.Type {
1089 return true
1090 }
1091 }
1092 }
1093 }
1094 return false
1095 }
1096 ast.SelectorExpr {
1097 if lhs.lhs is ast.Ident {
1098 mod_name := lhs.lhs.name
1099 if mod_name == 'C' {
1100 if g.is_c_type_name(lhs.rhs.name) {
1101 return true
1102 }
1103 // Check if a C.Type is known as a struct/type in the environment
1104 if g.is_type_name(lhs.rhs.name) || g.is_type_name('C__${lhs.rhs.name}') {
1105 return true
1106 }
1107 // C types starting with uppercase that aren't known functions
1108 // are likely type casts (e.g. C.FONScontext(ptr))
1109 c_name := lhs.rhs.name
1110 if c_name.len > 0 && c_name[0] >= `A` && c_name[0] <= `Z`
1111 && c_name !in g.fn_return_types && c_name !in g.fn_param_is_ptr {
1112 return true
1113 }
1114 if c_name.contains('__') && c_name !in g.fn_return_types
1115 && c_name !in g.fn_param_is_ptr {
1116 return true
1117 }
1118 return false
1119 }
1120 qualified := '${mod_name}__${lhs.rhs.name}'
1121 if qualified in g.fn_param_is_ptr || qualified in g.fn_return_types {
1122 return false
1123 }
1124 if g.is_type_name(qualified) || g.is_type_name(lhs.rhs.name) {
1125 return true
1126 }
1127 if mut scope := g.env_scope(mod_name) {
1128 if obj := scope.lookup_parent(lhs.rhs.name, 0) {
1129 return obj is types.Type
1130 }
1131 }
1132 }
1133 return false
1134 }
1135 ast.ParenExpr {
1136 return g.call_or_cast_lhs_is_type(lhs.expr)
1137 }
1138 ast.ModifierExpr {
1139 return g.call_or_cast_lhs_is_type(lhs.expr)
1140 }
1141 ast.PrefixExpr {
1142 return g.call_or_cast_lhs_is_type(lhs.expr)
1143 }
1144 ast.GenericArgOrIndexExpr {
1145 if _ := g.fn_type_alias_cast_type(lhs) {
1146 return true
1147 }
1148 return g.call_or_cast_lhs_is_type(lhs.lhs)
1149 }
1150 ast.GenericArgs {
1151 if _ := g.fn_type_alias_cast_type(lhs) {
1152 return true
1153 }
1154 return g.call_or_cast_lhs_is_type(lhs.lhs)
1155 }
1156 else {
1157 return false
1158 }
1159 }
1160}
1161
1162fn (mut g Gen) gen_call_or_cast_expr(node ast.CallOrCastExpr) {
1163 if node.expr is ast.EmptyExpr {
1164 g.call_expr(node.lhs, []ast.Expr{})
1165 return
1166 }
1167 if g.call_or_cast_lhs_is_type(node.lhs) {
1168 g.gen_type_cast_expr(g.cast_target_type_to_c(node.lhs), node.expr)
1169 return
1170 }
1171 g.call_expr(node.lhs, [node.expr])
1172}
1173
1174fn (mut g Gen) gen_c_pointer_cast_selector_field_access(sel ast.SelectorExpr) bool {
1175 mut target_type := ''
1176 if sel.lhs is ast.CallOrCastExpr {
1177 cast_expr := sel.lhs as ast.CallOrCastExpr
1178 if !g.call_or_cast_lhs_is_type(cast_expr.lhs) {
1179 return false
1180 }
1181 target_type = g.expr_type_to_c(cast_expr.lhs)
1182 if target_type == '' || target_type == 'int' {
1183 return false
1184 }
1185 target_ptr_type := if target_type.ends_with('*') { target_type } else { '${target_type}*' }
1186 g.sb.write_string('((${target_ptr_type})(')
1187 g.expr(cast_expr.expr)
1188 g.sb.write_string('))->${escape_c_keyword(sel.rhs.name)}')
1189 return true
1190 } else if sel.lhs is ast.CallExpr {
1191 call_expr := sel.lhs as ast.CallExpr
1192 if call_expr.args.len != 1 || !g.call_or_cast_lhs_is_type(call_expr.lhs) {
1193 return false
1194 }
1195 target_type = g.expr_type_to_c(call_expr.lhs)
1196 if target_type == '' || target_type == 'int' {
1197 return false
1198 }
1199 target_ptr_type := if target_type.ends_with('*') { target_type } else { '${target_type}*' }
1200 g.sb.write_string('((${target_ptr_type})(')
1201 g.expr(call_expr.args[0])
1202 g.sb.write_string('))->${escape_c_keyword(sel.rhs.name)}')
1203 return true
1204 } else if sel.lhs is ast.CastExpr {
1205 cast_expr := sel.lhs as ast.CastExpr
1206 target_type = g.expr_type_to_c(cast_expr.typ)
1207 if target_type == '' || target_type == 'int' {
1208 return false
1209 }
1210 target_ptr_type := if target_type.ends_with('*') { target_type } else { '${target_type}*' }
1211 g.sb.write_string('((${target_ptr_type})(')
1212 g.expr(cast_expr.expr)
1213 g.sb.write_string('))->${escape_c_keyword(sel.rhs.name)}')
1214 return true
1215 } else {
1216 return false
1217 }
1218}
1219
1220fn (mut g Gen) selector_use_ptr(lhs_expr ast.Expr) bool {
1221 if g.expr_is_pointer(lhs_expr) {
1222 return true
1223 }
1224 unwrapped := g.unwrap_parens(lhs_expr)
1225 if unwrapped is ast.Ident {
1226 if unwrapped.name in g.cur_fn_mut_params {
1227 return true
1228 }
1229 if local_type := g.local_var_c_type_for_expr(unwrapped) {
1230 return local_type.ends_with('*')
1231 }
1232 }
1233 lhs_type := g.get_expr_type(lhs_expr)
1234 if lhs_type == 'chan' {
1235 return true
1236 }
1237 return false
1238}
1239
1240fn (mut g Gen) gen_channel_receive_expr(node ast.PrefixExpr) bool {
1241 if node.op != .arrow {
1242 return false
1243 }
1244 mut expr_type := g.get_expr_type(ast.Expr(node))
1245 mut elem_type := if expr_type.starts_with('_option_') {
1246 option_value_type(expr_type)
1247 } else if expr_type.starts_with('_result_') {
1248 g.result_value_type(expr_type)
1249 } else {
1250 expr_type
1251 }
1252 if elem_type == '' || elem_type == 'int' || elem_type.starts_with('_option_')
1253 || elem_type.starts_with('_result_') {
1254 elem_type = g.channel_elem_type_from_expr(node.expr) or { '' }
1255 }
1256 if elem_type == '' || elem_type == 'int' {
1257 elem_type = 'void*'
1258 }
1259 g.force_emit_fn_names['sync__Channel__try_pop_priv'] = true
1260 g.mark_called_fn_name('sync__Channel__try_pop_priv')
1261 g.tmp_counter++
1262 if expr_type.starts_with('_option_') {
1263 g.force_emit_fn_names['error'] = true
1264 g.mark_called_fn_name('error')
1265 opt_tmp := '_chopt_${g.tmp_counter}'
1266 val_tmp := '_chval_${g.tmp_counter}'
1267 g.sb.write_string('({ ${expr_type} ${opt_tmp} = (${expr_type}){ .state = 2, .err = error((string){.str = "channel closed", .len = sizeof("channel closed") - 1, .is_lit = 1}) }; ${elem_type} ${val_tmp} = ${zero_value_for_type(elem_type)}; if (sync__Channel__try_pop_priv((sync__Channel*)')
1268 g.expr(node.expr)
1269 g.sb.write_string(', &${val_tmp}, false) == ChanState__success) { _option_ok(&${val_tmp}, (_option*)&${opt_tmp}, sizeof(${val_tmp})); } ${opt_tmp}; })')
1270 return true
1271 }
1272 val_tmp := '_chval_${g.tmp_counter}'
1273 g.sb.write_string('({ ${elem_type} ${val_tmp} = ${zero_value_for_type(elem_type)}; sync__Channel__try_pop_priv((sync__Channel*)')
1274 g.expr(node.expr)
1275 g.sb.write_string(', &${val_tmp}, false); ${val_tmp}; })')
1276 return true
1277}
1278
1279fn (mut g Gen) gen_unwrapped_value_expr(expr ast.Expr) bool {
1280 expr_type := g.get_expr_type(expr)
1281 if expr_type.starts_with('_result_') {
1282 base := g.result_value_type(expr_type)
1283 if g.gen_unwrapped_payload_expr(expr, expr_type, base) {
1284 return true
1285 }
1286 }
1287 if expr_type.starts_with('_option_') {
1288 base := option_value_type(expr_type)
1289 if g.gen_unwrapped_payload_expr(expr, expr_type, base) {
1290 return true
1291 }
1292 }
1293 return false
1294}
1295
1296fn (mut g Gen) gen_unwrapped_payload_expr(expr ast.Expr, wrapper_type string, payload_type string) bool {
1297 if payload_type == '' || payload_type == 'void' {
1298 return false
1299 }
1300 is_addressable := match expr {
1301 ast.Ident, ast.SelectorExpr, ast.IndexExpr {
1302 true
1303 }
1304 else {
1305 false
1306 }
1307 }
1308
1309 if is_addressable {
1310 g.sb.write_string('(*(${payload_type}*)(((u8*)(&')
1311 g.expr(expr)
1312 g.sb.write_string('.err)) + sizeof(IError)))')
1313 } else {
1314 g.sb.write_string('({ ${wrapper_type} _tmp = ')
1315 g.expr(expr)
1316 g.sb.write_string('; (*(${payload_type}*)(((u8*)(&_tmp.err)) + sizeof(IError))); })')
1317 }
1318 return true
1319}
1320
1321fn (g &Gen) should_use_known_enum_field(enum_name string, field_name string) bool {
1322 return enum_name != '' && !is_generic_placeholder_c_type_name(enum_name)
1323 && g.enum_has_field(enum_name, field_name)
1324}
1325
1326fn (mut g Gen) gen_enum_shorthand_for_type(expr ast.Expr, expected_type string) bool {
1327 enum_name := expected_type.trim_space().trim_right('*')
1328 if enum_name == '' || enum_name == 'int' || is_generic_placeholder_c_type_name(enum_name)
1329 || !g.is_enum_type(enum_name) {
1330 return false
1331 }
1332 if expr is ast.SelectorExpr {
1333 if expr.lhs is ast.EmptyExpr && g.enum_has_field(enum_name, expr.rhs.name) {
1334 g.sb.write_string(g.enum_member_c_name(enum_name, expr.rhs.name))
1335 return true
1336 }
1337 }
1338 return false
1339}
1340
1341fn (mut g Gen) enum_type_from_expr(expr ast.Expr, fallback string) string {
1342 mut enum_type := fallback.trim_space().trim_right('*')
1343 if enum_type != '' && enum_type != 'int' && !is_generic_placeholder_c_type_name(enum_type)
1344 && g.is_enum_type(enum_type) {
1345 return enum_type
1346 }
1347 if raw_type := g.get_raw_type(expr) {
1348 enum_type = g.types_type_to_c(raw_type).trim_space().trim_right('*')
1349 if enum_type != '' && enum_type != 'int' && !is_generic_placeholder_c_type_name(enum_type)
1350 && g.is_enum_type(enum_type) {
1351 return enum_type
1352 }
1353 }
1354 if env_type := g.get_expr_type_from_env(expr) {
1355 enum_type = env_type.trim_space().trim_right('*')
1356 if enum_type != '' && enum_type != 'int' && !is_generic_placeholder_c_type_name(enum_type)
1357 && g.is_enum_type(enum_type) {
1358 return enum_type
1359 }
1360 }
1361 cast_type := extract_compare_cast_type(expr).trim_space().trim_right('*')
1362 if cast_type != '' && cast_type != 'int' && !is_generic_placeholder_c_type_name(cast_type)
1363 && g.is_enum_type(cast_type) {
1364 return cast_type
1365 }
1366 return ''
1367}
1368
1369fn (mut g Gen) gen_enum_shorthand_compare(node &ast.InfixExpr, lhs_type string, rhs_type string) bool {
1370 if node.op !in [.eq, .ne] {
1371 return false
1372 }
1373 if node.rhs is ast.SelectorExpr {
1374 rhs_sel := node.rhs as ast.SelectorExpr
1375 if rhs_sel.lhs is ast.EmptyExpr {
1376 enum_type := g.enum_type_from_expr(node.lhs, lhs_type)
1377 if enum_type != '' && g.enum_has_field(enum_type, rhs_sel.rhs.name) {
1378 g.sb.write_string('(')
1379 g.expr(node.lhs)
1380 g.sb.write_string(if node.op == .eq { ' == ' } else { ' != ' })
1381 g.sb.write_string(g.enum_member_c_name(enum_type, rhs_sel.rhs.name))
1382 g.sb.write_string(')')
1383 return true
1384 }
1385 }
1386 }
1387 if node.lhs is ast.SelectorExpr {
1388 lhs_sel := node.lhs as ast.SelectorExpr
1389 if lhs_sel.lhs is ast.EmptyExpr {
1390 enum_type := g.enum_type_from_expr(node.rhs, rhs_type)
1391 if enum_type != '' && g.enum_has_field(enum_type, lhs_sel.rhs.name) {
1392 g.sb.write_string('(')
1393 g.sb.write_string(g.enum_member_c_name(enum_type, lhs_sel.rhs.name))
1394 g.sb.write_string(if node.op == .eq { ' == ' } else { ' != ' })
1395 g.expr(node.rhs)
1396 g.sb.write_string(')')
1397 return true
1398 }
1399 }
1400 }
1401 return false
1402}
1403
1404fn (mut g Gen) gen_infix_expr(node &ast.InfixExpr) {
1405 mut lhs_type := ''
1406 if node.lhs is ast.SelectorExpr {
1407 lhs_type = g.plain_local_selector_type(node.lhs) or { '' }
1408 }
1409 if lhs_type == '' {
1410 lhs_type = g.get_expr_type(node.lhs)
1411 }
1412 mut rhs_type := ''
1413 if node.rhs is ast.SelectorExpr {
1414 rhs_type = g.plain_local_selector_type(node.rhs) or { '' }
1415 }
1416 if rhs_type == '' {
1417 rhs_type = g.get_expr_type(node.rhs)
1418 }
1419 if local_type := g.local_var_c_type_for_expr(node.lhs) {
1420 lhs_type = local_type
1421 }
1422 if local_type := g.local_var_c_type_for_expr(node.rhs) {
1423 rhs_type = local_type
1424 }
1425 if node.lhs is ast.IndexExpr && (lhs_type == '' || lhs_type == 'int'
1426 || lhs_type == 'array' || lhs_type == 'void*' || lhs_type == 'voidptr'
1427 || (lhs_type.starts_with('Array_') && !lhs_type.starts_with('Array_fixed_'))) {
1428 if elem_type := g.index_expr_elem_type_from_lhs(node.lhs) {
1429 lhs_type = elem_type
1430 }
1431 }
1432 if node.rhs is ast.IndexExpr && (rhs_type == '' || rhs_type == 'int'
1433 || rhs_type == 'array' || rhs_type == 'void*' || rhs_type == 'voidptr'
1434 || (rhs_type.starts_with('Array_') && !rhs_type.starts_with('Array_fixed_'))) {
1435 if elem_type := g.index_expr_elem_type_from_lhs(node.rhs) {
1436 rhs_type = elem_type
1437 }
1438 }
1439 // Channel push: ch <- value → sync__Channel__try_push_priv(ch, &(elem_type){value}, false)
1440 if node.op == .arrow {
1441 mut elem_type := g.channel_elem_type_from_expr(node.lhs) or { 'bool' }
1442 if elem_type == '' || elem_type == 'int' {
1443 elem_type = 'bool'
1444 }
1445 g.force_emit_fn_names['sync__Channel__try_push_priv'] = true
1446 g.mark_called_fn_name('sync__Channel__try_push_priv')
1447 g.sb.write_string('sync__Channel__try_push_priv((sync__Channel*)')
1448 g.expr(node.lhs)
1449 g.sb.write_string(', &(${elem_type}[]){')
1450 g.expr(node.rhs)
1451 g.sb.write_string('}, false)')
1452 return
1453 }
1454 if g.gen_enum_shorthand_compare(node, lhs_type, rhs_type) {
1455 return
1456 }
1457 if node.op in [.logical_or, .and] {
1458 g.sb.write_string('(')
1459 if !g.gen_unwrapped_value_expr(node.lhs) {
1460 g.expr(node.lhs)
1461 }
1462 g.sb.write_string(if node.op == .logical_or { ' || ' } else { ' && ' })
1463 if !g.gen_unwrapped_value_expr(node.rhs) {
1464 g.expr(node.rhs)
1465 }
1466 g.sb.write_string(')')
1467 return
1468 }
1469 if node.op in [.amp, .pipe, .xor] && node.lhs is ast.InfixExpr {
1470 left := node.lhs as ast.InfixExpr
1471 if left.op == .left_shift {
1472 is_array_append, elem_type := g.array_append_elem_type(left.lhs, left.rhs)
1473 if is_array_append {
1474 combined_rhs := ast.InfixExpr{
1475 lhs: left.rhs
1476 op: node.op
1477 rhs: node.rhs
1478 pos: node.pos
1479 }
1480 g.sb.write_string('array__push((array*)')
1481 if g.expr_is_pointer(left.lhs) {
1482 g.expr(left.lhs)
1483 } else {
1484 g.sb.write_string('&')
1485 g.expr(left.lhs)
1486 }
1487 g.sb.write_string(', ')
1488 g.gen_addr_of_expr(ast.Expr(combined_rhs), elem_type)
1489 g.sb.write_string(')')
1490 return
1491 }
1492 }
1493 }
1494 // Option none comparison: `opt == none` / `opt != none`.
1495 if node.op in [.eq, .ne] {
1496 if lhs_type.starts_with('_option_') && is_none_like_expr(node.rhs) {
1497 sep := if g.expr_is_pointer(node.lhs) { '->' } else { '.' }
1498 cmp := if node.op == .eq { '!=' } else { '==' }
1499 g.sb.write_string('(')
1500 g.expr(node.lhs)
1501 g.sb.write_string('${sep}state ${cmp} 0)')
1502 return
1503 }
1504 if rhs_type.starts_with('_option_') && is_none_like_expr(node.lhs) {
1505 sep := if g.expr_is_pointer(node.rhs) { '->' } else { '.' }
1506 cmp := if node.op == .eq { '!=' } else { '==' }
1507 g.sb.write_string('(')
1508 g.expr(node.rhs)
1509 g.sb.write_string('${sep}state ${cmp} 0)')
1510 return
1511 }
1512 }
1513 if node.op in [.eq, .ne, .key_is, .not_is] && ((node.rhs is ast.Ident && node.rhs.name == 'Eof')
1514 || (node.rhs is ast.SelectorExpr && node.rhs.rhs.name == 'Eof')) {
1515 if node.op in [.ne, .not_is] {
1516 g.sb.write_string('!')
1517 }
1518 g.sb.write_string('string__eq(err.type_name(err._object), ${c_static_v_string_expr('Eof')})')
1519 return
1520 }
1521 if node.op in [.eq, .ne, .key_is, .not_is] && node.lhs is ast.Ident && node.lhs.name == 'err'
1522 && node.rhs is ast.Ident {
1523 rhs_ident := node.rhs as ast.Ident
1524 if rhs_ident.name.len > 0 && rhs_ident.name[0] >= `A` && rhs_ident.name[0] <= `Z` {
1525 if node.op in [.ne, .not_is] {
1526 g.sb.write_string('!')
1527 }
1528 type_name := rhs_ident.name
1529 g.sb.write_string('string__eq(err.type_name(err._object), ${c_static_v_string_expr(type_name)})')
1530 return
1531 }
1532 }
1533 if node.op in [.eq, .ne, .key_is, .not_is] && lhs_type == 'IError' && node.rhs is ast.Ident {
1534 rhs_ident := node.rhs as ast.Ident
1535 if g.is_type_name(rhs_ident.name)
1536 || (rhs_ident.name.len > 0 && rhs_ident.name[0] >= `A` && rhs_ident.name[0] <= `Z`) {
1537 if node.op in [.ne, .not_is] {
1538 g.sb.write_string('!')
1539 }
1540 type_name := rhs_ident.name
1541 g.sb.write_string('string__eq(')
1542 g.expr(node.lhs)
1543 g.sb.write_string('.type_name(')
1544 g.expr(node.lhs)
1545 g.sb.write_string('._object), ${c_static_v_string_expr(type_name)})')
1546 return
1547 }
1548 }
1549 if node.op in [.eq, .ne] && node.rhs is ast.BasicLiteral {
1550 tag_lhs := strip_expr_wrappers(node.lhs)
1551 if tag_lhs is ast.SelectorExpr && tag_lhs.rhs.name == '_tag' {
1552 sum_expr := strip_expr_wrappers(tag_lhs.lhs)
1553 if sum_expr is ast.SelectorExpr {
1554 tag_lit := node.rhs as ast.BasicLiteral
1555 if tag_lit.kind == .number {
1556 tag := tag_lit.value.int()
1557 if tag >= 0 {
1558 mut source_sum_type :=
1559 g.get_expr_type(sum_expr).trim_space().trim_right('*')
1560 if source_sum_type == '' {
1561 source_sum_type =
1562 g.selector_field_type(sum_expr).trim_space().trim_right('*')
1563 }
1564 variants := g.get_sum_type_variants_for(source_sum_type)
1565 if tag < variants.len {
1566 target_type := variants[tag]
1567 for storage_sum_type in [
1568 g.selector_storage_field_type(sum_expr).trim_space().trim_right('*'),
1569 g.selector_declared_field_type(sum_expr).trim_space().trim_right('*'),
1570 ] {
1571 if storage_sum_type == '' || storage_sum_type == source_sum_type {
1572 continue
1573 }
1574 if path := g.sum_variant_tag_path(storage_sum_type, target_type,
1575 []string{})
1576 {
1577 if path.len > 1 {
1578 g.gen_sum_variant_tag_path_check(sum_expr, path,
1579 node.op == .eq)
1580 return
1581 }
1582 }
1583 }
1584 }
1585 }
1586 }
1587 }
1588 }
1589 }
1590 if node.op in [.key_is, .not_is, .eq, .ne] {
1591 if raw_type := g.get_raw_type(node.lhs) {
1592 is_iface := raw_type is types.Interface
1593 || (raw_type is types.Pointer && raw_type.base_type is types.Interface)
1594 if is_iface {
1595 mut rhs_name := ''
1596 if node.rhs is ast.Ident {
1597 rhs_name = node.rhs.name
1598 } else if node.rhs is ast.SelectorExpr {
1599 rhs_name = node.rhs.rhs.name
1600 }
1601 type_id := interface_type_id_for_name(rhs_name)
1602 if type_id <= 0 {
1603 g.sb.write_string(if node.op in [.key_is, .eq] {
1604 'false'
1605 } else {
1606 'true'
1607 })
1608 return
1609 }
1610 sep := if g.expr_is_pointer(node.lhs) { '->' } else { '.' }
1611 g.sb.write_string('(')
1612 g.expr(node.lhs)
1613 op := if node.op in [.key_is, .eq] { '==' } else { '!=' }
1614 g.sb.write_string('${sep}_type_id ${op} ${type_id})')
1615 return
1616 }
1617 }
1618 }
1619 // Sum type checks: expr is Type and lowered expr ==/!= Type.
1620 rhs_can_match_sum := node.rhs is ast.Ident
1621 || (node.rhs is ast.SelectorExpr && node.rhs.lhs is ast.Ident)
1622 || node.rhs is ast.Type
1623 if node.op in [.key_is, .not_is] || (node.op in [.eq, .ne] && rhs_can_match_sum) {
1624 sum_lhs := strip_expr_wrappers(node.lhs)
1625 mut rhs_name := ''
1626 if node.rhs is ast.Ident {
1627 rhs_name = node.rhs.name
1628 } else if node.rhs is ast.SelectorExpr && node.rhs.lhs is ast.Ident {
1629 rhs_name = '${(node.rhs.lhs as ast.Ident).name}__${node.rhs.rhs.name}'
1630 } else if node.rhs is ast.Type {
1631 rhs_name = g.expr_type_to_c(node.rhs)
1632 }
1633 if rhs_name != '' {
1634 mut lhs_sum_type := g.get_expr_type(sum_lhs)
1635 if raw_lhs := g.get_raw_type(sum_lhs) {
1636 match raw_lhs {
1637 types.SumType {
1638 lhs_sum_type = g.types_type_to_c(raw_lhs)
1639 }
1640 types.Pointer {
1641 if raw_lhs.base_type is types.SumType {
1642 lhs_sum_type = g.types_type_to_c(raw_lhs.base_type)
1643 }
1644 }
1645 types.Alias {
1646 if raw_lhs.base_type is types.SumType {
1647 lhs_sum_type = g.types_type_to_c(raw_lhs.base_type)
1648 }
1649 }
1650 else {}
1651 }
1652 }
1653 if lhs_sum_type == '' {
1654 if lhs_env_type := g.get_expr_type_from_env(sum_lhs) {
1655 lhs_sum_type = lhs_env_type
1656 }
1657 }
1658 if lhs_sum_type == '' && sum_lhs is ast.SelectorExpr {
1659 lhs_sum_type = g.selector_field_type(sum_lhs)
1660 }
1661 lhs_sum_type = lhs_sum_type.trim_space().trim_right('*')
1662 if sum_lhs is ast.SelectorExpr {
1663 mut storage_candidates := []string{}
1664 mut selector_has_same_sumtype_storage := false
1665 for candidate in [
1666 g.selector_storage_field_type(sum_lhs).trim_space().trim_right('*'),
1667 g.selector_declared_field_type(sum_lhs).trim_space().trim_right('*'),
1668 ] {
1669 if candidate == '' {
1670 continue
1671 }
1672 if candidate == lhs_sum_type {
1673 selector_has_same_sumtype_storage = true
1674 continue
1675 }
1676 if candidate !in storage_candidates
1677 && g.get_sum_type_variants_for(candidate).len > 0 {
1678 storage_candidates << candidate
1679 }
1680 }
1681 for storage_sum_type in storage_candidates {
1682 if path := g.sum_variant_tag_path(storage_sum_type, rhs_name, []string{}) {
1683 if path.len <= 1 {
1684 continue
1685 }
1686 g.gen_sum_variant_tag_path_check(sum_lhs, path, node.op in [
1687 .key_is,
1688 .eq,
1689 ])
1690 return
1691 }
1692 }
1693 if !selector_has_same_sumtype_storage {
1694 if path := g.nested_sum_path_from_narrowed_source(lhs_sum_type, rhs_name) {
1695 if path.len > 1 {
1696 g.gen_sum_variant_tag_path_check(sum_lhs, path, node.op in [
1697 .key_is,
1698 .eq,
1699 ])
1700 return
1701 }
1702 }
1703 }
1704 }
1705 mut variants := []string{}
1706 if vs := g.sum_type_variants[lhs_sum_type] {
1707 variants = vs.clone()
1708 } else if lhs_sum_type.contains('__') {
1709 short_sum := lhs_sum_type.all_after_last('__')
1710 if vs := g.sum_type_variants[short_sum] {
1711 variants = vs.clone()
1712 }
1713 } else {
1714 qualified_sum := g.get_qualified_name(lhs_sum_type)
1715 if vs := g.sum_type_variants[qualified_sum] {
1716 variants = vs.clone()
1717 }
1718 }
1719 if variants.len == 0 && sum_lhs is ast.SelectorExpr {
1720 lhs_sum_type = g.selector_field_type(sum_lhs).trim_space().trim_right('*')
1721 if lhs_sum_type != '' {
1722 if vs := g.sum_type_variants[lhs_sum_type] {
1723 variants = vs.clone()
1724 } else if lhs_sum_type.contains('__') {
1725 short_sum := lhs_sum_type.all_after_last('__')
1726 if vs := g.sum_type_variants[short_sum] {
1727 variants = vs.clone()
1728 }
1729 } else {
1730 qualified_sum := g.get_qualified_name(lhs_sum_type)
1731 if vs := g.sum_type_variants[qualified_sum] {
1732 variants = vs.clone()
1733 }
1734 }
1735 }
1736 }
1737 if variants.len > 0 {
1738 mut tag := -1
1739 for i, v in variants {
1740 if sum_type_variant_matches(v, rhs_name) {
1741 tag = i
1742 break
1743 }
1744 }
1745 if tag >= 0 {
1746 sep := if g.expr_is_pointer(sum_lhs) { '->' } else { '.' }
1747 op := if node.op in [.key_is, .eq] { '==' } else { '!=' }
1748 g.sb.write_string('(')
1749 g.expr(sum_lhs)
1750 g.sb.write_string('${sep}_tag ${op} ${tag})')
1751 return
1752 }
1753 if path := g.sum_variant_tag_path(lhs_sum_type, rhs_name, []string{}) {
1754 g.gen_sum_variant_tag_path_check(sum_lhs, path, node.op in [
1755 .key_is,
1756 .eq,
1757 ])
1758 return
1759 }
1760 }
1761 // Fallback: interface is-check when get_raw_type didn't resolve
1762 // (e.g. through smartcast chains like child.layout is Widget)
1763 if node.op in [.key_is, .not_is] && variants.len == 0 {
1764 mut field_type := lhs_sum_type
1765 if field_type == '' && sum_lhs is ast.SelectorExpr {
1766 field_type = g.selector_field_type(sum_lhs)
1767 }
1768 if field_type != '' {
1769 base_ft := field_type.trim_right('*')
1770 if g.is_interface_type(base_ft) {
1771 type_id := interface_type_id_for_name(rhs_name)
1772 if type_id > 0 {
1773 sep := if g.expr_is_pointer(sum_lhs) { '->' } else { '.' }
1774 op := if node.op == .key_is { '==' } else { '!=' }
1775 g.sb.write_string('(')
1776 g.expr(sum_lhs)
1777 g.sb.write_string('${sep}_type_id ${op} ${type_id})')
1778 return
1779 }
1780 }
1781 }
1782 }
1783 }
1784 }
1785 if node.op == .left_shift {
1786 is_array_append, elem_type := g.array_append_elem_type(node.lhs, node.rhs)
1787 if is_array_append {
1788 if node.lhs is ast.IndexExpr
1789 && g.gen_map_index_array_append(node.lhs, node.rhs, elem_type) {
1790 return
1791 }
1792 if g.expr_is_array_value(node.rhs) {
1793 rhs_tmp := '_arr_append_tmp_${g.tmp_counter}'
1794 g.tmp_counter++
1795 arr_rhs_type := g.expr_array_runtime_type(node.rhs)
1796 g.sb.write_string('({ ${arr_rhs_type} ${rhs_tmp} = ')
1797 g.expr(node.rhs)
1798 g.sb.write_string('; array__push_many((array*)')
1799 g.gen_array_append_target(node.lhs)
1800 g.sb.write_string(', ${rhs_tmp}.data, ${rhs_tmp}.len); })')
1801 return
1802 }
1803 g.sb.write_string('array__push((array*)')
1804 g.gen_array_append_target(node.lhs)
1805 g.sb.write_string(', ')
1806 if elem_type == 'string' {
1807 g.sb.write_string('&(string[1]){ string__clone(')
1808 g.expr(node.rhs)
1809 g.sb.write_string(') }')
1810 } else {
1811 g.gen_array_push_elem_arg(node.rhs, elem_type)
1812 }
1813 g.sb.write_string(')')
1814 return
1815 }
1816 }
1817 if node.op == .plus && lhs_type == 'string' && rhs_type == 'string' {
1818 g.sb.write_string('string__plus(')
1819 g.expr(node.lhs)
1820 g.sb.write_string(', ')
1821 g.expr(node.rhs)
1822 g.sb.write_string(')')
1823 return
1824 }
1825 if node.op in [.key_in, .not_in] {
1826 if node.rhs is ast.ArrayInitExpr {
1827 join_op := if node.op == .key_in { ' || ' } else { ' && ' }
1828 g.sb.write_string('(')
1829 if node.rhs.exprs.len == 0 {
1830 g.sb.write_string(if node.op == .key_in { 'false' } else { 'true' })
1831 } else {
1832 for i in 0 .. node.rhs.exprs.len {
1833 elem := node.rhs.exprs[i]
1834 if i > 0 {
1835 g.sb.write_string(join_op)
1836 }
1837 if lhs_type == 'string' {
1838 if node.op == .not_in {
1839 g.sb.write_string('!')
1840 }
1841 g.sb.write_string('string__eq(')
1842 g.expr(node.lhs)
1843 g.sb.write_string(', ')
1844 g.expr(elem)
1845 g.sb.write_string(')')
1846 } else {
1847 cmp_op := if node.op == .key_in { '==' } else { '!=' }
1848 g.sb.write_string('(')
1849 g.expr(node.lhs)
1850 g.sb.write_string(' ${cmp_op} ')
1851 g.expr(elem)
1852 g.sb.write_string(')')
1853 }
1854 }
1855 }
1856 g.sb.write_string(')')
1857 return
1858 }
1859 if rhs_type == 'map' || rhs_type.starts_with('Map_') {
1860 mut key_type := if lhs_type == '' { 'int' } else { lhs_type }
1861 if raw_map_type := g.get_raw_type(node.rhs) {
1862 if raw_map_type is types.Map {
1863 kt := g.types_type_to_c(raw_map_type.key_type)
1864 if kt != '' {
1865 key_type = kt
1866 }
1867 }
1868 } else if rhs_type.starts_with('Map_') {
1869 kv := rhs_type.all_after('Map_')
1870 kt, _ := g.parse_map_kv_types(kv)
1871 if kt != '' {
1872 key_type = kt
1873 }
1874 }
1875 if key_type == 'bool' && node.lhs is ast.BasicLiteral {
1876 key_type = 'int'
1877 }
1878 if node.op == .not_in {
1879 g.sb.write_string('!')
1880 }
1881 if node.rhs is ast.Ident || node.rhs is ast.SelectorExpr {
1882 g.sb.write_string('map__exists(&')
1883 g.expr(node.rhs)
1884 g.sb.write_string(', ')
1885 g.gen_addr_of_expr(node.lhs, key_type)
1886 g.sb.write_string(')')
1887 } else {
1888 tmp_name := '_map_in_tmp_${g.tmp_counter}'
1889 g.tmp_counter++
1890 g.sb.write_string('({ map ${tmp_name} = ')
1891 g.expr(node.rhs)
1892 g.sb.write_string('; map__exists(&${tmp_name}, ')
1893 g.gen_addr_of_expr(node.lhs, key_type)
1894 g.sb.write_string('); })')
1895 }
1896 return
1897 }
1898 if rhs_type.starts_with('Array_fixed_') {
1899 // Fixed-size array: use a linear scan via statement expression.
1900 tmp := '_fixed_in_${g.tmp_counter}'
1901 g.tmp_counter++
1902 g.sb.write_string('({ bool ${tmp} = false; for (int _i = 0; _i < (int)(sizeof(')
1903 g.expr(node.rhs)
1904 g.sb.write_string(')/sizeof(')
1905 g.expr(node.rhs)
1906 g.sb.write_string('[0])); _i++) { if (')
1907 g.expr(node.rhs)
1908 g.sb.write_string('[_i] == ')
1909 g.expr(node.lhs)
1910 g.sb.write_string(') { ${tmp} = true; break; } } ')
1911 if node.op == .not_in {
1912 g.sb.write_string('!${tmp}')
1913 } else {
1914 g.sb.write_string(tmp)
1915 }
1916 g.sb.write_string('; })')
1917 return
1918 }
1919 if rhs_type == 'array' || rhs_type.starts_with('Array_') {
1920 key_type := if lhs_type == '' { 'int' } else { lhs_type }
1921 if node.op == .not_in {
1922 g.sb.write_string('!')
1923 }
1924 g.sb.write_string('array__contains(')
1925 g.expr(node.rhs)
1926 g.sb.write_string(', ')
1927 g.gen_addr_of_expr(node.lhs, key_type)
1928 g.sb.write_string(')')
1929 return
1930 }
1931 cmp_op := if node.op == .key_in { '==' } else { '!=' }
1932 g.sb.write_string('(')
1933 g.expr(node.lhs)
1934 g.sb.write_string(' ${cmp_op} ')
1935 g.expr(node.rhs)
1936 g.sb.write_string(')')
1937 return
1938 }
1939 is_lhs_fixed := lhs_type.starts_with('Array_fixed_')
1940 is_rhs_fixed := rhs_type.starts_with('Array_fixed_')
1941 if node.op in [.eq, .ne] && (is_lhs_fixed || is_rhs_fixed) {
1942 fixed_type := if is_lhs_fixed { lhs_type } else { rhs_type }
1943 cmp_op := if node.op == .eq { '==' } else { '!=' }
1944 g.sb.write_string('(memcmp(')
1945 g.gen_fixed_array_cmp_operand(node.lhs, fixed_type)
1946 g.sb.write_string(', ')
1947 g.gen_fixed_array_cmp_operand(node.rhs, fixed_type)
1948 g.sb.write_string(', sizeof(${fixed_type})) ${cmp_op} 0)')
1949 return
1950 }
1951 is_lhs_array := lhs_type == 'array'
1952 || (lhs_type.starts_with('Array_') && !lhs_type.starts_with('Array_fixed_'))
1953 || lhs_type in g.array_aliases
1954 is_rhs_array := rhs_type == 'array'
1955 || (rhs_type.starts_with('Array_') && !rhs_type.starts_with('Array_fixed_'))
1956 || rhs_type in g.array_aliases
1957 if node.op in [.eq, .ne] && is_lhs_array && is_rhs_array {
1958 if node.op == .ne {
1959 g.sb.write_string('!')
1960 }
1961 g.sb.write_string('__v2_array_eq(')
1962 if g.expr_is_pointer(node.lhs) {
1963 g.sb.write_string('*')
1964 }
1965 g.expr(node.lhs)
1966 g.sb.write_string(', ')
1967 if g.expr_is_pointer(node.rhs) {
1968 g.sb.write_string('*')
1969 }
1970 g.expr(node.rhs)
1971 g.sb.write_string(')')
1972 return
1973 }
1974 mut lhs_is_string_ptr := false
1975 lhs_local_type := g.local_var_c_type_for_expr(node.lhs) or { '' }
1976 lhs_may_need_raw_string_ptr := lhs_type == '' || lhs_type.ends_with('*')
1977 || lhs_type.ends_with('ptr') || lhs_local_type.ends_with('*')
1978 || lhs_local_type.ends_with('ptr')
1979 if lhs_may_need_raw_string_ptr {
1980 if raw_type := g.get_raw_type(node.lhs) {
1981 if raw_type is types.Pointer && raw_type.base_type is types.String {
1982 lhs_is_string_ptr = true
1983 }
1984 }
1985 }
1986 if lhs_type in ['string*', 'stringptr'] || lhs_local_type in ['string*', 'stringptr'] {
1987 lhs_is_string_ptr = true
1988 }
1989 mut rhs_is_string_ptr := false
1990 rhs_local_type := g.local_var_c_type_for_expr(node.rhs) or { '' }
1991 rhs_may_need_raw_string_ptr := rhs_type == '' || rhs_type.ends_with('*')
1992 || rhs_type.ends_with('ptr') || rhs_local_type.ends_with('*')
1993 || rhs_local_type.ends_with('ptr')
1994 if rhs_may_need_raw_string_ptr {
1995 if raw_type := g.get_raw_type(node.rhs) {
1996 if raw_type is types.Pointer && raw_type.base_type is types.String {
1997 rhs_is_string_ptr = true
1998 }
1999 }
2000 }
2001 if rhs_type in ['string*', 'stringptr'] || rhs_local_type in ['string*', 'stringptr'] {
2002 rhs_is_string_ptr = true
2003 }
2004 mut may_be_string_cmp := false
2005 if lhs_type == 'string' {
2006 may_be_string_cmp = true
2007 }
2008 if rhs_type == 'string' {
2009 may_be_string_cmp = true
2010 }
2011 if lhs_is_string_ptr {
2012 may_be_string_cmp = true
2013 }
2014 if rhs_is_string_ptr {
2015 may_be_string_cmp = true
2016 }
2017 if lhs_local_type == 'string' {
2018 may_be_string_cmp = true
2019 }
2020 if rhs_local_type == 'string' {
2021 may_be_string_cmp = true
2022 }
2023 mut is_string_cmp2 := false
2024 if may_be_string_cmp {
2025 // When either side is nil/0, this is a pointer comparison, not a string comparison
2026 mut lhs_is_nil := false
2027 mut rhs_is_nil := false
2028 if node.op in [.eq, .ne] {
2029 lhs_is_nil = is_nil_like_expr(node.lhs)
2030 rhs_is_nil = is_nil_like_expr(node.rhs)
2031 }
2032 // Also treat integer-typed operands as nil when compared with string pointers
2033 // (e.g., `&string == 0` where checker annotates 0 as int)
2034 lhs_is_nil2 := lhs_is_nil
2035 || (rhs_is_string_ptr && lhs_type in ['int', 'int_literal', 'i64', 'u64', 'voidptr'])
2036 rhs_is_nil2 := rhs_is_nil
2037 || (lhs_is_string_ptr && rhs_type in ['int', 'int_literal', 'i64', 'u64', 'voidptr'])
2038 is_string_cmp := if lhs_is_nil2 || rhs_is_nil2 {
2039 false
2040 } else if node.lhs is ast.StringLiteral || node.rhs is ast.StringLiteral {
2041 true
2042 } else {
2043 (lhs_type == 'string' || lhs_is_string_ptr)
2044 && (rhs_type == 'string' || rhs_is_string_ptr) && !g.is_enum_type(lhs_type)
2045 && !g.is_enum_type(rhs_type)
2046 }
2047 // Override: string pointer compared with a numeric literal (null check)
2048 // The checker may annotate `0` as `string` in `&string == 0` context,
2049 // but this is a pointer comparison, not a string comparison.
2050 is_string_cmp2 = if is_string_cmp && ((lhs_is_string_ptr && is_numeric_literal(node.rhs))
2051 || (rhs_is_string_ptr && is_numeric_literal(node.lhs))) {
2052 false
2053 } else {
2054 is_string_cmp
2055 }
2056 }
2057 if node.op in [.eq, .ne] && is_string_cmp2 {
2058 if node.op == .ne {
2059 g.sb.write_string('!')
2060 }
2061 g.sb.write_string('string__eq(')
2062 g.gen_string_cmp_operand(node.lhs, lhs_is_string_ptr)
2063 g.sb.write_string(', ')
2064 g.gen_string_cmp_operand(node.rhs, rhs_is_string_ptr)
2065 g.sb.write_string(')')
2066 return
2067 }
2068 if node.op in [.lt, .le, .gt, .ge] && is_string_cmp2 {
2069 op := match node.op {
2070 .gt { '>' }
2071 .lt { '<' }
2072 .ge { '>=' }
2073 .le { '<=' }
2074 else { '==' }
2075 }
2076
2077 g.sb.write_string('(string__compare(')
2078 g.gen_string_cmp_operand(node.lhs, lhs_is_string_ptr)
2079 g.sb.write_string(', ')
2080 g.gen_string_cmp_operand(node.rhs, rhs_is_string_ptr)
2081 g.sb.write_string(') ${op} 0)')
2082 return
2083 }
2084 if node.op in [.lt, .le, .gt, .ge] && lhs_type in primitive_types && rhs_type in primitive_types {
2085 op := match node.op {
2086 .gt { '>' }
2087 .lt { '<' }
2088 .ge { '>=' }
2089 .le { '<=' }
2090 else { '==' }
2091 }
2092
2093 g.sb.write_string('(')
2094 g.expr(node.lhs)
2095 g.sb.write_string(' ${op} ')
2096 g.expr(node.rhs)
2097 g.sb.write_string(')')
2098 return
2099 }
2100 if node.op in [.eq, .ne] && (lhs_type == 'map' || lhs_type.starts_with('Map_'))
2101 && (rhs_type == 'map' || rhs_type.starts_with('Map_')) {
2102 if node.op == .ne {
2103 g.sb.write_string('!')
2104 }
2105 map_eq_fn := if lhs_type.starts_with('Map_') && lhs_type == rhs_type {
2106 '${lhs_type}_map_eq'
2107 } else {
2108 'map_map_eq'
2109 }
2110 g.sb.write_string('${map_eq_fn}(')
2111 g.expr(node.lhs)
2112 g.sb.write_string(', ')
2113 g.expr(node.rhs)
2114 g.sb.write_string(')')
2115 return
2116 }
2117 mut cmp_type := ''
2118 if g.should_use_memcmp_eq(lhs_type, rhs_type) {
2119 // Use whichever type is non-empty; prefer lhs_type
2120 cmp_type = if lhs_type != '' { lhs_type } else { rhs_type }
2121 } else if node.op in [.eq, .ne] && g.should_use_memcmp_eq(lhs_type, lhs_type)
2122 && rhs_type == 'int' && node.rhs is ast.Ident && g.is_known_struct_type(lhs_type) {
2123 // RHS Ident defaulted to 'int' (unresolved constant) but LHS is a known struct
2124 cmp_type = lhs_type
2125 } else if node.op in [.eq, .ne] && g.should_use_memcmp_eq(rhs_type, rhs_type)
2126 && lhs_type == 'int' && node.lhs is ast.Ident && g.is_known_struct_type(rhs_type) {
2127 // LHS Ident defaulted to 'int' (unresolved constant) but RHS is a known struct
2128 cmp_type = rhs_type
2129 } else if node.op in [.eq, .ne] {
2130 lhs_cast_type := extract_compare_cast_type(node.lhs)
2131 rhs_cast_type := extract_compare_cast_type(node.rhs)
2132 if lhs_cast_type != '' && rhs_cast_type == '' {
2133 cmp_type = lhs_cast_type
2134 } else if rhs_cast_type != '' && lhs_cast_type == '' {
2135 cmp_type = rhs_cast_type
2136 } else if lhs_cast_type != '' && lhs_cast_type == rhs_cast_type {
2137 cmp_type = lhs_cast_type
2138 }
2139 if cmp_type in primitive_types || cmp_type == 'string' || cmp_type.ends_with('*')
2140 || cmp_type.ends_with('ptr') {
2141 cmp_type = ''
2142 }
2143 }
2144 if node.op in [.eq, .ne] && cmp_type != '' {
2145 ltmp := '_cmp_l_${g.tmp_counter}'
2146 g.tmp_counter++
2147 rtmp := '_cmp_r_${g.tmp_counter}'
2148 g.tmp_counter++
2149 g.sb.write_string('({ ${cmp_type} ${ltmp} = ')
2150 if !g.gen_unwrapped_value_expr(node.lhs) {
2151 g.expr(node.lhs)
2152 }
2153 g.sb.write_string('; ${cmp_type} ${rtmp} = ')
2154 if !g.gen_unwrapped_value_expr(node.rhs) {
2155 g.expr(node.rhs)
2156 }
2157 struct_type := g.lookup_struct_type_by_c_name(cmp_type)
2158 if struct_type.fields.len > 0 && g.struct_has_ref_fields(struct_type) {
2159 eq_expr := g.gen_struct_field_eq_expr(struct_type, ltmp, rtmp)
2160 if node.op == .eq {
2161 g.sb.write_string('; ${eq_expr}; })')
2162 } else {
2163 g.sb.write_string('; !(${eq_expr}); })')
2164 }
2165 } else {
2166 cmp_op := if node.op == .eq { '== 0' } else { '!= 0' }
2167 g.sb.write_string('; memcmp(&${ltmp}, &${rtmp}, sizeof(${cmp_type})) ${cmp_op}; })')
2168 }
2169 return
2170 }
2171 method_fn, operand_type := g.overloaded_arithmetic_method_for_infix(*node, lhs_type, rhs_type)
2172 if method_fn != '' {
2173 g.sb.write_string('${method_fn}(')
2174 g.gen_overloaded_arithmetic_arg(node.lhs, lhs_type, operand_type)
2175 g.sb.write_string(', ')
2176 g.gen_overloaded_arithmetic_arg(node.rhs, rhs_type, operand_type)
2177 g.sb.write_string(')')
2178 return
2179 }
2180 // Comparison operators (<, <=, >, >=) on struct types with overloaded `<` operator.
2181 // V derives: a < b → Type__lt(a, b), a > b → Type__lt(b, a),
2182 // a <= b → !Type__lt(b, a), a >= b → !Type__lt(a, b)
2183 if node.op in [.lt, .le, .gt, .ge] && lhs_type != '' && rhs_type != '' && lhs_type == rhs_type
2184 && lhs_type !in primitive_types && lhs_type != 'string' && !lhs_type.ends_with('*')
2185 && !lhs_type.ends_with('ptr') {
2186 mut lt_fn := '${lhs_type}__op_lt'
2187 if lt_fn !in g.fn_return_types && lt_fn !in g.fn_param_is_ptr {
2188 lt_fn = '${lhs_type}__lt'
2189 }
2190 if lt_fn in g.fn_return_types || lt_fn in g.fn_param_is_ptr {
2191 match node.op {
2192 .lt {
2193 g.sb.write_string('${lt_fn}(')
2194 g.expr(node.lhs)
2195 g.sb.write_string(', ')
2196 g.expr(node.rhs)
2197 g.sb.write_string(')')
2198 }
2199 .gt {
2200 g.sb.write_string('${lt_fn}(')
2201 g.expr(node.rhs)
2202 g.sb.write_string(', ')
2203 g.expr(node.lhs)
2204 g.sb.write_string(')')
2205 }
2206 .le {
2207 g.sb.write_string('!${lt_fn}(')
2208 g.expr(node.rhs)
2209 g.sb.write_string(', ')
2210 g.expr(node.lhs)
2211 g.sb.write_string(')')
2212 }
2213 .ge {
2214 g.sb.write_string('!${lt_fn}(')
2215 g.expr(node.lhs)
2216 g.sb.write_string(', ')
2217 g.expr(node.rhs)
2218 g.sb.write_string(')')
2219 }
2220 else {}
2221 }
2222
2223 return
2224 }
2225 }
2226 is_bitwise_op := node.op in [.amp, .pipe, .xor, .left_shift, .right_shift]
2227 lhs_is_float := lhs_type.starts_with('f') || lhs_type == 'float_literal'
2228 rhs_is_float := rhs_type.starts_with('f') || rhs_type == 'float_literal'
2229 if node.op == .mod && (lhs_is_float || rhs_is_float) {
2230 g.sb.write_string(if lhs_type == 'f32' || rhs_type == 'f32' { 'fmodf(' } else { 'fmod(' })
2231 if !g.gen_unwrapped_value_expr(node.lhs) {
2232 g.expr(node.lhs)
2233 }
2234 g.sb.write_string(', ')
2235 if !g.gen_unwrapped_value_expr(node.rhs) {
2236 g.expr(node.rhs)
2237 }
2238 g.sb.write_string(')')
2239 return
2240 }
2241 g.sb.write_string('(')
2242 if is_bitwise_op && lhs_is_float {
2243 g.sb.write_string('((u64)(')
2244 if !g.gen_unwrapped_value_expr(node.lhs) {
2245 g.expr(node.lhs)
2246 }
2247 g.sb.write_string('))')
2248 } else {
2249 if !g.gen_unwrapped_value_expr(node.lhs) {
2250 g.expr(node.lhs)
2251 }
2252 }
2253 op := match node.op {
2254 .plus { '+' }
2255 .minus { '-' }
2256 .mul { '*' }
2257 .div { '/' }
2258 .mod { '%' }
2259 .gt { '>' }
2260 .lt { '<' }
2261 .eq { '==' }
2262 .ne { '!=' }
2263 .ge { '>=' }
2264 .le { '<=' }
2265 .and { '&&' }
2266 .logical_or { '||' }
2267 .amp { '&' }
2268 .pipe { '|' }
2269 .xor { '^' }
2270 .left_shift { '<<' }
2271 .right_shift { '>>' }
2272 .key_is { '==' }
2273 .not_is { '!=' }
2274 .question { '==' } // match arm lowering uses ? as equality test
2275 else { '==' }
2276 }
2277
2278 g.sb.write_string(' ${op} ')
2279 if is_bitwise_op && rhs_is_float {
2280 cast_type := if node.op in [.left_shift, .right_shift] {
2281 'int'
2282 } else {
2283 'u64'
2284 }
2285 g.sb.write_string('((${cast_type})(')
2286 if !g.gen_unwrapped_value_expr(node.rhs) {
2287 g.expr(node.rhs)
2288 }
2289 g.sb.write_string('))')
2290 } else {
2291 if !g.gen_unwrapped_value_expr(node.rhs) {
2292 g.expr(node.rhs)
2293 }
2294 }
2295 g.sb.write_string(')')
2296}
2297
2298fn (mut g Gen) gen_string_cmp_operand(expr ast.Expr, is_string_ptr bool) {
2299 if is_string_ptr {
2300 g.sb.write_string('(*')
2301 g.expr(expr)
2302 g.sb.write_string(')')
2303 return
2304 }
2305 g.expr(expr)
2306}
2307
2308fn (mut g Gen) plain_index_selector_type(sel ast.SelectorExpr) ?string {
2309 if g.comptime_field_var != '' || g.comptime_method_var != '' {
2310 return none
2311 }
2312 if sel.lhs !is ast.IndexExpr {
2313 return none
2314 }
2315 index_expr := sel.lhs as ast.IndexExpr
2316 elem_type := g.index_expr_elem_type_from_lhs(index_expr) or { return none }
2317 base_type := strip_pointer_type_name(elem_type)
2318 if base_type == '' || base_type == 'string' || base_type in primitive_types
2319 || base_type in ['void', 'void*', 'voidptr'] {
2320 return none
2321 }
2322 field_name := sel.rhs.name
2323 if field_name == '' {
2324 return none
2325 }
2326 return g.lookup_struct_field_type_by_name(base_type, field_name)
2327}
2328
2329fn (mut g Gen) gen_plain_index_selector_expr(sel ast.SelectorExpr) bool {
2330 if g.comptime_field_var != '' || g.comptime_method_var != '' {
2331 return false
2332 }
2333 if sel.lhs !is ast.IndexExpr {
2334 return false
2335 }
2336 index_expr := sel.lhs as ast.IndexExpr
2337 elem_type := g.index_expr_elem_type_from_lhs(index_expr) or { return false }
2338 base_type := strip_pointer_type_name(elem_type)
2339 if base_type == '' || base_type == 'string' || base_type in primitive_types
2340 || base_type in ['void', 'void*', 'voidptr'] {
2341 return false
2342 }
2343 field_name := sel.rhs.name
2344 if field_name == '' {
2345 return false
2346 }
2347 _ = g.plain_index_selector_type(sel) or { return false }
2348 g.expr(index_expr)
2349 selector := if elem_type.ends_with('*') { '->' } else { '.' }
2350 g.sb.write_string('${selector}${escape_c_keyword(field_name)}')
2351 return true
2352}
2353
2354fn (mut g Gen) gen_plain_local_selector_expr(sel ast.SelectorExpr) bool {
2355 if g.comptime_field_var != '' || g.comptime_method_var != '' {
2356 return false
2357 }
2358 parts := cleanc_selector_expr_parts(sel)
2359 if parts.len < 2 {
2360 return false
2361 }
2362 root := parts[0]
2363 if root == '' {
2364 return false
2365 }
2366 mut cur_type := g.runtime_local_types[root] or { return false }
2367 mut segments := []string{cap: parts.len - 1}
2368 for i in 1 .. parts.len {
2369 field_name := parts[i]
2370 if field_name == '' {
2371 return false
2372 }
2373 base_type := strip_pointer_type_name(cur_type)
2374 if base_type == '' || base_type == 'string' || base_type in primitive_types
2375 || base_type in ['void', 'void*', 'voidptr'] || base_type in g.sum_type_variants
2376 || g.is_interface_type(base_type) {
2377 return false
2378 }
2379 if cur_type.starts_with('_option_') || cur_type.starts_with('_result_') {
2380 return false
2381 }
2382 if base_type.starts_with('Array_fixed_') && field_name == 'len' {
2383 if i != parts.len - 1 {
2384 return false
2385 }
2386 _, fixed_len := parse_fixed_array_elem_type(base_type)
2387 g.sb.write_string('${fixed_len}')
2388 return true
2389 }
2390 field_type := g.lookup_struct_field_type_by_name(base_type, field_name) or { return false }
2391 if field_type == '' {
2392 return false
2393 }
2394 use_ptr := cur_type.ends_with('*') || cur_type == 'chan'
2395 || (i == 1 && root in g.cur_fn_mut_params)
2396 selector := if use_ptr { '->' } else { '.' }
2397 owner := g.embedded_owner_for(base_type, field_name)
2398 field_c_name := escape_c_keyword(field_name)
2399 if owner != '' {
2400 segments << '${selector}${escape_c_keyword(owner)}.${field_c_name}'
2401 } else {
2402 segments << '${selector}${field_c_name}'
2403 }
2404 cur_type = field_type
2405 }
2406 g.sb.write_string(c_local_name(root))
2407 for segment in segments {
2408 g.sb.write_string(segment)
2409 }
2410 return true
2411}
2412
2413fn (mut g Gen) plain_local_selector_type(sel ast.SelectorExpr) ?string {
2414 if g.comptime_field_var != '' || g.comptime_method_var != '' {
2415 return none
2416 }
2417 parts := cleanc_selector_expr_parts(sel)
2418 if parts.len < 2 {
2419 return none
2420 }
2421 root := parts[0]
2422 if root == '' {
2423 return none
2424 }
2425 mut cur_type := g.runtime_local_types[root] or { return none }
2426 for i in 1 .. parts.len {
2427 field_name := parts[i]
2428 if field_name == '' {
2429 return none
2430 }
2431 base_type := strip_pointer_type_name(cur_type)
2432 if base_type == '' || base_type == 'string' || base_type in primitive_types
2433 || base_type in ['void', 'void*', 'voidptr'] || base_type in g.sum_type_variants
2434 || g.is_interface_type(base_type) {
2435 return none
2436 }
2437 if cur_type.starts_with('_option_') || cur_type.starts_with('_result_') {
2438 return none
2439 }
2440 cur_type = g.lookup_struct_field_type_by_name(base_type, field_name) or { return none }
2441 }
2442 return cur_type
2443}
2444
2445// Helper to extract FnType from an Expr (handles ast.Type wrapping)
2446fn (mut g Gen) expr(node ast.Expr) {
2447 if node is ast.SelectorExpr && g.gen_plain_index_selector_expr(node) {
2448 return
2449 }
2450 if node is ast.SelectorExpr && g.gen_plain_local_selector_expr(node) {
2451 return
2452 }
2453 if !expr_has_valid_data(node) {
2454 g.sb.write_string('0 /* corrupt expr */')
2455 return
2456 }
2457 if node is ast.CallExpr && g.try_gen_orm_create_call_expr(node) {
2458 return
2459 }
2460 match node {
2461 ast.BasicLiteral {
2462 if node.kind == .key_true {
2463 g.sb.write_string('true')
2464 } else if node.kind == .key_false {
2465 g.sb.write_string('false')
2466 } else if node.kind == .char {
2467 mut raw_value := ''
2468 raw_value = strip_literal_quotes(node.value)
2469 if raw_value.len > 1 && raw_value[0] != `\\` {
2470 // Multi-byte UTF-8 character: emit as numeric codepoint
2471 runes := raw_value.runes()
2472 if runes.len > 0 {
2473 g.sb.write_string(int(runes[0]).str())
2474 } else {
2475 g.sb.write_string("'${raw_value}'")
2476 }
2477 } else {
2478 escaped := escape_char_literal_content(raw_value)
2479 g.sb.write_u8(`'`)
2480 g.sb.write_string(escaped)
2481 g.sb.write_u8(`'`)
2482 }
2483 } else {
2484 g.sb.write_string(sanitize_c_number_literal(node.value))
2485 }
2486 }
2487 ast.StringLiteral {
2488 mut val := strip_literal_quotes(node.value)
2489 if node.kind == .raw {
2490 // Raw strings: backslash is literal, escape it for C
2491 val = val.replace('\\', '\\\\')
2492 } else {
2493 // Process V line continuations: `\` + newline + whitespace → strip
2494 val = process_line_continuations(val)
2495 }
2496 c_lit := c_string_literal_content_to_c(val)
2497 if node.kind == .c {
2498 // C string literal: emit raw C string
2499 g.sb.write_string(c_lit)
2500 } else {
2501 // Use sizeof on the emitted C literal so escape sequences
2502 // (e.g. `\t`) get the correct runtime byte length.
2503 g.sb.write_string(c_static_v_string_expr_from_c_literal(c_lit))
2504 }
2505 }
2506 ast.LifetimeExpr {
2507 g.sb.write_string('lt__')
2508 g.sb.write_string(node.name)
2509 }
2510 ast.Ident {
2511 g.mark_needed_ierror_wrapper_from_ident(node.name)
2512 if node.name == 'nil' {
2513 g.sb.write_string('NULL')
2514 } else if node.name == '@FN' || node.name == '@METHOD' || node.name == '@FUNCTION' {
2515 fn_name := g.cur_fn_name
2516 g.sb.write_string(c_static_v_string_expr(fn_name))
2517 } else if node.name == '@LOCATION' {
2518 fn_name := g.cur_fn_name
2519 g.sb.write_string(c_static_v_string_expr('${g.cur_module}.${fn_name}'))
2520 } else if node.name == '@MOD' {
2521 mod_name := g.cur_module
2522 g.sb.write_string(c_static_v_string_expr(mod_name))
2523 } else if node.name == '@FILE' {
2524 g.sb.write_string(c_v_string_expr_from_ptr_len('__FILE__', 'sizeof(__FILE__)-1',
2525 true))
2526 } else if node.name == '@LINE' {
2527 g.sb.write_string('__LINE__')
2528 } else if node.name == '@VCURRENTHASH' || node.name == '@VHASH' {
2529 g.sb.write_string(c_static_v_string_expr(g.get_v_hash()))
2530 } else if node.name == '@VEXE' {
2531 g.sb.write_string(c_empty_v_string_expr())
2532 } else if node.name == '@VEXEROOT' || node.name == '@VROOT' {
2533 vroot := if g.pref != unsafe { nil } { g.pref.vroot } else { '' }
2534 g.sb.write_string(c_static_v_string_expr(vroot))
2535 } else if node.name.starts_with('__type_id_') {
2536 type_name := node.name['__type_id_'.len..]
2537 type_id := interface_type_id_for_name(type_name)
2538 g.sb.write_string('${type_id}')
2539 } else {
2540 is_local_var := g.get_local_var_c_type(node.name) != none
2541 mut handled_ident := false
2542 if !is_local_var {
2543 if c_name := g.renamed_const_name_for_ident(node.name) {
2544 g.sb.write_string(c_name)
2545 handled_ident = true
2546 }
2547 }
2548 if handled_ident {
2549 return
2550 }
2551 const_key := 'const_${g.cur_module}__${node.name}'
2552 global_key := 'global_${g.cur_module}__${node.name}'
2553 module_storage_name := module_storage_c_name(g.cur_module, node.name)
2554 if !is_local_var && node.name in g.global_var_types
2555 && module_storage_name !in g.global_var_types {
2556 g.sb.write_string(node.name)
2557 } else if !is_local_var && module_storage_name in g.c_extern_module_storage {
2558 g.sb.write_string(g.c_extern_module_storage[module_storage_name])
2559 } else if !is_local_var && module_storage_name in g.module_storage_vars {
2560 g.sb.write_string(module_storage_name)
2561 } else if g.cur_module != '' && g.cur_module != 'main' && g.cur_module != 'builtin'
2562 && !node.name.contains('__') && !is_local_var
2563 && ((const_key in g.emitted_types || global_key in g.emitted_types)
2564 || g.is_module_local_const_or_global(node.name)) {
2565 g.sb.write_string('${g.cur_module}__${node.name}')
2566 } else if g.cur_module != '' && g.cur_module != 'main' && g.cur_module != 'builtin'
2567 && !node.name.contains('__') && !is_local_var && !g.is_module_ident(node.name)
2568 && g.is_module_local_fn(node.name) && !g.is_type_name(node.name) {
2569 g.sb.write_string('${g.cur_module}__${sanitize_fn_ident(node.name)}')
2570 } else {
2571 mut ident_name := node.name
2572 if g.cur_module != '' {
2573 double_prefix := '${g.cur_module}__${g.cur_module}__'
2574 if ident_name.starts_with(double_prefix) {
2575 ident_name = ident_name[g.cur_module.len + 2..]
2576 }
2577 }
2578 g.sb.write_string(c_local_name(ident_name))
2579 }
2580 }
2581 }
2582 ast.ParenExpr {
2583 g.sb.write_string('(')
2584 g.expr(node.expr)
2585 g.sb.write_string(')')
2586 }
2587 ast.InfixExpr {
2588 g.gen_infix_expr(&node)
2589 }
2590 ast.PrefixExpr {
2591 if node.op == .arrow && g.gen_channel_receive_expr(node) {
2592 return
2593 }
2594 // &T(x) in unsafe contexts is used as a pointer cast in V stdlib code.
2595 // Emit it as (T*)(x) so `*unsafe { &T(p) }` becomes `*((T*)p)`.
2596 if node.op == .amp {
2597 if is_nil_like_expr(node.expr) {
2598 g.sb.write_string('NULL')
2599 return
2600 }
2601 if node.expr is ast.CastExpr {
2602 cast_expr := node.expr as ast.CastExpr
2603 expr_type := g.get_expr_type(cast_expr.expr)
2604 if (expr_type.starts_with('_option_') && option_value_type(expr_type) != '')
2605 || (expr_type.starts_with('_result_')
2606 && g.result_value_type(expr_type) != '') {
2607 if expr_type.starts_with('_option_')
2608 && g.cur_fn_ret_type.starts_with('_option_') {
2609 g.sb.write_string('({ if (')
2610 g.expr(cast_expr.expr)
2611 g.sb.write_string('.state != 0) { return (${g.cur_fn_ret_type}){ .state = 2 }; } &')
2612 g.gen_unwrapped_value_expr(cast_expr.expr)
2613 g.sb.write_string('; })')
2614 return
2615 }
2616 g.sb.write_string('&')
2617 g.gen_unwrapped_value_expr(cast_expr.expr)
2618 return
2619 }
2620 }
2621 // &&T(x) is a pointer-to-pointer cast pattern used in builtin code.
2622 // Lower it directly to (T**)(x) instead of taking address of a cast rvalue.
2623 if node.expr is ast.PrefixExpr && node.expr.op == .amp {
2624 inner := node.expr as ast.PrefixExpr
2625 if inner.expr is ast.CastExpr {
2626 target_type := g.expr_type_to_c(inner.expr.typ)
2627 g.sb.write_string('((${target_type}**)(')
2628 g.expr(inner.expr.expr)
2629 g.sb.write_string('))')
2630 return
2631 }
2632 if inner.expr is ast.CallOrCastExpr
2633 && g.call_or_cast_lhs_is_type(inner.expr.lhs) {
2634 mut target_type := g.expr_type_to_c(inner.expr.lhs)
2635 if !target_type.ends_with('*') {
2636 target_type += '*'
2637 }
2638 g.sb.write_string('((${target_type}*)(')
2639 g.expr(inner.expr.expr)
2640 g.sb.write_string('))')
2641 return
2642 }
2643 }
2644 if node.expr is ast.IndexExpr {
2645 idx := node.expr as ast.IndexExpr
2646 if idx.lhs is ast.Ident {
2647 if idx.lhs.name in g.fixed_array_globals || idx.lhs.name == 'rune_maps' {
2648 g.sb.write_string('&')
2649 g.expr(idx.lhs)
2650 g.sb.write_string('[')
2651 g.expr(idx.expr)
2652 g.sb.write_string(']')
2653 return
2654 }
2655 if raw_type := g.get_raw_type(idx.lhs) {
2656 if raw_type is types.ArrayFixed {
2657 // Fixed arrays: &arr[i]
2658 g.sb.write_string('&')
2659 g.expr(idx.lhs)
2660 g.sb.write_string('[')
2661 g.expr(idx.expr)
2662 g.sb.write_string(']')
2663 return
2664 }
2665 }
2666 lhs_type := g.get_expr_type(idx.lhs)
2667 // Fixed arrays: direct C array indexing (no .data field)
2668 if lhs_type.starts_with('Array_fixed_') {
2669 g.sb.write_string('&')
2670 g.expr(idx.lhs)
2671 g.sb.write_string('[')
2672 g.expr(idx.expr)
2673 g.sb.write_string(']')
2674 return
2675 }
2676 if lhs_type == 'array' || lhs_type.starts_with('Array_') {
2677 mut elem_type := g.get_expr_type(idx)
2678 if elem_type == '' || elem_type == 'int' {
2679 if lhs_type.starts_with('Array_') {
2680 elem_type = lhs_type['Array_'.len..].trim_right('*')
2681 }
2682 }
2683 if elem_type == '' {
2684 elem_type = 'u8'
2685 }
2686 g.sb.write_string('&((')
2687 g.sb.write_string(elem_type)
2688 g.sb.write_string('*)')
2689 g.expr(idx.lhs)
2690 if lhs_type.ends_with('*') {
2691 g.sb.write_string('->data)[')
2692 } else {
2693 g.sb.write_string('.data)[')
2694 }
2695 g.expr(idx.expr)
2696 g.sb.write_string(']')
2697 return
2698 }
2699 }
2700 }
2701 addr_inner := unwrap_sum_common_field_address_base(node.expr)
2702 if addr_inner is ast.SelectorExpr {
2703 sel := addr_inner as ast.SelectorExpr
2704 if g.gen_sum_common_field_selector_addr(sel) {
2705 return
2706 }
2707 }
2708 if node.expr is ast.SelectorExpr {
2709 sel := node.expr as ast.SelectorExpr
2710 saved_sb := g.sb
2711 g.sb = strings.new_builder(128)
2712 has_cast_field_access := g.gen_c_pointer_cast_selector_field_access(sel)
2713 cast_field_access := g.sb.str()
2714 g.sb = saved_sb
2715 if has_cast_field_access {
2716 g.sb.write_string(cast_field_access)
2717 return
2718 }
2719 c_typedef := g.c_typedef_for_interface_object_access(sel)
2720 if c_typedef != '' {
2721 g.sb.write_string('&(((${c_typedef}*)(')
2722 g.expr(sel.lhs)
2723 g.sb.write_string('))->${escape_c_keyword(sel.rhs.name)})')
2724 return
2725 }
2726 field_idx := vector_field_index(sel.rhs.name)
2727 if field_idx >= 0 {
2728 mut lhs_type := g.get_expr_type(sel.lhs)
2729 if lhs_type in ['', 'int'] {
2730 if raw := g.get_raw_type(sel.lhs) {
2731 lhs_type = g.types_type_to_c(raw)
2732 }
2733 }
2734 mut elem_type := vector_elem_type_for_name(lhs_type)
2735 if elem_type == '' && sel.lhs is ast.IndexExpr {
2736 base_type := g.get_expr_type(sel.lhs.lhs)
2737 if base_type.contains('Simd') {
2738 elem_type = if base_type.contains('Float') {
2739 'f32'
2740 } else if base_type.contains('Uint') || base_type.contains('U32') {
2741 'u32'
2742 } else {
2743 'i32'
2744 }
2745 }
2746 }
2747 if elem_type != '' {
2748 g.sb.write_string('(&(((')
2749 g.sb.write_string(elem_type)
2750 g.sb.write_string('*)(&(')
2751 g.expr(sel.lhs)
2752 g.sb.write_string(')))[')
2753 g.sb.write_string(field_idx.str())
2754 g.sb.write_string(']))')
2755 return
2756 }
2757 }
2758 }
2759 if node.expr is ast.CallExpr {
2760 if node.expr.args.len == 1 && node.expr.lhs is ast.Ident
2761 && (g.is_type_name(node.expr.lhs.name)
2762 || g.is_c_type_name(node.expr.lhs.name)
2763 || node.expr.lhs.name.starts_with('cgltf_')) {
2764 mut target_type := g.expr_type_to_c(node.expr.lhs)
2765 if target_type == '' || target_type == 'int' {
2766 target_type = node.expr.lhs.name
2767 }
2768 // `&T(value)` where value is a struct rvalue (e.g. string) cannot
2769 // be lowered to `(T*)(value)` — that casts an rvalue to a pointer.
2770 // Emit a temporary copy so the address is valid. Only applies when
2771 // arg is a value (not pointer) and target_type is a non-pointer
2772 // struct alias compatible with the arg type.
2773 arg_type := g.get_expr_type(node.expr.args[0])
2774 if !target_type.ends_with('*') && !arg_type.ends_with('*')
2775 && arg_type == 'string' && g.alias_resolves_to_string(target_type) {
2776 g.tmp_counter++
2777 tmp := '_aof${g.tmp_counter}'
2778 g.sb.write_string('({ ${target_type} ${tmp} = ')
2779 g.expr(node.expr.args[0])
2780 g.sb.write_string('; &${tmp}; })')
2781 return
2782 }
2783 g.sb.write_string('((${target_type}*)(')
2784 g.expr(node.expr.args[0])
2785 g.sb.write_string('))')
2786 return
2787 }
2788 if node.expr.args.len == 1 && node.expr.lhs is ast.SelectorExpr {
2789 sel := node.expr.lhs as ast.SelectorExpr
2790 arg := node.expr.args[0]
2791 if sel.lhs is ast.Ident {
2792 lhs_ident := sel.lhs as ast.Ident
2793 if lhs_ident.name == 'C' && (g.is_c_type_name(sel.rhs.name)
2794 || sel.rhs.name.starts_with('cgltf_')
2795 || is_none_like_expr(arg)
2796 || (arg is ast.BasicLiteral && arg.value == '0')) {
2797 mut target_type := g.expr_type_to_c(node.expr.lhs)
2798 if target_type == '' || target_type == 'int' {
2799 target_type = sel.rhs.name
2800 }
2801 g.sb.write_string('((${target_type}*)(')
2802 g.expr(arg)
2803 g.sb.write_string('))')
2804 return
2805 }
2806 }
2807 }
2808 }
2809 if node.expr is ast.CastExpr {
2810 target_type := g.expr_type_to_c(node.expr.typ)
2811 // For interface types, generate vtable construction on the heap
2812 if g.gen_heap_interface_cast(target_type, node.expr.expr) {
2813 return
2814 }
2815 if g.gen_heap_address_of_cast_expr(node.expr, target_type) {
2816 return
2817 }
2818 // `&T(value)` where T aliases the value's type (e.g. NamedType=string)
2819 // cannot be lowered to `(T*)(value)` — that casts an rvalue to a pointer.
2820 // Emit a temporary copy instead. Only applies for compatible alias casts;
2821 // reinterpret casts of pointer args use the original `(T*)(v)` form.
2822 arg_type := g.get_expr_type(node.expr.expr)
2823 if !target_type.ends_with('*') && !arg_type.ends_with('*')
2824 && arg_type == 'string' && g.alias_resolves_to_string(target_type) {
2825 g.tmp_counter++
2826 tmp := '_aof${g.tmp_counter}'
2827 g.sb.write_string('({ ${target_type} ${tmp} = ')
2828 g.expr(node.expr.expr)
2829 g.sb.write_string('; &${tmp}; })')
2830 return
2831 }
2832 g.sb.write_string('((${target_type}*)(')
2833 g.expr(node.expr.expr)
2834 g.sb.write_string('))')
2835 return
2836 }
2837 if node.expr is ast.CallOrCastExpr && g.call_or_cast_lhs_is_type(node.expr.lhs) {
2838 base_target_type := g.expr_type_to_c(node.expr.lhs)
2839 // `&T(value)` where T aliases the value's type (e.g. NamedType=string)
2840 // cannot be lowered to `(T*)(value)` — that casts an rvalue to a pointer.
2841 // Emit a temporary copy instead. Only applies for compatible alias casts.
2842 arg_type := g.get_expr_type(node.expr.expr)
2843 if !base_target_type.ends_with('*') && !arg_type.ends_with('*')
2844 && arg_type == 'string' && g.alias_resolves_to_string(base_target_type) {
2845 g.tmp_counter++
2846 tmp := '_aof${g.tmp_counter}'
2847 g.sb.write_string('({ ${base_target_type} ${tmp} = ')
2848 g.expr(node.expr.expr)
2849 g.sb.write_string('; &${tmp}; })')
2850 return
2851 }
2852 mut target_type := base_target_type
2853 if !target_type.ends_with('*') {
2854 target_type += '*'
2855 }
2856 g.sb.write_string('((${target_type})(')
2857 g.expr(node.expr.expr)
2858 g.sb.write_string('))')
2859 return
2860 }
2861 if node.expr is ast.ModifierExpr {
2862 if node.expr.expr is ast.CastExpr {
2863 target_type := g.expr_type_to_c(node.expr.expr.typ)
2864 g.sb.write_string('((${target_type}*)(')
2865 g.expr(node.expr.expr.expr)
2866 g.sb.write_string('))')
2867 return
2868 }
2869 }
2870 if node.expr is ast.ParenExpr {
2871 if node.expr.expr is ast.CastExpr {
2872 target_type := g.expr_type_to_c(node.expr.expr.typ)
2873 g.sb.write_string('((${target_type}*)(')
2874 g.expr(node.expr.expr.expr)
2875 g.sb.write_string('))')
2876 return
2877 }
2878 if node.expr.expr is ast.CallExpr {
2879 if node.expr.expr.args.len == 1 {
2880 target_type := g.expr_type_to_c(node.expr.expr.lhs)
2881 g.sb.write_string('((${target_type}*)(')
2882 g.expr(node.expr.expr.args[0])
2883 g.sb.write_string('))')
2884 return
2885 }
2886 }
2887 }
2888 }
2889 // Handle &fn_call() where fn_call returns a struct (rvalue)
2890 // Can't take address of rvalue, use compound statement expression
2891 if node.op == .amp && node.expr is ast.CallExpr {
2892 if !(node.expr.args.len == 1 && node.expr.lhs is ast.Ident
2893 && g.is_type_name(node.expr.lhs.name)) {
2894 // This is a function call, not a type cast
2895 ret_type := g.get_expr_type(node.expr)
2896 if ret_type != '' && ret_type != 'void' && ret_type != 'int' {
2897 tmp_name := '_sumtmp${g.tmp_counter}'
2898 g.tmp_counter++
2899 g.sb.write_string('({ ${ret_type} ${tmp_name} = ')
2900 g.expr(node.expr)
2901 g.sb.write_string('; &${tmp_name}; })')
2902 return
2903 }
2904 }
2905 }
2906 // Fixed array literal: &[1.1, 2.2]! needs type prefix for compound literal
2907 if node.op == .amp && node.expr is ast.ArrayInitExpr {
2908 if !g.is_dynamic_array_type(node.expr.typ) && node.expr.exprs.len > 0 {
2909 elem_type := g.extract_array_elem_type(node.expr.typ)
2910 if elem_type != '' {
2911 g.sb.write_string('&(${elem_type}[${node.expr.exprs.len}]){')
2912 is_iface_elem := g.is_interface_type(elem_type)
2913 for i in 0 .. node.expr.exprs.len {
2914 e := node.expr.exprs[i]
2915 if i > 0 {
2916 g.sb.write_string(', ')
2917 }
2918 if is_iface_elem && g.gen_interface_cast(elem_type, e) {
2919 // interface wrapping handled
2920 } else {
2921 g.expr(e)
2922 }
2923 }
2924 g.sb.write_string('}')
2925 return
2926 }
2927 }
2928 g.sb.write_string('&')
2929 g.expr(node.expr)
2930 return
2931 }
2932 // V `&Type{...}` must allocate on the heap.
2933 // Taking the address of a C compound literal here would create a dangling pointer.
2934 if node.op == .amp && node.expr is ast.InitExpr {
2935 type_name := g.expr_type_to_c(node.expr.typ)
2936 tmp_name := '_heap_t${g.tmp_counter}'
2937 g.tmp_counter++
2938 malloc_call := g.c_heap_malloc_call('sizeof(${type_name})')
2939 g.sb.write_string('({ ${type_name}* ${tmp_name} = (${type_name}*)${malloc_call}; *${tmp_name} = ')
2940 g.expr(node.expr)
2941 g.sb.write_string('; ${tmp_name}; })')
2942 return
2943 }
2944 if node.op == .mul {
2945 if raw_type := g.get_raw_type(node.expr) {
2946 // Pointer to interface: *ptr just dereferences to get the interface struct.
2947 // Do NOT extract ._object — that's for smartcasts, not plain deref.
2948 if raw_type is types.Pointer && raw_type.base_type is types.Interface {
2949 g.sb.write_string('(*(')
2950 g.expr(node.expr)
2951 g.sb.write_string('))')
2952 return
2953 }
2954 is_iface := raw_type is types.Interface
2955 if is_iface {
2956 // If the expression is actually a pointer (e.g., mut for-in loop var
2957 // registered as Interface but declared as Interface*), just deref normally.
2958 if g.expr_is_pointer(node.expr) {
2959 g.sb.write_string('(*(')
2960 g.expr(node.expr)
2961 g.sb.write_string('))')
2962 return
2963 }
2964 target_type := g.get_expr_type(node)
2965 if target_type != '' && target_type != 'int' {
2966 g.sb.write_string('(*((')
2967 g.sb.write_string(target_type)
2968 g.sb.write_string('*)(')
2969 g.expr(node.expr)
2970 g.sb.write_string('._object)))')
2971 return
2972 }
2973 }
2974 }
2975 }
2976 if node.op == .amp {
2977 // &*(ptr) cancellation: when taking address of a deref of a pointer,
2978 // the two operations cancel out. This avoids &(rvalue) errors when
2979 // the deref gets null-guard expansion for sum type data pointers.
2980 inner := g.unwrap_parens(node.expr)
2981 if inner is ast.PrefixExpr && inner.op == .mul {
2982 if g.expr_is_pointer(inner.expr) || g.expr_produces_pointer(inner.expr) {
2983 g.expr(inner.expr)
2984 return
2985 }
2986 }
2987 // Generate inner expression to check if it produces a GCC statement
2988 // expression `({...})` which is an rvalue — can't take address of rvalue.
2989 saved_sb := g.sb
2990 g.sb = strings.new_builder(256)
2991 g.expr(node.expr)
2992 inner_code := g.sb.str()
2993 g.sb = saved_sb
2994 // Detect rvalue expressions: GCC statement expressions ({...}),
2995 // or null-guarded ternaries (ptr ? *ptr : (T){0}) from smartcast.
2996 is_rvalue := inner_code.starts_with('({') || inner_code.starts_with('(({')
2997 || inner_code.contains('){0})')
2998 if is_rvalue {
2999 inner_type := g.get_expr_type(node.expr)
3000 if inner_type != '' && inner_type != 'int' && inner_type != 'void' {
3001 tmp_name := '_addr_t${g.tmp_counter}'
3002 g.tmp_counter++
3003 g.sb.write_string('({ ${inner_type} ${tmp_name} = ${inner_code}; &${tmp_name}; })')
3004 } else {
3005 g.sb.write_string('&${inner_code}')
3006 }
3007 } else {
3008 g.sb.write_string('&${inner_code}')
3009 }
3010 } else {
3011 // For dereference of a sum type data pointer (from smartcast),
3012 // add null guard to avoid crash on zero-initialized sum types.
3013 if node.op == .mul && g.is_sum_data_ptr_deref(node.expr) {
3014 saved_sb := g.sb
3015 g.sb = strings.new_builder(256)
3016 g.expr(node.expr)
3017 ptr_code := g.sb.str()
3018 g.sb = saved_sb
3019 cast_type := g.get_cast_expr_type_name(node.expr)
3020 if cast_type != '' {
3021 g.sb.write_string('(${ptr_code} ? (*(${ptr_code})) : (${cast_type}){0})')
3022 } else {
3023 g.sb.write_string('*(')
3024 g.sb.write_string(ptr_code)
3025 g.sb.write_string(')')
3026 }
3027 } else {
3028 op := match node.op {
3029 .minus { '-' }
3030 .not { '!' }
3031 .mul { '*' }
3032 .bit_not { '~' }
3033 else { '' }
3034 }
3035
3036 g.sb.write_string(op)
3037 g.expr(node.expr)
3038 }
3039 }
3040 }
3041 ast.CallExpr {
3042 g.call_expr(node.lhs, node.args)
3043 }
3044 ast.CallOrCastExpr {
3045 g.gen_call_or_cast_expr(node)
3046 }
3047 ast.SelectorExpr {
3048 sel := node as ast.SelectorExpr
3049 sel_expr := ast.Expr(sel)
3050 lhs_expr := sel.lhs
3051 rhs_name := sel.rhs.name
3052 lhs_name := if lhs_expr is ast.Ident { sel.lhs.name() } else { '' }
3053 // Comptime field access interception: field.name, field.typ, val.$(field.name), etc.
3054 if g.comptime_field_var != '' {
3055 if g.gen_comptime_field_selector(sel) {
3056 return
3057 }
3058 }
3059 // Comptime method access interception: method.name, method.attrs, method.args, etc.
3060 if g.comptime_method_var != '' {
3061 if g.gen_comptime_method_selector(sel) {
3062 return
3063 }
3064 }
3065 // C.<ident> references C macros/constants directly (e.g. C.EOF -> EOF).
3066 if lhs_expr is ast.Ident {
3067 if lhs_name == 'C' {
3068 g.sb.write_string(rhs_name)
3069 return
3070 }
3071 }
3072 if rhs_name == 'bytestr' {
3073 if lhs_expr is ast.IndexExpr && lhs_expr.expr is ast.RangeExpr {
3074 g.sb.write_string('Array_u8__bytestr(')
3075 g.expr(lhs_expr)
3076 g.sb.write_string(')')
3077 return
3078 }
3079 if lhs_expr is ast.CallExpr && lhs_expr.lhs is ast.Ident {
3080 call_lhs := lhs_expr.lhs as ast.Ident
3081 if call_lhs.name in ['array__slice', 'array__slice_ni', 'builtin__array__slice',
3082 'builtin__array__slice_ni'] {
3083 g.sb.write_string('Array_u8__bytestr(')
3084 g.expr(lhs_expr)
3085 g.sb.write_string(')')
3086 return
3087 }
3088 }
3089 elem_type := g.infer_array_elem_type_from_expr(lhs_expr).trim_right('*')
3090 if elem_type == 'u8' || elem_type == 'byte' {
3091 g.sb.write_string('Array_u8__bytestr(')
3092 g.expr(lhs_expr)
3093 g.sb.write_string(')')
3094 return
3095 }
3096 }
3097 if g.gen_plain_index_selector_expr(sel) {
3098 return
3099 }
3100 if g.gen_plain_local_selector_expr(sel) {
3101 return
3102 }
3103 if rhs_name == 'name' {
3104 if lhs_expr is ast.Ident {
3105 // Generic type parameter access: T.name -> string literal with type name.
3106 if concrete := g.active_generic_types[lhs_name] {
3107 g.sb.write_string(c_static_v_string_expr(concrete.name()))
3108 return
3109 }
3110 if is_generic_placeholder_type_name(lhs_name) {
3111 g.sb.write_string(c_static_v_string_expr(lhs_name))
3112 return
3113 }
3114 }
3115 if type_name := g.typeof_expr_type_name(lhs_expr) {
3116 g.sb.write_string(c_static_v_string_expr(type_name))
3117 return
3118 }
3119 if raw_lhs_type := g.get_raw_type(lhs_expr) {
3120 if raw_lhs_type is types.Enum {
3121 enum_name := g.types_type_to_c(raw_lhs_type)
3122 if enum_name != '' && !is_generic_placeholder_c_type_name(enum_name) {
3123 g.sb.write_string('${enum_name}__str(')
3124 g.expr(lhs_expr)
3125 g.sb.write_string(')')
3126 return
3127 }
3128 }
3129 }
3130 lhs_type_for_name := g.get_expr_type(lhs_expr).trim_right('*')
3131 if lhs_type_for_name != '' && lhs_type_for_name != 'int'
3132 && g.is_enum_type(lhs_type_for_name) {
3133 g.sb.write_string('${lhs_type_for_name}__str(')
3134 g.expr(lhs_expr)
3135 g.sb.write_string(')')
3136 return
3137 }
3138 }
3139 if lhs_expr is ast.Ident {
3140 if lhs_name in ['bool', 'string', 'int', 'i8', 'i16', 'i32', 'i64', 'u8', 'u16',
3141 'u32', 'u64', 'f32', 'f64', 'byte', 'rune'] {
3142 if enum_name := g.enum_value_to_enum[rhs_name] {
3143 if g.should_use_known_enum_field(enum_name, rhs_name) {
3144 g.sb.write_string(g.enum_member_c_name(enum_name, rhs_name))
3145 return
3146 }
3147 }
3148 }
3149 is_known_var := g.local_var_c_type_for_expr(lhs_expr) != none
3150 if !is_known_var && !g.is_module_ident(lhs_name) {
3151 if enum_name := g.get_expr_type_from_env(sel_expr) {
3152 if enum_name != '' && g.is_enum_type(enum_name) {
3153 g.sb.write_string(g.enum_member_c_name(enum_name, rhs_name))
3154 return
3155 }
3156 }
3157 if lhs_name in ['bool', 'string', 'int', 'i8', 'i16', 'i32', 'i64', 'u8', 'u16',
3158 'u32', 'u64', 'f32', 'f64', 'byte', 'rune'] {
3159 if enum_name := g.enum_value_to_enum[rhs_name] {
3160 if g.should_use_known_enum_field(enum_name, rhs_name) {
3161 g.sb.write_string(g.enum_member_c_name(enum_name, rhs_name))
3162 return
3163 }
3164 }
3165 }
3166 }
3167 }
3168 // If checker already resolved this selector as an enum value, use Enum__field.
3169 if raw_type := g.get_raw_type(sel_expr) {
3170 if raw_type is types.Enum {
3171 // Verify the field actually belongs to this enum.
3172 // The checker may annotate branch values with the match expression's
3173 // enum type instead of the return type's enum.
3174 mut field_valid := false
3175 for f in raw_type.fields {
3176 if f.name == rhs_name {
3177 field_valid = true
3178 break
3179 }
3180 }
3181 if field_valid {
3182 mut emit_enum_value := false
3183 if lhs_expr is ast.EmptyExpr {
3184 emit_enum_value = true
3185 } else if lhs_expr is ast.Ident {
3186 if g.is_enum_type(lhs_name) {
3187 emit_enum_value = true
3188 } else if !g.is_module_ident(lhs_name) {
3189 emit_enum_value = g.local_var_c_type_for_expr(lhs_expr) == none
3190 }
3191 }
3192 if emit_enum_value {
3193 enum_name := g.types_type_to_c(raw_type)
3194 if !is_generic_placeholder_c_type_name(enum_name) {
3195 g.sb.write_string(g.enum_member_c_name(enum_name, rhs_name))
3196 return
3197 }
3198 }
3199 }
3200 }
3201 }
3202 if g.gen_sum_narrowed_selector(sel) {
3203 return
3204 }
3205 if g.gen_sum_narrowed_selector_lhs_field(sel) {
3206 return
3207 }
3208 if g.gen_sum_common_field_selector(sel) {
3209 return
3210 }
3211 if g.gen_sum_variant_field_selector(sel) {
3212 return
3213 }
3214 if lhs_expr is ast.SelectorExpr {
3215 lhs_sel := lhs_expr as ast.SelectorExpr
3216 if lhs_sel.lhs is ast.Ident {
3217 lhs_mod := lhs_sel.lhs as ast.Ident
3218 mut module_names := [lhs_mod.name]
3219 resolved_mod_name := g.resolve_module_name(lhs_mod.name)
3220 if resolved_mod_name !in module_names {
3221 module_names << resolved_mod_name
3222 }
3223 for mod_name in module_names {
3224 enum_name := '${mod_name}__${lhs_sel.rhs.name}'
3225 if g.enum_has_field(enum_name, rhs_name) {
3226 g.sb.write_string(g.enum_member_c_name(enum_name, rhs_name))
3227 return
3228 }
3229 }
3230 }
3231 }
3232 if rhs_name == 'values' {
3233 if raw_type := g.get_raw_type(sel_expr) {
3234 if raw_type is types.Array {
3235 mut enum_name := ''
3236 if lhs_expr is ast.Ident {
3237 lhs_ident := lhs_expr as ast.Ident
3238 if g.is_enum_type(lhs_ident.name) {
3239 enum_name = lhs_ident.name
3240 } else if g.cur_module != '' && g.cur_module != 'main'
3241 && g.cur_module != 'builtin' {
3242 qualified := '${g.cur_module}__${lhs_ident.name}'
3243 if g.is_enum_type(qualified) {
3244 enum_name = qualified
3245 }
3246 }
3247 } else if lhs_expr is ast.SelectorExpr {
3248 lhs_sel := lhs_expr as ast.SelectorExpr
3249 if lhs_sel.lhs is ast.Ident {
3250 lhs_mod := lhs_sel.lhs as ast.Ident
3251 mod_name := g.resolve_module_name(lhs_mod.name)
3252 qualified := '${mod_name}__${lhs_sel.rhs.name}'
3253 if g.is_enum_type(qualified) {
3254 enum_name = qualified
3255 }
3256 }
3257 }
3258 if enum_name != '' {
3259 g.sb.write_string('${enum_name}__values_array')
3260 return
3261 }
3262 }
3263 }
3264 }
3265 lhs_type := g.get_expr_type(lhs_expr)
3266 if rhs_name in ['x', 'y', 'z', 'w'] {
3267 base_lhs_type := lhs_type.trim_right('*')
3268 if base_lhs_type in primitive_types {
3269 if lhs_type.ends_with('*') {
3270 g.sb.write_string('(*')
3271 g.expr(lhs_expr)
3272 g.sb.write_string(')')
3273 } else {
3274 g.expr(lhs_expr)
3275 }
3276 return
3277 }
3278 }
3279 if rhs_name == 'data' {
3280 if lhs_type.starts_with('_result_') && g.result_value_type(lhs_type) != '' {
3281 g.gen_unwrapped_value_expr(lhs_expr)
3282 return
3283 }
3284 if lhs_type.starts_with('_option_') && option_value_type(lhs_type) != '' {
3285 g.gen_unwrapped_value_expr(lhs_expr)
3286 return
3287 }
3288 if lhs_expr is ast.Ident && lhs_expr.name.starts_with('_or_t') {
3289 payload_type := g.get_expr_type(sel_expr)
3290 if payload_type != ''
3291 && payload_type !in ['int_literal', 'float_literal', 'void'] {
3292 if g.gen_unwrapped_payload_expr(lhs_expr, '', payload_type) {
3293 return
3294 }
3295 }
3296 }
3297 }
3298 // Transformer-generated or-block temps (`_or_t*`) can end up with a
3299 // field-name mismatch when the checker resolved the call to the wrong
3300 // overload (e.g. `header.get` typed as `http.get`). Trust the temp's
3301 // actual C type and rewrite the field name accordingly.
3302 if lhs_expr is ast.Ident && lhs_expr.name.starts_with('_or_t') {
3303 if rhs_name == 'is_error' && lhs_type.starts_with('_option_') {
3304 g.sb.write_string(lhs_expr.name)
3305 g.sb.write_string('.state')
3306 return
3307 }
3308 if rhs_name == 'state' && lhs_type.starts_with('_result_') {
3309 g.sb.write_string(lhs_expr.name)
3310 g.sb.write_string('.is_error')
3311 return
3312 }
3313 }
3314 if variant_field := g.sum_data_variant_selector_field(sel) {
3315 g.expr(lhs_expr)
3316 g.sb.write_string('.${variant_field}')
3317 return
3318 }
3319 if lhs_type.trim_right('*') == 'chan' && rhs_name in ['len', 'closed'] {
3320 if rhs_name == 'len' {
3321 g.sb.write_string('((int)atomic_load_u32(&((sync__Channel*)(')
3322 g.expr(lhs_expr)
3323 g.sb.write_string('))->read_avail))')
3324 } else {
3325 g.sb.write_string('(atomic_load_u16(&((sync__Channel*)(')
3326 g.expr(lhs_expr)
3327 g.sb.write_string('))->closed) != 0)')
3328 }
3329 return
3330 }
3331 lhs_string_type := g.builtin_string_field_lhs_type(lhs_expr, rhs_name)
3332 if lhs_string_type != '' {
3333 use_ptr := lhs_string_type.ends_with('*')
3334 selector := if use_ptr { '->' } else { '.' }
3335 g.expr(lhs_expr)
3336 g.sb.write_string('${selector}${escape_c_keyword(rhs_name)}')
3337 return
3338 }
3339 // Fixed-size array `.len` becomes compile-time length.
3340 if rhs_name == 'len' {
3341 if lhs_expr is ast.Ident {
3342 lhs_ident := lhs_expr as ast.Ident
3343 mut fixed_name := lhs_ident.name
3344 module_const_key := 'const_${g.cur_module}__${lhs_ident.name}'
3345 if g.cur_module != '' && g.cur_module != 'main' && g.cur_module != 'builtin'
3346 && module_const_key in g.emitted_types {
3347 fixed_name = '${g.cur_module}__${lhs_ident.name}'
3348 }
3349 if fixed_name in g.fixed_array_globals {
3350 g.sb.write_string('((int)(sizeof(${fixed_name}) / sizeof(${fixed_name}[0])))')
3351 return
3352 }
3353 if fixed_name in ['strconv__pow5_inv_split_32', 'strconv__pow5_split_32',
3354 'strconv__pow5_inv_split_64_x', 'strconv__pow5_split_64_x'] {
3355 g.sb.write_string('((int)(sizeof(${fixed_name}) / sizeof(${fixed_name}[0])))')
3356 return
3357 }
3358 }
3359 if lhs_expr is ast.SelectorExpr {
3360 lhs_sel := lhs_expr as ast.SelectorExpr
3361 if lhs_sel.lhs is ast.Ident {
3362 lhs_mod := lhs_sel.lhs as ast.Ident
3363 if g.is_module_ident(lhs_mod.name) {
3364 mod_name := g.resolve_module_name(lhs_mod.name)
3365 fixed_name := '${mod_name}__${lhs_sel.rhs.name}'
3366 if fixed_name in g.fixed_array_globals {
3367 g.sb.write_string('((int)(sizeof(${fixed_name}) / sizeof(${fixed_name}[0])))')
3368 return
3369 }
3370 }
3371 }
3372 if g.is_fixed_array_selector(lhs_sel) {
3373 g.sb.write_string('((int)(sizeof(')
3374 g.expr(lhs_expr)
3375 g.sb.write_string(') / sizeof((')
3376 g.expr(lhs_expr)
3377 g.sb.write_string(')[0])))')
3378 return
3379 }
3380 }
3381 if raw_type := g.get_raw_type(lhs_expr) {
3382 if raw_type is types.ArrayFixed {
3383 g.sb.write_string('((int)(sizeof(')
3384 g.expr(lhs_expr)
3385 g.sb.write_string(') / sizeof((')
3386 g.expr(lhs_expr)
3387 g.sb.write_string(')[0])))')
3388 return
3389 }
3390 }
3391 }
3392 // Enum shorthand: `.field` -> `EnumName__field` (using type checker info).
3393 if lhs_expr is ast.EmptyExpr {
3394 // The enum_value_to_enum map definitively tells us which enum owns a field.
3395 // Use it to validate/override type checker results that may be wrong
3396 // (e.g., match branch values annotated with the match expression's enum type
3397 // instead of the return type's enum).
3398 known_enum := g.enum_value_to_enum[rhs_name] or { '' }
3399 if raw_type := g.get_raw_type(sel_expr) {
3400 if raw_type is types.Enum {
3401 enum_name := g.types_type_to_c(raw_type)
3402 if !is_generic_placeholder_c_type_name(enum_name)
3403 && g.enum_has_field(enum_name, rhs_name) {
3404 g.sb.write_string(g.enum_member_c_name(enum_name, rhs_name))
3405 return
3406 }
3407 }
3408 }
3409 if enum_name := g.get_expr_type_from_env(sel_expr) {
3410 if enum_name != '' && enum_name != 'int'
3411 && !is_generic_placeholder_c_type_name(enum_name)
3412 && g.is_enum_type(enum_name) {
3413 if g.enum_has_field(enum_name, rhs_name) {
3414 g.sb.write_string(g.enum_member_c_name(enum_name, rhs_name))
3415 return
3416 }
3417 }
3418 }
3419 // Use the definitive enum field mapping
3420 if g.should_use_known_enum_field(known_enum, rhs_name) {
3421 g.sb.write_string(g.enum_member_c_name(known_enum, rhs_name))
3422 return
3423 }
3424 // Last resort: use function return type as context
3425 if g.cur_fn_ret_type != '' && g.is_enum_type(g.cur_fn_ret_type) {
3426 g.sb.write_string(g.enum_member_c_name(g.cur_fn_ret_type, rhs_name))
3427 return
3428 }
3429 }
3430 // module.const / module.var => module__const / module__var
3431 if lhs_expr is ast.Ident {
3432 lhs_ident := lhs_expr as ast.Ident
3433 is_local := g.local_var_c_type_for_expr(lhs_expr) != none
3434 if g.is_module_ident(lhs_ident.name) && !is_local {
3435 mod_name := g.resolve_module_name(lhs_ident.name)
3436 g.sb.write_string(g.module_selector_storage_c_name(mod_name, rhs_name))
3437 return
3438 }
3439 if !is_local {
3440 if mod_name := g.known_module_runtime_symbol(lhs_ident.name, rhs_name) {
3441 g.sb.write_string(g.module_selector_storage_c_name(mod_name, rhs_name))
3442 return
3443 }
3444 }
3445 }
3446 // Method value in expression position: `obj.method` -> `Type__method`.
3447 // Prefer regular field access when a concrete field exists (e.g. `string.str`).
3448 if g.selector_field_type(sel) == '' {
3449 if method_value_name := g.selector_method_value_name(sel) {
3450 mut target_type := g.get_expr_type(sel_expr)
3451 if (target_type == '' || target_type == 'int') && g.env != unsafe { nil } {
3452 if raw := g.get_raw_type(sel_expr) {
3453 if raw is types.Alias {
3454 alias_raw := raw
3455 if alias_raw.base_type is types.FnType {
3456 target_type = alias_raw.name
3457 }
3458 }
3459 }
3460 }
3461 if target_type != '' && target_type !in ['int', 'void*', 'voidptr'] {
3462 if g.gen_bound_method_value_expr(sel, target_type) {
3463 return
3464 }
3465 g.sb.write_string('((${target_type})${method_value_name})')
3466 } else {
3467 g.sb.write_string(method_value_name)
3468 }
3469 return
3470 }
3471 }
3472 if _ := g.selector_interface_data_field(sel) {
3473 mut use_ptr := g.selector_use_ptr(lhs_expr)
3474 if lhs_name in g.cur_fn_mut_params {
3475 use_ptr = true
3476 } else if local_type := g.local_var_c_type_for_expr(lhs_expr) {
3477 use_ptr = local_type.ends_with('*') || local_type == 'chan'
3478 }
3479 lhs_struct := g.selector_struct_name(lhs_expr)
3480 owner := g.embedded_owner_for(lhs_struct, rhs_name)
3481 field_name := escape_c_keyword(rhs_name)
3482 selector := if use_ptr { '->' } else { '.' }
3483 g.sb.write_string('(*(')
3484 g.expr(lhs_expr)
3485 if owner != '' {
3486 g.sb.write_string('${selector}${escape_c_keyword(owner)}.${field_name}')
3487 } else {
3488 g.sb.write_string('${selector}${field_name}')
3489 }
3490 g.sb.write_string('))')
3491 return
3492 }
3493 // Check if LHS is an enum type name -> emit EnumName__field
3494 if lhs_expr is ast.Ident {
3495 is_local_var := g.local_var_c_type_for_expr(lhs_expr) != none
3496 || lhs_name in g.cur_fn_mut_params
3497 if !is_local_var && g.is_enum_type(lhs_name) {
3498 enum_name := g.get_qualified_name(lhs_name)
3499 g.sb.write_string(g.enum_member_c_name(enum_name, rhs_name))
3500 } else {
3501 mut use_ptr := g.selector_use_ptr(lhs_expr)
3502 if lhs_name in g.cur_fn_mut_params {
3503 use_ptr = true
3504 } else if local_type := g.local_var_c_type_for_expr(lhs_expr) {
3505 // Local declaration type is authoritative for value vs pointer access.
3506 use_ptr = local_type.ends_with('*') || local_type == 'chan'
3507 }
3508 lhs_struct := g.selector_struct_name(lhs_expr)
3509 owner := g.embedded_owner_for(lhs_struct, rhs_name)
3510 field_name := escape_c_keyword(rhs_name)
3511 selector := if use_ptr { '->' } else { '.' }
3512 g.expr(lhs_expr)
3513 if owner != '' {
3514 g.sb.write_string('${selector}${escape_c_keyword(owner)}.${field_name}')
3515 } else {
3516 g.sb.write_string('${selector}${field_name}')
3517 }
3518 }
3519 } else {
3520 use_ptr := g.selector_use_ptr(lhs_expr)
3521 lhs_struct := g.selector_struct_name(lhs_expr)
3522 owner := g.embedded_owner_for(lhs_struct, rhs_name)
3523 field_name := escape_c_keyword(rhs_name)
3524 selector := if use_ptr { '->' } else { '.' }
3525 g.expr(lhs_expr)
3526 if owner != '' {
3527 g.sb.write_string('${selector}${escape_c_keyword(owner)}.${field_name}')
3528 } else {
3529 g.sb.write_string('${selector}${field_name}')
3530 }
3531 }
3532 }
3533 ast.IfExpr {
3534 g.gen_if_expr_value(&node)
3535 }
3536 ast.PostfixExpr {
3537 op := match node.op {
3538 .inc { '++' }
3539 .dec { '--' }
3540 else { '' }
3541 }
3542
3543 if node.expr is ast.Ident && node.expr.name in g.cur_fn_mut_params {
3544 local_type := (g.get_local_var_c_type(node.expr.name) or { '' }).trim_space()
3545 if local_type.ends_with('*') {
3546 g.sb.write_string('(*')
3547 g.expr(node.expr)
3548 g.sb.write_string(')')
3549 g.sb.write_string(op)
3550 return
3551 }
3552 }
3553 g.expr(node.expr)
3554 g.sb.write_string(op)
3555 }
3556 ast.ModifierExpr {
3557 g.expr(node.expr)
3558 }
3559 ast.CastExpr {
3560 g.gen_cast_expr(node)
3561 }
3562 ast.IndexExpr {
3563 g.gen_index_expr(node)
3564 }
3565 ast.ArrayInitExpr {
3566 g.gen_array_init_expr(node)
3567 }
3568 ast.InitExpr {
3569 g.gen_init_expr(node)
3570 }
3571 ast.MapInitExpr {
3572 panic('bug in v2 compiler: MapInitExpr should have been lowered in v2.transformer')
3573 }
3574 ast.MatchExpr {
3575 panic('bug in v2 compiler: MatchExpr should have been lowered in v2.transformer')
3576 }
3577 ast.UnsafeExpr {
3578 g.gen_unsafe_expr(node)
3579 }
3580 ast.OrExpr {
3581 panic('bug in v2 compiler: OrExpr should have been expanded in v2.transformer (${g.cur_file_name}:${g.cur_fn_name} pos=${node.pos} expr=${node.expr.name()})')
3582 }
3583 ast.AsCastExpr {
3584 g.gen_as_cast_expr(node)
3585 }
3586 ast.StringInterLiteral {
3587 g.gen_string_inter_literal(node)
3588 }
3589 ast.FnLiteral {
3590 g.gen_fn_literal(node)
3591 }
3592 ast.LambdaExpr {
3593 g.sb.write_string('/* [TODO] LambdaExpr */ NULL')
3594 }
3595 ast.ComptimeExpr {
3596 if node.expr is ast.IfExpr {
3597 g.gen_comptime_if_expr(node.expr)
3598 return
3599 }
3600 g.gen_comptime_expr(node)
3601 }
3602 ast.Keyword {
3603 g.gen_keyword(node)
3604 }
3605 ast.KeywordOperator {
3606 g.gen_keyword_operator(node)
3607 }
3608 ast.RangeExpr {
3609 panic('bug in v2 compiler: RangeExpr should have been lowered in v2.transformer (${g.cur_file_name}:${g.cur_fn_name} pos=${node.pos})')
3610 }
3611 ast.SelectExpr {
3612 g.sb.write_string('/* [TODO] SelectExpr */ 0')
3613 }
3614 ast.LockExpr {
3615 panic('bug in v2 compiler: LockExpr should have been lowered in v2.transformer')
3616 }
3617 ast.Type {
3618 if node is ast.NilType {
3619 g.sb.write_string('NULL')
3620 } else {
3621 g.sb.write_string('/* [TODO] Type */ 0')
3622 }
3623 }
3624 ast.AssocExpr {
3625 panic('bug in v2 compiler: AssocExpr should have been lowered in v2.transformer')
3626 }
3627 ast.Tuple {
3628 tuple_type := g.get_expr_type(node)
3629 g.sb.write_string('((${tuple_type}){')
3630 for i in 0 .. node.exprs.len {
3631 expr := node.exprs[i]
3632 if i > 0 {
3633 g.sb.write_string(', ')
3634 }
3635 g.sb.write_string('.arg${i} = ')
3636 g.expr(expr)
3637 }
3638 g.sb.write_string('})')
3639 }
3640 ast.FieldInit {
3641 panic('bug in v2 compiler: FieldInit in expression position should have been lowered in v2.transformer (${g.cur_file_name}:${g.cur_fn_name} field=${node.name})')
3642 }
3643 ast.IfGuardExpr {
3644 panic('bug in v2 compiler: IfGuardExpr should have been expanded in v2.transformer')
3645 }
3646 ast.GenericArgs {
3647 if g.generic_args_expr_is_index(node) {
3648 g.gen_index_expr(ast.IndexExpr{
3649 lhs: node.lhs
3650 expr: node.args[0]
3651 pos: node.pos
3652 })
3653 return
3654 }
3655 if g.try_emit_generic_fn_value(node) {
3656 return
3657 }
3658 g.expr(node.lhs)
3659 }
3660 ast.GenericArgOrIndexExpr {
3661 is_index_expr := g.generic_arg_or_index_expr_is_index(node)
3662 if is_index_expr {
3663 g.gen_index_expr(ast.IndexExpr{
3664 lhs: node.lhs
3665 expr: node.expr
3666 pos: node.pos
3667 })
3668 return
3669 }
3670 if g.try_emit_generic_fn_value(node) {
3671 return
3672 }
3673 if !is_index_expr && g.try_emit_generic_fn_value(node.lhs) {
3674 return
3675 }
3676 if raw_type := g.get_raw_type(node.lhs) {
3677 match raw_type {
3678 types.FnType {
3679 g.expr(node.lhs)
3680 return
3681 }
3682 types.Struct {
3683 if raw_type.generic_params.len > 0 {
3684 g.expr(node.lhs)
3685 return
3686 }
3687 }
3688 types.Alias {
3689 if raw_type.base_type is types.FnType {
3690 g.expr(node.lhs)
3691 return
3692 }
3693 if raw_type.base_type is types.Struct
3694 && raw_type.base_type.generic_params.len > 0 {
3695 g.expr(node.lhs)
3696 return
3697 }
3698 }
3699 else {}
3700 }
3701 }
3702 g.gen_index_expr(ast.IndexExpr{
3703 lhs: node.lhs
3704 expr: node.expr
3705 pos: node.pos
3706 })
3707 }
3708 ast.SqlExpr {
3709 g.gen_sql_expr_placeholder(node)
3710 }
3711 ast.EmptyExpr {}
3712 }
3713}
3714
3715fn generic_fn_value_base_expr(expr ast.Expr) ast.Expr {
3716 return match expr {
3717 ast.GenericArgs {
3718 expr.lhs
3719 }
3720 ast.GenericArgOrIndexExpr {
3721 expr.lhs
3722 }
3723 else {
3724 expr
3725 }
3726 }
3727}
3728
3729fn raw_type_is_indexable_for_generic_disambiguation(raw_type types.Type) bool {
3730 mut typ := raw_type
3731 for {
3732 if typ is types.Alias {
3733 typ = typ.base_type
3734 continue
3735 }
3736 break
3737 }
3738 return typ is types.Array || typ is types.ArrayFixed || typ is types.Map || typ is types.String
3739 || typ is types.Pointer
3740}
3741
3742fn c_type_is_indexable_for_generic_disambiguation(c_type string) bool {
3743 typ := c_type.trim_space().trim_right('*')
3744 return typ == 'string' || typ.starts_with('Array_') || typ.starts_with('Map_')
3745}
3746
3747fn (mut g Gen) exp