v / vlib / v2 / gen / cleanc / assign.v
1977 lines · 1942 sloc · 65.05 KB · e7738c112c787d477501fa4a87edd0e1d72159bd
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.token
9import v2.types
10
11fn is_zero_number_expr(expr ast.Expr) bool {
12 return expr is ast.BasicLiteral && expr.kind == .number && expr.value == '0'
13}
14
15fn decl_lhs_name(expr ast.Expr) string {
16 match expr {
17 ast.Ident {
18 return expr.name
19 }
20 ast.ModifierExpr {
21 return decl_lhs_name(expr.expr)
22 }
23 else {
24 return ''
25 }
26 }
27}
28
29fn decl_lhs_has_modifier(expr ast.Expr, kind token.Token) bool {
30 match expr {
31 ast.ModifierExpr {
32 return expr.kind == kind || decl_lhs_has_modifier(expr.expr, kind)
33 }
34 else {
35 return false
36 }
37 }
38}
39
40fn decl_lhs_storage_prefix(expr ast.Expr) string {
41 if decl_lhs_has_modifier(expr, .key_static) {
42 return 'static '
43 }
44 return ''
45}
46
47fn is_array_result_call_name(call_name string) bool {
48 if call_name in ['__new_array_with_default_noscan', '__new_array_with_default',
49 'builtin____new_array_with_default_noscan', 'builtin____new_array_with_default',
50 'new_array_from_c_array', 'builtin__new_array_from_c_array',
51 'builtin__new_array_from_c_array_noscan', 'array__slice', 'array__slice_ni'] {
52 return true
53 }
54 return call_name.starts_with('Array_')
55 && (call_name.ends_with('__slice') || call_name.ends_with('__slice_ni'))
56}
57
58fn (g &Gen) c_type_needs_typed_zero_expr(typ string) bool {
59 t := typ.trim_space()
60 if t == '' || t in primitive_types || t.ends_with('*') || g.is_enum_type(t) {
61 return false
62 }
63 if t in ['void*', 'char*', 'u8*', 'byteptr', 'charptr', 'voidptr'] {
64 return false
65 }
66 return true
67}
68
69fn (mut g Gen) gen_array_push_elem_arg(rhs ast.Expr, elem_type string) {
70 if elem_type.ends_with('*') && !g.can_take_address(rhs) {
71 addr_type := unmangle_c_ptr_type(elem_type)
72 g.sb.write_string('&((${addr_type}[1]){')
73 g.expr(rhs)
74 g.sb.write_string('}[0])')
75 return
76 }
77 base_elem_type := elem_type.trim_space().trim_right('*')
78 if base_elem_type != '' && g.get_sum_type_variants_for(base_elem_type).len > 0 {
79 g.sb.write_string('&(${elem_type}[1]){')
80 if elem_type.ends_with('*') {
81 rhs_type := g.get_expr_type(rhs).trim_space()
82 if rhs_type == elem_type {
83 g.expr(rhs)
84 } else {
85 value_expr := g.unwrap_addr_of_value_expr(rhs) or { rhs }
86 heap_name := '_heap_t${g.tmp_counter}'
87 g.tmp_counter++
88 malloc_call := g.c_heap_malloc_call('sizeof(${base_elem_type})')
89 g.sb.write_string('({ ${base_elem_type}* ${heap_name} = (${base_elem_type}*)${malloc_call}; *${heap_name} = ')
90 g.gen_type_cast_expr(base_elem_type, value_expr)
91 g.sb.write_string('; ${heap_name}; })')
92 }
93 } else {
94 g.gen_type_cast_expr(elem_type, rhs)
95 }
96 g.sb.write_string('}')
97 return
98 }
99 g.gen_addr_of_expr(rhs, elem_type)
100}
101
102fn (mut g Gen) option_result_payload_type(wrapper_type string) string {
103 if wrapper_type.starts_with('_result_') {
104 return g.result_value_type(wrapper_type)
105 }
106 if wrapper_type.starts_with('_option_') {
107 return option_value_type(wrapper_type)
108 }
109 return ''
110}
111
112fn valid_decl_cast_type(typ string) bool {
113 return typ != '' && typ != 'void' && typ != 'int_literal' && typ != 'float_literal'
114}
115
116fn (mut g Gen) option_result_data_cast_type(wrapper_type string, lhs_type string, rhs_type string) string {
117 payload_type := g.option_result_payload_type(wrapper_type)
118 if valid_decl_cast_type(payload_type) {
119 return payload_type
120 }
121 if valid_decl_cast_type(lhs_type) {
122 return lhs_type
123 }
124 if valid_decl_cast_type(rhs_type) {
125 return rhs_type
126 }
127 return 'int'
128}
129
130fn (mut g Gen) option_result_wrapper_type_for_selector(lhs ast.Expr, fallback string) string {
131 lhs_type := g.get_expr_type(lhs)
132 if lhs_type.starts_with('_result_') || lhs_type.starts_with('_option_') {
133 return lhs_type
134 }
135 if fallback.starts_with('_result_') || fallback.starts_with('_option_') {
136 return fallback
137 }
138 if lhs is ast.Ident {
139 if local_type := g.get_local_var_c_type(lhs.name) {
140 if local_type.starts_with('_result_') || local_type.starts_with('_option_') {
141 return local_type
142 }
143 }
144 }
145 return lhs_type
146}
147
148fn (mut g Gen) option_result_rhs_type(rhs ast.Expr) string {
149 rhs_type := g.get_expr_type(rhs)
150 if rhs_type.starts_with('_result_') || rhs_type.starts_with('_option_') {
151 return rhs_type
152 }
153 if rhs is ast.SelectorExpr {
154 return g.option_result_wrapper_type_for_selector(rhs.lhs, rhs_type)
155 }
156 return rhs_type
157}
158
159fn (mut g Gen) decl_rhs_has_concrete_type(rhs ast.Expr) bool {
160 match rhs {
161 ast.InitExpr {
162 typ := g.expr_type_to_c(rhs.typ)
163 return typ != '' && typ != 'void'
164 }
165 ast.SelectorExpr, ast.CastExpr, ast.InfixExpr {
166 return true
167 }
168 ast.PrefixExpr {
169 return rhs.op in [.amp, .mul] && g.decl_rhs_has_concrete_type(rhs.expr)
170 }
171 ast.UnsafeExpr {
172 if typ := g.unsafe_expr_result_type(rhs) {
173 return typ != '' && typ !in ['void', 'void*', 'voidptr']
174 }
175 }
176 ast.CallExpr {
177 if ret := g.get_call_return_type(rhs.lhs, rhs.args) {
178 return ret != '' && ret !in ['void*', 'voidptr']
179 }
180 }
181 ast.CallOrCastExpr {
182 if g.call_or_cast_lhs_is_type(rhs.lhs) {
183 return true
184 }
185 if ret := g.get_call_return_type(rhs.lhs, [rhs.expr]) {
186 return ret != '' && ret !in ['void*', 'voidptr']
187 }
188 }
189 else {}
190 }
191
192 return false
193}
194
195fn (mut g Gen) decl_lhs_scope_raw_type(lhs ast.Expr) ?types.Type {
196 if lhs is ast.Ident {
197 if mut fn_scope := g.ensure_cur_fn_scope() {
198 if obj := fn_scope.lookup_parent(lhs.name, 0) {
199 if obj is types.Module {
200 return none
201 }
202 return obj.typ()
203 }
204 }
205 return none
206 }
207 return g.get_raw_type(lhs)
208}
209
210fn valid_decl_env_type(typ string) bool {
211 return typ != '' && typ !in ['void', 'void*', 'voidptr', 'int /*corrupt type*/']
212}
213
214fn (mut g Gen) decl_lhs_synth_env_type(lhs ast.Expr) ?string {
215 if lhs is ast.Ident && lhs.pos.id < 0 {
216 typ := g.get_env_c_type(lhs) or { return none }
217 if valid_decl_env_type(typ) {
218 return typ
219 }
220 }
221 return none
222}
223
224fn (mut g Gen) decl_selector_rhs_type(rhs ast.SelectorExpr) string {
225 if g.comptime_field_var != '' {
226 ct_type := g.get_comptime_selector_type(rhs)
227 if ct_type != '' {
228 return ct_type
229 }
230 }
231 field_name := rhs.rhs.name
232 if field_name == 'err' {
233 lhs_type := g.get_expr_type(rhs.lhs)
234 is_or_tmp := rhs.lhs is ast.Ident && rhs.lhs.name.starts_with('_or_t')
235 if lhs_type.starts_with('_result_') || lhs_type.starts_with('_option_') || is_or_tmp {
236 return 'IError'
237 }
238 }
239 if field_name.starts_with('arg') {
240 lhs_type := g.get_expr_type(rhs.lhs)
241 if lhs_type.starts_with('Tuple_') {
242 if field_types := g.tuple_aliases[lhs_type] {
243 idx := field_name['arg'.len..].int()
244 if idx >= 0 && idx < field_types.len {
245 return field_types[idx]
246 }
247 }
248 }
249 }
250 declared := g.selector_declared_field_type(rhs)
251 if declared != '' && !is_generic_placeholder_c_type_name(declared) {
252 return declared
253 }
254 field_type := g.selector_field_type(rhs)
255 if field_type != '' && !is_generic_placeholder_c_type_name(field_type) {
256 return field_type
257 }
258 return ''
259}
260
261fn (mut g Gen) gen_map_index_assign_fallback(lhs ast.IndexExpr, rhs ast.Expr) bool {
262 mut key_type := ''
263 mut value_type := ''
264 mut lhs_is_ptr := g.expr_is_pointer(lhs.lhs)
265 mut lhs_c_type := g.get_expr_type(lhs.lhs).trim_space()
266 if lhs_c_type.ends_with('*') {
267 lhs_is_ptr = true
268 }
269 if raw_type := g.get_raw_type(lhs.lhs) {
270 match raw_type {
271 types.Map {
272 key_type = g.types_type_to_c(raw_type.key_type)
273 value_type = g.types_type_to_c(raw_type.value_type)
274 }
275 types.Pointer {
276 lhs_is_ptr = true
277 match raw_type.base_type {
278 types.Map {
279 key_type = g.types_type_to_c(raw_type.base_type.key_type)
280 value_type = g.types_type_to_c(raw_type.base_type.value_type)
281 }
282 types.Alias {
283 if raw_type.base_type.base_type is types.Map {
284 key_type = g.types_type_to_c(raw_type.base_type.base_type.key_type)
285 value_type = g.types_type_to_c(raw_type.base_type.base_type.value_type)
286 }
287 }
288 else {}
289 }
290 }
291 types.Alias {
292 if raw_type.base_type is types.Map {
293 key_type = g.types_type_to_c(raw_type.base_type.key_type)
294 value_type = g.types_type_to_c(raw_type.base_type.value_type)
295 }
296 }
297 else {}
298 }
299 }
300 if key_type == '' || value_type == '' {
301 mut map_c_type := lhs_c_type
302 if local_type := g.local_var_c_type_for_expr(lhs.lhs) {
303 local_map_type := local_type.trim_space()
304 if local_map_type != '' && local_map_type != 'int' {
305 map_c_type = local_map_type
306 }
307 }
308 if lhs.lhs is ast.SelectorExpr {
309 mut selector_type := g.selector_storage_field_type(lhs.lhs).trim_space()
310 if selector_type == '' {
311 selector_type = g.selector_field_type(lhs.lhs).trim_space()
312 }
313 if selector_type == '' || selector_type == 'int' {
314 if plain_selector_type := g.plain_local_selector_type(lhs.lhs) {
315 selector_type = plain_selector_type.trim_space()
316 }
317 }
318 if selector_type != '' {
319 map_c_type = selector_type
320 }
321 }
322 if map_c_type.ends_with('*') {
323 map_c_type = map_c_type[..map_c_type.len - 1].trim_space()
324 }
325 if map_c_type.starts_with('Map_') {
326 key, value := g.parse_map_kv_types(map_c_type['Map_'.len..])
327 key_type = key
328 value_type = value
329 }
330 }
331 if key_type == '' || value_type == '' {
332 return false
333 }
334 key_tmp := '_map_key${g.tmp_counter}'
335 g.tmp_counter++
336 value_tmp := '_map_val${g.tmp_counter}'
337 g.tmp_counter++
338 g.write_indent()
339 g.sb.write_string('{ ${key_type} ${key_tmp} = ')
340 g.expr(lhs.expr)
341 g.sb.write_string('; ${value_type} ${value_tmp} = ')
342 g.expr(rhs)
343 g.sb.write_string('; map__set(')
344 if lhs_is_ptr {
345 g.expr(lhs.lhs)
346 } else {
347 g.sb.write_string('&(')
348 g.expr(lhs.lhs)
349 g.sb.write_string(')')
350 }
351 g.sb.writeln(', (void*)&${key_tmp}, (void*)&${value_tmp}); }')
352 return true
353}
354
355fn (mut g Gen) gen_overloaded_compound_assign(lhs ast.Expr, rhs ast.Expr, op token.Token) bool {
356 if lhs !is ast.Ident {
357 return false
358 }
359 lhs_ident := lhs as ast.Ident
360 op_name := match op {
361 .plus_assign { 'op_plus' }
362 .minus_assign { 'op_minus' }
363 .mul_assign { 'op_mul' }
364 .div_assign { 'op_div' }
365 .mod_assign { 'op_mod' }
366 else { '' }
367 }
368
369 if op_name == '' {
370 return false
371 }
372 mut lhs_type := g.get_expr_type(lhs)
373 if local_type := g.get_local_var_c_type(lhs_ident.name) {
374 lhs_type = local_type
375 }
376 if lhs_type == '' || lhs_type in primitive_types || lhs_type == 'string'
377 || lhs_type.ends_with('*') || lhs_type.ends_with('ptr') {
378 return false
379 }
380 mut rhs_type := g.get_expr_type(rhs)
381 if rhs is ast.Ident {
382 rhs_ident := rhs as ast.Ident
383 if local_type := g.get_local_var_c_type(rhs_ident.name) {
384 rhs_type = local_type
385 }
386 }
387 if rhs_type != '' && rhs_type != 'int' && rhs_type != lhs_type {
388 return false
389 }
390 method_fn := '${lhs_type}__${op_name}'
391 if method_fn !in g.fn_return_types && method_fn !in g.fn_param_is_ptr {
392 return false
393 }
394 g.write_indent()
395 g.expr(lhs)
396 g.sb.write_string(' = ${method_fn}(')
397 g.expr(lhs)
398 g.sb.write_string(', ')
399 g.expr(rhs)
400 g.sb.writeln(');')
401 return true
402}
403
404fn (mut g Gen) gen_assign_stmt(node ast.AssignStmt) {
405 lhs := node.lhs[0]
406 rhs := node.rhs[0]
407 // Multi-assignment with parallel RHS values (non-declaration):
408 // `p, q = q, p` needs temp variables for correct swap semantics.
409 if node.op != .decl_assign && node.lhs.len > 1 && node.rhs.len == node.lhs.len {
410 swap_id := g.tmp_counter
411 // First, evaluate all RHS values into temporaries
412 for i, rhs_expr in node.rhs {
413 mut typ := g.get_expr_type(rhs_expr)
414 if typ == '' || typ == 'int_literal' {
415 typ = 'int'
416 }
417 if typ == 'float_literal' {
418 typ = 'f64'
419 }
420 g.write_indent()
421 g.sb.write_string('${typ} _swap_${swap_id}_${i} = ')
422 g.expr(rhs_expr)
423 g.sb.writeln(';')
424 }
425 // Then assign from temporaries to LHS
426 for i, lhs_expr in node.lhs {
427 if lhs_expr is ast.Ident && lhs_expr.name == '_' {
428 g.write_indent()
429 g.sb.writeln('(void)_swap_${swap_id}_${i};')
430 continue
431 }
432 g.write_indent()
433 g.expr(lhs_expr)
434 g.sb.writeln(' = _swap_${swap_id}_${i};')
435 }
436 g.tmp_counter++
437 return
438 }
439
440 // Multi-declaration with parallel RHS values:
441 // `a, b := x, y` should declare both variables (not just the first one).
442 if node.op == .decl_assign && node.lhs.len > 1 && node.rhs.len == node.lhs.len {
443 for i, lhs_expr in node.lhs {
444 rhs_expr := node.rhs[i]
445 mut name := decl_lhs_name(lhs_expr)
446 if name == '_' {
447 g.write_indent()
448 g.sb.write_string('(void)(')
449 g.expr(rhs_expr)
450 g.sb.writeln(');')
451 continue
452 }
453 mut typ := g.get_expr_type(lhs_expr)
454 if typ == '' || typ == 'int' {
455 typ = g.get_expr_type(rhs_expr)
456 }
457 if typ == '' || typ == 'int_literal' {
458 typ = 'int'
459 }
460 if typ == 'float_literal' {
461 typ = 'f64'
462 }
463 c_name := c_local_name(name)
464 g.write_indent()
465 g.sb.write_string('${typ} ${c_name} = ')
466 if is_zero_number_expr(rhs_expr) && g.c_type_needs_typed_zero_expr(typ) {
467 g.sb.write_string(zero_value_for_type(typ))
468 } else {
469 g.expr(rhs_expr)
470 }
471 g.sb.writeln(';')
472 g.remember_runtime_local_type(name, typ)
473 }
474 return
475 }
476
477 mut tuple_lhs := []ast.Expr{}
478 if node.lhs.len > 1 {
479 tuple_lhs = shallow_copy_exprs(node.lhs)
480 } else if node.lhs.len == 1 && node.lhs[0] is ast.Tuple {
481 lhs_tuple := node.lhs[0] as ast.Tuple
482 tuple_lhs = shallow_copy_exprs(lhs_tuple.exprs)
483 }
484 if tuple_lhs.len > 1 && node.rhs.len == 1 {
485 mut tuple_type := g.get_expr_type(rhs)
486 // For tuple-LHS assignments, prefer explicit call return metadata even when
487 // positional inference produced a scalar type.
488 if rhs is ast.CallExpr {
489 if ret := g.get_call_return_type(rhs.lhs, rhs.args) {
490 if ret != '' && ret != 'int' {
491 tuple_type = ret
492 }
493 }
494 // For interface vtable method calls, look up the method's return type
495 if (tuple_type == '' || tuple_type == 'int') && rhs.lhs is ast.SelectorExpr {
496 receiver_type := g.get_expr_type(rhs.lhs.lhs).trim_right('*')
497 if g.is_interface_type(receiver_type) {
498 method_name := rhs.lhs.rhs.name
499 if method := g.interface_method_by_name(receiver_type, method_name) {
500 if method.ret_type != '' && method.ret_type != 'int'
501 && method.ret_type != 'void' {
502 tuple_type = method.ret_type
503 }
504 }
505 }
506 }
507 } else if rhs is ast.UnsafeExpr {
508 // Handle inline or-block expansion: the transformer wraps or-blocks in
509 // UnsafeExpr (GCC statement expressions) when it can't expand them to
510 // separate statements. The stmts are: [_or_t := call(), if error, _or_t.data]
511 // Extract the call return type from the first AssignStmt inside.
512 for stmt_inner in rhs.stmts {
513 if stmt_inner is ast.AssignStmt && stmt_inner.op == .decl_assign
514 && stmt_inner.rhs.len == 1 {
515 inner_rhs := stmt_inner.rhs[0]
516 mut inner_ret := ''
517 if inner_rhs is ast.CallExpr {
518 if ret := g.get_call_return_type(inner_rhs.lhs, inner_rhs.args) {
519 inner_ret = ret
520 }
521 }
522 // Strip _option_/_result_ wrapper since the UnsafeExpr already
523 // unwraps the value (last expr is _or_t.data)
524 if inner_ret.starts_with('_option_') {
525 tuple_type = option_value_type(inner_ret)
526 } else if inner_ret.starts_with('_result_') {
527 tuple_type = g.result_value_type(inner_ret)
528 }
529 break
530 }
531 }
532 }
533 if tuple_type == 'int' {
534 if rhs is ast.CastExpr {
535 cast_type := g.expr_type_to_c(rhs.typ)
536 if cast_type != '' {
537 tuple_type = cast_type
538 }
539 }
540 }
541 // Function pointer call: tuple_type may be the fn pointer typedef name
542 // (e.g. 'ui__CanvasLayoutSizeFn') or 'int' (unresolved) instead of the actual
543 // return type (e.g. 'Tuple_int_int'). Resolve via the raw type's return type.
544 if tuple_type !in g.tuple_aliases && rhs is ast.CallExpr {
545 // Method 1: Use fn_pointer_return_type on the callee expression
546 fn_ptr_ret := g.fn_pointer_return_type(rhs.lhs)
547 if fn_ptr_ret != '' && fn_ptr_ret != 'int' && fn_ptr_ret in g.tuple_aliases {
548 tuple_type = fn_ptr_ret
549 }
550 // Method 2: For selector-based fn pointer calls, resolve the field's
551 // fn type from the receiver struct and extract the return type.
552 if tuple_type !in g.tuple_aliases && rhs.lhs is ast.SelectorExpr {
553 if receiver_raw := g.get_raw_type(rhs.lhs.lhs) {
554 base_raw := if receiver_raw is types.Pointer {
555 receiver_raw.base_type
556 } else {
557 receiver_raw
558 }
559 if base_raw is types.Struct {
560 field_name := rhs.lhs.rhs.name
561 fn_ret := g.resolve_struct_field_fn_return(base_raw, field_name)
562 if fn_ret != '' && fn_ret in g.tuple_aliases {
563 tuple_type = fn_ret
564 }
565 }
566 }
567 }
568 // Method 3: Try raw type resolution on the callee expression directly
569 if tuple_type !in g.tuple_aliases {
570 if fn_raw := g.get_raw_type(rhs.lhs) {
571 fn_ret_type := match fn_raw {
572 types.FnType {
573 if rt := fn_raw.get_return_type() {
574 g.fn_return_type_to_c(rt)
575 } else {
576 ''
577 }
578 }
579 types.Alias {
580 if fn_raw.base_type is types.FnType {
581 if rt := fn_raw.base_type.get_return_type() {
582 g.fn_return_type_to_c(rt)
583 } else {
584 ''
585 }
586 } else {
587 ''
588 }
589 }
590 else {
591 ''
592 }
593 }
594
595 if fn_ret_type != '' && fn_ret_type != 'int' && fn_ret_type in g.tuple_aliases {
596 tuple_type = fn_ret_type
597 }
598 }
599 }
600 }
601 // If tuple_type is still unresolved (empty, 'int', or 'void'), synthesize from LHS types
602 if (tuple_type == '' || tuple_type == 'int' || tuple_type == 'void')
603 && tuple_type !in g.tuple_aliases {
604 mut lhs_types := []string{cap: tuple_lhs.len}
605 mut all_resolved := true
606 for lhs_expr in tuple_lhs {
607 mut lhs_t := g.get_expr_type(lhs_expr)
608 if (lhs_t == '' || lhs_t == 'int') && lhs_expr is ast.Ident {
609 lhs_t = g.get_local_var_c_type(lhs_expr.name) or { lhs_t }
610 }
611 if lhs_t == '' {
612 all_resolved = false
613 break
614 }
615 lhs_types << lhs_t
616 }
617 if all_resolved && lhs_types.len > 1 {
618 synthesized := g.register_tuple_alias(lhs_types)
619 if synthesized != '' {
620 tuple_type = synthesized
621 }
622 }
623 }
624 mut wrapped_tuple_type := ''
625 if tuple_type.starts_with('_result_') {
626 base := g.result_value_type(tuple_type)
627 if base in g.tuple_aliases {
628 wrapped_tuple_type = tuple_type
629 tuple_type = base
630 }
631 } else if tuple_type.starts_with('_option_') {
632 base := option_value_type(tuple_type)
633 if base in g.tuple_aliases {
634 wrapped_tuple_type = tuple_type
635 tuple_type = base
636 }
637 }
638 if field_types := g.tuple_aliases[tuple_type] {
639 tmp_name := '_tuple_tmp_${g.tmp_counter}'
640 g.tmp_counter++
641 g.write_indent()
642 if wrapped_tuple_type != '' {
643 res_tmp := '_tuple_res_tmp_${g.tmp_counter}'
644 g.tmp_counter++
645 g.sb.write_string('${wrapped_tuple_type} ${res_tmp} = ')
646 g.expr(rhs)
647 g.sb.writeln(';')
648 g.write_indent()
649 g.sb.writeln('${tuple_type} ${tmp_name} = (*(${tuple_type}*)(((u8*)(&${res_tmp}.err)) + sizeof(IError)));')
650 } else {
651 g.sb.write_string('${tuple_type} ${tmp_name} = ')
652 g.expr(rhs)
653 g.sb.writeln(';')
654 }
655 for i, lhs_expr in tuple_lhs {
656 g.write_indent()
657 if node.op == .decl_assign {
658 mut name := decl_lhs_name(lhs_expr)
659 if name == '_' {
660 g.sb.writeln('(void)${tmp_name}.arg${i};')
661 continue
662 }
663 elem_type := if i < field_types.len { field_types[i] } else { 'int' }
664 if elem_type.starts_with('Array_fixed_') {
665 g.sb.writeln('${elem_type} ${c_local_name(name)};')
666 g.write_indent()
667 g.sb.writeln('memcpy(${c_local_name(name)}, ${tmp_name}.arg${i}, sizeof(${elem_type}));')
668 g.remember_runtime_local_type(name, elem_type)
669 continue
670 }
671 g.sb.writeln('${elem_type} ${c_local_name(name)} = ${tmp_name}.arg${i};')
672 g.remember_runtime_local_type(name, elem_type)
673 } else {
674 mut assign_name := ''
675 if lhs_expr is ast.Ident {
676 assign_name = lhs_expr.name
677 }
678 if assign_name == '_' {
679 g.sb.writeln('(void)${tmp_name}.arg${i};')
680 continue
681 }
682 elem_type := if i < field_types.len { field_types[i] } else { '' }
683 if elem_type.starts_with('Array_fixed_') {
684 g.sb.write_string('memcpy(')
685 g.expr(lhs_expr)
686 g.sb.writeln(', ${tmp_name}.arg${i}, sizeof(${elem_type}));')
687 continue
688 }
689 g.expr(lhs_expr)
690 g.sb.writeln(' = ${tmp_name}.arg${i};')
691 }
692 }
693 return
694 }
695 }
696
697 // Check for blank identifier
698 if lhs is ast.Ident && lhs.name == '_' {
699 g.write_indent()
700 g.sb.write_string('(void)(')
701 g.expr(rhs)
702 g.sb.writeln(');')
703 return
704 }
705
706 if node.op == .assign && lhs is ast.IndexExpr {
707 if g.gen_map_index_assign_fallback(lhs, rhs) {
708 return
709 }
710 }
711
712 g.write_indent()
713 if node.op == .decl_assign {
714 // Variable declaration: type name = expr
715 mut name := decl_lhs_name(lhs)
716 storage_prefix := decl_lhs_storage_prefix(lhs)
717 // Rename V variables that clash with C type names
718 if name == 'array' {
719 name = '_v_array'
720 }
721 decl_c_name := c_local_name(name)
722 if rhs is ast.IndexExpr {
723 if g.gen_decl_assign_from_local_index(name, storage_prefix, decl_c_name, rhs) {
724 return
725 }
726 }
727 // Keep fixed-size arrays as C arrays in local declarations.
728 if rhs is ast.ArrayInitExpr {
729 array_init := rhs as ast.ArrayInitExpr
730 if (array_init.typ is ast.Type && array_init.typ is ast.ArrayFixedType)
731 || array_init_has_fixed_len_marker(array_init) {
732 mut elem_type := ''
733 mut fixed_len := ast.Expr(ast.BasicLiteral{
734 kind: .number
735 value: '${array_init.exprs.len}'
736 })
737 if array_init.typ is ast.Type && array_init.typ is ast.ArrayFixedType {
738 fixed_typ := array_init.typ as ast.ArrayFixedType
739 elem_type = g.expr_type_to_c(fixed_typ.elem_type)
740 fixed_len = fixed_typ.len
741 } else {
742 elem_type = g.extract_array_elem_type(array_init.typ)
743 if elem_type == '' && array_init.exprs.len > 0 {
744 elem_type = g.get_expr_type(array_init.exprs[0])
745 if elem_type == 'int_literal' {
746 elem_type = 'int'
747 } else if elem_type == 'float_literal' {
748 elem_type = 'f64'
749 }
750 }
751 }
752 if elem_type == '' {
753 elem_type = 'int'
754 }
755 mut fixed_arr_size := array_init.exprs.len
756 if fixed_arr_size == 0 {
757 // For init-based fixed arrays, get size from the type annotation
758 if fixed_len is ast.BasicLiteral && fixed_len.kind == .number {
759 fixed_arr_size = fixed_len.value.int()
760 }
761 }
762 fixed_name := 'Array_fixed_' + mangle_alias_component(elem_type) + '_' +
763 fixed_arr_size.str()
764 g.register_alias_type(fixed_name)
765 g.collected_fixed_array_types[fixed_name] = FixedArrayInfo{
766 elem_type: elem_type
767 size: fixed_arr_size
768 }
769 g.remember_runtime_local_type(name, fixed_name)
770 is_literal_size := fixed_len is ast.BasicLiteral
771 && (fixed_len as ast.BasicLiteral).kind == .number
772 g.sb.write_string('${storage_prefix}${elem_type} ${decl_c_name}[')
773 g.expr(fixed_len)
774 if array_init.exprs.len == 0 {
775 if array_init.init !is ast.EmptyExpr && is_literal_size {
776 // Has init: clause - expand to repeated init values
777 g.sb.write_string('] = ')
778 g.gen_array_init_expr(array_init)
779 g.sb.writeln(';')
780 } else if is_literal_size {
781 g.sb.writeln('] = {0};')
782 } else {
783 // Non-literal sizes are VLAs in C99 and cannot use = {0}
784 g.sb.writeln('];')
785 g.write_indent()
786 g.sb.writeln('memset(${decl_c_name}, 0, sizeof(${decl_c_name}));')
787 }
788 } else {
789 g.sb.write_string('] = {')
790 for i in 0 .. array_init.exprs.len {
791 expr := array_init.exprs[i]
792 if i > 0 {
793 g.sb.write_string(', ')
794 }
795 if !(elem_type.starts_with('Array_fixed_')
796 && g.gen_fixed_array_initializer_from_expr(expr)) {
797 g.expr(expr)
798 }
799 }
800 g.sb.writeln('};')
801 }
802 return
803 }
804 }
805 if rhs is ast.CallExpr {
806 if orm_ret := g.orm_create_call_result_type(rhs) {
807 g.sb.write_string('${orm_ret} ${decl_c_name} = ')
808 g.expr(rhs)
809 g.sb.writeln(';')
810 g.remember_runtime_local_type(name, orm_ret)
811 if orm_ret.starts_with('_result_') || orm_ret.starts_with('_option_') {
812 g.register_alias_type(orm_ret)
813 }
814 return
815 }
816 }
817 mut typ := g.get_expr_type(rhs)
818 if rhs is ast.InitExpr && rhs.typ is ast.Ident {
819 if g.type_expr_has_metadata(rhs.typ) {
820 // Position metadata is authoritative for synthesized type expressions.
821 } else if fn_local_type := g.current_fn_module_local_type_name(rhs.typ.name) {
822 typ = fn_local_type
823 }
824 }
825 lhs_env_type := g.get_expr_type_from_env(lhs) or { '' }
826 lhs_synth_env_type := g.decl_lhs_synth_env_type(lhs) or { '' }
827 if lhs_synth_env_type != '' {
828 typ = lhs_synth_env_type
829 }
830 lhs_env_type_is_decl := valid_decl_env_type(lhs_env_type)
831 && ((!name.starts_with('_or_t') && !name.starts_with('_tmp_')
832 && !name.starts_with('_defer_t') && !name.starts_with('_assoc_t'))
833 || lhs_synth_env_type != '')
834 if rhs is ast.CallExpr {
835 if ret := g.get_call_return_type(rhs.lhs, rhs.args) {
836 if ret.starts_with('_result_') || ret.starts_with('_option_')
837 || (ret != '' && ret != 'void' && ret != 'int' && !(ret in ['void*', 'voidptr']
838 && typ !in ['', 'int', 'void*', 'voidptr'])) {
839 typ = ret
840 }
841 }
842 if typ == '' || typ == 'int' || typ == 'array' {
843 call_name := g.resolve_call_name(rhs.lhs, rhs.args.len)
844 if is_array_result_call_name(call_name) {
845 elem_type := g.infer_array_elem_type_from_expr(rhs)
846 if elem_type != '' && elem_type != 'array' && elem_type != 'int'
847 && elem_type != 'void' {
848 typ = 'Array_' + mangle_alias_component(elem_type)
849 g.register_alias_type(typ)
850 }
851 }
852 }
853 } else if rhs is ast.CallOrCastExpr && !g.call_or_cast_lhs_is_type(rhs.lhs) {
854 if ret := g.get_call_return_type(rhs.lhs, [rhs.expr]) {
855 if ret.starts_with('_result_') || ret.starts_with('_option_')
856 || (ret != '' && ret != 'void' && ret != 'int' && !(ret in ['void*', 'voidptr']
857 && typ !in ['', 'int', 'void*', 'voidptr'])) {
858 typ = ret
859 }
860 }
861 }
862 mut elem_type_from_array := false
863 mut typ_from_active_generic_init := false
864 if rhs is ast.InitExpr && g.active_generic_types.len > 0 {
865 init_type_name := rhs.typ.name()
866 if concrete := g.active_generic_types[init_type_name] {
867 concrete_type := g.types_type_to_c(concrete).trim_space()
868 if concrete_type != '' {
869 typ = concrete_type
870 typ_from_active_generic_init = true
871 }
872 }
873 }
874 // For Ident RHS referencing a struct-typed constant (e.g., `col := no_color`
875 // where no_color is `#define`d as a Color struct literal), use the const type.
876 if (typ == 'int' || typ == '') && rhs is ast.Ident {
877 if ct := g.const_types[rhs.name] {
878 typ = ct
879 } else if g.cur_module != '' {
880 qualified := g.cur_module + '__' + rhs.name
881 if ct := g.const_types[qualified] {
882 typ = ct
883 }
884 }
885 }
886 // For CastExpr RHS (e.g., `(i64)(0)`), derive type from the cast target.
887 if rhs is ast.CastExpr {
888 cast_type := g.expr_type_to_c(rhs.typ)
889 if cast_type != '' {
890 typ = cast_type
891 }
892 }
893 if rhs is ast.IndexExpr {
894 index_elem_type := g.infer_array_elem_type_from_expr(rhs.lhs)
895 if index_elem_type != '' && index_elem_type !in ['array', 'int', 'void'] {
896 specialized := specialized_generic_elem_type_from_value(typ, index_elem_type)
897 if typ == '' || typ == 'int' || typ == 'void*' || typ == 'voidptr'
898 || specialized != '' {
899 typ = if specialized != '' { specialized } else { index_elem_type }
900 elem_type_from_array = true
901 }
902 }
903 }
904 // For temp variables registered by the transformer with a specific type,
905 // prefer the scope-registered type over the RHS expression type.
906 if name.starts_with('_or_t') || name.starts_with('_tmp_') || name.starts_with('_defer_t')
907 || name.starts_with('_assoc_t') {
908 if lhs_synth_env_type != '' {
909 typ = lhs_synth_env_type
910 if typ.starts_with('_result_') || typ.starts_with('_option_') {
911 g.register_alias_type(typ)
912 }
913 } else if raw_type := g.decl_lhs_scope_raw_type(lhs) {
914 scope_type := g.types_type_to_c(raw_type)
915 if scope_type != '' && scope_type != 'int' && scope_type !in ['void*', 'voidptr'] {
916 typ_is_wrapper := typ.starts_with('_result_') || typ.starts_with('_option_')
917 scope_type_is_wrapper := scope_type.starts_with('_result_')
918 || scope_type.starts_with('_option_')
919 if typ_is_wrapper && scope_type_is_wrapper && typ != scope_type {
920 g.register_alias_type(typ)
921 } else {
922 typ = scope_type
923 // Ensure result/option wrapper types are registered so their
924 // typedef and struct definitions get emitted in the C output.
925 if scope_type_is_wrapper {
926 g.register_alias_type(scope_type)
927 }
928 }
929 } else if scope_type == 'int' && typ == 'bool' {
930 // Fix: literal like `1` mistyped as bool in env
931 typ = 'int'
932 }
933 }
934 // Transformer-created temp vars may not exist in the checker's scope.
935 // Try to infer the type from the RHS: if RHS is an Ident referencing
936 // another temp var already registered in runtime_local_types, use that.
937 if typ == 'int' || typ == '' {
938 if rhs is ast.Ident {
939 if rhs_local_type := g.runtime_local_types[rhs.name] {
940 if rhs_local_type != '' && rhs_local_type != 'int' {
941 typ = rhs_local_type
942 }
943 }
944 }
945 }
946 // For UnsafeExpr RHS (GCC statement expression from nested or-expressions),
947 // the result type is determined by the last ExprStmt. Find its declaration
948 // in the preceding stmts to extract the type.
949 if typ == 'int' || typ == '' {
950 if rhs is ast.UnsafeExpr && rhs.stmts.len > 1 {
951 last_s := rhs.stmts[rhs.stmts.len - 1]
952 if last_s is ast.ExprStmt && last_s.expr is ast.Ident {
953 result_name := last_s.expr.name
954 // Scan preceding stmts for the decl_assign of the result variable
955 for inner_stmt in rhs.stmts[..rhs.stmts.len - 1] {
956 if inner_stmt is ast.AssignStmt && inner_stmt.op == .decl_assign
957 && inner_stmt.lhs.len == 1 {
958 inner_lhs := inner_stmt.lhs[0]
959 if inner_lhs is ast.Ident && inner_lhs.name == result_name {
960 inner_typ := g.get_expr_type(inner_stmt.rhs[0])
961 if inner_typ != '' && inner_typ != 'int' {
962 typ = inner_typ
963 }
964 break
965 }
966 }
967 }
968 }
969 }
970 }
971 // For _or_t vars where RHS is a call through a function pointer variable,
972 // infer the return type from the function pointer's type.
973 if typ == 'int' || typ == '' {
974 if rhs is ast.CallExpr {
975 fn_ptr_ret := g.fn_pointer_return_type(rhs.lhs)
976 if fn_ptr_ret != '' && fn_ptr_ret != 'int' && fn_ptr_ret != 'void' {
977 typ = fn_ptr_ret
978 }
979 }
980 }
981 }
982 // Fix: &T(x) pattern - the checker may assign only the inner type T instead of T*.
983 // Derive the pointer type directly from the expression structure.
984 if rhs is ast.PrefixExpr && rhs.op == .amp && rhs.expr is ast.CastExpr {
985 target_type := g.expr_type_to_c(rhs.expr.typ)
986 if target_type != '' {
987 typ = target_type + '*'
988 }
989 }
990 if rhs is ast.CallExpr {
991 if rhs.lhs is ast.Ident
992 && rhs.lhs.name in ['array__pop', 'array__pop_left', 'array__first', 'array__last'] {
993 if rhs.args.len > 0 {
994 pop_elem := g.infer_array_elem_type_from_expr(rhs.args[0])
995 if pop_elem != '' {
996 typ = pop_elem
997 elem_type_from_array = true
998 }
999 }
1000 } else if rhs.lhs is ast.SelectorExpr
1001 && rhs.lhs.rhs.name in ['pop', 'pop_left', 'first', 'last'] {
1002 arr_expr := if rhs.args.len > 0 { rhs.args[0] } else { rhs.lhs.lhs }
1003 pop_elem := g.infer_array_elem_type_from_expr(arr_expr)
1004 if pop_elem != '' {
1005 typ = pop_elem
1006 elem_type_from_array = true
1007 }
1008 }
1009 }
1010 if typ == 'array' {
1011 mut elem_type := g.infer_array_elem_type_from_expr(rhs)
1012 if elem_type == '' && rhs is ast.CallExpr {
1013 call_name := g.resolve_call_name(rhs.lhs, rhs.args.len)
1014 if call_name in ['new_array_from_c_array', 'builtin__new_array_from_c_array', 'builtin__new_array_from_c_array_noscan']
1015 && rhs.args.len > 3 {
1016 elem_type = g.infer_array_elem_type_from_expr(rhs.args[3])
1017 }
1018 }
1019 if elem_type != '' && elem_type != 'array' && elem_type != 'void' {
1020 typ = 'Array_' + mangle_alias_component(elem_type)
1021 g.register_alias_type(typ)
1022 }
1023 }
1024 if typ in ['void*', 'voidptr'] {
1025 // Handle array__first/array__last/pop/pop_left for element type inference
1026 if rhs is ast.CallExpr {
1027 if rhs.lhs is ast.SelectorExpr {
1028 if rhs.lhs.rhs.name in ['first', 'last', 'pop', 'pop_left'] {
1029 arr2 := if rhs.args.len > 0 { rhs.args[0] } else { rhs.lhs.lhs }
1030 elem := g.infer_array_elem_type_from_expr(arr2)
1031 if elem != '' {
1032 typ = elem
1033 }
1034 }
1035 }
1036 }
1037 mut call_name := ''
1038 mut arr_expr := rhs
1039 mut has_arr_expr := false
1040 if rhs is ast.CallExpr {
1041 call_name = g.resolve_call_name(rhs.lhs, rhs.args.len)
1042 if rhs.args.len > 0 {
1043 arr_expr = rhs.args[0]
1044 has_arr_expr = true
1045 } else if rhs.lhs is ast.SelectorExpr {
1046 arr_expr = rhs.lhs.lhs
1047 has_arr_expr = true
1048 }
1049 }
1050 if has_arr_expr
1051 && call_name in ['array__pop', 'array__pop_left', 'array__first', 'array__last'] {
1052 pop_elem := g.infer_array_elem_type_from_expr(arr_expr)
1053 if pop_elem != '' {
1054 typ = pop_elem
1055 }
1056 }
1057 }
1058 // Check scope-resolved type first (most reliable for declarations).
1059 // Treat `void*`/`voidptr` as an "unknown" fallback when we have a concrete
1060 // type from the (checker/transformer) scope; this is important for patterns
1061 // like `tmp := map__get_check(...)` where the C builtin returns `void*` but
1062 // the variable is known to be `T*`.
1063 // But skip if element type was already inferred from array methods.
1064 // Also skip for tuple field selectors (_tuple_tN.argN) where the field type
1065 // is authoritative — scope lookup may find a wrong-scoped variable of the same name.
1066 mut type_from_tuple_field := false
1067 if rhs is ast.SelectorExpr {
1068 sel_rhs := rhs as ast.SelectorExpr
1069 if sel_rhs.rhs.name.starts_with('arg') && sel_rhs.lhs is ast.Ident {
1070 sel_lhs := sel_rhs.lhs as ast.Ident
1071 type_from_tuple_field = sel_lhs.name.starts_with('_tuple_t')
1072 }
1073 }
1074 rhs_has_concrete_type := g.decl_rhs_has_concrete_type(rhs)
1075 if lhs_env_type_is_decl && (typ == '' || typ == 'int_literal'
1076 || typ == 'float_literal' || typ == 'void*' || typ == 'voidptr'
1077 || (typ == 'int' && !rhs_has_concrete_type)) {
1078 typ = lhs_env_type
1079 }
1080 if !elem_type_from_array && !type_from_tuple_field && !typ_from_active_generic_init
1081 && name != '' && g.cur_fn_scope != unsafe { nil } {
1082 if obj := g.cur_fn_scope.lookup_parent(name, 0) {
1083 if obj !is types.Module {
1084 obj_type := obj.typ()
1085 if obj_type !is types.Alias {
1086 scoped_type := g.types_type_to_c(obj_type)
1087 generic_container_fallback :=
1088 (typ == 'array' && scoped_type.starts_with('Array_'))
1089 || (typ == 'map' && scoped_type.starts_with('Map_'))
1090 typ_needs_scope := typ == '' || typ == 'int_literal'
1091 || typ == 'void*' || typ == 'voidptr'
1092 || generic_container_fallback
1093 || (typ == 'int' && !rhs_has_concrete_type && !lhs_env_type_is_decl)
1094 if typ_needs_scope && scoped_type != ''
1095 && scoped_type !in ['int', 'void', 'void*', 'voidptr'] {
1096 typ = scoped_type
1097 }
1098 }
1099 }
1100 }
1101 }
1102 // For decl_assign (:=), the LHS is a brand new variable, so don't use
1103 // cached types from runtime_local_types (which may hold stale types
1104 // from a previous variable with the same name, e.g. _filter_it).
1105 if node.op != .decl_assign {
1106 lhs_typ := g.get_expr_type(lhs)
1107 if lhs_typ != '' && lhs_typ !in ['int', 'int_literal', 'float_literal']
1108 && lhs_typ != 'void'
1109 && (typ == '' || typ == 'int' || typ == 'void*' || typ == 'voidptr') {
1110 typ = lhs_typ
1111 }
1112 }
1113 if !elem_type_from_array && rhs is ast.CallExpr {
1114 if ret := g.get_call_return_type(rhs.lhs, rhs.args) {
1115 call_is_string_slice := rhs.lhs is ast.Ident && rhs.lhs.name == 'array__slice'
1116 && rhs.args.len > 0
1117 && g.expr_resolves_to_string(rhs.args[0], g.get_expr_type(rhs.args[0]).trim_right('*'))
1118 if call_is_string_slice {
1119 typ = 'string'
1120 } else if (ret == 'array' && typ.starts_with('Array_'))
1121 || (ret == 'map' && typ.starts_with('Map_')) {
1122 // Preserve more specific local container types inferred from call arguments.
1123 } else if ret != ''
1124 && (ret != 'int' || typ in ['', 'void*', 'voidptr'] || typ.starts_with('Array_')
1125 || typ.starts_with('Map_')) {
1126 if !(ret in ['void*', 'voidptr'] && typ !in ['', 'int', 'void*', 'voidptr']) {
1127 typ = ret
1128 }
1129 }
1130 }
1131 // For interface vtable method calls (e.g., w->size(w->_object)),
1132 // look up the method's return type from the interface declaration.
1133 if (typ == '' || typ == 'int') && rhs.lhs is ast.SelectorExpr {
1134 receiver_type := g.get_expr_type(rhs.lhs.lhs).trim_right('*')
1135 if g.is_interface_type(receiver_type) {
1136 iface_method_name := rhs.lhs.rhs.name
1137 if method := g.interface_method_by_name(receiver_type, iface_method_name) {
1138 if method.ret_type != '' && method.ret_type != 'int'
1139 && method.ret_type != 'void' {
1140 typ = method.ret_type
1141 }
1142 }
1143 }
1144 }
1145 } else if !elem_type_from_array && rhs is ast.CallOrCastExpr
1146 && !g.call_or_cast_lhs_is_type(rhs.lhs) {
1147 if ret := g.get_call_return_type(rhs.lhs, [rhs.expr]) {
1148 if ret != ''
1149 && (ret != 'int' || typ in ['', 'void*', 'voidptr'] || typ.starts_with('Array_')
1150 || typ.starts_with('Map_')) {
1151 if !(ret in ['void*', 'voidptr'] && typ !in ['', 'int', 'void*', 'voidptr']) {
1152 typ = ret
1153 }
1154 }
1155 }
1156 }
1157 if rhs is ast.KeywordOperator && rhs.op in [.key_sizeof, .key_offsetof] {
1158 typ = 'usize'
1159 }
1160 mut rhs_type := g.option_result_rhs_type(rhs)
1161 if rhs is ast.IfExpr {
1162 if_type := g.get_if_expr_type(&rhs)
1163 if if_type != '' && if_type != 'int' {
1164 rhs_type = if_type
1165 }
1166 }
1167 if rhs_type == 'int' {
1168 if rhs is ast.CallExpr {
1169 if ret := g.get_call_return_type(rhs.lhs, rhs.args) {
1170 rhs_type = ret
1171 }
1172 } else if rhs is ast.CallOrCastExpr && !g.call_or_cast_lhs_is_type(rhs.lhs) {
1173 if ret := g.get_call_return_type(rhs.lhs, [rhs.expr]) {
1174 rhs_type = ret
1175 }
1176 }
1177 }
1178 if rhs is ast.SelectorExpr && rhs.rhs.name == 'err' {
1179 container_type := g.get_expr_type(rhs.lhs)
1180 is_or_tmp := rhs.lhs is ast.Ident && rhs.lhs.name.starts_with('_or_t')
1181 if container_type.starts_with('_result_') || container_type.starts_with('_option_')
1182 || is_or_tmp {
1183 rhs_type = 'IError'
1184 typ = 'IError'
1185 }
1186 }
1187 if name != '' && rhs is ast.SelectorExpr && rhs.rhs.name == 'data' {
1188 container_type := g.option_result_wrapper_type_for_selector(rhs.lhs, rhs_type)
1189 is_or_tmp := rhs.lhs is ast.Ident && rhs.lhs.name.starts_with('_or_t')
1190 if container_type.starts_with('_result_') || container_type.starts_with('_option_')
1191 || is_or_tmp {
1192 cast_type := g.option_result_data_cast_type(container_type, typ, rhs_type)
1193 if cast_type.starts_with('Array_fixed_') {
1194 g.sb.write_string('${cast_type} ${decl_c_name}; memcpy(${decl_c_name}, (${cast_type}*)(((u8*)(&')
1195 g.expr(rhs.lhs)
1196 g.sb.writeln('.err)) + sizeof(IError)), sizeof(${cast_type}));')
1197 g.remember_runtime_local_type(name, cast_type)
1198 return
1199 }
1200 g.sb.write_string('${cast_type} ${decl_c_name} = (*(${cast_type}*)(((u8*)(&')
1201 g.expr(rhs.lhs)
1202 g.sb.writeln('.err)) + sizeof(IError)));')
1203 g.remember_runtime_local_type(name, cast_type)
1204 return
1205 }
1206 }
1207 if !elem_type_from_array && (typ == '' || typ == 'int'
1208 || typ == 'int_literal' || typ == 'void*' || typ == 'voidptr') && rhs_type != ''
1209 && rhs_type !in ['int', 'int_literal', 'float_literal']
1210 && !rhs_type.starts_with('_result_') && !rhs_type.starts_with('_option_') {
1211 typ = rhs_type
1212 }
1213 if !elem_type_from_array && typ != '' && rhs_type.starts_with('${typ}_T_') {
1214 typ = rhs_type
1215 }
1216 if (typ == '' || typ == 'int' || typ == 'int_literal') && rhs is ast.InfixExpr {
1217 inferred := g.infer_numeric_expr_type(rhs)
1218 if inferred != '' && inferred !in ['int', 'int_literal'] {
1219 typ = inferred
1220 }
1221 }
1222 typ = unmangle_c_ptr_type(typ)
1223 if name != '' && rhs_type.starts_with('_result_') && !typ.starts_with('_result_') {
1224 if typ.starts_with('Array_fixed_') {
1225 g.sb.write_string('${typ} ${decl_c_name}; { ${rhs_type} _tmp = ')
1226 g.expr(rhs)
1227 g.sb.writeln('; memcpy(${decl_c_name}, (${typ}*)(((u8*)(&_tmp.err)) + sizeof(IError)), sizeof(${typ})); }')
1228 g.remember_runtime_local_type(name, typ)
1229 return
1230 }
1231 g.sb.write_string('${typ} ${decl_c_name} = ({ ${rhs_type} _tmp = ')
1232 g.expr(rhs)
1233 g.sb.writeln('; (*(${typ}*)(((u8*)(&_tmp.err)) + sizeof(IError))); });')
1234 g.remember_runtime_local_type(name, typ)
1235 return
1236 }
1237 if name != '' && rhs_type.starts_with('_option_') && !typ.starts_with('_option_') {
1238 if typ.starts_with('Array_fixed_') {
1239 g.sb.write_string('${typ} ${decl_c_name}; { ${rhs_type} _tmp = ')
1240 g.expr(rhs)
1241 g.sb.writeln('; memcpy(${decl_c_name}, (${typ}*)(((u8*)(&_tmp.err)) + sizeof(IError)), sizeof(${typ})); }')
1242 g.remember_runtime_local_type(name, typ)
1243 return
1244 }
1245 g.sb.write_string('${typ} ${decl_c_name} = ({ ${rhs_type} _tmp = ')
1246 g.expr(rhs)
1247 g.sb.writeln('; (*(${typ}*)(((u8*)(&_tmp.err)) + sizeof(IError))); });')
1248 g.remember_runtime_local_type(name, typ)
1249 return
1250 }
1251 if rhs is ast.IfExpr {
1252 if typ.starts_with('Array_fixed_') && rhs.else_expr !is ast.EmptyExpr {
1253 g.sb.writeln('${typ} ${decl_c_name};')
1254 if name != '' {
1255 g.remember_runtime_local_type(name, typ)
1256 }
1257 g.gen_decl_if_expr(decl_c_name, typ, &rhs)
1258 return
1259 }
1260 if !g.if_expr_can_be_ternary(&rhs) && rhs.else_expr !is ast.EmptyExpr {
1261 // If type is void/empty, infer from the branch's last expression
1262 if typ == 'void' || typ == '' {
1263 if rhs.stmts.len > 0 {
1264 last := rhs.stmts[rhs.stmts.len - 1]
1265 if last is ast.ExprStmt {
1266 branch_type := g.get_expr_type(last.expr)
1267 if branch_type != '' && branch_type != 'void' {
1268 typ = branch_type
1269 }
1270 }
1271 }
1272 }
1273 g.sb.writeln('${typ} ${decl_c_name};')
1274 if name != '' {
1275 g.remember_runtime_local_type(name, typ)
1276 }
1277 g.gen_decl_if_expr(decl_c_name, typ, &rhs)
1278 return
1279 }
1280 }
1281 // `ptr := &local` where `ptr` is returned from this function must not
1282 // keep a stack address. Heap-clone the local value for that binding.
1283 if name != '' && name in g.cur_fn_returned_idents && typ.ends_with('*')
1284 && rhs is ast.PrefixExpr && rhs.op == .amp && rhs.expr is ast.Ident {
1285 prefix_rhs := rhs as ast.PrefixExpr
1286 base_type := typ.trim_right('*')
1287 if base_type != '' && base_type != 'void' {
1288 heap_name := '_heap_t${g.tmp_counter}'
1289 g.tmp_counter++
1290 malloc_call := g.c_heap_malloc_call('sizeof(${base_type})')
1291 g.sb.write_string('${typ} ${decl_c_name} = ({ ${base_type}* ${heap_name} = (${base_type}*)${malloc_call}; *${heap_name} = ')
1292 g.expr(prefix_rhs.expr)
1293 g.sb.writeln('; ${heap_name}; });')
1294 g.remember_runtime_local_type(name, typ)
1295 return
1296 }
1297 }
1298 if typ.ends_with('**') && rhs is ast.PrefixExpr && rhs.op == .amp {
1299 prefix_rhs := rhs as ast.PrefixExpr
1300 g.sb.write_string('${typ} ${decl_c_name} = ((${typ})(')
1301 g.expr(prefix_rhs.expr)
1302 g.sb.writeln('));')
1303 if name != '' {
1304 g.remember_runtime_local_type(name, typ)
1305 }
1306 return
1307 }
1308 if name != '' && rhs is ast.PrefixExpr && rhs.op == .amp && rhs.expr is ast.SelectorExpr {
1309 prefix_rhs := rhs as ast.PrefixExpr
1310 sel := prefix_rhs.expr as ast.SelectorExpr
1311 if sel.lhs is ast.CallExpr {
1312 call_expr := sel.lhs as ast.CallExpr
1313 if call_expr.args.len == 1 && g.call_or_cast_lhs_is_type(call_expr.lhs) {
1314 target_type := g.expr_type_to_c(call_expr.lhs)
1315 if target_type != '' && target_type != 'int' {
1316 mut field_type := typ.trim_right('*')
1317 if field_type == 'voidptr' {
1318 field_type = 'void*'
1319 }
1320 if field_type == '' || field_type == 'void' {
1321 field_type = 'void*'
1322 }
1323 g.sb.write_string('${field_type} ${decl_c_name} = ((${target_type}*)(')
1324 g.expr(call_expr.args[0])
1325 g.sb.writeln('))->${escape_c_keyword(sel.rhs.name)};')
1326 g.remember_runtime_local_type(name, field_type)
1327 return
1328 }
1329 }
1330 } else if sel.lhs is ast.CastExpr {
1331 cast_expr := sel.lhs as ast.CastExpr
1332 target_type := g.expr_type_to_c(cast_expr.typ)
1333 if target_type != '' && target_type != 'int' {
1334 mut field_type := typ.trim_right('*')
1335 if field_type == 'voidptr' {
1336 field_type = 'void*'
1337 }
1338 if field_type == '' || field_type == 'void' {
1339 field_type = 'void*'
1340 }
1341 g.sb.write_string('${field_type} ${decl_c_name} = ((${target_type}*)(')
1342 g.expr(cast_expr.expr)
1343 g.sb.writeln('))->${escape_c_keyword(sel.rhs.name)};')
1344 g.remember_runtime_local_type(name, field_type)
1345 return
1346 }
1347 }
1348 c_typedef := g.c_typedef_for_interface_object_access(sel)
1349 if c_typedef != '' {
1350 mut field_type := typ.trim_right('*')
1351 if field_type == 'voidptr' {
1352 field_type = 'void*'
1353 }
1354 if field_type == '' || field_type == 'void' {
1355 field_type = 'void*'
1356 }
1357 g.sb.write_string('${field_type} ${decl_c_name} = ((${c_typedef}*)(')
1358 g.expr(sel.lhs)
1359 g.sb.writeln('))->${escape_c_keyword(sel.rhs.name)};')
1360 g.remember_runtime_local_type(name, field_type)
1361 return
1362 }
1363 }
1364 if typ.ends_with('*') && rhs is ast.PrefixExpr && rhs.op == .amp {
1365 prefix_rhs := rhs as ast.PrefixExpr
1366 if prefix_rhs.expr is ast.CallExpr && prefix_rhs.expr.args.len == 1 {
1367 g.sb.write_string('${typ} ${decl_c_name} = ((${typ})(')
1368 g.expr(prefix_rhs.expr.args[0])
1369 g.sb.writeln('));')
1370 if name != '' {
1371 g.remember_runtime_local_type(name, typ)
1372 }
1373 return
1374 }
1375 if prefix_rhs.expr is ast.CastExpr {
1376 g.sb.write_string('${typ} ${decl_c_name} = ((${typ})(')
1377 g.expr(prefix_rhs.expr.expr)
1378 g.sb.writeln('));')
1379 if name != '' {
1380 g.remember_runtime_local_type(name, typ)
1381 }
1382 return
1383 }
1384 if prefix_rhs.expr is ast.ParenExpr {
1385 if prefix_rhs.expr.expr is ast.CallExpr && prefix_rhs.expr.expr.args.len == 1 {
1386 g.sb.write_string('${typ} ${decl_c_name} = ((${typ})(')
1387 g.expr(prefix_rhs.expr.expr.args[0])
1388 g.sb.writeln('));')
1389 if name != '' {
1390 g.remember_runtime_local_type(name, typ)
1391 }
1392 return
1393 }
1394 if prefix_rhs.expr.expr is ast.CastExpr {
1395 g.sb.write_string('${typ} ${decl_c_name} = ((${typ})(')
1396 g.expr(prefix_rhs.expr.expr.expr)
1397 g.sb.writeln('));')
1398 if name != '' {
1399 g.remember_runtime_local_type(name, typ)
1400 }
1401 return
1402 }
1403 }
1404 }
1405 // FnLiteral: generate proper function pointer declaration
1406 if rhs is ast.FnLiteral {
1407 ret_type := if rhs.typ.return_type !is ast.EmptyExpr {
1408 g.expr_type_to_c(rhs.typ.return_type)
1409 } else {
1410 'void'
1411 }
1412 mut params_str := ''
1413 for i, param in rhs.typ.params {
1414 if i > 0 {
1415 params_str += ', '
1416 }
1417 params_str += g.expr_type_to_c(param.typ)
1418 }
1419 if params_str == '' {
1420 params_str = 'void'
1421 }
1422 g.sb.write_string('${ret_type} (*${decl_c_name})(${params_str}) = ')
1423 g.expr(rhs)
1424 g.sb.writeln(';')
1425 // Register the return type so map/filter can infer result type
1426 g.fn_return_types[name] = ret_type
1427 g.remember_runtime_local_type(name, 'fn_ptr')
1428 return
1429 }
1430 if typ.starts_with('Array_fixed_') && rhs is ast.CallExpr {
1431 if call_ret := g.get_call_return_type(rhs.lhs, rhs.args) {
1432 if call_ret == typ {
1433 wrapper_type := g.c_fn_return_type_from_v(typ)
1434 g.sb.writeln('${typ} ${decl_c_name};')
1435 g.write_indent()
1436 g.sb.write_string('{ ${wrapper_type} _tmp = ')
1437 g.expr(rhs)
1438 g.sb.writeln('; memcpy(${decl_c_name}, _tmp.ret_arr, sizeof(${typ})); }')
1439 g.remember_runtime_local_type(name, typ)
1440 return
1441 }
1442 }
1443 }
1444 if typ.starts_with('Array_fixed_') && !typ.ends_with('*') && rhs !is ast.ArrayInitExpr
1445 && rhs !is ast.CallExpr {
1446 g.sb.writeln('${typ} ${decl_c_name};')
1447 g.write_indent()
1448 // For fixed arrays, check if RHS is a zero-init or type-incompatible pattern.
1449 // UnsafeExpr wrapping an array init, plain InitExpr, or dynamic array type
1450 // should all use memset for zero-init instead of memcpy.
1451 mut is_fixed_arr_zero := rhs is ast.InitExpr && rhs.fields.len == 0
1452 if !is_fixed_arr_zero && rhs is ast.UnsafeExpr {
1453 is_fixed_arr_zero = true // unsafe { [N]T{init: expr} } → memset for now
1454 }
1455 if !is_fixed_arr_zero {
1456 fa_rhs_type := g.get_expr_type(rhs)
1457 if fa_rhs_type == '' || fa_rhs_type == 'array' || fa_rhs_type == 'int' {
1458 is_fixed_arr_zero = true
1459 }
1460 }
1461 if is_fixed_arr_zero {
1462 g.sb.writeln('memset(${decl_c_name}, 0, sizeof(${typ}));')
1463 } else {
1464 g.sb.write_string('memcpy(${decl_c_name}, ')
1465 g.expr(rhs)
1466 g.sb.writeln(', sizeof(${typ}));')
1467 }
1468 g.remember_runtime_local_type(name, typ)
1469 return
1470 }
1471 if rhs is ast.CallExpr {
1472 call_name := g.resolve_call_name(rhs.lhs, rhs.args.len)
1473 if call_name in ['math__min', 'math__max'] && rhs.args.len == 2 {
1474 arg0_type := g.get_expr_type(rhs.args[0])
1475 arg1_type := g.get_expr_type(rhs.args[1])
1476 if !arg0_type.starts_with('f') && arg0_type != 'float_literal'
1477 && !arg1_type.starts_with('f') && arg1_type != 'float_literal' {
1478 typ = 'int'
1479 }
1480 }
1481 }
1482 rhs_is_none := is_none_expr(rhs) || (rhs is ast.Type && rhs is ast.NoneType)
1483 rhs_is_option_zero := rhs is ast.BasicLiteral && rhs.value == '0'
1484 && typ.starts_with('_option_')
1485 if rhs is ast.SelectorExpr {
1486 selector_typ := g.decl_selector_rhs_type(rhs)
1487 if selector_typ != '' {
1488 typ = selector_typ
1489 }
1490 }
1491 if (typ == '' || typ == 'int' || typ == 'int_literal') && name.starts_with('_defer_t')
1492 && g.cur_fn_ret_type != '' && g.cur_fn_ret_type != 'void'
1493 && (rhs_is_none || rhs_is_option_zero) {
1494 typ = g.cur_fn_ret_type
1495 }
1496 can_emit_none := typ.starts_with('_option_') || typ in ['IError', 'builtin__IError']
1497 || is_type_name_pointer_like(typ) || typ in ['void*', 'voidptr', 'byteptr', 'charptr']
1498 if name != '' && (rhs_is_none || rhs_is_option_zero) && can_emit_none {
1499 g.sb.write_string('${typ} ${decl_c_name} = ')
1500 g.gen_none_literal_for_type(typ)
1501 g.sb.writeln(';')
1502 g.remember_runtime_local_type(name, typ)
1503 return
1504 }
1505 if typ == '' || typ == 'void' {
1506 typ = 'int'
1507 }
1508 typ = g.normalize_builtin_qualified_c_type(typ)
1509 if name != '' && is_zero_number_expr(rhs) && g.c_type_needs_typed_zero_expr(typ) {
1510 g.sb.write_string('${storage_prefix}${typ} ${c_local_name(name)} = ${zero_value_for_type(typ)};')
1511 g.remember_runtime_local_type(name, typ)
1512 return
1513 }
1514 // Check if declaring an interface pointer initialized with a concrete type
1515 if typ.ends_with('*') && name != '' {
1516 decl_base := typ.trim_right('*')
1517 if g.is_interface_type(decl_base) {
1518 decl_rhs_type := g.get_expr_type(rhs)
1519 decl_rhs_base := decl_rhs_type.trim_right('*')
1520 if decl_rhs_base != '' && decl_rhs_base != 'int' && decl_rhs_base != decl_base
1521 && !g.is_interface_type(decl_rhs_base) {
1522 g.sb.write_string('${typ} ${decl_c_name} = ')
1523 if !g.gen_heap_interface_cast(decl_base, rhs) {
1524 g.expr(rhs)
1525 }
1526 g.sb.writeln(';')
1527 g.remember_runtime_local_type(name, typ)
1528 return
1529 }
1530 }
1531 }
1532 g.sb.write_string('${storage_prefix}${typ} ${c_local_name(name)} = ')
1533 if !g.gen_auto_deref_value_param_arg(typ, rhs) {
1534 g.expr_with_expected_init_type(rhs, typ)
1535 }
1536 g.sb.writeln(';')
1537 g.remember_runtime_local_type(name, typ)
1538 } else {
1539 // Assignment
1540 mut lhs_fixed_type := g.get_expr_type(lhs)
1541 if lhs_fixed_type == '' && lhs is ast.Ident {
1542 if local_type := g.get_local_var_c_type(lhs.name) {
1543 lhs_fixed_type = local_type
1544 }
1545 }
1546 // C has no string compound assignment; lower `s += x` to V's string concat helper.
1547 if node.op == .plus_assign && lhs_fixed_type == 'string' && g.get_expr_type(rhs) == 'string' {
1548 g.expr(lhs)
1549 g.sb.write_string(' = string__plus(')
1550 g.expr(lhs)
1551 g.sb.write_string(', ')
1552 g.expr(rhs)
1553 g.sb.writeln(');')
1554 return
1555 }
1556 if node.op == .assign && lhs is ast.Ident && lhs.name in g.cur_fn_returned_idents
1557 && rhs is ast.PrefixExpr && rhs.op == .amp && rhs.expr is ast.Ident {
1558 prefix_rhs := rhs as ast.PrefixExpr
1559 base_type := lhs_fixed_type.trim_right('*')
1560 if lhs_fixed_type.ends_with('*') && base_type != '' && base_type != 'void' {
1561 heap_name := '_heap_t${g.tmp_counter}'
1562 g.tmp_counter++
1563 malloc_call := g.c_heap_malloc_call('sizeof(${base_type})')
1564 g.sb.write_string('${c_local_name(lhs.name)} = ({ ${base_type}* ${heap_name} = (${base_type}*)${malloc_call}; *${heap_name} = ')
1565 g.expr(prefix_rhs.expr)
1566 g.sb.writeln('; ${heap_name}; });')
1567 return
1568 }
1569 }
1570 if lhs_fixed_type.starts_with('Array_fixed_') && lhs !is ast.IndexExpr
1571 && rhs is ast.CallExpr {
1572 if call_ret := g.get_call_return_type(rhs.lhs, rhs.args) {
1573 if call_ret == lhs_fixed_type {
1574 wrapper_type := g.c_fn_return_type_from_v(lhs_fixed_type)
1575 g.write_indent()
1576 g.sb.write_string('{ ${wrapper_type} _tmp = ')
1577 g.expr(rhs)
1578 g.sb.write_string('; memcpy(')
1579 g.expr(lhs)
1580 g.sb.writeln(', _tmp.ret_arr, sizeof(${lhs_fixed_type})); }')
1581 return
1582 }
1583 }
1584 }
1585 if lhs_fixed_type.starts_with('Array_fixed_') && lhs !is ast.IndexExpr && node.op == .assign
1586 && rhs !is ast.CallExpr {
1587 g.write_indent()
1588 g.sb.write_string('memcpy(')
1589 g.expr(lhs)
1590 g.sb.write_string(', ')
1591 if rhs is ast.ArrayInitExpr {
1592 g.sb.write_string('((${lhs_fixed_type})')
1593 g.expr(rhs)
1594 g.sb.write_string(')')
1595 } else {
1596 g.expr(rhs)
1597 }
1598 g.sb.writeln(', sizeof(${lhs_fixed_type}));')
1599 return
1600 }
1601 if node.op == .left_shift_assign {
1602 is_array_append, elem_type := g.array_append_elem_type(lhs, rhs)
1603 if is_array_append {
1604 if g.expr_is_array_value(rhs) {
1605 rhs_tmp := '_arr_append_tmp_${g.tmp_counter}'
1606 g.tmp_counter++
1607 arr_rhs_type := g.expr_array_runtime_type(rhs)
1608 g.write_indent()
1609 g.sb.write_string('${arr_rhs_type} ${rhs_tmp} = ')
1610 g.expr(rhs)
1611 g.sb.writeln(';')
1612 g.write_indent()
1613 g.sb.write_string('array__push_many((array*)')
1614 g.gen_array_append_target(lhs)
1615 g.sb.writeln(', ${rhs_tmp}.data, ${rhs_tmp}.len);')
1616 return
1617 }
1618 g.write_indent()
1619 g.sb.write_string('array__push((array*)')
1620 g.gen_array_append_target(lhs)
1621 g.sb.write_string(', ')
1622 // When pushing a concrete type into an interface array, wrap it
1623 if g.is_interface_type(elem_type) {
1624 g.sb.write_string('&(${elem_type}[1]){')
1625 if !g.gen_interface_cast(elem_type, rhs) {
1626 g.expr(rhs)
1627 }
1628 g.sb.write_string('}')
1629 } else {
1630 g.gen_array_push_elem_arg(rhs, elem_type)
1631 }
1632 g.sb.writeln(');')
1633 return
1634 }
1635 }
1636 if node.op == .assign && lhs is ast.Ident && g.get_local_var_c_type(lhs.name) == none
1637 && !g.is_module_ident(lhs.name) && !g.is_module_local_const_or_global(lhs.name)
1638 && lhs.name !in ['errno', 'stdin', 'stdout', 'stderr', 'environ'] {
1639 mut decl_type := g.get_expr_type(rhs)
1640 mut rhs_array_elem_type := g.infer_array_method_elem_type(rhs)
1641 if rhs_array_elem_type != '' && decl_type in ['void*', 'voidptr'] {
1642 decl_type = rhs_array_elem_type
1643 }
1644 if decl_type == '' || decl_type in ['int_literal', 'float_literal'] {
1645 decl_type = 'int'
1646 }
1647 if decl_type in ['void', 'void*', 'voidptr'] {
1648 decl_type = 'int'
1649 }
1650 decl_type = unmangle_c_ptr_type(decl_type)
1651 g.write_indent()
1652 g.sb.write_string('${decl_type} ${c_local_name(lhs.name)} = ')
1653 if rhs_array_elem_type != '' && decl_type !in ['void*', 'voidptr']
1654 && !decl_type.ends_with('*') {
1655 g.sb.write_string('(*(${decl_type}*)')
1656 g.expr(rhs)
1657 g.sb.write_string(')')
1658 } else {
1659 g.expr(rhs)
1660 }
1661 g.sb.writeln(';')
1662 g.remember_runtime_local_type(lhs.name, decl_type)
1663 return
1664 }
1665 // Handle result/option .data field write: _t.data = val -> unwrapped value pointer = val
1666 if lhs is ast.SelectorExpr && lhs.rhs.name == 'data' {
1667 lhs_type := g.get_expr_type(lhs.lhs)
1668 is_or_tmp := lhs.lhs is ast.Ident && lhs.lhs.name.starts_with('_or_t')
1669 if lhs_type.starts_with('_result_') || lhs_type.starts_with('_option_') || is_or_tmp {
1670 base := if lhs_type.starts_with('_result_') {
1671 g.result_value_type(lhs_type)
1672 } else if lhs_type.starts_with('_option_') {
1673 option_value_type(lhs_type)
1674 } else {
1675 g.get_expr_type(rhs)
1676 }
1677 if base != '' && base != 'void' {
1678 g.write_indent()
1679 g.sb.write_string('(*(${base}*)(((u8*)(&')
1680 g.expr(lhs.lhs)
1681 g.sb.write_string('.err)) + sizeof(IError))) = ')
1682 g.expr(rhs)
1683 g.sb.writeln(';')
1684 return
1685 }
1686 g.write_indent()
1687 g.sb.write_string('(void)(')
1688 g.expr(rhs)
1689 g.sb.writeln(');')
1690 return
1691 }
1692 }
1693 if node.op == .assign && lhs is ast.SelectorExpr {
1694 if g.gen_sum_common_field_assign(lhs, rhs) {
1695 return
1696 }
1697 }
1698 if g.gen_overloaded_compound_assign(lhs, rhs, node.op) {
1699 return
1700 }
1701 if lhs is ast.SelectorExpr {
1702 if g.gen_plain_selector_assign(lhs, rhs, node.op) {
1703 return
1704 }
1705 }
1706 mut lhs_needs_deref := false
1707 // Only dereference for plain assignment, not compound assignments (+=, -=, etc.)
1708 // For compound assignments on pointers (ptr += x), we want pointer arithmetic.
1709 if node.op == .assign && lhs is ast.Ident {
1710 if lhs.name in g.cur_fn_mut_params {
1711 lhs_needs_deref = true
1712 } else if local_type := g.get_local_var_c_type(lhs.name) {
1713 if local_type.ends_with('*') {
1714 rhs_type := g.get_expr_type(rhs)
1715 // Only dereference if we're sure the RHS is not a pointer.
1716 // When rhs_type is '' or 'int' (unknown), skip deref for function
1717 // calls or unsafe blocks which may return pointers (e.g. malloc).
1718 rhs_is_ptr := rhs_type.ends_with('*') || rhs_type == 'voidptr'
1719 || rhs_type == 'void*'
1720 if !rhs_is_ptr && rhs_type != '' && rhs_type != 'int' {
1721 lhs_needs_deref = true
1722 } else if !rhs_is_ptr && rhs !is ast.CallExpr && rhs !is ast.UnsafeExpr {
1723 lhs_needs_deref = true
1724 }
1725 }
1726 }
1727 }
1728 if lhs_needs_deref {
1729 g.sb.write_string('*')
1730 }
1731 g.expr(lhs)
1732 op_str := match node.op {
1733 .assign { '=' }
1734 .plus_assign { '+=' }
1735 .minus_assign { '-=' }
1736 .mul_assign { '*=' }
1737 .div_assign { '/=' }
1738 .mod_assign { '%=' }
1739 .and_assign { '&=' }
1740 .or_assign { '|=' }
1741 .xor_assign { '^=' }
1742 .left_shift_assign { '<<=' }
1743 .right_shift_assign { '>>=' }
1744 else { '=' }
1745 }
1746
1747 g.sb.write_string(' ${op_str} ')
1748 mut rhs_array_elem_type := g.infer_array_method_elem_type(rhs)
1749 mut assign_lhs_type := g.get_expr_type(lhs)
1750 if lhs is ast.Ident {
1751 if local_type := g.get_local_var_c_type(lhs.name) {
1752 assign_lhs_type = local_type
1753 } else {
1754 // For globals, use the declared type from global_var_types.
1755 // The env-based expr type may reflect the RHS type, not the LHS.
1756 mut global_name := lhs.name
1757 if global_name !in g.global_var_types && g.cur_module != ''
1758 && g.cur_module != 'main' && g.cur_module != 'builtin' {
1759 global_name = '${g.cur_module}__${lhs.name}'
1760 }
1761 if global_name in g.global_var_types {
1762 assign_lhs_type = g.global_var_types[global_name]
1763 }
1764 }
1765 } else if lhs is ast.SelectorExpr {
1766 if assign_lhs_type == '' || assign_lhs_type == 'int' {
1767 field_type := g.selector_field_type(lhs)
1768 if field_type != '' {
1769 assign_lhs_type = field_type
1770 }
1771 }
1772 }
1773 if lhs_needs_deref && assign_lhs_type.ends_with('*') {
1774 assign_lhs_type = assign_lhs_type[..assign_lhs_type.len - 1]
1775 }
1776 if node.op == .assign && is_none_like_expr(rhs)
1777 && g.gen_none_literal_for_type(assign_lhs_type) {
1778 g.sb.writeln(';')
1779 return
1780 }
1781 if node.op == .assign && g.gen_enum_shorthand_for_type(rhs, assign_lhs_type) {
1782 g.sb.writeln(';')
1783 return
1784 }
1785 // When RHS is an array method (first/last/pop/pop_left), the call emission
1786 // in fn.v already wraps with (*(elem_type*)call(...)). Skip the outer
1787 // assign-level cast to avoid double dereference.
1788 mut call_already_casts := false
1789 if rhs_array_elem_type != '' {
1790 if rhs is ast.CallExpr {
1791 if rhs.lhs is ast.Ident
1792 && rhs.lhs.name in ['array__pop', 'array__pop_left', 'array__first', 'array__last']
1793 && rhs.args.len > 0 {
1794 call_elem := g.infer_array_elem_type_from_expr(rhs.args[0])
1795 if call_elem != '' {
1796 call_already_casts = true
1797 }
1798 }
1799 }
1800 }
1801 if rhs_array_elem_type != '' && assign_lhs_type !in ['', 'void*', 'voidptr']
1802 && !assign_lhs_type.ends_with('*') && !call_already_casts {
1803 g.sb.write_string('(*(${assign_lhs_type}*)')
1804 g.expr(rhs)
1805 g.sb.write_string(')')
1806 } else if node.op == .assign && assign_lhs_type != '' && assign_lhs_type.ends_with('*') {
1807 // Pointer assignment: check if LHS is a pointer to an interface type
1808 // and RHS is a pointer to a concrete type (e.g., Logger* = new_thread_safe_log())
1809 rhs_type := g.get_expr_type(rhs)
1810 lhs_base := assign_lhs_type.trim_right('*')
1811 rhs_base2 := rhs_type.trim_right('*')
1812 if g.is_interface_type(lhs_base) && rhs_base2 != '' && rhs_base2 != 'int'
1813 && rhs_base2 != lhs_base && !g.is_interface_type(rhs_base2)
1814 && !lhs_base.starts_with('Array_') && lhs_base != 'array'
1815 && !lhs_base.starts_with('Map_') && lhs_base != 'map' {
1816 if !g.gen_heap_interface_cast(lhs_base, rhs) {
1817 g.expr(rhs)
1818 }
1819 } else {
1820 g.expr(rhs)
1821 }
1822 } else if node.op == .assign && assign_lhs_type != '' && !assign_lhs_type.ends_with('*') {
1823 rhs_type := g.get_expr_type(rhs)
1824 lhs_base := assign_lhs_type.trim_right('*')
1825 rhs_base2 := rhs_type.trim_right('*')
1826 rhs_casts_to_lhs := rhs is ast.CastExpr && g.expr_type_to_c(rhs.typ) == assign_lhs_type
1827 // Auto-wrap raw value into Option/Result when LHS is Option/Result and RHS is not.
1828 if assign_lhs_type.starts_with('_option_') && !rhs_casts_to_lhs
1829 && !rhs_type.starts_with('_option_') && rhs_type !in ['', 'int', 'void']
1830 && !is_none_like_expr(rhs) {
1831 value_type := option_value_type(assign_lhs_type)
1832 if value_type != '' && value_type != 'void' {
1833 g.sb.write_string('({ ${assign_lhs_type} _opt = (${assign_lhs_type}){ .state = 2 }; ${value_type} _val = ')
1834 g.expr(rhs)
1835 g.sb.write_string('; _option_ok(&_val, (_option*)&_opt, sizeof(_val)); _opt; })')
1836 g.sb.writeln(';')
1837 return
1838 }
1839 }
1840 if assign_lhs_type.starts_with('_result_') && !rhs_casts_to_lhs
1841 && !rhs_type.starts_with('_result_') && rhs_type !in ['', 'int', 'void'] {
1842 value_type := g.result_value_type(assign_lhs_type)
1843 if value_type != '' && value_type != 'void' {
1844 g.sb.write_string('({ ${assign_lhs_type} _res = (${assign_lhs_type}){0}; ${value_type} _val = ')
1845 g.expr(rhs)
1846 g.sb.write_string('; _result_ok(&_val, (_result*)&_res, sizeof(_val)); _res; })')
1847 g.sb.writeln(';')
1848 return
1849 }
1850 }
1851 if lhs_base in g.sum_type_variants && rhs_base2 != lhs_base && rhs_base2 != 'void' {
1852 g.gen_type_cast_expr(lhs_base, rhs)
1853 } else if g.is_interface_type(lhs_base) && rhs_base2 != '' && rhs_base2 != 'int'
1854 && rhs_base2 != lhs_base && !g.is_interface_type(rhs_base2)
1855 && !lhs_base.starts_with('Array_') && lhs_base != 'array'
1856 && !lhs_base.starts_with('Map_') && lhs_base != 'map' {
1857 if g.gen_interface_cast(lhs_base, rhs) {
1858 } else {
1859 g.expr(rhs)
1860 }
1861 } else if rhs_type.ends_with('*') {
1862 rhs_base := rhs_type.trim_right('*')
1863 if rhs_base == assign_lhs_type
1864 || short_type_name(rhs_base) == short_type_name(assign_lhs_type) {
1865 g.sb.write_string('(*')
1866 g.expr(rhs)
1867 g.sb.write_string(')')
1868 } else {
1869 g.expr(rhs)
1870 }
1871 } else {
1872 g.expr(rhs)
1873 }
1874 } else {
1875 g.expr(rhs)
1876 }
1877 g.sb.writeln(';')
1878 }
1879}
1880
1881fn (mut g Gen) gen_plain_selector_assign(lhs ast.SelectorExpr, rhs ast.Expr, op token.Token) bool {
1882 if op != .assign || lhs.rhs.name == 'data' {
1883 return false
1884 }
1885 local_type := g.local_var_c_type_for_expr(lhs.lhs) or { return false }
1886 lhs_struct := strip_pointer_type_name(local_type)
1887 if lhs_struct == '' {
1888 return false
1889 }
1890 field_type := g.lookup_struct_field_type_by_name(lhs_struct, lhs.rhs.name) or { return false }
1891 if field_type.starts_with('Array_fixed_') || field_type.starts_with('_option_')
1892 || field_type.starts_with('_result_') || field_type in g.sum_type_variants
1893 || g.is_interface_type(field_type.trim_right('*')) {
1894 return false
1895 }
1896 g.write_indent()
1897 g.gen_selector_lvalue(lhs, local_type, lhs_struct)
1898 g.sb.write_string(' = ')
1899 if !g.gen_enum_shorthand_for_type(rhs, field_type) {
1900 g.expr(rhs)
1901 }
1902 g.sb.writeln(';')
1903 return true
1904}
1905
1906fn (mut g Gen) gen_selector_lvalue(sel ast.SelectorExpr, local_type string, lhs_struct string) {
1907 mut use_ptr := g.selector_use_ptr(sel.lhs)
1908 if sel.lhs is ast.Ident && sel.lhs.name in g.cur_fn_mut_params {
1909 use_ptr = true
1910 } else {
1911 use_ptr = local_type.ends_with('*') || local_type == 'chan'
1912 }
1913 owner := g.embedded_owner_for(lhs_struct, sel.rhs.name)
1914 field_name := escape_c_keyword(sel.rhs.name)
1915 selector := if use_ptr { '->' } else { '.' }
1916 g.expr(sel.lhs)
1917 if owner != '' {
1918 g.sb.write_string('${selector}${escape_c_keyword(owner)}.${field_name}')
1919 } else {
1920 g.sb.write_string('${selector}${field_name}')
1921 }
1922}
1923
1924fn (mut g Gen) gen_decl_assign_from_local_index(name string, storage_prefix string, decl_c_name string, rhs ast.IndexExpr) bool {
1925 if name == '' || rhs.expr is ast.RangeExpr || rhs.lhs !is ast.Ident {
1926 return false
1927 }
1928 lhs_ident := rhs.lhs as ast.Ident
1929 local_type := g.get_local_var_c_type(lhs_ident.name) or { return false }
1930 local_c_type := local_type.trim_space()
1931 if local_c_type == '' || local_c_type.ends_with('*') {
1932 return false
1933 }
1934 elem_type := g.array_alias_elem_type_from_c_type(local_c_type)
1935 if elem_type == '' || elem_type == 'array' || elem_type == 'void' {
1936 return false
1937 }
1938 g.write_indent()
1939 g.sb.write_string('${storage_prefix}${elem_type} ${decl_c_name} = ')
1940 if local_c_type.starts_with('Array_fixed_') {
1941 g.expr(rhs.lhs)
1942 g.sb.write_string('[')
1943 } else {
1944 g.sb.write_string('((${elem_type}*)')
1945 g.expr(rhs.lhs)
1946 g.sb.write_string('.data)[')
1947 }
1948 g.gen_index_expr_value(rhs.expr)
1949 g.sb.writeln('];')
1950 g.remember_runtime_local_type(name, elem_type)
1951 return true
1952}
1953
1954// resolve_struct_field_fn_return looks up a field by name in a struct type,
1955// checks if it's a function pointer (possibly wrapped in an alias), and
1956// returns the C type name of the function's return type.
1957fn (mut g Gen) resolve_struct_field_fn_return(st types.Struct, field_name string) string {
1958 for field in st.fields {
1959 if field.name == field_name {
1960 ft := field.typ
1961 if ft is types.FnType {
1962 if rt := ft.get_return_type() {
1963 return g.fn_return_type_to_c(rt)
1964 }
1965 } else if ft is types.Alias {
1966 if ft.base_type is types.FnType {
1967 fn_t := ft.base_type as types.FnType
1968 if rt := fn_t.get_return_type() {
1969 return g.fn_return_type_to_c(rt)
1970 }
1971 }
1972 }
1973 break
1974 }
1975 }
1976 return ''
1977}
1978