vxx2 / vlib / v2_toberemoved / gen / cleanc / expr.v
7293 lines · 7116 sloc · 215.68 KB · c0624b274a458fe3e487d89ae9554c2e8c25bdb6
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) expr_is_indexable_for_generic_disambiguation(expr ast.Expr) bool {
3748 if raw_type := g.get_raw_type(expr) {
3749 return raw_type_is_indexable_for_generic_disambiguation(raw_type)
3750 }
3751 if expr is ast.SelectorExpr {
3752 field_type := g.selector_field_type(expr)
3753 if c_type_is_indexable_for_generic_disambiguation(field_type) {
3754 return true
3755 }
3756 }
3757 return c_type_is_indexable_for_generic_disambiguation(g.get_expr_type(expr))
3758}
3759
3760fn (mut g Gen) generic_args_expr_is_index(expr ast.GenericArgs) bool {
3761 return expr.args.len == 1 && g.expr_is_indexable_for_generic_disambiguation(expr.lhs)
3762}
3763
3764fn (mut g Gen) generic_arg_or_index_expr_is_index(expr ast.GenericArgOrIndexExpr) bool {
3765 return g.expr_is_indexable_for_generic_disambiguation(expr.lhs)
3766}
3767
3768fn (mut g Gen) generic_fn_value_specialized_name(expr ast.Expr) ?string {
3769 decl := g.generic_call_decl_from_lhs(expr) or { return none }
3770 generic_params := g.generic_fn_param_names(decl)
3771 if generic_params.len == 0 {
3772 return none
3773 }
3774 mut bindings := map[string]types.Type{}
3775 type_args := generic_call_type_args(expr)
3776 mut has_unresolved_explicit_type_arg := false
3777 for i, param_name in generic_params {
3778 if i >= type_args.len {
3779 break
3780 }
3781 concrete := g.generic_type_arg_concrete_type(type_args[i]) or {
3782 has_unresolved_explicit_type_arg = true
3783 continue
3784 }
3785 bindings[param_name] = concrete
3786 }
3787 if has_unresolved_explicit_type_arg {
3788 return none
3789 }
3790 // Function values like request_handler[A, X] are emitted without call
3791 // arguments, so generic placeholders must be resolved from the active
3792 // enclosing specialization before choosing the C function pointer symbol.
3793 for param_name in generic_params {
3794 if param_name in bindings {
3795 continue
3796 }
3797 if concrete := g.active_generic_types[param_name] {
3798 bindings[param_name] = concrete
3799 }
3800 }
3801 if type_args.len == 0 && bindings.len != generic_params.len
3802 && !g.bind_embedded_generic_type_args(expr, generic_params, mut bindings) {
3803 return none
3804 }
3805 if bindings.len != generic_params.len {
3806 return none
3807 }
3808 mut suffixes := []string{cap: generic_params.len}
3809 for param_name in generic_params {
3810 concrete := bindings[param_name] or { return none }
3811 if !generic_concrete_type_is_runtime_specializable(concrete) {
3812 return none
3813 }
3814 suffixes << g.generic_specialization_token_from_type(concrete)
3815 }
3816 base_lhs := generic_fn_value_base_expr(expr)
3817 base_name := g.resolve_call_name(base_lhs, 0)
3818 if base_name == '' || suffixes.len == 0 {
3819 return none
3820 }
3821 candidate := '${base_name}_T_${suffixes.join('_')}'
3822 short_name := generic_call_short_name(expr)
3823 if short_name != '' {
3824 g.record_late_generic_call_spec(short_name, bindings)
3825 }
3826 g.ensure_specialized_call_signature(candidate)
3827 return candidate
3828}
3829
3830fn (mut g Gen) write_specialized_fn_value(name string) {
3831 ret_type := g.fn_return_types[name] or {
3832 g.sb.write_string(name)
3833 return
3834 }
3835 param_types := g.fn_param_types[name] or {
3836 g.sb.write_string(name)
3837 return
3838 }
3839 // Generic function values can be referenced before the prototype pass has
3840 // seen their late specialization. A block-scope prototype keeps the function
3841 // pointer expression valid without depending on file-level declaration order.
3842 g.sb.write_string('({ ${ret_type} ${name}(')
3843 for i, param_type in param_types {
3844 if i > 0 {
3845 g.sb.write_string(', ')
3846 }
3847 g.sb.write_string(param_type)
3848 }
3849 g.sb.write_string('); ${name}; })')
3850}
3851
3852fn (mut g Gen) try_emit_generic_fn_value(expr ast.Expr) bool {
3853 match expr {
3854 ast.GenericArgs {
3855 if g.generic_args_expr_is_index(expr) {
3856 return false
3857 }
3858 }
3859 ast.GenericArgOrIndexExpr {
3860 if g.generic_arg_or_index_expr_is_index(expr) {
3861 return false
3862 }
3863 }
3864 else {}
3865 }
3866
3867 lhs := generic_fn_value_base_expr(expr)
3868 if lhs is ast.Ident {
3869 if _ := g.get_local_var_c_type(lhs.name) {
3870 return false
3871 }
3872 }
3873 if specialized_name := g.generic_fn_value_specialized_name(expr) {
3874 g.mark_called_fn_name(specialized_name)
3875 g.write_specialized_fn_value(specialized_name)
3876 return true
3877 }
3878 name := g.resolve_call_name(lhs, 0)
3879 if name == '' {
3880 return false
3881 }
3882 if name in g.fn_param_is_ptr || name in g.fn_return_types {
3883 g.mark_called_fn_name(name)
3884 g.sb.write_string(name)
3885 return true
3886 }
3887 if g.has_generic_fn_decl_by_base_name(name) {
3888 g.sb.write_string(name)
3889 return true
3890 }
3891 return false
3892}
3893
3894fn (mut g Gen) gen_sql_expr_placeholder(node ast.SqlExpr) {
3895 mut c_type := g.get_expr_type(ast.Expr(node)).trim_space()
3896 if c_type == '' || c_type == 'int' {
3897 if node.is_count {
3898 c_type = 'int'
3899 } else if node.table_name == '' {
3900 c_type = '_result_void'
3901 }
3902 }
3903 g.sb.write_string(zero_value_for_type(c_type))
3904}
3905
3906fn extract_compare_cast_type(expr ast.Expr) string {
3907 if expr is ast.PrefixExpr && expr.op == .mul {
3908 if expr.expr is ast.CastExpr {
3909 if expr.expr.typ is ast.PrefixExpr && expr.expr.typ.op == .amp
3910 && expr.expr.typ.expr is ast.Ident {
3911 return expr.expr.typ.expr.name
3912 }
3913 }
3914 }
3915 return ''
3916}
3917
3918fn (mut g Gen) gen_index_expr_value(expr ast.Expr) {
3919 g.sb.write_string('((int)(')
3920 g.expr(expr)
3921 g.sb.write_string('))')
3922}
3923
3924fn (mut g Gen) gen_stmts_from_expr(e ast.Expr) {
3925 match e {
3926 ast.IfExpr {
3927 g.gen_if_expr_stmt(&e)
3928 }
3929 ast.UnsafeExpr {
3930 for stmt in e.stmts {
3931 g.gen_stmt(stmt)
3932 }
3933 }
3934 ast.EmptyExpr {}
3935 else {
3936 g.write_indent()
3937 g.expr(e)
3938 g.sb.writeln(';')
3939 }
3940 }
3941}
3942
3943fn (mut g Gen) expr_is_explicit_value_of_type(expr ast.Expr, type_name string) bool {
3944 match expr {
3945 ast.InitExpr {
3946 return g.expr_type_to_c(expr.typ) == type_name
3947 }
3948 ast.CastExpr {
3949 return g.expr_type_to_c(expr.typ) == type_name
3950 }
3951 ast.CallOrCastExpr {
3952 return g.call_or_cast_lhs_is_type(expr.lhs) && g.expr_type_to_c(expr.lhs) == type_name
3953 }
3954 ast.CallExpr {
3955 return expr.args.len == 1 && g.call_or_cast_lhs_is_type(expr.lhs)
3956 && g.expr_type_to_c(expr.lhs) == type_name
3957 }
3958 else {
3959 return false
3960 }
3961 }
3962}
3963
3964fn (mut g Gen) cast_target_type_to_c(typ ast.Expr) string {
3965 mut type_name := g.expr_type_to_c(typ)
3966 if !param_type_is_pointer_expr(typ) {
3967 if local_type := g.local_non_fn_alias_homonym_cast_target_name(typ, type_name) {
3968 type_name = local_type
3969 } else if alias_name := g.exact_fn_type_alias_cast_target_name(typ) {
3970 type_name = g.local_non_fn_alias_homonym_cast_target_name(typ, alias_name) or {
3971 alias_name
3972 }
3973 }
3974 }
3975 if type_name.ends_with('*') && !param_type_is_pointer_expr(typ) {
3976 base_type := type_name[..type_name.len - 1].trim_space()
3977 if g.c_type_is_fn_pointer_alias(base_type) {
3978 type_name = base_type
3979 }
3980 }
3981 return type_name
3982}
3983
3984fn (mut g Gen) gen_type_cast_expr(type_name string, expr ast.Expr) {
3985 expr_type := g.get_expr_type(expr)
3986 if type_name.starts_with('_option_') && is_none_like_expr(expr) {
3987 g.sb.write_string('(${type_name}){ .state = 2 }')
3988 return
3989 }
3990 if expr is ast.BasicLiteral && expr.kind == .number && expr.value == '0' {
3991 if type_name !in primitive_types && !type_name.ends_with('*') && !g.is_enum_type(type_name)
3992 && type_name !in ['void*', 'char*', 'byteptr', 'charptr', 'voidptr'] {
3993 g.sb.write_string(zero_value_for_type(type_name))
3994 return
3995 }
3996 }
3997 if expr_type.starts_with('_result_') && g.result_value_type(expr_type) != '' {
3998 g.sb.write_string('((${type_name})(')
3999 g.gen_unwrapped_value_expr(expr)
4000 g.sb.write_string('))')
4001 return
4002 }
4003 if expr_type.starts_with('_option_') && option_value_type(expr_type) != '' {
4004 g.sb.write_string('((${type_name})(')
4005 g.gen_unwrapped_value_expr(expr)
4006 g.sb.write_string('))')
4007 return
4008 }
4009 if g.expr_is_sum_variant_extract(expr, type_name) {
4010 g.gen_as_cast_expr(ast.AsCastExpr{
4011 expr: expr
4012 typ: ast.Expr(ast.Ident{
4013 name: type_name
4014 })
4015 })
4016 return
4017 }
4018 if variants := g.sum_type_variants[type_name] {
4019 if g.expr_is_explicit_value_of_type(expr, type_name) {
4020 g.expr(expr)
4021 return
4022 }
4023 mut inner_type := expr_type
4024 if local_type := g.local_var_c_type_for_expr(expr) {
4025 if local_type != '' && local_type != 'int' {
4026 inner_type = local_type
4027 }
4028 }
4029 if expr is ast.SelectorExpr {
4030 declared_field_type := g.selector_declared_field_type(expr)
4031 if declared_field_type == type_name || (declared_field_type != ''
4032 && short_type_name(declared_field_type) == short_type_name(type_name)) {
4033 g.expr(expr)
4034 return
4035 }
4036 }
4037 if expr is ast.AsCastExpr {
4038 cast_type := g.expr_type_to_c(expr.typ)
4039 if cast_type != '' && cast_type != 'int' {
4040 inner_type = cast_type
4041 }
4042 }
4043 if expr is ast.InitExpr {
4044 init_type := g.expr_type_to_c(expr.typ)
4045 if init_type != '' && init_type != 'int' {
4046 inner_type = init_type
4047 }
4048 }
4049 mut wrap_expr := expr
4050 base_inner_type := sumtype_variant_pointer_base(inner_type, variants)
4051 if base_inner_type != '' {
4052 inner_type = base_inner_type
4053 wrap_expr = ast.PrefixExpr{
4054 op: .mul
4055 expr: expr
4056 }
4057 }
4058 if inner_type == '' || inner_type == 'int' {
4059 match expr {
4060 ast.CallExpr {
4061 if ret := g.get_call_return_type(expr.lhs, expr.args) {
4062 if ret != '' {
4063 inner_type = ret
4064 }
4065 }
4066 }
4067 ast.InitExpr {
4068 inner_type = g.expr_type_to_c(expr.typ)
4069 }
4070 ast.SelectorExpr {
4071 field_type := g.selector_field_type(expr)
4072 if field_type != '' {
4073 inner_type = field_type
4074 }
4075 }
4076 else {
4077 if local_type := g.local_var_c_type_for_expr(expr) {
4078 if local_type != '' && local_type != 'int' {
4079 inner_type = local_type
4080 }
4081 }
4082 }
4083 }
4084 }
4085 // Identity cast: inner type is already the target sum type, no wrapping needed.
4086 // But verify with local var type — env can return the sum type for a variant value.
4087 if inner_type == type_name {
4088 if expr is ast.Ident {
4089 local_t := g.get_local_var_c_type(expr.name) or { '' }
4090 if local_t != '' && local_t != type_name && local_t != 'int' {
4091 inner_type = local_t
4092 }
4093 }
4094 if inner_type == type_name {
4095 g.expr(expr)
4096 return
4097 }
4098 }
4099 if inner_type != '' {
4100 mut inner_match_type := inner_type
4101 if inner_match_type.contains('_T_') {
4102 inner_match_type = inner_match_type.all_before('_T_')
4103 }
4104 mut tag := -1
4105 mut field_name := ''
4106 for i, v in variants {
4107 if v == inner_match_type || inner_match_type.ends_with('__${v}')
4108 || v.ends_with('__${inner_match_type}') {
4109 tag = i
4110 field_name = v
4111 break
4112 }
4113 }
4114 // If direct matching failed, try qualifying inner_type with the sum type's module prefix
4115 // (e.g. 'Array_Attribute' → 'Array_ast__Attribute' when sum type is 'ast__Stmt')
4116 if tag < 0 && type_name.contains('__') {
4117 mod_prefix := type_name.all_before_last('__') + '__'
4118 // Qualify the type: Array_X → Array_mod__X, or just X → mod__X
4119 qualified := if inner_match_type.starts_with('Array_')
4120 && !inner_match_type[6..].contains('__') {
4121 'Array_${mod_prefix}${inner_match_type[6..]}'
4122 } else if inner_match_type.starts_with('Map_')
4123 && !inner_match_type[4..].contains('__') {
4124 'Map_${mod_prefix}${inner_match_type[4..]}'
4125 } else if !inner_match_type.contains('__') {
4126 '${mod_prefix}${inner_match_type}'
4127 } else {
4128 ''
4129 }
4130 if qualified != '' {
4131 for i, v in variants {
4132 if v == qualified {
4133 tag = i
4134 field_name = v
4135 break
4136 }
4137 }
4138 }
4139 }
4140 // If direct matching failed, check if inner_type is a known sum type
4141 // that appears as a variant of the target sum type (e.g. ast__Type -> ast__Expr._Type)
4142 if tag < 0 && inner_match_type in g.sum_type_variants {
4143 inner_short := if inner_match_type.contains('__') {
4144 inner_match_type.all_after_last('__')
4145 } else {
4146 inner_match_type
4147 }
4148 for i, v in variants {
4149 if v == inner_short {
4150 tag = i
4151 field_name = v
4152 break
4153 }
4154 }
4155 }
4156 if tag >= 0 {
4157 is_primitive :=
4158 inner_type in ['int', 'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64', 'f32', 'f64', 'bool', 'rune', 'byte', 'usize', 'isize']
4159 || inner_type in g.primitive_type_aliases
4160 g.gen_sum_type_wrap(type_name, field_name, tag, is_primitive, wrap_expr, inner_type)
4161 return
4162 }
4163 }
4164 // Fallback: try to infer variant from expression structure
4165 inferred := g.infer_sum_variant_from_expr(type_name, variants, expr)
4166 if inferred.tag >= 0 {
4167 g.gen_sum_type_wrap(type_name, inferred.field_name, inferred.tag,
4168 inferred.is_primitive, expr, inferred.inner_type)
4169 return
4170 }
4171 }
4172 if g.gen_interface_cast(type_name, expr) {
4173 return
4174 }
4175 // For non-sum-types, use C cast
4176 g.sb.write_string('((${type_name})(')
4177 g.expr(expr)
4178 g.sb.write_string('))')
4179}
4180
4181fn (mut g Gen) typeof_expr_type_name(expr ast.Expr) ?string {
4182 if expr is ast.KeywordOperator && expr.op == .key_typeof && expr.exprs.len > 0 {
4183 if expr.exprs[0] is ast.Ident && expr.exprs[0].name == g.comptime_field_var {
4184 return c_name_to_v_name(g.comptime_field_type)
4185 }
4186 if raw_type := g.get_raw_type(expr.exprs[0]) {
4187 return g.types_type_to_v(raw_type)
4188 }
4189 type_name := g.expr_type_to_c(expr.exprs[0])
4190 if type_name != '' {
4191 return c_name_to_v_name(type_name)
4192 }
4193 }
4194 expr_name := expr.name()
4195 if expr_name.starts_with('typeof[') {
4196 inside := expr_name.all_after('typeof[').all_before(']')
4197 if concrete := g.active_generic_types[inside] {
4198 return concrete.name()
4199 }
4200 if inside != '' {
4201 return inside
4202 }
4203 }
4204 return none
4205}
4206
4207struct SumVariantMatch {
4208 tag int
4209 field_name string
4210 is_primitive bool
4211 inner_type string
4212}
4213
4214fn pointer_c_type_base_name(type_name string) string {
4215 name := type_name.trim_space()
4216 if name == '' || name in ['voidptr', 'charptr', 'byteptr'] {
4217 return ''
4218 }
4219 if name.ends_with('*') {
4220 base_name := name.trim_right('*')
4221 if base_name != '' {
4222 return base_name
4223 }
4224 }
4225 if name.ends_with('ptr') {
4226 base_name := name[..name.len - 3]
4227 if base_name != '' {
4228 return base_name
4229 }
4230 }
4231 return ''
4232}
4233
4234fn sumtype_variant_pointer_base(type_name string, variants []string) string {
4235 base_name := pointer_c_type_base_name(type_name)
4236 if base_name == '' {
4237 return ''
4238 }
4239 base_short := if base_name.contains('__') { base_name.all_after_last('__') } else { base_name }
4240 for variant in variants {
4241 variant_short := if variant.contains('__') {
4242 variant.all_after_last('__')
4243 } else {
4244 variant
4245 }
4246 if base_name == variant || base_short == variant_short
4247 || base_name.ends_with('__${variant_short}') || variant.ends_with('__${base_short}') {
4248 return base_name
4249 }
4250 }
4251 return ''
4252}
4253
4254fn (mut g Gen) unwrap_addr_of_value_expr(expr ast.Expr) ?ast.Expr {
4255 match expr {
4256 ast.PrefixExpr {
4257 if expr.op == .amp {
4258 return expr.expr
4259 }
4260 }
4261 ast.CastExpr {
4262 cast_type := g.expr_type_to_c(expr.typ)
4263 if cast_type in ['void*', 'voidptr'] {
4264 if expr.expr is ast.PrefixExpr && expr.expr.op == .amp {
4265 return expr.expr.expr
4266 }
4267 }
4268 }
4269 ast.ParenExpr {
4270 return g.unwrap_addr_of_value_expr(expr.expr)
4271 }
4272 else {}
4273 }
4274
4275 return none
4276}
4277
4278// expr_is_voidptr_cast detects expressions whose top-level form is a cast to
4279// void*/voidptr (possibly through a chain of ParenExprs). The transformer uses
4280// this exact shape to pre-encode sum type variant payloads, so the cleanc
4281// backend can emit them directly without re-wrapping.
4282fn (mut g Gen) expr_is_voidptr_cast(expr ast.Expr) bool {
4283 match expr {
4284 ast.CastExpr {
4285 cast_type := g.expr_type_to_c(expr.typ)
4286 return cast_type in ['void*', 'voidptr']
4287 }
4288 ast.ParenExpr {
4289 return g.expr_is_voidptr_cast(expr.expr)
4290 }
4291 else {
4292 return false
4293 }
4294 }
4295}
4296
4297// expr_yields_struct_value detects expressions that evaluate to a struct VALUE
4298// (not a pointer), e.g. struct literals or already-emitted sum type wraps.
4299// Used to decide if `memdup(expr, sizeof)` is unsafe (memdup expects a pointer).
4300fn (mut g Gen) expr_yields_struct_value(expr ast.Expr) bool {
4301 match expr {
4302 ast.InitExpr {
4303 return true
4304 }
4305 ast.ParenExpr {
4306 return g.expr_yields_struct_value(expr.expr)
4307 }
4308 ast.ModifierExpr {
4309 return g.expr_yields_struct_value(expr.expr)
4310 }
4311 ast.CastExpr {
4312 cast_type := g.expr_type_to_c(expr.typ)
4313 if cast_type in ['void*', 'voidptr'] {
4314 return false
4315 }
4316 if cast_type.ends_with('*') {
4317 return false
4318 }
4319 return g.expr_yields_struct_value(expr.expr)
4320 }
4321 ast.SelectorExpr {
4322 mut typ := g.selector_declared_field_type(expr).trim_space()
4323 if typ == '' || typ == 'int' {
4324 typ = g.selector_field_type(expr).trim_space()
4325 }
4326 if typ == '' || typ == 'int' {
4327 typ = g.get_expr_type(expr).trim_space()
4328 }
4329 return typ != '' && !typ.ends_with('*')
4330 && g.lookup_struct_type_by_c_name(typ).fields.len > 0
4331 }
4332 ast.CallExpr {
4333 typ := g.get_call_return_type(expr.lhs, expr.args) or { g.get_expr_type(expr) }
4334 return typ != '' && !typ.ends_with('*')
4335 && g.lookup_struct_type_by_c_name(typ).fields.len > 0
4336 }
4337 else {
4338 return false
4339 }
4340 }
4341}
4342
4343fn (mut g Gen) expr_type_matches_value_type(expr ast.Expr, expected_type string) bool {
4344 if expected_type == '' || expected_type.ends_with('*') {
4345 return false
4346 }
4347 mut typ := ''
4348 if expr is ast.SelectorExpr {
4349 typ = g.selector_declared_field_type(expr).trim_space()
4350 if typ == '' || typ == 'int' {
4351 typ = g.selector_field_type(expr).trim_space()
4352 }
4353 }
4354 if typ == '' || typ == 'int' {
4355 typ = g.get_expr_type(expr).trim_space()
4356 }
4357 if typ == '' || typ == 'int' {
4358 return false
4359 }
4360 return typ == expected_type || short_type_name(typ) == short_type_name(expected_type)
4361}
4362
4363fn (mut g Gen) is_type_reference_expr(node ast.Expr) bool {
4364 match node {
4365 ast.Ident {
4366 if g.get_local_var_c_type(node.name) != none {
4367 return false
4368 }
4369 return g.is_type_name(node.name)
4370 }
4371 ast.SelectorExpr {
4372 if node.lhs is ast.Ident {
4373 if g.is_module_ident(node.lhs.name) {
4374 return true
4375 }
4376 return g.is_type_name(node.lhs.name)
4377 }
4378 }
4379 else {}
4380 }
4381
4382 return false
4383}
4384
4385fn (g &Gen) contains_as_cast_expr(node ast.Expr) bool {
4386 match node {
4387 ast.AsCastExpr {
4388 return true
4389 }
4390 ast.CastExpr {
4391 return g.contains_as_cast_expr(node.expr)
4392 }
4393 ast.ParenExpr {
4394 return g.contains_as_cast_expr(node.expr)
4395 }
4396 ast.ModifierExpr {
4397 return g.contains_as_cast_expr(node.expr)
4398 }
4399 ast.PrefixExpr {
4400 return g.contains_as_cast_expr(node.expr)
4401 }
4402 else {}
4403 }
4404
4405 return false
4406}
4407
4408fn (mut g Gen) expr_cast_target_type(node ast.Expr) string {
4409 match node {
4410 ast.CastExpr {
4411 return g.expr_type_to_c(node.typ)
4412 }
4413 ast.ParenExpr {
4414 return g.expr_cast_target_type(node.expr)
4415 }
4416 ast.ModifierExpr {
4417 return g.expr_cast_target_type(node.expr)
4418 }
4419 ast.PrefixExpr {
4420 return g.expr_cast_target_type(node.expr)
4421 }
4422 else {}
4423 }
4424
4425 return ''
4426}
4427
4428fn (mut g Gen) gen_sum_narrowed_selector_lhs_field(node ast.SelectorExpr) bool {
4429 lhs_expr := strip_expr_wrappers(node.lhs)
4430 if node.rhs.name.starts_with('_') || lhs_expr !is ast.SelectorExpr {
4431 return false
4432 }
4433 lhs_sel := lhs_expr as ast.SelectorExpr
4434 decl_type := g.selector_storage_field_type(lhs_sel).trim_right('*')
4435 if decl_type == '' || g.get_sum_type_variants_for(decl_type).len == 0 {
4436 return false
4437 }
4438 narrowed := (g.get_expr_type_from_env(lhs_expr) or { return false }).trim_right('*')
4439 if narrowed == '' || narrowed == decl_type {
4440 return false
4441 }
4442 variants := g.get_sum_type_variants_for(decl_type)
4443 mut variant_field := g.sum_type_variant_field_name(decl_type, narrowed)
4444 if variant_field !in variants {
4445 variant_field = ''
4446 }
4447 for v in variants {
4448 if sum_type_variant_matches(v, narrowed) {
4449 variant_field = v
4450 break
4451 }
4452 }
4453 if variant_field == '' {
4454 return false
4455 }
4456 payload_type := g.sum_type_variant_payload_type(decl_type, narrowed, variant_field)
4457 field_name := escape_c_keyword(node.rhs.name)
4458 owner := g.embedded_owner_for(payload_type, node.rhs.name)
4459 g.sb.write_string('(((${payload_type}*)(((')
4460 g.expr(lhs_expr)
4461 g.sb.write_string(')._data._${variant_field})))')
4462 if owner != '' {
4463 g.sb.write_string('->${escape_c_keyword(owner)}.${field_name}')
4464 } else {
4465 g.sb.write_string('->${field_name}')
4466 }
4467 g.sb.write_string(')')
4468 return true
4469}
4470
4471fn (mut g Gen) gen_sum_narrowed_selector(node ast.SelectorExpr) bool {
4472 // Internal sum fields (`_tag`, `_data`, `_Variant`) must never go through
4473 // smartcast field lowering; they are already the raw representation.
4474 if node.rhs.name.starts_with('_') {
4475 return false
4476 }
4477 if node.lhs !is ast.Ident {
4478 return false
4479 }
4480 decl_type := g.local_var_c_type_for_expr(node.lhs) or { return false }
4481 narrowed := g.get_expr_type_from_env(node.lhs) or { return false }
4482 if narrowed == '' || narrowed == decl_type {
4483 return false
4484 }
4485 // Pointer-only difference (e.g. ui__Widget vs ui__Widget*) is not a real
4486 // narrowing — let the normal selector path handle it.
4487 if narrowed == decl_type + '*' || decl_type == narrowed + '*'
4488 || narrowed.trim_right('*') == decl_type.trim_right('*') {
4489 return false
4490 }
4491 // Check if the declared type is an interface — use _object access pattern
4492 base_decl_type := decl_type.trim_right('*')
4493 if g.is_interface_type(base_decl_type) {
4494 field_name := escape_c_keyword(node.rhs.name)
4495 is_ptr := g.expr_is_pointer(node.lhs)
4496 sep := if is_ptr { '->' } else { '.' }
4497 // Check if narrowed type is also an interface
4498 base_narrowed := narrowed.trim_right('*')
4499 if g.is_interface_type(base_narrowed) {
4500 // Interface-to-interface narrowing: field is on the narrowed interface,
4501 // not the source. Cast the source to the narrowed interface type.
4502 // Note: is-checks against interface types generate unreachable code
4503 // (type_id hashes never match interface hashes), so this cast is safe.
4504 if is_ptr {
4505 g.sb.write_string('(*(((${narrowed}*)(')
4506 g.expr(node.lhs)
4507 g.sb.write_string('))->${field_name}))')
4508 } else {
4509 g.sb.write_string('(*(((${narrowed}*)(&(')
4510 g.expr(node.lhs)
4511 g.sb.write_string(')))->${field_name}))')
4512 }
4513 return true
4514 }
4515 // Interface-to-concrete narrowing: cast _object to concrete type
4516 g.sb.write_string('(((${narrowed}*)((')
4517 g.expr(node.lhs)
4518 g.sb.write_string(')${sep}_object))')
4519 owner := g.embedded_owner_for(narrowed, node.rhs.name)
4520 if owner != '' {
4521 g.sb.write_string('->${escape_c_keyword(owner)}.${field_name}')
4522 } else {
4523 g.sb.write_string('->${field_name}')
4524 }
4525 g.sb.write_string(')')
4526 return true
4527 }
4528 // Sum type narrowing path
4529 variants := g.sum_type_variants[decl_type] or { return false }
4530 mut narrowed_payload := strip_pointer_type_name(narrowed)
4531 mut variant_field := g.sum_type_variant_field_name(decl_type, narrowed_payload)
4532 if variant_field !in variants {
4533 variant_field = ''
4534 }
4535 for v in variants {
4536 if sum_type_variant_matches(v, narrowed_payload) {
4537 variant_field = v
4538 break
4539 }
4540 }
4541 if variant_field == '' {
4542 return false
4543 }
4544 narrowed_payload = g.sum_type_variant_payload_type(decl_type, narrowed_payload, variant_field)
4545 field_name := escape_c_keyword(node.rhs.name)
4546 owner := g.embedded_owner_for(narrowed_payload, node.rhs.name)
4547 // Inside a smartcast block, the is-check already confirmed the tag, so the
4548 // variant pointer is guaranteed non-NULL. Dereference directly without a
4549 // null guard (the previous ternary `ptr ? *ptr->field : 0` produced C type
4550 // mismatches when the field type was a struct/array).
4551 g.sb.write_string('(((${narrowed_payload}*)(((')
4552 g.expr(node.lhs)
4553 g.sb.write_string(')._data._${variant_field})))')
4554 if owner != '' {
4555 g.sb.write_string('->${escape_c_keyword(owner)}.${field_name}')
4556 } else {
4557 g.sb.write_string('->${field_name}')
4558 }
4559 g.sb.write_string(')')
4560 return true
4561}
4562
4563// gen_sum_narrowed_ident handles bare Ident usage where the variable is a sum type
4564// that has been narrowed via an `is` check. When a sum type variable is used directly
4565// (e.g., passed as a function argument expecting the narrowed type), we need to emit
4566// the variant extraction: (*(NarrowedType*)(var._data._Variant))
4567fn (mut g Gen) gen_sum_narrowed_ident(node ast.Ident) bool {
4568 decl_type := g.get_local_var_c_type(node.name) or { return false }
4569 narrowed := g.get_expr_type_from_env(ast.Expr(node)) or { return false }
4570 if narrowed == '' || narrowed == decl_type {
4571 return false
4572 }
4573 // Pointer-only difference is not a real narrowing
4574 if narrowed == decl_type + '*' || decl_type == narrowed + '*'
4575 || narrowed.trim_right('*') == decl_type.trim_right('*') {
4576 return false
4577 }
4578 // Check if the declared type is a sum type
4579 variants := g.sum_type_variants[decl_type] or { return false }
4580 mut narrowed_payload := strip_pointer_type_name(narrowed)
4581 mut variant_field := g.sum_type_variant_field_name(decl_type, narrowed_payload)
4582 if variant_field !in variants {
4583 variant_field = ''
4584 }
4585 for v in variants {
4586 if sum_type_variant_matches(v, narrowed_payload) {
4587 variant_field = v
4588 break
4589 }
4590 }
4591 if variant_field == '' {
4592 return false
4593 }
4594 narrowed_payload = g.sum_type_variant_payload_type(decl_type, narrowed_payload, variant_field)
4595 // Generate the extraction expression
4596 if g.is_scalar_sum_payload_type(narrowed_payload) {
4597 // Scalar types: ((type)(intptr_t)(var._data._variant))
4598 g.sb.write_string('((${narrowed_payload})(intptr_t)(${node.name}._data._${variant_field}))')
4599 } else if narrowed_payload == 'string' {
4600 // String: same as struct dereference
4601 g.sb.write_string('(*((${narrowed_payload}*)(${node.name}._data._${variant_field})))')
4602 } else {
4603 // Struct types: (*(NarrowedType*)(var._data._Variant))
4604 g.sb.write_string('(*((${narrowed_payload}*)(${node.name}._data._${variant_field})))')
4605 }
4606 return true
4607}
4608
4609fn (mut g Gen) gen_sum_ident_as_payload_type(node ast.Ident, expected_type string) bool {
4610 if expected_type == '' || expected_type == 'int' {
4611 return false
4612 }
4613 decl_type := g.get_local_var_c_type(node.name) or { return false }
4614 if decl_type == expected_type || decl_type == expected_type + '*' {
4615 return false
4616 }
4617 variants := g.get_sum_type_variants_for(decl_type)
4618 if variants.len == 0 {
4619 return false
4620 }
4621 mut variant_field := g.sum_type_variant_field_name(decl_type, expected_type)
4622 if variant_field !in variants {
4623 for v in variants {
4624 if sum_type_variant_matches(v, expected_type) {
4625 variant_field = v
4626 break
4627 }
4628 }
4629 }
4630 if variant_field !in variants {
4631 return false
4632 }
4633 payload_type := g.sum_type_variant_payload_type(decl_type, expected_type, variant_field)
4634 if g.is_scalar_sum_payload_type(payload_type) {
4635 sep := if decl_type.ends_with('*') { '->' } else { '.' }
4636 g.sb.write_string('((${payload_type})(intptr_t)(${c_local_name(node.name)}${sep}_data._${variant_field}))')
4637 return true
4638 }
4639 sep := if decl_type.ends_with('*') { '->' } else { '.' }
4640 g.sb.write_string('((((${payload_type}*)(${c_local_name(node.name)}${sep}_data._${variant_field})) ? (*(((${payload_type}*)(${c_local_name(node.name)}${sep}_data._${variant_field})))) : ${zero_value_for_type(payload_type)}))')
4641 return true
4642}
4643
4644fn (mut g Gen) gen_sum_ident_payload_pointer_arg(expr ast.Expr, expected_type string) bool {
4645 if expected_type == '' || expected_type == 'int' {
4646 return false
4647 }
4648 ident := sum_payload_ident_from_expr(expr) or { return false }
4649 decl_type := g.get_local_var_c_type(ident.name) or { return false }
4650 if decl_type == '' || decl_type == expected_type || decl_type == expected_type + '*' {
4651 return false
4652 }
4653 variants := g.get_sum_type_variants_for(decl_type)
4654 if variants.len == 0 {
4655 return false
4656 }
4657 mut variant_field := g.sum_type_variant_field_name(decl_type, expected_type)
4658 if variant_field !in variants {
4659 for v in variants {
4660 if sum_type_variant_matches(v, expected_type) {
4661 variant_field = v
4662 break
4663 }
4664 }
4665 }
4666 if variant_field !in variants {
4667 return false
4668 }
4669 payload_type := g.sum_type_variant_payload_type(decl_type, expected_type, variant_field)
4670 if payload_type == '' || payload_type != expected_type {
4671 return false
4672 }
4673 sep := if decl_type.ends_with('*') { '->' } else { '.' }
4674 g.sb.write_string('(((${payload_type}*)(${c_local_name(ident.name)}${sep}_data._${variant_field})))')
4675 return true
4676}
4677
4678fn sum_payload_ident_from_expr(expr ast.Expr) ?ast.Ident {
4679 match expr {
4680 ast.Ident {
4681 return expr
4682 }
4683 ast.AsCastExpr {
4684 return sum_payload_ident_from_expr(expr.expr)
4685 }
4686 ast.CastExpr {
4687 return sum_payload_ident_from_expr(expr.expr)
4688 }
4689 ast.ParenExpr {
4690 return sum_payload_ident_from_expr(expr.expr)
4691 }
4692 ast.PrefixExpr {
4693 if expr.op == .mul {
4694 return sum_payload_ident_from_expr(expr.expr)
4695 }
4696 }
4697 ast.SelectorExpr {
4698 if expr.lhs is ast.SelectorExpr && expr.lhs.rhs.name == '_data' {
4699 return sum_payload_ident_from_expr(expr.lhs.lhs)
4700 }
4701 }
4702 ast.ModifierExpr {
4703 return sum_payload_ident_from_expr(expr.expr)
4704 }
4705 else {}
4706 }
4707
4708 return none
4709}
4710
4711// resolve_narrowed_selector_field_type resolves the field type of a SelectorExpr
4712// when the LHS is a sum type variable narrowed via an `is` check.
4713// Returns the raw types.Type of the field on the narrowed variant struct.
4714fn (mut g Gen) resolve_narrowed_selector_field_type(sel ast.SelectorExpr) ?types.Type {
4715 if sel.lhs !is ast.Ident {
4716 return none
4717 }
4718 ident := sel.lhs as ast.Ident
4719 // Get the narrowed C type from the checker environment
4720 narrowed_c := g.get_expr_type_from_env(sel.lhs) or { return none }
4721 decl_type := g.get_local_var_c_type(ident.name) or { return none }
4722 if narrowed_c == '' || narrowed_c == decl_type {
4723 return none
4724 }
4725 // Resolve the narrowed C type name to a raw types.Type
4726 narrowed_type := g.resolve_c_type_to_raw(narrowed_c) or { return none }
4727 // Look up the field on the narrowed struct
4728 if field_type := selector_struct_field_type_from_type(narrowed_type, sel.rhs.name) {
4729 return field_type
4730 }
4731 return none
4732}
4733
4734fn (mut g Gen) gen_unsafe_expr(node ast.UnsafeExpr) {
4735 if node.stmts.len == 0 {
4736 g.sb.write_string('0')
4737 return
4738 }
4739 if node.stmts.len == 1 {
4740 stmt := node.stmts[0]
4741 if stmt is ast.ExprStmt {
4742 g.expr(stmt.expr)
4743 } else if stmt is ast.AssignStmt && stmt.lhs.len == 1 && stmt.rhs.len == 1 {
4744 g.expr(stmt.rhs[0])
4745 } else {
4746 // Single non-expression statement (e.g., return) - emit directly
4747 g.gen_stmt(stmt)
4748 }
4749 return
4750 }
4751 // Detect the addr-of-temp pattern: { tmp := expr; &tmp }
4752 // Generated by transformer's addr_of_expr_with_temp().
4753 // A GCC statement expression ({ type tmp = expr; &tmp; }) is wrong here because
4754 // tmp's lifetime ends at }), producing a dangling pointer.
4755 // Instead, use a compound literal &((type[1]){expr}[0]) whose storage lives in
4756 // the enclosing block scope.
4757 if node.stmts.len == 2 {
4758 first := node.stmts[0]
4759 last_s := node.stmts[1]
4760 if first is ast.AssignStmt && first.op == .decl_assign && first.lhs.len == 1
4761 && first.rhs.len == 1 && last_s is ast.ExprStmt {
4762 if last_s.expr is ast.PrefixExpr && last_s.expr.op == .amp
4763 && last_s.expr.expr is ast.Ident {
4764 lhs_ident := first.lhs[0]
4765 addr_ident := last_s.expr.expr as ast.Ident
4766 if lhs_ident is ast.Ident && lhs_ident.name == addr_ident.name {
4767 mut c_type := g.get_expr_type(first.rhs[0])
4768 if c_type == ''
4769 || c_type in ['int', 'int_literal', 'float_literal', 'void', 'void*', 'voidptr'] {
4770 c_type = g.local_var_c_type_for_expr(lhs_ident) or { '' }
4771 }
4772 if c_type != '' {
4773 g.sb.write_string('&((${c_type}[1]){')
4774 g.expr(first.rhs[0])
4775 g.sb.write_string('}[0])')
4776 return
4777 }
4778 }
4779 }
4780 }
4781 }
4782 // Multi-statement: use GCC compound expression ({ ... })
4783 g.sb.write_string('({ ')
4784 for i, stmt in node.stmts {
4785 if i < node.stmts.len - 1 {
4786 g.gen_stmt(stmt)
4787 }
4788 }
4789 // Last statement - if it's an ExprStmt, its value is the block's value
4790 last := node.stmts[node.stmts.len - 1]
4791 if last is ast.ExprStmt {
4792 if is_empty_expr(last.expr) {
4793 if payload_expr := g.unsafe_expr_or_payload_value(node.stmts) {
4794 g.expr(payload_expr)
4795 }
4796 } else {
4797 g.expr(last.expr)
4798 }
4799 g.sb.write_string('; ')
4800 } else {
4801 g.gen_stmt(last)
4802 g.sb.write_string('0; ')
4803 }
4804 g.sb.write_string('})')
4805}
4806
4807fn (mut g Gen) unsafe_expr_or_payload_value(stmts []ast.Stmt) ?ast.Expr {
4808 for stmt in stmts {
4809 if stmt is ast.AssignStmt && stmt.op == .decl_assign && stmt.lhs.len == 1
4810 && stmt.rhs.len == 1 {
4811 lhs := stmt.lhs[0]
4812 if lhs !is ast.Ident {
4813 continue
4814 }
4815 lhs_ident := lhs as ast.Ident
4816 name := lhs_ident.name
4817 if !name.starts_with('_or_t') {
4818 continue
4819 }
4820 rhs := stmt.rhs[0]
4821 mut wrapper_type := g.get_expr_type(rhs)
4822 if (wrapper_type == '' || wrapper_type == 'int') && rhs is ast.CallExpr {
4823 if ret_type := g.get_call_return_type(rhs.lhs, rhs.args) {
4824 wrapper_type = ret_type
4825 }
4826 }
4827 if wrapper_type.starts_with('_result_') {
4828 if g.result_value_type(wrapper_type) == 'void' {
4829 continue
4830 }
4831 } else if wrapper_type.starts_with('_option_') {
4832 if option_value_type(wrapper_type) == 'void' {
4833 continue
4834 }
4835 } else {
4836 continue
4837 }
4838 return ast.Expr(ast.SelectorExpr{
4839 lhs: ast.Ident{
4840 name: name
4841 }
4842 rhs: ast.Ident{
4843 name: 'data'
4844 }
4845 })
4846 }
4847 }
4848 return none
4849}
4850
4851fn (mut g Gen) gen_index_expr(node ast.IndexExpr) {
4852 // Slice syntax: arr[a..b], arr[..b], arr[a..], s[a..b]
4853 if node.expr is ast.RangeExpr {
4854 g.gen_slice_index_expr(node, node.expr as ast.RangeExpr)
4855 return
4856 }
4857 if node.lhs is ast.SelectorExpr {
4858 sel := node.lhs as ast.SelectorExpr
4859 if sel.rhs.name == 'str' {
4860 lhs_string_type := g.builtin_string_field_lhs_type(sel.lhs, sel.rhs.name)
4861 if lhs_string_type != '' {
4862 selector := if lhs_string_type.ends_with('*') { '->' } else { '.' }
4863 g.expr(sel.lhs)
4864 g.sb.write_string('${selector}${escape_c_keyword(sel.rhs.name)}[')
4865 g.gen_index_expr_value(node.expr)
4866 g.sb.write_string(']')
4867 return
4868 }
4869 }
4870 }
4871 if !g.expr_is_indexable_for_generic_disambiguation(node.lhs)
4872 && g.try_emit_generic_fn_value(node.lhs) {
4873 return
4874 }
4875 if node.lhs is ast.Ident {
4876 if node.lhs.name in g.fixed_array_globals || node.lhs.name == 'rune_maps' {
4877 g.expr(node.lhs)
4878 g.sb.write_string('[')
4879 g.gen_index_expr_value(node.expr)
4880 g.sb.write_string(']')
4881 return
4882 }
4883 }
4884 unwrapped_lhs := strip_expr_wrappers(node.lhs)
4885 if unwrapped_lhs is ast.CallExpr {
4886 call_name := g.resolve_call_name(unwrapped_lhs.lhs, unwrapped_lhs.args.len)
4887 if call_name in ['new_array_from_c_array', 'builtin__new_array_from_c_array',
4888 'builtin__new_array_from_c_array_noscan'] {
4889 elem_type := g.infer_array_elem_type_from_expr(unwrapped_lhs)
4890 if elem_type != '' && elem_type != 'array' && elem_type != 'int' && elem_type != 'void' {
4891 g.sb.write_string('((${elem_type}*)')
4892 g.expr(node.lhs)
4893 g.sb.write_string('.data)[')
4894 g.gen_index_expr_value(node.expr)
4895 g.sb.write_string(']')
4896 return
4897 }
4898 }
4899 }
4900 // Fixed-size array struct fields are emitted as plain C arrays.
4901 if node.lhs is ast.SelectorExpr && g.is_fixed_array_selector(node.lhs) {
4902 g.expr(node.lhs)
4903 g.sb.write_string('[')
4904 g.gen_index_expr_value(node.expr)
4905 g.sb.write_string(']')
4906 return
4907 }
4908 // Check LHS type from environment to determine indexing strategy
4909 if raw_type := g.get_raw_type(node.lhs) {
4910 if raw_type is types.ArrayFixed {
4911 // Fixed arrays are C arrays - direct indexing
4912 g.expr(node.lhs)
4913 g.sb.write_string('[')
4914 g.gen_index_expr_value(node.expr)
4915 g.sb.write_string(']')
4916 return
4917 }
4918 if raw_type is types.Array {
4919 // Dynamic arrays: ((elem_type*)arr.data)[idx]
4920 mut elem_type := g.types_type_to_c(raw_type.elem_type)
4921 if node.lhs is ast.ArrayInitExpr {
4922 value_elem := g.infer_array_init_value_elem_type(node.lhs)
4923 specialized := specialized_generic_elem_type_from_value(elem_type, value_elem)
4924 if specialized != '' {
4925 elem_type = specialized
4926 }
4927 }
4928 g.sb.write_string('((${elem_type}*)')
4929 g.expr(node.lhs)
4930 g.sb.write_string('.data)[')
4931 g.gen_index_expr_value(node.expr)
4932 g.sb.write_string(']')
4933 return
4934 }
4935 if raw_type is types.Alias {
4936 match raw_type.base_type {
4937 types.Array {
4938 mut elem_type := g.types_type_to_c(raw_type.base_type.elem_type)
4939 if node.lhs is ast.ArrayInitExpr {
4940 value_elem := g.infer_array_init_value_elem_type(node.lhs)
4941 specialized := specialized_generic_elem_type_from_value(elem_type,
4942 value_elem)
4943 if specialized != '' {
4944 elem_type = specialized
4945 }
4946 }
4947 g.sb.write_string('((${elem_type}*)')
4948 g.expr(node.lhs)
4949 g.sb.write_string('.data)[')
4950 g.gen_index_expr_value(node.expr)
4951 g.sb.write_string(']')
4952 return
4953 }
4954 types.ArrayFixed {
4955 g.expr(node.lhs)
4956 g.sb.write_string('[')
4957 g.gen_index_expr_value(node.expr)
4958 g.sb.write_string(']')
4959 return
4960 }
4961 types.String {
4962 g.expr(node.lhs)
4963 g.sb.write_string('.str[')
4964 g.gen_index_expr_value(node.expr)
4965 g.sb.write_string(']')
4966 return
4967 }
4968 else {}
4969 }
4970 }
4971 if raw_type is types.Map {
4972 g.gen_map_index_expr(node, raw_type, false)
4973 return
4974 }
4975 if raw_type is types.String {
4976 if node.lhs is ast.SelectorExpr && g.is_fixed_array_selector(node.lhs) {
4977 g.expr(node.lhs)
4978 g.sb.write_string('[')
4979 g.gen_index_expr_value(node.expr)
4980 g.sb.write_string(']')
4981 return
4982 }
4983 // Distinguish true string indexing (u8 result) from array-like indexing.
4984 if out_type := g.get_raw_type(node) {
4985 out_name := g.types_type_to_c(out_type)
4986 if out_name !in ['u8', 'byte', 'char'] {
4987 g.expr(node.lhs)
4988 g.sb.write_string('[')
4989 g.gen_index_expr_value(node.expr)
4990 g.sb.write_string(']')
4991 return
4992 }
4993 }
4994 g.expr(node.lhs)
4995 g.sb.write_string('.str[')
4996 g.gen_index_expr_value(node.expr)
4997 g.sb.write_string(']')
4998 return
4999 }
5000 if raw_type is types.Pointer {
5001 // Pointer to array: use -> accessor
5002 if raw_type.base_type is types.Array {
5003 elem_type := g.types_type_to_c(raw_type.base_type.elem_type)
5004 g.sb.write_string('((${elem_type}*)')
5005 g.expr(node.lhs)
5006 g.sb.write_string('->data)[')
5007 g.gen_index_expr_value(node.expr)
5008 g.sb.write_string(']')
5009 return
5010 } else if raw_type.base_type is types.ArrayFixed {
5011 // Pointer to fixed array: dereference then index
5012 g.sb.write_string('(*')
5013 g.expr(node.lhs)
5014 g.sb.write_string(')[')
5015 g.gen_index_expr_value(node.expr)
5016 g.sb.write_string(']')
5017 return
5018 } else if raw_type.base_type is types.Map {
5019 g.gen_map_index_expr(node, raw_type.base_type as types.Map, true)
5020 return
5021 } else if raw_type.base_type is types.Pointer || raw_type.base_type is types.String {
5022 // Pointer to pointer (e.g. &&char) or pointer to string (e.g. &string used as array):
5023 // plain C pointer arithmetic
5024 g.expr(node.lhs)
5025 g.sb.write_string('[')
5026 g.gen_index_expr_value(node.expr)
5027 g.sb.write_string(']')
5028 return
5029 }
5030 }
5031 }
5032 if lhs_raw_type := g.get_raw_type(node.lhs) {
5033 if lhs_raw_type is types.ArrayFixed {
5034 // Fixed arrays are C arrays: direct indexing
5035 g.expr(node.lhs)
5036 g.sb.write_string('[')
5037 g.gen_index_expr_value(node.expr)
5038 g.sb.write_string(']')
5039 return
5040 }
5041 }
5042 // Sum type narrowing: when indexing a field of a smartcasted sum type variable
5043 // (e.g. stmt.lhs[0] where stmt is narrowed from ast.Stmt to ast.AssignStmt),
5044 // get_raw_type fails because the declared type (SumType) has no struct fields.
5045 // Resolve the field type through the narrowed variant type instead.
5046 if node.lhs is ast.SelectorExpr && node.lhs.lhs is ast.Ident {
5047 if narrowed_field_type := g.resolve_narrowed_selector_field_type(node.lhs) {
5048 if narrowed_field_type is types.Array {
5049 elem_type := g.types_type_to_c(narrowed_field_type.elem_type)
5050 g.sb.write_string('((${elem_type}*)')
5051 g.expr(node.lhs)
5052 g.sb.write_string('.data)[')
5053 g.gen_index_expr_value(node.expr)
5054 g.sb.write_string(']')
5055 return
5056 }
5057 if narrowed_field_type is types.ArrayFixed {
5058 g.expr(node.lhs)
5059 g.sb.write_string('[')
5060 g.gen_index_expr_value(node.expr)
5061 g.sb.write_string(']')
5062 return
5063 }
5064 }
5065 }
5066 // Fallback: check string-based local type for pointer-to-fixed-array params
5067 // (e.g., `mut state &[8]u32` → type string `Array_fixed_u32_8*`)
5068 // get_raw_type may miss these when the env doesn't record the parameter type.
5069 if node.lhs is ast.Ident {
5070 if local_type := g.get_local_var_c_type(node.lhs.name) {
5071 if local_type.starts_with('Array_fixed_') && local_type.ends_with('*') {
5072 g.sb.write_string('(*')
5073 g.expr(node.lhs)
5074 g.sb.write_string(')[')
5075 g.gen_index_expr_value(node.expr)
5076 g.sb.write_string(']')
5077 return
5078 }
5079 }
5080 }
5081 lhs_type := g.get_expr_type(node.lhs)
5082 if lhs_type == 'map' || lhs_type.starts_with('Map_') {
5083 // Try to resolve the full Map type for fallback code generation.
5084 if map_raw := g.get_raw_type(node.lhs) {
5085 if map_raw is types.Map {
5086 g.gen_map_index_expr(node, map_raw, lhs_type.ends_with('*'))
5087 return
5088 }
5089 }
5090 map_c_type := lhs_type.trim_right('*')
5091 if info := g.ensure_map_type_info(map_c_type) {
5092 g.gen_map_index_expr_from_c_types(node, info.key_c_type, info.value_c_type,
5093 lhs_type.ends_with('*'))
5094 return
5095 }
5096 // Cannot resolve map key/value types — emit a C-level error
5097 // instead of silently generating incorrect casts.
5098 g.sb.write_string('/* [TODO] cannot resolve map type for index expr */ 0')
5099 return
5100 }
5101 if lhs_type == 'string' {
5102 g.expr(node.lhs)
5103 g.sb.write_string('.str[')
5104 g.gen_index_expr_value(node.expr)
5105 g.sb.write_string(']')
5106 return
5107 }
5108 if lhs_type == 'string*' {
5109 elem_type := g.get_expr_type(node)
5110 if elem_type in ['u8', 'byte', 'char'] {
5111 g.expr(node.lhs)
5112 g.sb.write_string('->str[')
5113 g.gen_index_expr_value(node.expr)
5114 g.sb.write_string(']')
5115 } else {
5116 g.expr(node.lhs)
5117 g.sb.write_string('[')
5118 g.gen_index_expr_value(node.expr)
5119 g.sb.write_string(']')
5120 }
5121 return
5122 }
5123 if lhs_type.trim_right('*') in ['strings__Builder', 'Builder'] {
5124 g.sb.write_string('((u8*)')
5125 g.expr(node.lhs)
5126 if lhs_type.ends_with('*') {
5127 g.sb.write_string('->data)[')
5128 } else {
5129 g.sb.write_string('.data)[')
5130 }
5131 g.gen_index_expr_value(node.expr)
5132 g.sb.write_string(']')
5133 return
5134 }
5135 // Fixed arrays (including pointer-to-fixed for mut params): direct C array indexing
5136 if lhs_type.trim_right('*').starts_with('Array_fixed_') {
5137 if lhs_type.ends_with('*') {
5138 g.sb.write_string('(*')
5139 g.expr(node.lhs)
5140 g.sb.write_string(')[')
5141 } else {
5142 g.expr(node.lhs)
5143 g.sb.write_string('[')
5144 }
5145 g.gen_index_expr_value(node.expr)
5146 g.sb.write_string(']')
5147 return
5148 }
5149 if lhs_type in g.array_aliases {
5150 alias_base := g.array_alias_base_type(lhs_type)
5151 if alias_base.starts_with('Array_fixed_') {
5152 g.expr(node.lhs)
5153 g.sb.write_string('[')
5154 g.gen_index_expr_value(node.expr)
5155 g.sb.write_string(']')
5156 return
5157 }
5158 elem_type := g.array_alias_elem_type_from_c_type(lhs_type)
5159 if elem_type != '' {
5160 g.sb.write_string('((${elem_type}*)')
5161 g.expr(node.lhs)
5162 if lhs_type.ends_with('*') {
5163 g.sb.write_string('->data)[')
5164 } else {
5165 g.sb.write_string('.data)[')
5166 }
5167 g.gen_index_expr_value(node.expr)
5168 g.sb.write_string(']')
5169 return
5170 }
5171 }
5172 // CastExpr to Array_* pointer: transformer-generated cast already points
5173 // to the correct element type (e.g., for 2D array init), just index directly
5174 if node.lhs is ast.CastExpr && lhs_type.starts_with('Array_') && lhs_type.ends_with('*') {
5175 g.expr(node.lhs)
5176 g.sb.write_string('[')
5177 g.gen_index_expr_value(node.expr)
5178 g.sb.write_string(']')
5179 return
5180 }
5181 if lhs_type == 'array' || lhs_type.starts_with('Array_') {
5182 mut elem_type := g.get_expr_type(node)
5183 if elem_type == '' || elem_type == 'int' {
5184 if lhs_type.starts_with('Array_fixed_') {
5185 if finfo := g.collected_fixed_array_types[lhs_type] {
5186 elem_type = finfo.elem_type
5187 }
5188 } else if lhs_type.starts_with('Array_') {
5189 elem_type = lhs_type['Array_'.len..].trim_right('*')
5190 } else if lhs_type == 'array' {
5191 // Unparameterized array - try to infer element type from the call
5192 if node.lhs is ast.CallExpr {
5193 call_name := g.resolve_call_name(node.lhs.lhs, node.lhs.args.len)
5194 if call_name.contains('array_from_c_array') && node.lhs.args.len >= 3 {
5195 // Extract elem type from sizeof(T) in 3rd arg
5196 sizeof_arg := node.lhs.args[2]
5197 if sizeof_arg is ast.KeywordOperator && sizeof_arg.op == .key_sizeof {
5198 if sizeof_arg.exprs.len > 0 {
5199 elem_type = g.expr_type_to_c(sizeof_arg.exprs[0])
5200 }
5201 }
5202 } else if node.lhs.args.len > 0 {
5203 src_type := g.get_expr_type(node.lhs.args[0])
5204 if src_type.starts_with('Array_') {
5205 elem_type = src_type['Array_'.len..].trim_right('*')
5206 }
5207 }
5208 }
5209 }
5210 }
5211 if elem_type == '' {
5212 elem_type = 'u8'
5213 }
5214 g.sb.write_string('((${elem_type}*)')
5215 g.expr(node.lhs)
5216 if lhs_type.ends_with('*') {
5217 g.sb.write_string('->data)[')
5218 } else {
5219 g.sb.write_string('.data)[')
5220 }
5221 g.gen_index_expr_value(node.expr)
5222 g.sb.write_string(']')
5223 return
5224 }
5225 // void*/voidptr/byteptr: cast to u8* for indexing (V treats malloc result as &u8)
5226 if lhs_type in ['void*', 'voidptr', 'byteptr'] {
5227 g.sb.write_string('((u8*)')
5228 g.expr(node.lhs)
5229 g.sb.write_string(')[')
5230 g.gen_index_expr_value(node.expr)
5231 g.sb.write_string(']')
5232 return
5233 }
5234 // When lhs_type is unresolved ('int') and the LHS is a SelectorExpr,
5235 // try to resolve the actual field type via struct_field_types map.
5236 // This handles cases like stmt.lhs[0] where stmt is smartcasted from
5237 // a sum type — get_expr_type/get_raw_type fail because they see the
5238 // declared sum type, not the narrowed variant.
5239 if (lhs_type == '' || lhs_type == 'int') && node.lhs is ast.SelectorExpr {
5240 sel := node.lhs as ast.SelectorExpr
5241 // Get the struct C type for the selector's LHS. When the LHS ident
5242 // is narrowed via an is-check, get_expr_type may return the narrowed type.
5243 mut struct_name := g.get_expr_type(sel.lhs)
5244 if struct_name == '' || struct_name == 'int' {
5245 // Also try the env type
5246 if env_t := g.get_expr_type_from_env(sel.lhs) {
5247 struct_name = env_t
5248 }
5249 }
5250 if struct_name != '' && struct_name != 'int' {
5251 if field_type := g.lookup_struct_field_type_by_name(struct_name, sel.rhs.name) {
5252 if field_type.starts_with('Array_') && !field_type.starts_with('Array_fixed_') {
5253 elem_type := field_type['Array_'.len..].trim_right('*')
5254 if elem_type != '' {
5255 g.sb.write_string('((${elem_type}*)')
5256 g.expr(node.lhs)
5257 g.sb.write_string('.data)[')
5258 g.gen_index_expr_value(node.expr)
5259 g.sb.write_string(']')
5260 return
5261 }
5262 }
5263 }
5264 }
5265 }
5266 // Fallback: direct C array indexing
5267 if lhs_type == '' || lhs_type == 'int' {
5268 // Generate LHS to temp buffer to analyze the C code and determine
5269 // if it's a dynamic array that needs .data accessor.
5270 saved := g.sb
5271 g.sb = strings.new_builder(256)
5272 g.expr(node.lhs)
5273 tmp := g.sb.str()
5274 g.sb = saved
5275 // Detect sum type narrowed field access: )->field_name)
5276 // Extract the struct name from the cast pattern to look up field type.
5277 if tmp.contains(')->') {
5278 // Extract field name from the end: ...)->field_name)
5279 field_part := tmp.all_after_last(')->')
5280 field_name := field_part.trim_right(')')
5281 // Extract struct name from the cast: ((struct_name*)(
5282 struct_part := tmp.all_after('((').all_before('*)(')
5283 if struct_part != '' && field_name != '' {
5284 if field_type := g.lookup_struct_field_type_by_name(struct_part, field_name) {
5285 if field_type.starts_with('Array_') && !field_type.starts_with('Array_fixed_') {
5286 elem_type := field_type['Array_'.len..].trim_right('*')
5287 if elem_type != '' {
5288 g.sb.write_string('((${elem_type}*)')
5289 g.sb.write_string(tmp)
5290 g.sb.write_string('.data)[')
5291 g.gen_index_expr_value(node.expr)
5292 g.sb.write_string(']')
5293 return
5294 }
5295 }
5296 }
5297 }
5298 }
5299 g.sb.write_string(tmp)
5300 g.sb.write_string('[')
5301 g.expr(node.expr)
5302 g.sb.write_string(']')
5303 return
5304 }
5305 g.expr(node.lhs)
5306 g.sb.write_string('[')
5307 g.expr(node.expr)
5308 g.sb.write_string(']')
5309}
5310
5311fn (mut g Gen) gen_slice_index_expr(node ast.IndexExpr, range ast.RangeExpr) {
5312 // The transformer normally lowers slices, but unresolved method receivers can
5313 // still carry raw slice syntax into cgen (for example `buf[..n].bytestr()`).
5314 lhs_type := g.get_expr_type(node.lhs)
5315 lhs_base_type := lhs_type.trim_right('*')
5316 is_string := g.expr_resolves_to_string(node.lhs, lhs_base_type)
5317 if is_string {
5318 g.sb.write_string('string__substr(')
5319 } else if range.end is ast.EmptyExpr {
5320 g.sb.write_string('array__slice_ni(')
5321 } else {
5322 g.sb.write_string('array__slice(')
5323 }
5324 g.expr(node.lhs)
5325 g.sb.write_string(', ')
5326 if range.start is ast.EmptyExpr {
5327 g.sb.write_string('0')
5328 } else {
5329 g.expr(range.start)
5330 }
5331 g.sb.write_string(', ')
5332 if range.end is ast.EmptyExpr {
5333 if is_string {
5334 g.sb.write_string('2147483647')
5335 } else {
5336 g.expr(node.lhs)
5337 if lhs_type.ends_with('*') {
5338 g.sb.write_string('->len')
5339 } else {
5340 g.sb.write_string('.len')
5341 }
5342 }
5343 } else if range.op == .ellipsis {
5344 g.sb.write_string('(')
5345 g.expr(range.end)
5346 g.sb.write_string(' + 1)')
5347 } else {
5348 g.expr(range.end)
5349 }
5350 g.sb.write_string(')')
5351}
5352
5353fn (mut g Gen) expr_resolves_to_string(expr ast.Expr, lhs_base_type string) bool {
5354 if lhs_base_type == 'string' {
5355 return true
5356 }
5357 if expr is ast.SelectorExpr {
5358 if g.selector_declared_field_type(expr).trim_right('*') == 'string' {
5359 return true
5360 }
5361 if g.selector_field_type(expr).trim_right('*') == 'string' {
5362 return true
5363 }
5364 }
5365 if raw_type := g.get_raw_type(expr) {
5366 match raw_type {
5367 types.String {
5368 return true
5369 }
5370 types.Pointer {
5371 return raw_type.base_type is types.String
5372 }
5373 types.Alias {
5374 return raw_type.base_type is types.String
5375 }
5376 else {}
5377 }
5378 }
5379 return false
5380}
5381
5382fn (mut g Gen) gen_map_index_expr(node ast.IndexExpr, map_type types.Map, lhs_is_ptr bool) {
5383 // The transformer normally lowers map reads to map__get. Generic functions
5384 // kept for late function-value specialization can still reach cgen with raw
5385 // `m[key]`, so emit the same expression-shaped map__get fallback here.
5386 key_type := g.types_type_to_c(map_type.key_type)
5387 value_type := g.types_type_to_c(map_type.value_type)
5388 g.gen_map_index_expr_from_c_types(node, key_type, value_type, lhs_is_ptr)
5389}
5390
5391fn (mut g Gen) gen_map_index_expr_from_c_types(node ast.IndexExpr, key_type string, value_type string, lhs_is_ptr bool) {
5392 actual_lhs_is_ptr := lhs_is_ptr || g.expr_is_pointer(node.lhs)
5393 || g.get_expr_type(node.lhs).ends_with('*')
5394 key_tmp := '_map_key${g.tmp_counter}'
5395 g.tmp_counter++
5396 zero_tmp := '_map_zero${g.tmp_counter}'
5397 g.tmp_counter++
5398 g.sb.write_string('({ ${key_type} ${key_tmp} = ')
5399 g.expr(node.expr)
5400 if value_type.ends_with('*') {
5401 g.sb.write_string('; ${value_type} ${zero_tmp} = 0; (*(${value_type}*)map__get(')
5402 } else {
5403 g.sb.write_string('; ${value_type} ${zero_tmp} = (${value_type}){0}; (*(${value_type}*)map__get(')
5404 }
5405 if actual_lhs_is_ptr {
5406 g.expr(node.lhs)
5407 } else {
5408 g.sb.write_string('&(')
5409 g.expr(node.lhs)
5410 g.sb.write_string(')')
5411 }
5412 g.sb.write_string(', (void*)&${key_tmp}, (void*)&${zero_tmp})); })')
5413}
5414
5415fn cleanc_pref_comptime_flag_name(name string) bool {
5416 match name {
5417 'macos', 'darwin', 'mac', 'linux', 'windows', 'bsd', 'freebsd', 'openbsd', 'netbsd',
5418 'dragonfly', 'android', 'termux', 'ios', 'solaris', 'qnx', 'serenity', 'plan9', 'vinix',
5419 'cross', 'none', 'freestanding' {
5420 return true
5421 }
5422 else {
5423 return false
5424 }
5425 }
5426}
5427
5428fn (g &Gen) eval_comptime_flag(name string) bool {
5429 if cleanc_pref_comptime_flag_name(name) {
5430 return vpref.comptime_flag_value(g.pref, name)
5431 }
5432 match name {
5433 'x64', 'amd64' {
5434 return g.pref != unsafe { nil } && g.pref.arch == .x64
5435 }
5436 'arm64', 'aarch64' {
5437 return g.pref != unsafe { nil } && g.pref.arch == .arm64
5438 }
5439 'debug' {
5440 return g.pref != unsafe { nil } && g.pref.debug
5441 }
5442 'native' {
5443 return g.pref != unsafe { nil } && (g.pref.backend == .arm64 || g.pref.backend == .x64)
5444 }
5445 'builtin_write_buf_to_fd_should_use_c_write' {
5446 return g.pref != unsafe { nil } && (g.pref.backend == .arm64 || g.pref.backend == .x64)
5447 }
5448 'tinyc' {
5449 return g.pref != unsafe { nil } && (g.pref.backend == .arm64 || g.pref.backend == .x64)
5450 }
5451 'prealloc' {
5452 return g.pref != unsafe { nil } && g.pref.prealloc
5453 }
5454 'new_int', 'gcboehm', 'autofree', 'ppc64' {
5455 return false
5456 }
5457 else {
5458 return g.pref != unsafe { nil } && name in g.pref.user_defines
5459 }
5460 }
5461}
5462
5463fn (g &Gen) eval_comptime_cond(cond ast.Expr) bool {
5464 match cond {
5465 ast.Ident {
5466 return g.eval_comptime_flag(cond.name)
5467 }
5468 ast.PrefixExpr {
5469 if cond.op == .not {
5470 return !g.eval_comptime_cond(cond.expr)
5471 }
5472 }
5473 ast.InfixExpr {
5474 if cond.op == .and {
5475 return g.eval_comptime_cond(cond.lhs) && g.eval_comptime_cond(cond.rhs)
5476 }
5477 if cond.op == .logical_or {
5478 return g.eval_comptime_cond(cond.lhs) || g.eval_comptime_cond(cond.rhs)
5479 }
5480 // Handle `$if T is string`, `$if T.unaliased_typ is $struct`, `$if field.typ is string`, etc.
5481 if cond.op == .key_is || cond.op == .not_is {
5482 // Special case: `method.return_type is X` — compare AST exprs by name
5483 if cond.lhs is ast.SelectorExpr {
5484 lhs_sel := cond.lhs as ast.SelectorExpr
5485 if lhs_sel.lhs is ast.Ident && lhs_sel.lhs.name == g.comptime_method_var
5486 && lhs_sel.rhs.name == 'return_type' {
5487 result := g.ast_expr_type_matches(g.comptime_method_return_type, cond.rhs)
5488 return if cond.op == .key_is { result } else { !result }
5489 }
5490 }
5491 resolved := g.resolve_comptime_is_lhs(cond.lhs)
5492 if resolved.matched {
5493 result := g.comptime_type_matches(resolved.typ, cond.rhs)
5494 return if cond.op == .key_is { result } else { !result }
5495 }
5496 }
5497 // Handle `$if field.is_array` or `$if !field.is_array` style
5498 if cond.op == .eq || cond.op == .ne {
5499 // field.name == "x" — string comparison
5500 if cond.lhs is ast.SelectorExpr {
5501 lhs_sel := cond.lhs as ast.SelectorExpr
5502 if lhs_sel.lhs is ast.Ident && lhs_sel.lhs.name == g.comptime_field_var {
5503 if lhs_sel.rhs.name == 'name' {
5504 if cond.rhs is ast.BasicLiteral {
5505 rhs_lit := cond.rhs as ast.BasicLiteral
5506 rhs_name := strip_literal_quotes(rhs_lit.value)
5507 matched := g.comptime_field_name == rhs_name
5508 return if cond.op == .eq { matched } else { !matched }
5509 }
5510 }
5511 }
5512 // method.name == "x" — string comparison
5513 if lhs_sel.lhs is ast.Ident && lhs_sel.lhs.name == g.comptime_method_var {
5514 if lhs_sel.rhs.name == 'name' {
5515 if cond.rhs is ast.BasicLiteral {
5516 rhs_lit := cond.rhs as ast.BasicLiteral
5517 rhs_name := strip_literal_quotes(rhs_lit.value)
5518 matched := g.comptime_method_name == rhs_name
5519 return if cond.op == .eq { matched } else { !matched }
5520 }
5521 }
5522 }
5523 }
5524 }
5525 }
5526 ast.PostfixExpr {
5527 if cond.op == .question && cond.expr is ast.Ident {
5528 return vpref.comptime_optional_flag_value(g.pref, cond.expr.name)
5529 }
5530 }
5531 ast.ParenExpr {
5532 return g.eval_comptime_cond(cond.expr)
5533 }
5534 else {}
5535 }
5536
5537 return false
5538}
5539
5540fn (g &Gen) eval_comptime_cond_cursor(cond ast.Cursor) bool {
5541 if !cond.is_valid() {
5542 return false
5543 }
5544 match cond.kind() {
5545 .expr_ident {
5546 return g.eval_comptime_flag(cond.name())
5547 }
5548 .expr_comptime, .expr_paren {
5549 return g.eval_comptime_cond_cursor(cond.edge(0))
5550 }
5551 .expr_prefix {
5552 if unsafe { token.Token(int(cond.aux())) } == .not {
5553 return !g.eval_comptime_cond_cursor(cond.edge(0))
5554 }
5555 }
5556 .expr_infix {
5557 op := unsafe { token.Token(int(cond.aux())) }
5558 if op == .and {
5559 return g.eval_comptime_cond_cursor(cond.edge(0))
5560 && g.eval_comptime_cond_cursor(cond.edge(1))
5561 }
5562 if op == .logical_or {
5563 return g.eval_comptime_cond_cursor(cond.edge(0))
5564 || g.eval_comptime_cond_cursor(cond.edge(1))
5565 }
5566 if op == .key_is || op == .not_is {
5567 lhs := cond.edge(0)
5568 rhs := cond.edge(1)
5569 if lhs.kind() == .expr_selector && lhs.edge(0).kind() == .expr_ident
5570 && lhs.edge(0).name() == g.comptime_method_var
5571 && lhs.edge(1).name() == 'return_type' {
5572 result := g.ast_expr_type_matches_cursor(g.comptime_method_return_type, rhs)
5573 return if op == .key_is { result } else { !result }
5574 }
5575 resolved := g.resolve_comptime_is_lhs_cursor(lhs)
5576 if resolved.matched {
5577 result := g.comptime_type_matches_cursor(resolved.typ, rhs)
5578 return if op == .key_is { result } else { !result }
5579 }
5580 }
5581 if op == .eq || op == .ne {
5582 lhs := cond.edge(0)
5583 rhs := cond.edge(1)
5584 if lhs.kind() == .expr_selector && lhs.edge(0).kind() == .expr_ident {
5585 lhs_name := lhs.edge(0).name()
5586 field_name := lhs.edge(1).name()
5587 if lhs_name == g.comptime_field_var && field_name == 'name'
5588 && (rhs.kind() == .expr_basic_literal || rhs.kind() == .expr_string) {
5589 rhs_name := strip_literal_quotes(rhs.name())
5590 matched := g.comptime_field_name == rhs_name
5591 return if op == .eq { matched } else { !matched }
5592 }
5593 if lhs_name == g.comptime_method_var && field_name == 'name'
5594 && (rhs.kind() == .expr_basic_literal || rhs.kind() == .expr_string) {
5595 rhs_name := strip_literal_quotes(rhs.name())
5596 matched := g.comptime_method_name == rhs_name
5597 return if op == .eq { matched } else { !matched }
5598 }
5599 }
5600 }
5601 }
5602 .expr_postfix {
5603 if unsafe { token.Token(int(cond.aux())) } == .question
5604 && cond.edge(0).kind() == .expr_ident {
5605 return vpref.comptime_optional_flag_value(g.pref, cond.edge(0).name())
5606 }
5607 }
5608 else {}
5609 }
5610
5611 return false
5612}
5613
5614struct ComptimeResolvedType {
5615 matched bool
5616 typ types.Type = types.Struct{}
5617}
5618
5619// resolve_comptime_is_lhs resolves the LHS of a comptime `is` check to a concrete type.
5620// Handles: T, T.unaliased_typ, field.typ, field.unaliased_typ
5621fn (g &Gen) resolve_comptime_is_lhs(lhs ast.Expr) ComptimeResolvedType {
5622 if lhs is ast.Ident {
5623 // Simple `T is ...` — look up generic type
5624 if concrete := g.active_generic_types[lhs.name] {
5625 return ComptimeResolvedType{
5626 matched: true
5627 typ: concrete
5628 }
5629 }
5630 }
5631 if lhs is ast.SelectorExpr {
5632 sel := lhs as ast.SelectorExpr
5633 if sel.lhs is ast.Ident {
5634 lhs_name := sel.lhs.name
5635 rhs_name := sel.rhs.name
5636 // T.unaliased_typ — resolve generic T then strip aliases
5637 if concrete := g.active_generic_types[lhs_name] {
5638 if rhs_name == 'unaliased_typ' {
5639 return ComptimeResolvedType{
5640 matched: true
5641 typ: comptime_unalias_type(concrete)
5642 }
5643 }
5644 return ComptimeResolvedType{
5645 matched: true
5646 typ: concrete
5647 }
5648 }
5649 // field.typ or field.unaliased_typ — use comptime field state
5650 if lhs_name == g.comptime_field_var {
5651 if rhs_name == 'typ' || rhs_name == 'unaliased_typ' {
5652 field_type := g.comptime_field_raw_type
5653 typ := if rhs_name == 'unaliased_typ' {
5654 comptime_unalias_type(field_type)
5655 } else {
5656 field_type
5657 }
5658 return ComptimeResolvedType{
5659 matched: true
5660 typ: typ
5661 }
5662 }
5663 }
5664 }
5665 }
5666 return ComptimeResolvedType{}
5667}
5668
5669fn (g &Gen) resolve_comptime_is_lhs_cursor(lhs ast.Cursor) ComptimeResolvedType {
5670 if !lhs.is_valid() {
5671 return ComptimeResolvedType{}
5672 }
5673 if lhs.kind() == .expr_ident {
5674 if concrete := g.active_generic_types[lhs.name()] {
5675 return ComptimeResolvedType{
5676 matched: true
5677 typ: concrete
5678 }
5679 }
5680 }
5681 if lhs.kind() == .expr_selector && lhs.edge(0).kind() == .expr_ident {
5682 lhs_name := lhs.edge(0).name()
5683 rhs_name := lhs.edge(1).name()
5684 if concrete := g.active_generic_types[lhs_name] {
5685 if rhs_name == 'unaliased_typ' {
5686 return ComptimeResolvedType{
5687 matched: true
5688 typ: comptime_unalias_type(concrete)
5689 }
5690 }
5691 return ComptimeResolvedType{
5692 matched: true
5693 typ: concrete
5694 }
5695 }
5696 if lhs_name == g.comptime_field_var && (rhs_name == 'typ' || rhs_name == 'unaliased_typ') {
5697 field_type := g.comptime_field_raw_type
5698 return ComptimeResolvedType{
5699 matched: true
5700 typ: if rhs_name == 'unaliased_typ' {
5701 comptime_unalias_type(field_type)
5702 } else {
5703 field_type
5704 }
5705 }
5706 }
5707 }
5708 return ComptimeResolvedType{}
5709}
5710
5711// ast_expr_type_matches compares two AST type expressions by their normalized name.
5712// Used for `$if method.return_type is X` where both sides are ast.Exprs.
5713fn (g &Gen) ast_expr_type_matches(lhs ast.Expr, rhs ast.Expr) bool {
5714 lhs_name := ast_type_expr_name(lhs)
5715 rhs_name := ast_type_expr_name(rhs)
5716 if lhs_name == '' || rhs_name == '' {
5717 return false
5718 }
5719 if lhs_name == rhs_name {
5720 return true
5721 }
5722 // Normalize: bare name on one side matches `mod.Name` on the other if the bare
5723 // name is the same. e.g., `Result` matches `veb.Result`.
5724 if lhs_name.ends_with('.' + rhs_name) || rhs_name.ends_with('.' + lhs_name) {
5725 return true
5726 }
5727 // Common aliases
5728 if (lhs_name == 'byte' && rhs_name == 'u8') || (lhs_name == 'u8' && rhs_name == 'byte') {
5729 return true
5730 }
5731 if (lhs_name == 'int' && rhs_name == 'i32') || (lhs_name == 'i32' && rhs_name == 'int') {
5732 return true
5733 }
5734 return false
5735}
5736
5737fn (g &Gen) ast_expr_type_matches_cursor(lhs ast.Expr, rhs ast.Cursor) bool {
5738 lhs_name := ast_type_expr_name(lhs)
5739 rhs_name := cursor_type_expr_name(rhs)
5740 if lhs_name == '' || rhs_name == '' {
5741 return false
5742 }
5743 if lhs_name == rhs_name {
5744 return true
5745 }
5746 if lhs_name.ends_with('.' + rhs_name) || rhs_name.ends_with('.' + lhs_name) {
5747 return true
5748 }
5749 if (lhs_name == 'byte' && rhs_name == 'u8') || (lhs_name == 'u8' && rhs_name == 'byte') {
5750 return true
5751 }
5752 if (lhs_name == 'int' && rhs_name == 'i32') || (lhs_name == 'i32' && rhs_name == 'int') {
5753 return true
5754 }
5755 return false
5756}
5757
5758// ast_type_expr_name extracts a normalized name from an AST type expression.
5759fn ast_type_expr_name(e ast.Expr) string {
5760 if e is ast.Ident {
5761 return e.name
5762 }
5763 if e is ast.SelectorExpr {
5764 lhs_name := ast_type_expr_name(e.lhs)
5765 if lhs_name == '' {
5766 return e.rhs.name
5767 }
5768 return lhs_name + '.' + e.rhs.name
5769 }
5770 if e is ast.EmptyExpr {
5771 return 'void'
5772 }
5773 return ''
5774}
5775
5776fn cursor_type_expr_name(e ast.Cursor) string {
5777 if !e.is_valid() {
5778 return ''
5779 }
5780 match e.kind() {
5781 .expr_ident {
5782 return e.name()
5783 }
5784 .expr_selector {
5785 lhs_name := cursor_type_expr_name(e.edge(0))
5786 if lhs_name == '' {
5787 return e.edge(1).name()
5788 }
5789 return lhs_name + '.' + e.edge(1).name()
5790 }
5791 .expr_empty {
5792 return 'void'
5793 }
5794 .expr_comptime {
5795 return cursor_type_expr_name(e.edge(0))
5796 }
5797 else {
5798 return ''
5799 }
5800 }
5801}
5802
5803// comptime_unalias_type strips Alias layers from a type.
5804fn comptime_unalias_type(t types.Type) types.Type {
5805 mut cur := t
5806 for {
5807 if cur is types.Alias {
5808 a := cur as types.Alias
5809 cur = a.base_type
5810 continue
5811 }
5812 break
5813 }
5814 return cur
5815}
5816
5817// comptime_type_matches checks if a resolved type matches the RHS of a comptime `is` check.
5818// Handles: Ident (concrete type name), ComptimeExpr ($struct, $enum, etc.), PrefixExpr (&type)
5819fn (g &Gen) comptime_type_matches(typ types.Type, rhs ast.Expr) bool {
5820 if rhs is ast.Ident {
5821 return g.comptime_matches_type_name(typ, rhs.name)
5822 }
5823 if rhs is ast.SelectorExpr {
5824 return g.comptime_matches_selector_type_name(typ, rhs)
5825 }
5826 if rhs is ast.ComptimeExpr {
5827 // $struct, $enum, $int, $float, $array, $map, $option, etc.
5828 if rhs.expr is ast.Ident {
5829 return g.comptime_matches_keyword(typ, rhs.expr.name)
5830 }
5831 return false
5832 }
5833 if rhs is ast.PrefixExpr {
5834 // e.g., &int — pointer to type
5835 return false
5836 }
5837 return false
5838}
5839
5840fn (g &Gen) comptime_type_matches_cursor(typ types.Type, rhs ast.Cursor) bool {
5841 if !rhs.is_valid() {
5842 return false
5843 }
5844 match rhs.kind() {
5845 .expr_ident {
5846 return g.comptime_matches_type_name(typ, rhs.name())
5847 }
5848 .expr_selector {
5849 return g.comptime_matches_selector_type_cursor(typ, rhs)
5850 }
5851 .expr_comptime {
5852 inner := rhs.edge(0)
5853 if inner.kind() == .expr_ident {
5854 return g.comptime_matches_keyword(typ, inner.name())
5855 }
5856 }
5857 else {}
5858 }
5859
5860 return false
5861}
5862
5863fn (g &Gen) comptime_matches_selector_type_cursor(typ types.Type, selector ast.Cursor) bool {
5864 if selector.kind() != .expr_selector || selector.edge(0).kind() != .expr_ident {
5865 return false
5866 }
5867 mod_name := selector.edge(0).name()
5868 type_name := selector.edge(1).name()
5869 dot_name := '${mod_name}.${type_name}'
5870 c_name := '${mod_name}__${type_name}'
5871 resolved_name := typ.name()
5872 if resolved_name == dot_name || resolved_name == c_name {
5873 return true
5874 }
5875 resolved_c_name := g.types_type_to_c(typ)
5876 if mod_name == g.cur_module && (resolved_name == type_name || resolved_c_name == type_name) {
5877 return true
5878 }
5879 return resolved_c_name == c_name
5880}
5881
5882fn (g &Gen) comptime_matches_selector_type_name(typ types.Type, selector ast.SelectorExpr) bool {
5883 if selector.lhs !is ast.Ident {
5884 return false
5885 }
5886 lhs_ident := selector.lhs as ast.Ident
5887 mod_name := lhs_ident.name
5888 type_name := selector.rhs.name
5889 dot_name := '${mod_name}.${type_name}'
5890 c_name := '${mod_name}__${type_name}'
5891 resolved_name := typ.name()
5892 if resolved_name == dot_name || resolved_name == c_name {
5893 return true
5894 }
5895 resolved_c_name := g.types_type_to_c(typ)
5896 if mod_name == g.cur_module && (resolved_name == type_name || resolved_c_name == type_name) {
5897 return true
5898 }
5899 return resolved_c_name == c_name
5900}
5901
5902// comptime_matches_type_name checks if a type matches a concrete type name.
5903fn (g &Gen) comptime_matches_type_name(typ types.Type, name string) bool {
5904 type_name := typ.name()
5905 if type_name == name {
5906 return true
5907 }
5908 // Normalize aliases
5909 if (name == 'byte' && type_name == 'u8') || (name == 'u8' && type_name == 'byte') {
5910 return true
5911 }
5912 if (name == 'int' && type_name == 'i32') || (name == 'i32' && type_name == 'int') {
5913 return true
5914 }
5915 // Check C type name
5916 c_name := g.types_type_to_c(typ)
5917 if c_name == name {
5918 return true
5919 }
5920 if g.comptime_type_implements_interface(typ, name) {
5921 return true
5922 }
5923 return false
5924}
5925
5926fn comptime_type_name_short(name string) string {
5927 dot_short := name.all_after_last('.')
5928 return dot_short.all_after_last('__')
5929}
5930
5931fn comptime_type_name_matches(candidate string, target string) bool {
5932 if candidate == target {
5933 return true
5934 }
5935 if candidate.replace('.', '__') == target.replace('.', '__') {
5936 return true
5937 }
5938 return comptime_type_name_short(candidate) == comptime_type_name_short(target)
5939}
5940
5941fn (g &Gen) comptime_struct_for_type(typ types.Type) ?types.Struct {
5942 if typ is types.Struct {
5943 if typ.implements.len > 0 || typ.fields.len > 0 {
5944 return typ
5945 }
5946 }
5947 type_name := typ.name()
5948 mut candidates := []string{}
5949 if type_name != '' {
5950 candidates << type_name
5951 candidates << type_name.replace('__', '.')
5952 candidates << type_name.all_after_last('__')
5953 candidates << type_name.all_after_last('.')
5954 }
5955 c_name := g.types_type_to_c(typ).trim_space().trim_right('*')
5956 if c_name != '' {
5957 candidates << c_name
5958 candidates << c_name.replace('__', '.')
5959 candidates << c_name.all_after_last('__')
5960 }
5961 for candidate in candidates {
5962 mut mod_name := ''
5963 mut short_name := candidate
5964 if candidate.contains('.') {
5965 mod_name = candidate.all_before_last('.')
5966 short_name = candidate.all_after_last('.')
5967 } else if candidate.contains('__') {
5968 mod_name = candidate.all_before_last('__').replace('__', '.')
5969 short_name = candidate.all_after_last('__')
5970 }
5971 if mod_name != '' {
5972 if scope := g.env_scope(mod_name) {
5973 if resolved := scope.lookup_type_parent(short_name, 0) {
5974 if resolved is types.Struct {
5975 return resolved
5976 }
5977 }
5978 }
5979 }
5980 if scope := g.env_scope(g.cur_module) {
5981 if resolved := scope.lookup_type_parent(short_name, 0) {
5982 if resolved is types.Struct {
5983 return resolved
5984 }
5985 }
5986 }
5987 mut tried := map[string]bool{}
5988 if mod_name != '' {
5989 tried[mod_name] = true
5990 }
5991 if g.cur_module != '' {
5992 tried[g.cur_module] = true
5993 }
5994 for try_mod in ['main', 'builtin'] {
5995 if tried[try_mod] {
5996 continue
5997 }
5998 tried[try_mod] = true
5999 if scope := g.env_scope(try_mod) {
6000 if resolved := scope.lookup_type_parent(short_name, 0) {
6001 if resolved is types.Struct {
6002 return resolved
6003 }
6004 }
6005 }
6006 }
6007 if g.has_flat() {
6008 for i in 0 .. g.flat.files.len {
6009 file_mod := flat_file_module_name(g.flat.file_cursor(i))
6010 if file_mod == '' || tried[file_mod] {
6011 continue
6012 }
6013 tried[file_mod] = true
6014 if scope := g.env_scope(file_mod) {
6015 if resolved := scope.lookup_type_parent(short_name, 0) {
6016 if resolved is types.Struct {
6017 return resolved
6018 }
6019 }
6020 }
6021 }
6022 } else {
6023 for file in g.files {
6024 if file.mod == '' || tried[file.mod] {
6025 continue
6026 }
6027 tried[file.mod] = true
6028 if scope := g.env_scope(file.mod) {
6029 if resolved := scope.lookup_type_parent(short_name, 0) {
6030 if resolved is types.Struct {
6031 return resolved
6032 }
6033 }
6034 }
6035 }
6036 }
6037 }
6038 return none
6039}
6040
6041fn (g &Gen) comptime_type_implements_interface(typ types.Type, interface_name string) bool {
6042 st := g.comptime_struct_for_type(typ) or { return false }
6043 for impl_name in st.implements {
6044 if comptime_type_name_matches(impl_name, interface_name) {
6045 return true
6046 }
6047 if interface_name.contains('__') || interface_name.contains('.') {
6048 continue
6049 }
6050 if g.cur_module != '' && g.cur_module != 'main'
6051 && comptime_type_name_matches(impl_name, '${g.cur_module}.${interface_name}') {
6052 return true
6053 }
6054 }
6055 return false
6056}
6057
6058// comptime_matches_keyword checks if a type matches a comptime keyword like $struct, $enum, etc.
6059fn (g &Gen) comptime_matches_keyword(typ types.Type, keyword string) bool {
6060 match keyword {
6061 'struct' {
6062 if typ is types.Struct {
6063 return true
6064 }
6065 c_name := g.types_type_to_c(typ).trim_space().trim_right('*')
6066 if c_name != '' {
6067 return g.has_struct_type_by_c_name(c_name)
6068 }
6069 return false
6070 }
6071 'enum' {
6072 return typ is types.Enum
6073 }
6074 'int' {
6075 name := typ.name()
6076 return name in ['int', 'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64', 'byte',
6077 'isize', 'usize']
6078 }
6079 'float' {
6080 name := typ.name()
6081 return name in ['f32', 'f64']
6082 }
6083 'array' {
6084 return typ is types.Array
6085 }
6086 'map' {
6087 return typ is types.Map
6088 }
6089 'option' {
6090 return typ is types.OptionType
6091 }
6092 'string' {
6093 return typ is types.String || typ.name() == 'string'
6094 }
6095 else {
6096 return false
6097 }
6098 }
6099}
6100
6101fn (g &Gen) has_struct_type_by_c_name(c_name string) bool {
6102 if g.env == unsafe { nil } || c_name == '' {
6103 return false
6104 }
6105 mut mod_name := ''
6106 mut struct_name := c_name
6107 if idx := c_name.index('__') {
6108 mod_name = c_name[..idx]
6109 struct_name = c_name[idx + 2..]
6110 }
6111 mut tried := map[string]bool{}
6112 if mod_name != '' {
6113 tried[mod_name] = true
6114 if scope := g.env_scope(mod_name) {
6115 if obj := scope.lookup_parent(struct_name, 0) {
6116 return obj.typ() is types.Struct
6117 }
6118 }
6119 }
6120 cur_mod := if g.cur_module != '' { g.cur_module } else { 'main' }
6121 for try_mod in [cur_mod, 'main', 'builtin'] {
6122 if tried[try_mod] {
6123 continue
6124 }
6125 tried[try_mod] = true
6126 if scope := g.env_scope(try_mod) {
6127 if obj := scope.lookup_parent(struct_name, 0) {
6128 return obj.typ() is types.Struct
6129 }
6130 }
6131 }
6132 if g.has_flat() {
6133 for i in 0 .. g.flat.files.len {
6134 file_mod := flat_file_module_name(g.flat.file_cursor(i))
6135 if file_mod == '' || tried[file_mod] {
6136 continue
6137 }
6138 tried[file_mod] = true
6139 if scope := g.env_scope(file_mod) {
6140 if obj := scope.lookup_parent(struct_name, 0) {
6141 return obj.typ() is types.Struct
6142 }
6143 }
6144 }
6145 } else {
6146 for file in g.files {
6147 file_mod := file.mod
6148 if file_mod == '' || tried[file_mod] {
6149 continue
6150 }
6151 tried[file_mod] = true
6152 if scope := g.env_scope(file_mod) {
6153 if obj := scope.lookup_parent(struct_name, 0) {
6154 return obj.typ() is types.Struct
6155 }
6156 }
6157 }
6158 }
6159 return false
6160}
6161
6162// gen_comptime_field_selector handles comptime field access patterns:
6163// - field.name → V string literal with field name
6164// - field.name.str → C string literal
6165// - field.name.len → integer literal
6166// - field.attrs → array literal
6167// - val.$(field.name) → val->field_name or val.field_name
6168// Returns true if the selector was handled.
6169fn (mut g Gen) gen_comptime_field_selector(sel ast.SelectorExpr) bool {
6170 rhs_name := sel.rhs.name
6171 // Handle val.$(field.name) — comptime selector placeholder
6172 if is_comptime_selector_rhs_name(rhs_name) {
6173 lhs_expr := sel.lhs
6174 mut use_ptr := g.selector_use_ptr(lhs_expr)
6175 if lhs_expr is ast.Ident {
6176 if lhs_expr.name in g.cur_fn_mut_params {
6177 use_ptr = true
6178 } else if local_type := g.local_var_c_type_for_expr(lhs_expr) {
6179 use_ptr = local_type.ends_with('*') || local_type == 'chan'
6180 }
6181 }
6182 g.expr(lhs_expr)
6183 selector := if use_ptr { '->' } else { '.' }
6184 g.sb.write_string('${selector}${escape_c_keyword(g.comptime_field_name)}')
6185 return true
6186 }
6187 // Handle field.name, field.typ, field.is_mut, etc.
6188 if sel.lhs is ast.Ident {
6189 if sel.lhs.name == g.comptime_field_var {
6190 match rhs_name {
6191 'name' {
6192 g.sb.write_string(c_static_v_string_expr(g.comptime_field_name))
6193 return true
6194 }
6195 'typ' {
6196 g.sb.write_string(comptime_type_idx_literal(g.comptime_field_type))
6197 return true
6198 }
6199 'unaliased_typ' {
6200 unaliased := comptime_unalias_type(g.comptime_field_raw_type)
6201 g.sb.write_string(comptime_type_idx_literal(g.types_type_to_c(unaliased)))
6202 return true
6203 }
6204 'is_mut' {
6205 g.sb.write_string('true')
6206 return true
6207 }
6208 'is_pub' {
6209 g.sb.write_string('true')
6210 return true
6211 }
6212 'is_embed' {
6213 g.sb.write_string(if g.comptime_field_is_embed { 'true' } else { 'false' })
6214 return true
6215 }
6216 'is_shared' {
6217 g.sb.write_string('false')
6218 return true
6219 }
6220 'is_atomic' {
6221 g.sb.write_string('false')
6222 return true
6223 }
6224 'is_array' {
6225 is_arr := g.comptime_field_raw_type is types.Array
6226 g.sb.write_string(if is_arr { 'true' } else { 'false' })
6227 return true
6228 }
6229 'is_map' {
6230 is_map := g.comptime_field_raw_type is types.Map
6231 g.sb.write_string(if is_map { 'true' } else { 'false' })
6232 return true
6233 }
6234 'is_option' {
6235 is_opt := g.comptime_field_raw_type is types.OptionType
6236 g.sb.write_string(if is_opt { 'true' } else { 'false' })
6237 return true
6238 }
6239 'is_chan' {
6240 is_chan := g.comptime_field_raw_type is types.Channel
6241 g.sb.write_string(if is_chan { 'true' } else { 'false' })
6242 return true
6243 }
6244 'is_enum' {
6245 field_type := comptime_unalias_type(g.comptime_field_raw_type)
6246 g.sb.write_string(if field_type is types.Enum { 'true' } else { 'false' })
6247 return true
6248 }
6249 'is_struct' {
6250 field_type := comptime_unalias_type(g.comptime_field_raw_type)
6251 g.sb.write_string(if field_type is types.Struct { 'true' } else { 'false' })
6252 return true
6253 }
6254 'is_alias' {
6255 g.sb.write_string(if g.comptime_field_raw_type is types.Alias {
6256 'true'
6257 } else {
6258 'false'
6259 })
6260 return true
6261 }
6262 'attrs' {
6263 if g.comptime_field_attrs.len == 0 {
6264 g.sb.write_string('((Array_string){0})')
6265 } else {
6266 g.sb.write_string('new_array_from_c_array(${g.comptime_field_attrs.len}, ${g.comptime_field_attrs.len}, sizeof(string), (string[${g.comptime_field_attrs.len}]){')
6267 for i, attr in g.comptime_field_attrs {
6268 if i > 0 {
6269 g.sb.write_string(', ')
6270 }
6271 g.sb.write_string(c_static_v_string_expr(attr))
6272 }
6273 g.sb.write_string('})')
6274 }
6275 return true
6276 }
6277 else {}
6278 }
6279 }
6280 }
6281 // Handle field.name.str, field.name.len
6282 if sel.lhs is ast.SelectorExpr {
6283 inner := sel.lhs as ast.SelectorExpr
6284 if inner.lhs is ast.Ident && inner.lhs.name == g.comptime_field_var {
6285 if inner.rhs.name == 'name' {
6286 match rhs_name {
6287 'str' {
6288 g.sb.write_string('"${g.comptime_field_name}"')
6289 return true
6290 }
6291 'len' {
6292 g.sb.write_string('${g.comptime_field_name.len}')
6293 return true
6294 }
6295 else {}
6296 }
6297 }
6298 }
6299 }
6300 return false
6301}
6302
6303fn comptime_type_idx_literal(c_type string) string {
6304 mut type_name := c_type.trim_space().trim_right('*')
6305 if type_name.starts_with('_option_') {
6306 type_name = type_name['_option_'.len..]
6307 }
6308 if type_name.starts_with('_result_') {
6309 type_name = type_name['_result_'.len..]
6310 }
6311 v_name := c_name_to_v_name(type_name)
6312 idx := match v_name {
6313 'void' { 0 }
6314 'voidptr' { 1 }
6315 'byteptr' { 2 }
6316 'charptr' { 3 }
6317 'i8' { 4 }
6318 'i16' { 5 }
6319 'i32' { 6 }
6320 'int' { 7 }
6321 'i64' { 8 }
6322 'isize' { 9 }
6323 'u8', 'byte' { 10 }
6324 'u16' { 11 }
6325 'u32' { 12 }
6326 'u64' { 13 }
6327 'usize' { 14 }
6328 'f32' { 15 }
6329 'f64' { 16 }
6330 'char' { 17 }
6331 'bool' { 18 }
6332 'none' { 19 }
6333 'string' { 20 }
6334 'rune' { 21 }
6335 else { 0 }
6336 }
6337
6338 return idx.str()
6339}
6340
6341// gen_comptime_method_selector handles comptime method access patterns inside
6342// `$for method in T.methods` loop bodies:
6343// - method.name → V string literal
6344// - method.location → V string literal (empty for now)
6345// - method.attrs → []string array
6346// - method.return_type → integer literal (placeholder type id 0)
6347// - method.typ → integer literal (placeholder)
6348// - method.args → []FunctionParam array literal
6349// - method.args.len → integer literal
6350// - method.name.str/.len → C string / int literal
6351// Returns true if the selector was handled.
6352fn (mut g Gen) gen_comptime_method_selector(sel ast.SelectorExpr) bool {
6353 rhs_name := sel.rhs.name
6354 if sel.lhs is ast.Ident {
6355 if sel.lhs.name == g.comptime_method_var {
6356 match rhs_name {
6357 'name' {
6358 g.sb.write_string(c_static_v_string_expr(g.comptime_method_name))
6359 return true
6360 }
6361 'location' {
6362 g.sb.write_string(c_static_v_string_expr(''))
6363 return true
6364 }
6365 'return_type' {
6366 g.sb.write_string('0 /* method.return_type */')
6367 return true
6368 }
6369 'typ' {
6370 g.sb.write_string('0 /* method.typ */')
6371 return true
6372 }
6373 'attrs' {
6374 g.write_string_array_literal(g.comptime_method_attrs)
6375 return true
6376 }
6377 'args' {
6378 g.write_function_param_array_literal(g.comptime_method_args, 0)
6379 return true
6380 }
6381 else {}
6382 }
6383 }
6384 }
6385 // Handle method.name.str / method.name.len / method.args.len
6386 if sel.lhs is ast.SelectorExpr {
6387 inner := sel.lhs as ast.SelectorExpr
6388 if inner.lhs is ast.Ident && inner.lhs.name == g.comptime_method_var {
6389 inner_rhs := inner.rhs.name
6390 if inner_rhs == 'name' {
6391 match rhs_name {
6392 'str' {
6393 g.sb.write_string('"${g.comptime_method_name}"')
6394 return true
6395 }
6396 'len' {
6397 g.sb.write_string('${g.comptime_method_name.len}')
6398 return true
6399 }
6400 else {}
6401 }
6402 }
6403 if inner_rhs == 'args' && rhs_name == 'len' {
6404 g.sb.write_string('${g.comptime_method_args.len}')
6405 return true
6406 }
6407 if inner_rhs == 'attrs' && rhs_name == 'len' {
6408 g.sb.write_string('${g.comptime_method_attrs.len}')
6409 return true
6410 }
6411 }
6412 }
6413 return false
6414}
6415
6416// write_string_array_literal emits a V []string literal as a C
6417// new_array_from_c_array(...) call.
6418fn (mut g Gen) write_string_array_literal(items []string) {
6419 if items.len == 0 {
6420 g.sb.write_string('((Array_string){0})')
6421 return
6422 }
6423 g.sb.write_string('new_array_from_c_array(${items.len}, ${items.len}, sizeof(string), (string[${items.len}]){')
6424 for i, item in items {
6425 if i > 0 {
6426 g.sb.write_string(', ')
6427 }
6428 g.sb.write_string(c_static_v_string_expr(item))
6429 }
6430 g.sb.write_string('})')
6431}
6432
6433// write_function_param_array_literal emits a V []FunctionParam literal.
6434// `skip` is the number of leading params to drop (e.g. 1 to skip receiver — but
6435// `method.args` historically did NOT include the receiver, so default is 0).
6436fn (mut g Gen) write_function_param_array_literal(params []ast.Parameter, skip int) {
6437 effective := if skip < params.len { params[skip..] } else { []ast.Parameter{} }
6438 if effective.len == 0 {
6439 g.sb.write_string('((Array_FunctionParam){0})')
6440 return
6441 }
6442 g.sb.write_string('new_array_from_c_array(${effective.len}, ${effective.len}, sizeof(FunctionParam), (FunctionParam[${effective.len}]){')
6443 for i, p in effective {
6444 if i > 0 {
6445 g.sb.write_string(', ')
6446 }
6447 g.sb.write_string('{.typ = 0, .name = ')
6448 g.sb.write_string(c_static_v_string_expr(p.name))
6449 g.sb.write_string('}')
6450 }
6451 g.sb.write_string('})')
6452}
6453
6454fn (mut g Gen) gen_comptime_if_branch(stmts []ast.Stmt) {
6455 if stmts.len == 0 {
6456 g.sb.write_string('0')
6457 return
6458 }
6459 if stmts.len == 1 && stmts[0] is ast.ExprStmt {
6460 stmt := stmts[0] as ast.ExprStmt
6461 g.expr(stmt.expr)
6462 return
6463 }
6464 g.sb.write_string('({ ')
6465 for i, stmt in stmts {
6466 if i < stmts.len - 1 {
6467 g.gen_stmt(stmt)
6468 }
6469 }
6470 last := stmts[stmts.len - 1]
6471 if last is ast.ExprStmt {
6472 g.expr(last.expr)
6473 g.sb.write_string('; ')
6474 } else {
6475 g.gen_stmt(last)
6476 g.sb.write_string('0; ')
6477 }
6478 g.sb.write_string('})')
6479}
6480
6481fn (mut g Gen) gen_comptime_if_expr(node ast.IfExpr) {
6482 if g.eval_comptime_cond(node.cond) {
6483 g.gen_comptime_if_branch(node.stmts)
6484 return
6485 }
6486 if node.else_expr is ast.IfExpr {
6487 else_if := node.else_expr as ast.IfExpr
6488 if else_if.cond is ast.EmptyExpr {
6489 g.gen_comptime_if_branch(else_if.stmts)
6490 } else {
6491 g.gen_comptime_if_expr(else_if)
6492 }
6493 return
6494 }
6495 if node.else_expr !is ast.EmptyExpr {
6496 g.expr(node.else_expr)
6497 return
6498 }
6499 g.sb.write_string('0')
6500}
6501
6502fn (mut g Gen) gen_comptime_expr(node ast.ComptimeExpr) {
6503 if is_veb_html_comptime_call(node.expr) {
6504 g.sb.write_string('(veb__Result){0}')
6505 return
6506 }
6507 if node.expr is ast.Ident {
6508 name := node.expr.name
6509 match name {
6510 'FN', 'METHOD', 'FUNCTION' {
6511 fn_name := g.cur_fn_name
6512 g.sb.write_string(c_static_v_string_expr(fn_name))
6513 }
6514 'LOCATION' {
6515 fn_name := g.cur_fn_name
6516 g.sb.write_string(c_static_v_string_expr('${g.cur_module}.${fn_name}'))
6517 }
6518 'MOD' {
6519 mod_name := g.cur_module
6520 g.sb.write_string(c_static_v_string_expr(mod_name))
6521 }
6522 'FILE' {
6523 g.sb.write_string(c_v_string_expr_from_ptr_len('__FILE__', 'sizeof(__FILE__)-1',
6524 true))
6525 }
6526 'LINE' {
6527 g.sb.write_string('__LINE__')
6528 }
6529 'VCURRENTHASH' {
6530 g.sb.write_string(c_static_v_string_expr(g.get_v_hash()))
6531 }
6532 'VEXE' {
6533 g.sb.write_string('__vexe_path()')
6534 }
6535 else {
6536 g.sb.write_string('${c_empty_v_string_expr()} /* unknown comptime: ${name} */')
6537 }
6538 }
6539
6540 return
6541 }
6542 if node.expr is ast.CallExpr {
6543 if node.expr.lhs is ast.Ident {
6544 match node.expr.lhs.name {
6545 'res' {
6546 g.sb.write_string('false')
6547 return
6548 }
6549 'env' {
6550 if node.expr.args.len > 0 {
6551 env_key := comptime_string_arg(node.expr.args[0])
6552 g.sb.write_string(c_static_v_string_expr(os.getenv(env_key)))
6553 } else {
6554 g.sb.write_string(c_empty_v_string_expr())
6555 }
6556 return
6557 }
6558 'compile_error' {
6559 msg := if node.expr.args.len > 0 {
6560 comptime_string_arg(node.expr.args[0])
6561 } else {
6562 'compile error'
6563 }
6564 panic('comptime compile_error: ${msg}')
6565 }
6566 'compile_warn' {
6567 // Keep compilation moving; warnings are not surfaced by cleanc yet.
6568 g.sb.write_string(c_empty_v_string_expr())
6569 return
6570 }
6571 else {}
6572 }
6573 }
6574 }
6575 if node.expr is ast.CallOrCastExpr {
6576 if node.expr.lhs is ast.Ident {
6577 match node.expr.lhs.name {
6578 'res' {
6579 g.sb.write_string('false')
6580 return
6581 }
6582 'env' {
6583 env_key := comptime_string_arg(node.expr.expr)
6584 g.sb.write_string(c_static_v_string_expr(os.getenv(env_key)))
6585 return
6586 }
6587 'compile_error' {
6588 msg := comptime_string_arg(node.expr.expr)
6589 panic('comptime compile_error: ${msg}')
6590 }
6591 'compile_warn' {
6592 g.sb.write_string(c_empty_v_string_expr())
6593 return
6594 }
6595 else {}
6596 }
6597 }
6598 }
6599 // Fallback: emit the inner expression
6600 g.expr(node.expr)
6601}
6602
6603fn is_veb_html_comptime_call(expr ast.Expr) bool {
6604 match expr {
6605 ast.CallExpr {
6606 return is_veb_html_comptime_lhs(expr.lhs)
6607 }
6608 ast.CallOrCastExpr {
6609 return is_veb_html_comptime_lhs(expr.lhs)
6610 }
6611 else {
6612 return false
6613 }
6614 }
6615}
6616
6617fn is_veb_html_comptime_lhs(lhs ast.Expr) bool {
6618 if lhs is ast.SelectorExpr {
6619 if lhs.lhs is ast.Ident {
6620 return lhs.lhs.name == 'veb' && lhs.rhs.name == 'html'
6621 }
6622 }
6623 return false
6624}
6625
6626fn comptime_string_arg(expr ast.Expr) string {
6627 match expr {
6628 ast.StringLiteral {
6629 return expr.value
6630 }
6631 ast.BasicLiteral {
6632 if expr.kind == .string {
6633 return expr.value
6634 }
6635 return expr.value
6636 }
6637 else {
6638 return expr.name()
6639 }
6640 }
6641}
6642
6643fn (mut g Gen) expr_to_string(expr ast.Expr) string {
6644 saved_sb := g.sb
6645 mut tmp_sb := strings.new_builder(64)
6646 g.sb = tmp_sb
6647 g.expr(expr)
6648 value := g.sb.str()
6649 g.sb = saved_sb
6650 return value
6651}
6652
6653fn is_header_type_only_const_expr(expr ast.Expr) bool {
6654 return match expr {
6655 ast.Type {
6656 true
6657 }
6658 ast.Ident {
6659 name := expr.name
6660 name in ['bool', 'byte', 'char', 'f32', 'f64', 'i8', 'i16', 'i32', 'int', 'i64', 'isize', 'rune', 'string', 'u8', 'u16', 'u32', 'u64', 'usize', 'void', 'voidptr', 'byteptr', 'charptr']
6661 || name.starts_with('&') || name.starts_with('[]') || name.starts_with('?')
6662 || name.starts_with('!') || name.contains('[') || name.contains('__')
6663 }
6664 else {
6665 false
6666 }
6667 }
6668}
6669
6670fn (mut g Gen) gen_heap_address_of_cast_expr(node ast.CastExpr, target_type string) bool {
6671 if target_type == '' || target_type == 'int' || target_type.ends_with('*') {
6672 return false
6673 }
6674 if node.expr is ast.PrefixExpr && node.expr.op == .amp {
6675 return false
6676 }
6677 if target_type !in g.sum_type_variants && node.expr !is ast.AsCastExpr {
6678 return false
6679 }
6680 tmp_name := '_heap_t${g.tmp_counter}'
6681 g.tmp_counter++
6682 malloc_call := g.c_heap_malloc_call('sizeof(${target_type})')
6683 g.sb.write_string('({ ${target_type}* ${tmp_name} = (${target_type}*)${malloc_call}; *${tmp_name} = ')
6684 g.expr(ast.Expr(node))
6685 g.sb.write_string('; ${tmp_name}; })')
6686 return true
6687}
6688
6689fn (g &Gen) cast_target_is_aggregate_value(type_name string) bool {
6690 name := type_name.trim_space()
6691 if name == '' || is_type_name_pointer_like(name) || name in primitive_types
6692 || g.is_enum_type(name) {
6693 return false
6694 }
6695 if g.c_type_is_fn_pointer_alias(name) {
6696 return false
6697 }
6698 return name !in ['void', 'void*', 'voidptr', 'char*', 'charptr', 'byteptr']
6699}
6700
6701fn same_aggregate_cast_type(expr_type string, type_name string) bool {
6702 expr_name := expr_type.trim_space()
6703 target_name := type_name.trim_space()
6704 if expr_name == '' || target_name == '' || is_type_name_pointer_like(expr_name) {
6705 return false
6706 }
6707 if expr_name == target_name {
6708 return true
6709 }
6710 return (!expr_name.contains('__') || !target_name.contains('__'))
6711 && short_type_name(expr_name) == short_type_name(target_name)
6712}
6713
6714fn (mut g Gen) gen_cast_expr(node ast.CastExpr) {
6715 mut type_name := g.cast_target_type_to_c(node.typ)
6716 if resolved_type := g.resolved_sum_data_cast_type(node) {
6717 type_name = resolved_type
6718 }
6719 if type_name.starts_with('_option_') && is_none_like_expr(node.expr) {
6720 g.sb.write_string('(${type_name}){ .state = 2 }')
6721 return
6722 }
6723 if node.expr is ast.BasicLiteral && node.expr.kind == .number && node.expr.value == '0' {
6724 if type_name !in primitive_types && !type_name.ends_with('*') && !g.is_enum_type(type_name)
6725 && type_name !in ['void*', 'char*', 'byteptr', 'charptr', 'voidptr'] {
6726 g.sb.write_string(zero_value_for_type(type_name))
6727 return
6728 }
6729 }
6730 if type_name.starts_with('_option_') || type_name.starts_with('_result_') {
6731 expr_type := g.get_expr_type(node.expr)
6732 if expr_type == type_name {
6733 g.expr(node.expr)
6734 return
6735 }
6736 if node.expr is ast.CastExpr && g.expr_type_to_c(node.expr.typ) == type_name {
6737 g.expr(node.expr)
6738 return
6739 }
6740 }
6741 if type_name.starts_with('_option_') {
6742 value_type := option_value_type(type_name)
6743 if value_type != '' && value_type != 'void' {
6744 g.sb.write_string('({ ${type_name} _opt = (${type_name}){ .state = 2 }; ${value_type} _val = ')
6745 // For interface value types, wrap the expression in an interface cast
6746 if g.is_interface_type(value_type) && g.gen_interface_cast(value_type, node.expr) {
6747 // interface wrapping handled
6748 } else if g.gen_auto_deref_value_param_arg(value_type, node.expr) {
6749 } else {
6750 g.expr(node.expr)
6751 }
6752 g.sb.write_string('; _option_ok(&_val, (_option*)&_opt, sizeof(_val)); _opt; })')
6753 return
6754 }
6755 }
6756 if type_name.starts_with('_result_') {
6757 value_type := g.result_value_c_type(type_name)
6758 if value_type != '' && value_type != 'void' {
6759 g.sb.write_string('({ ${type_name} _res = (${type_name}){0}; ${value_type} _val = ')
6760 if !g.gen_auto_deref_value_param_arg(value_type, node.expr) {
6761 g.expr(node.expr)
6762 }
6763 g.sb.write_string('; _result_ok(&_val, (_result*)&_res, sizeof(_val)); _res; })')
6764 return
6765 }
6766 }
6767 if type_name == 'void' {
6768 // Preserve side effects when discarding expression results.
6769 // e.g. `(void)(f())` must still call `f()`.
6770 mut inner := strings.new_builder(64)
6771 saved := g.sb
6772 g.sb = inner
6773 g.expr(node.expr)
6774 inner_str := g.sb.str()
6775 g.sb = saved
6776 if inner_str == '' {
6777 g.sb.write_string('((void)0)')
6778 } else {
6779 g.sb.write_string('((void)(${inner_str}))')
6780 }
6781 return
6782 }
6783 if type_name !in g.sum_type_variants && g.cast_target_is_aggregate_value(type_name)
6784 && type_name.contains('__') && g.expr_is_pointer(node.expr) {
6785 g.sb.write_string('((${type_name}*)(')
6786 g.expr(node.expr)
6787 g.sb.write_string('))')
6788 return
6789 }
6790 expr_type := g.get_expr_type(node.expr)
6791 if expr_type.starts_with('_result_') && g.result_value_type(expr_type) != '' {
6792 g.sb.write_string('((${type_name})(')
6793 g.gen_unwrapped_value_expr(node.expr)
6794 g.sb.write_string('))')
6795 return
6796 }
6797 if expr_type.starts_with('_option_') && option_value_type(expr_type) != '' {
6798 g.sb.write_string('((${type_name})(')
6799 g.gen_unwrapped_value_expr(node.expr)
6800 g.sb.write_string('))')
6801 return
6802 }
6803 if g.expr_is_sum_variant_extract(node.expr, type_name) {
6804 g.gen_as_cast_expr(ast.AsCastExpr{
6805 expr: node.expr
6806 typ: node.typ
6807 pos: node.pos
6808 })
6809 return
6810 }
6811 // Handle sum type and interface casts
6812 if type_name in g.sum_type_variants {
6813 g.gen_type_cast_expr(type_name, node.expr)
6814 return
6815 }
6816 if g.cast_target_is_aggregate_value(type_name) && same_aggregate_cast_type(expr_type, type_name) {
6817 g.expr(node.expr)
6818 return
6819 }
6820 if g.gen_interface_cast(type_name, node.expr) {
6821 return
6822 }
6823 // Capture inner expression to check if it's empty
6824 mut inner := strings.new_builder(64)
6825 saved := g.sb
6826 g.sb = inner
6827 g.expr(node.expr)
6828 inner_str := g.sb.str()
6829 g.sb = saved
6830 if inner_str == '' {
6831 // Empty inner expression (e.g. unresolved C function call) - emit no-op
6832 g.sb.write_string('((void)0)')
6833 } else {
6834 // When casting a pointer field (.data, .metas, const_s, pvalue) to a
6835 // scalar type like u8, the intended C semantics is a pointer cast
6836 // (u8*), not a value cast (u8). Detect this and add the pointer star.
6837 mut cast_name := type_name
6838 if !type_name.ends_with('*') && type_name in ['u8', 'i8', 'char'] {
6839 if inner_str.ends_with('.data)') || inner_str.ends_with('.data)')
6840 || inner_str.ends_with('->data)') || inner_str.ends_with('.metas)')
6841 || inner_str.ends_with('->metas)')
6842 || (node.expr is ast.Ident && node.expr.name in ['const_s', 'pvalue']) {
6843 cast_name = '${type_name}*'
6844 }
6845 }
6846 if heap_payload_type := g.heap_sum_payload_cast_type(node, type_name) {
6847 g.sb.write_string('((${cast_name})(*((${heap_payload_type}*)(${inner_str}))))')
6848 return
6849 }
6850 g.sb.write_string('((${cast_name})(${inner_str}))')
6851 }
6852}
6853
6854fn (mut g Gen) heap_sum_payload_cast_type(node ast.CastExpr, type_name string) ?string {
6855 if type_name.ends_with('*') || g.is_scalar_sum_payload_type(type_name) {
6856 return none
6857 }
6858 if !g.is_sum_data_payload_selector(node.expr) {
6859 return none
6860 }
6861 return type_name
6862}
6863
6864fn (mut g Gen) cast_expr_is_sum_variant_extract(node ast.CastExpr, target_type string) bool {
6865 return g.expr_is_sum_variant_extract(node.expr, target_type)
6866}
6867
6868fn (mut g Gen) expr_is_sum_variant_extract(expr ast.Expr, target_type string) bool {
6869 if target_type == '' || target_type == 'int' || target_type in g.sum_type_variants {
6870 return false
6871 }
6872 source_sum_type := g.cast_source_sum_type(expr)
6873 if source_sum_type == '' {
6874 return false
6875 }
6876 variants := g.get_sum_type_variants_for(source_sum_type)
6877 if variants.len == 0 {
6878 return false
6879 }
6880 for variant in variants {
6881 if sum_type_variant_matches(variant, target_type) {
6882 return true
6883 }
6884 }
6885 return false
6886}
6887
6888fn (mut g Gen) cast_source_sum_type(expr ast.Expr) string {
6889 match expr {
6890 ast.ParenExpr, ast.ModifierExpr {
6891 return g.cast_source_sum_type(expr.expr)
6892 }
6893 ast.SelectorExpr {
6894 for candidate in [
6895 g.selector_declared_field_type(expr).trim_space().trim_right('*'),
6896 g.selector_storage_field_type(expr).trim_space().trim_right('*'),
6897 ] {
6898 if candidate != '' && g.get_sum_type_variants_for(candidate).len > 0 {
6899 return candidate
6900 }
6901 }
6902 }
6903 ast.CallExpr {
6904 elem_type := g.infer_array_method_elem_type(expr).trim_space().trim_right('*')
6905 if elem_type != '' && g.get_sum_type_variants_for(elem_type).len > 0 {
6906 return elem_type
6907 }
6908 }
6909 else {}
6910 }
6911
6912 mut source_sum_type := g.get_expr_type(expr).trim_space().trim_right('*')
6913 if expr is ast.PrefixExpr && expr.op == .mul {
6914 inner := g.unwrap_parens(expr.expr)
6915 if inner is ast.CastExpr {
6916 cast_source_type := g.expr_type_to_c(inner.typ).trim_space().trim_right('*')
6917 if g.get_sum_type_variants_for(cast_source_type).len > 0 {
6918 source_sum_type = cast_source_type
6919 }
6920 }
6921 }
6922 if raw_type := g.get_raw_type(expr) {
6923 match raw_type {
6924 types.SumType {
6925 source_sum_type = g.types_type_to_c(raw_type)
6926 }
6927 types.Pointer {
6928 if raw_type.base_type is types.SumType {
6929 source_sum_type = g.types_type_to_c(raw_type.base_type)
6930 }
6931 }
6932 else {}
6933 }
6934 }
6935 if g.get_sum_type_variants_for(source_sum_type).len > 0 {
6936 return source_sum_type
6937 }
6938 return ''
6939}
6940
6941fn (mut g Gen) gen_as_cast_expr(node ast.AsCastExpr) {
6942 target_type_name := g.expr_type_to_c(node.typ)
6943 if g.gen_nested_sum_as_cast_for_selector_source(node.expr, target_type_name) {
6944 return
6945 }
6946 mut type_name := target_type_name
6947 mut source_sum_type := g.cast_source_sum_type(node.expr)
6948 if source_sum_type == '' {
6949 source_sum_type = g.get_expr_type(node.expr).trim_space().trim_right('*')
6950 }
6951 mut raw_source_expr := ''
6952 if node.expr is ast.SelectorExpr {
6953 declared_field_type := g.selector_declared_field_type(node.expr).trim_right('*')
6954 if g.get_sum_type_variants_for(declared_field_type).len > 0 {
6955 source_sum_type = declared_field_type
6956 raw_source_expr = g.raw_selector_expr_code(node.expr)
6957 }
6958 }
6959 if raw_source_expr == '' {
6960 if raw_type := g.get_raw_type(node.expr) {
6961 match raw_type {
6962 types.SumType {
6963 source_sum_type = g.types_type_to_c(raw_type)
6964 }
6965 types.Pointer {
6966 if raw_type.base_type is types.SumType {
6967 source_sum_type = g.types_type_to_c(raw_type.base_type)
6968 }
6969 }
6970 else {}
6971 }
6972 }
6973 }
6974 mut variant_field := g.sum_type_variant_field_name(source_sum_type, target_type_name)
6975 type_name = g.sum_type_variant_payload_type(source_sum_type, target_type_name, variant_field)
6976 short_name := if type_name.contains('__') {
6977 type_name.all_after_last('__')
6978 } else {
6979 type_name
6980 }
6981 // Interface cast: `iface as T` => `*((T*)iface._object)`
6982 mut source_is_iface := false
6983 if raw_type := g.get_raw_type(node.expr) {
6984 source_is_iface = raw_type is types.Interface
6985 || (raw_type is types.Pointer && raw_type.base_type is types.Interface)
6986 }
6987 // Fallback: check C type name if raw_type didn't resolve to interface
6988 if !source_is_iface {
6989 source_c_type := g.get_expr_type(node.expr)
6990 if source_c_type != '' {
6991 source_is_iface = g.is_interface_type(source_c_type.trim_right('*'))
6992 }
6993 }
6994 if source_is_iface {
6995 sep := if g.expr_is_pointer(node.expr) { '->' } else { '.' }
6996 g.sb.write_string('(*((${type_name}*)(')
6997 g.expr(node.expr)
6998 g.sb.write_string('${sep}_object)))')
6999 return
7000 }
7001 mut inner := strings.new_builder(64)
7002 saved := g.sb
7003 mut inner_str := raw_source_expr
7004 if inner_str == '' {
7005 g.sb = inner
7006 g.expr(node.expr)
7007 inner_str = g.sb.str()
7008 }
7009 g.sb = saved
7010 if g.contains_as_cast_expr(node.expr)
7011 && !g.as_cast_expr_extracts_direct_sum_variant(node.expr, source_sum_type, target_type_name) {
7012 g.sb.write_string('((${type_name})(${inner_str}))')
7013 return
7014 }
7015 if node.expr is ast.SelectorExpr {
7016 storage_sum_type := g.selector_storage_field_type(node.expr).trim_right('*')
7017 if storage_sum_type != '' && storage_sum_type != source_sum_type
7018 && g.get_sum_type_variants_for(storage_sum_type).len > 0
7019 && !g.sum_type_has_direct_variant(source_sum_type, target_type_name) {
7020 if path := g.sum_variant_tag_path(storage_sum_type, target_type_name, []string{}) {
7021 if path.len > 1 {
7022 nested_target_type := path[path.len - 1].payload_type
7023 if g.gen_nested_sum_as_cast(g.raw_selector_expr_code(node.expr), node.expr,
7024 nested_target_type, path)
7025 {
7026 return
7027 }
7028 }
7029 }
7030 }
7031 }
7032 mut emitted_nested_sum_cast := false
7033 if !g.sum_type_has_direct_variant(source_sum_type, target_type_name) {
7034 if path := g.sum_variant_tag_path(source_sum_type, target_type_name, []string{}) {
7035 if path.len > 1 {
7036 nested_target_type := path[path.len - 1].payload_type
7037 if g.gen_nested_sum_as_cast(inner_str, node.expr, nested_target_type, path) {
7038 emitted_nested_sum_cast = true
7039 return
7040 }
7041 }
7042 }
7043 }
7044 if !emitted_nested_sum_cast && node.expr is ast.SelectorExpr
7045 && !g.sum_type_has_direct_variant(source_sum_type, target_type_name) {
7046 if path := g.nested_sum_path_from_narrowed_source(source_sum_type, target_type_name) {
7047 nested_target_type := path[path.len - 1].payload_type
7048 if g.gen_nested_sum_as_cast(inner_str, node.expr, nested_target_type, path) {
7049 return
7050 }
7051 }
7052 }
7053 marker := ')->_data._${variant_field}'
7054 if idx := inner_str.index(marker) {
7055 simplified := inner_str[..idx + 1]
7056 g.sb.write_string('(*(${simplified}))')
7057 return
7058 }
7059 marker2 := ')->_data)._${variant_field}'
7060 if idx := inner_str.index(marker2) {
7061 simplified := inner_str[..idx + 1]
7062 g.sb.write_string('(*(${simplified}))')
7063 return
7064 }
7065 short_marker := ')->_data._${short_name}'
7066 if idx := inner_str.index(short_marker) {
7067 simplified := inner_str[..idx + 1]
7068 g.sb.write_string('(*(${simplified}))')
7069 return
7070 }
7071 short_marker2 := ')->_data)._${short_name}'
7072 if idx := inner_str.index(short_marker2) {
7073 simplified := inner_str[..idx + 1]
7074 g.sb.write_string('(*(${simplified}))')
7075 return
7076 }
7077 expr_is_declared_sum_value := g.expr_is_declared_sum_value(node.expr)
7078 if (inner_str.starts_with('((${type_name}*)') || inner_str.starts_with('(${type_name}*)'))
7079 && !expr_is_declared_sum_value {
7080 g.sb.write_string('(*(${inner_str}))')
7081 return
7082 }
7083 if inner_str.starts_with('(*(') || inner_str.starts_with('*(') {
7084 is_sum_value_deref := source_sum_type != ''
7085 && g.get_sum_type_variants_for(source_sum_type).len > 0
7086 && inner_str.contains('${source_sum_type}*')
7087 if !is_sum_value_deref && !expr_is_declared_sum_value {
7088 g.sb.write_string('((${type_name})(${inner_str}))')
7089 return
7090 }
7091 }
7092 if g.is_sum_payload_expr(node.expr, variant_field)
7093 || g.is_sum_payload_expr(node.expr, short_name) {
7094 g.sb.write_string('(*((${type_name}*)(${inner_str})))')
7095 return
7096 }
7097 // Sum type cast: a as Cat => (*((main__Cat*)(((a))._data._Cat)))
7098 mut sum_source_expr := inner_str
7099 mut sep := if g.expr_is_pointer(node.expr) { '->' } else { '.' }
7100 unwrapped_source_expr := strip_expr_wrappers(node.expr)
7101 if unwrapped_source_expr is ast.PrefixExpr && unwrapped_source_expr.op == .mul {
7102 if !sum_source_expr.starts_with('(') {
7103 sum_source_expr = '(${sum_source_expr})'
7104 }
7105 sep = '.'
7106 }
7107 // Non-scalar sum type variants are stored as heap pointers that can be NULL
7108 // when the sum type is zero-initialized. Add null guard with zero fallback.
7109 if !g.is_scalar_sum_payload_type(type_name) && type_name != 'string' {
7110 g.sb.write_string('(((${sum_source_expr})${sep}_data._${variant_field}) ? (*((${type_name}*)(((${sum_source_expr})${sep}_data._${variant_field})))) : (${type_name}){0})')
7111 } else {
7112 g.sb.write_string('(*((${type_name}*)(((${sum_source_expr})${sep}_data._${variant_field}))))')
7113 }
7114}
7115
7116fn (mut g Gen) as_cast_expr_extracts_direct_sum_variant(expr ast.Expr, source_sum_type string, target_type_name string) bool {
7117 if source_sum_type == '' || target_type_name == '' {
7118 return false
7119 }
7120 if !g.sum_type_has_direct_variant(source_sum_type, target_type_name) {
7121 return false
7122 }
7123 unwrapped := strip_expr_wrappers(expr)
7124 return unwrapped is ast.AsCastExpr
7125}
7126
7127fn (mut g Gen) expr_is_declared_sum_value(expr ast.Expr) bool {
7128 match expr {
7129 ast.SelectorExpr {
7130 declared := g.selector_declared_field_type(expr).trim_space().trim_right('*')
7131 return declared != '' && g.get_sum_type_variants_for(declared).len > 0
7132 }
7133 ast.ParenExpr {
7134 return g.expr_is_declared_sum_value(expr.expr)
7135 }
7136 ast.ModifierExpr {
7137 return g.expr_is_declared_sum_value(expr.expr)
7138 }
7139 else {}
7140 }
7141
7142 return false
7143}
7144
7145fn (mut g Gen) raw_selector_expr_code(sel ast.SelectorExpr) string {
7146 saved := g.sb
7147 g.sb = strings.new_builder(128)
7148 g.expr(sel.lhs)
7149 lhs_code := g.sb.str()
7150 g.sb = saved
7151 mut use_ptr := g.selector_use_ptr(sel.lhs)
7152 if sel.lhs is ast.Ident {
7153 lhs_name := sel.lhs.name
7154 if lhs_name in g.cur_fn_mut_params {
7155 use_ptr = true
7156 } else if local_type := g.local_var_c_type_for_expr(sel.lhs) {
7157 use_ptr = local_type.ends_with('*') || local_type == 'chan'
7158 }
7159 }
7160 selector := if use_ptr { '->' } else { '.' }
7161 return '${lhs_code}${selector}${escape_c_keyword(sel.rhs.name)}'
7162}
7163
7164fn (g &Gen) unwrap_parens(expr ast.Expr) ast.Expr {
7165 if expr is ast.ParenExpr {
7166 return g.unwrap_parens(expr.expr)
7167 }
7168 return expr
7169}
7170
7171// is_sum_data_ptr_deref detects CastExpr(Type*, SelectorExpr{..._data._Variant})
7172// patterns generated by smartcast lowering. These pointers can be NULL when a
7173// sum type is zero-initialized.
7174fn (mut g Gen) is_sum_data_ptr_deref(expr ast.Expr) bool {
7175 inner := g.unwrap_parens(expr)
7176 if inner is ast.CastExpr {
7177 type_name := g.resolved_sum_data_cast_type(inner) or { g.expr_type_to_c(inner.typ) }
7178 if type_name.ends_with('*') && !g.is_scalar_sum_payload_type(type_name.trim_right('*')) {
7179 cast_inner := g.unwrap_parens(inner.expr)
7180 if cast_inner is ast.SelectorExpr {
7181 if cast_inner.rhs.name.starts_with('_') {
7182 // Check if LHS is also a _data selector
7183 if cast_inner.lhs is ast.SelectorExpr {
7184 lhs_sel := cast_inner.lhs as ast.SelectorExpr
7185 if lhs_sel.rhs.name == '_data' {
7186 return true
7187 }
7188 }
7189 }
7190 }
7191 }
7192 }
7193 return false
7194}
7195
7196fn (mut g Gen) is_sum_data_payload_selector(expr ast.Expr) bool {
7197 inner := g.unwrap_parens(expr)
7198 sel := match inner {
7199 ast.SelectorExpr {
7200 inner
7201 }
7202 else {
7203 return false
7204 }
7205 }
7206
7207 if !sel.rhs.name.starts_with('_') {
7208 return false
7209 }
7210 lhs_sel := match sel.lhs {
7211 ast.SelectorExpr {
7212 sel.lhs
7213 }
7214 else {
7215 return false
7216 }
7217 }
7218
7219 return lhs_sel.rhs.name == '_data'
7220}
7221
7222fn (mut g Gen) resolved_sum_data_cast_type(node ast.CastExpr) ?string {
7223 mut type_name := g.expr_type_to_c(node.typ)
7224 mut ptr_suffix := ''
7225 for type_name.ends_with('*') {
7226 ptr_suffix += '*'
7227 type_name = type_name[..type_name.len - 1]
7228 }
7229 cast_inner := g.unwrap_parens(node.expr)
7230 sel := match cast_inner {
7231 ast.SelectorExpr {
7232 cast_inner
7233 }
7234 else {
7235 return none
7236 }
7237 }
7238
7239 if !sel.rhs.name.starts_with('_') {
7240 return none
7241 }
7242 lhs_sel := match sel.lhs {
7243 ast.SelectorExpr {
7244 sel.lhs
7245 }
7246 else {
7247 return none
7248 }
7249 }
7250
7251 if lhs_sel.rhs.name != '_data' {
7252 return none
7253 }
7254 raw_variant := sel.rhs.name[1..]
7255 mut sum_type := ''
7256 if raw_type := g.get_raw_type(lhs_sel.lhs) {
7257 match raw_type {
7258 types.SumType {
7259 sum_type = g.types_type_to_c(raw_type)
7260 }
7261 types.Pointer {
7262 if raw_type.base_type is types.SumType {
7263 sum_type = g.types_type_to_c(raw_type.base_type)
7264 }
7265 }
7266 types.Alias {
7267 if raw_type.base_type is types.SumType {
7268 sum_type = g.types_type_to_c(raw_type.base_type)
7269 }
7270 }
7271 else {}
7272 }
7273 }
7274 if sum_type == '' {
7275 sum_type = g.get_expr_type(lhs_sel.lhs).trim_space().trim_right('*')
7276 }
7277 variant_field := g.sum_type_variant_field_name(sum_type, raw_variant)
7278 payload_type := g.sum_type_variant_payload_type(sum_type, type_name, variant_field)
7279 if payload_type == '' || payload_type == type_name {
7280 return none
7281 }
7282 return payload_type + ptr_suffix
7283}
7284
7285// get_cast_expr_type_name extracts the base type name from a CastExpr's target type.
7286fn (mut g Gen) get_cast_expr_type_name(expr ast.Expr) string {
7287 inner := g.unwrap_parens(expr)
7288 if inner is ast.CastExpr {
7289 type_name := g.resolved_sum_data_cast_type(inner) or { g.expr_type_to_c(inner.typ) }
7290 return type_name.trim_right('*')
7291 }
7292 return ''
7293}
7294