vxx / vlib / v / gen / c / assign.v
2811 lines · 2761 sloc · 97.01 KB · ab695faf0cc6c36b8cc4785211ff567a985b3dd6
Raw
1// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module c
5
6import v.ast
7import v.util
8import v.token
9
10fn smartcast_selector_expr_str(expr ast.SelectorExpr) string {
11 mut expr_str := expr.expr.str()
12 if expr.expr is ast.ParExpr && expr.expr.expr is ast.AsCast {
13 expr_str = expr.expr.expr.expr.str()
14 }
15 return expr_str
16}
17
18fn (g &Gen) is_smartcast_assign_lhs(expr ast.Expr) bool {
19 match expr {
20 ast.Ident {
21 if expr.obj is ast.Var && expr.obj.smartcasts.len > 0 {
22 if expr.obj.is_mut && expr.obj.orig_type != 0 {
23 orig_sym := g.table.final_sym(expr.obj.orig_type)
24 if orig_sym.kind == .sum_type {
25 return false
26 }
27 }
28 return true
29 }
30 return false
31 }
32 ast.SelectorExpr {
33 if expr.expr_type == 0 {
34 return false
35 }
36 scope_field := expr.scope.find_struct_field(smartcast_selector_expr_str(expr),
37 expr.expr_type, expr.field_name)
38 if scope_field == unsafe { nil } || scope_field.smartcasts.len == 0 {
39 return false
40 }
41 // Option field smartcast on LHS: the assignment replaces the option
42 // as a whole (e.g. `s.x = 10` or `s.x = none` inside `if s.x != none`),
43 // so skip unwrap treatment that's only meant for sumtype reassignments.
44 if scope_field.orig_type.has_flag(.option) {
45 return false
46 }
47 return true
48 }
49 else {
50 return false
51 }
52 }
53}
54
55fn (mut g Gen) expr_in_value_context(expr ast.Expr, value_type ast.Type, expected_type ast.Type) {
56 mut expr_copy := expr
57 match mut expr_copy {
58 ast.IfExpr {
59 if !expr_copy.is_expr || expr_copy.typ == 0 || expr_copy.typ == ast.void_type {
60 expr_copy.is_expr = true
61 expr_copy.typ = if value_type != 0 && value_type != ast.void_type {
62 value_type
63 } else {
64 expected_type
65 }
66 }
67 }
68 ast.MatchExpr {
69 if !expr_copy.is_expr || expr_copy.return_type == 0
70 || expr_copy.return_type == ast.void_type {
71 resolved_value_type := if value_type != 0 && value_type != ast.void_type {
72 value_type
73 } else {
74 expected_type
75 }
76 expr_copy.is_expr = true
77 expr_copy.return_type = resolved_value_type
78 if expr_copy.expected_type in [0, ast.void_type, ast.none_type] {
79 expr_copy.expected_type = expected_type
80 }
81 }
82 }
83 else {}
84 }
85
86 if expected_type.has_flag(.shared_f) && !value_type.has_flag(.shared_f) && value_type.is_ptr()
87 && !expected_type.has_option_or_result() {
88 g.expr_with_cast(expr_copy, value_type, expected_type)
89 return
90 }
91 g.expr(expr_copy)
92}
93
94fn (mut g Gen) auto_heap_assignment_uses_existing_storage(expr ast.Expr, expr_type ast.Type) bool {
95 // Reusing container-backed storage avoids leaking a fresh HEAP() copy for
96 // `@[heap]` values like `x := arr[i]`. Keep the old path under autofree,
97 // because aliasing array storage there would hand ownership to the local.
98 if g.is_autofree {
99 return false
100 }
101 return match expr {
102 ast.IndexExpr { g.auto_heap_array_index_uses_existing_storage(expr, expr_type) }
103 else { false }
104 }
105}
106
107fn (mut g Gen) auto_heap_array_index_uses_existing_storage(node ast.IndexExpr, expr_type ast.Type) bool {
108 if node.index is ast.RangeExpr || node.or_expr.kind != .absent || node.is_option {
109 return false
110 }
111 mut resolved_expr_type := g.recheck_concrete_type(g.resolved_expr_type(ast.Expr(node),
112 expr_type))
113 if resolved_expr_type == 0 || resolved_expr_type == ast.void_type {
114 resolved_expr_type = g.recheck_concrete_type(expr_type)
115 }
116 if resolved_expr_type == 0 || resolved_expr_type == ast.void_type {
117 resolved_expr_type = g.recheck_concrete_type(node.typ)
118 }
119 elem_type := g.unwrap_generic(resolved_expr_type)
120 if elem_type == 0 || elem_type.is_ptr() || !g.table.final_sym(elem_type).is_heap() {
121 return false
122 }
123 mut resolved_left_type := g.recheck_concrete_type(g.resolved_expr_type(node.left,
124 node.left_type))
125 if resolved_left_type == 0 || resolved_left_type == ast.void_type {
126 resolved_left_type = g.recheck_concrete_type(node.left_type)
127 }
128 left_sym := g.table.final_sym(g.unwrap_generic(resolved_left_type))
129 return left_sym.kind == .array
130}
131
132fn (mut g Gen) write_auto_heap_assignment_expr(expr ast.Expr, expr_type ast.Type) bool {
133 return match expr {
134 ast.IndexExpr { g.write_auto_heap_array_index_expr(expr, expr_type) }
135 else { false }
136 }
137}
138
139fn (mut g Gen) write_auto_heap_array_index_expr(node ast.IndexExpr, expr_type ast.Type) bool {
140 if !g.auto_heap_array_index_uses_existing_storage(node, expr_type) {
141 return false
142 }
143 mut resolved_expr_type := g.recheck_concrete_type(g.resolved_expr_type(ast.Expr(node),
144 expr_type))
145 if resolved_expr_type == 0 || resolved_expr_type == ast.void_type {
146 resolved_expr_type = g.recheck_concrete_type(expr_type)
147 }
148 if resolved_expr_type == 0 || resolved_expr_type == ast.void_type {
149 resolved_expr_type = g.recheck_concrete_type(node.typ)
150 }
151 elem_type := g.unwrap_generic(resolved_expr_type)
152 elem_type_str := g.styp(elem_type)
153 mut resolved_left_type := g.recheck_concrete_type(g.resolved_expr_type(node.left,
154 node.left_type))
155 if resolved_left_type == 0 || resolved_left_type == ast.void_type {
156 resolved_left_type = g.recheck_concrete_type(node.left_type)
157 }
158 left_type := if resolved_left_type != 0 { resolved_left_type } else { node.left_type }
159 left_is_ptr := left_type.is_ptr() || node.left.is_auto_deref_var()
160 left_is_shared := left_type.has_flag(.shared_f)
161 array_get_fn := if node.is_gated { 'builtin__array_get_ni' } else { 'builtin__array_get' }
162 g.write('((${elem_type_str}*)${array_get_fn}(')
163 if left_is_ptr && !left_is_shared {
164 g.write('*')
165 }
166 if node.left is ast.IndexExpr {
167 g.inside_array_index = true
168 g.expr(ast.Expr(node.left))
169 g.inside_array_index = false
170 } else {
171 g.expr(ast.Expr(node.left))
172 }
173 if left_is_shared {
174 if left_is_ptr {
175 g.write('->val')
176 } else {
177 g.write('.val')
178 }
179 }
180 g.write(', ')
181 g.expr(node.index)
182 g.write('))')
183 return true
184}
185
186fn (mut g Gen) write_assign_target_expr(left ast.Expr, var_type ast.Type) {
187 if left.is_auto_deref_var() && !var_type.has_flag(.shared_f) {
188 g.write('*')
189 }
190 g.expr(left)
191 if var_type.has_flag(.shared_f) {
192 g.write('->val')
193 }
194}
195
196fn (mut g Gen) write_assign_value_expr(left ast.Expr, var_type ast.Type) {
197 old_is_assign_lhs := g.is_assign_lhs
198 g.is_assign_lhs = false
199 defer {
200 g.is_assign_lhs = old_is_assign_lhs
201 }
202 if left.is_auto_deref_var() && !var_type.has_flag(.shared_f) {
203 g.write('*')
204 }
205 g.expr(left)
206 if var_type.has_flag(.shared_f) {
207 g.write('->val')
208 }
209}
210
211fn (mut g Gen) gen_power_assign_expr(left ast.Expr, left_type ast.Type, right ast.Expr, right_type ast.Type) {
212 power_result_type := g.normalized_power_result_type(left_type.clear_flag(.shared_f).clear_flag(.atomic_f),
213 left_type.clear_flag(.shared_f).clear_flag(.atomic_f), right_type)
214 builtin_power_type := g.table.unalias_num_type(power_result_type)
215 result_styp := g.styp(power_result_type)
216 g.uses_power = true
217 if builtin_power_type == ast.f32_type {
218 g.write('(${result_styp})powf((${g.styp(ast.f32_type)})(')
219 g.write_assign_value_expr(left, left_type)
220 g.write('), ')
221 g.expr_with_cast(right, right_type, ast.f32_type)
222 g.write(')')
223 return
224 }
225 if builtin_power_type.is_float() {
226 g.write('(${result_styp})pow((${g.styp(ast.f64_type)})(')
227 g.write_assign_value_expr(left, left_type)
228 g.write('), ')
229 g.expr_with_cast(right, right_type, ast.f64_type)
230 g.write(')')
231 return
232 }
233 if builtin_power_type.is_unsigned() {
234 g.uses_power_u64 = true
235 g.write('(${result_styp})__v_pow_u64((${g.styp(ast.u64_type)})(')
236 g.write_assign_value_expr(left, left_type)
237 g.write('), ')
238 g.expr_with_cast(right, right_type, ast.i64_type)
239 g.write(')')
240 return
241 }
242 g.write('(${result_styp})__v_pow_i64((${g.styp(ast.i64_type)})(')
243 g.write_assign_value_expr(left, left_type)
244 g.write('), ')
245 g.expr_with_cast(right, right_type, ast.i64_type)
246 g.write(')')
247}
248
249fn assign_expr_unwraps_option_or_result(expr ast.Expr) bool {
250 return match expr {
251 ast.CallExpr { expr.or_block.kind != .absent }
252 ast.ComptimeCall { expr.or_block.kind != .absent }
253 ast.ComptimeSelector { expr.or_block.kind != .absent }
254 ast.Ident { expr.or_expr.kind != .absent }
255 ast.IndexExpr { expr.or_expr.kind != .absent && !expr.typ.has_option_or_result() }
256 ast.InfixExpr { expr.or_block.kind != .absent }
257 ast.PostfixExpr { expr.op == .question }
258 ast.PrefixExpr { expr.or_block.kind != .absent }
259 ast.SelectorExpr { expr.or_block.kind != .absent }
260 else { false }
261 }
262}
263
264fn (mut g Gen) decl_assign_struct_init_needs_tmp(expr ast.Expr) bool {
265 mut node := ast.StructInit{}
266 match expr {
267 ast.StructInit {
268 node = expr
269 }
270 ast.ParExpr {
271 if expr.expr !is ast.StructInit {
272 return false
273 }
274 node = expr.expr as ast.StructInit
275 }
276 else {
277 return false
278 }
279 }
280
281 sym := g.table.final_sym(g.unwrap_generic(g.recheck_concrete_type(node.typ)))
282 if sym.info !is ast.Struct {
283 return false
284 }
285 if g.styp(node.typ) in skip_struct_init {
286 return false
287 }
288 info := sym.info as ast.Struct
289 if node.no_keys {
290 return node.init_fields.len < info.fields.len
291 }
292 init_field_names := node.init_fields.map(it.name)
293 return info.fields.any(it.name !in init_field_names)
294}
295
296fn (mut g Gen) gen_self_recursing_anon_fn_capture_patch(left ast.Expr, anon_fn ast.AnonFn) {
297 if left !is ast.Ident || anon_fn.inherited_vars.len == 0 {
298 return
299 }
300 ident := left as ast.Ident
301 if ident.name !in anon_fn.inherited_vars.map(it.name) {
302 return
303 }
304 ctx_struct := g.closure_ctx(anon_fn.decl)
305 left_expr := g.expr_string(left)
306 g.writeln('((${ctx_struct}*)builtin__closure__closure_data(${left_expr}))->${c_name(ident.name)} = ${left_expr};')
307}
308
309fn (mut g Gen) expr_with_opt_or_block(expr ast.Expr, expr_typ ast.Type, var_expr ast.Expr, ret_typ ast.Type,
310 in_heap bool) {
311 gen_or := expr is ast.Ident && expr.or_expr.kind != .absent
312 if gen_or {
313 old_inside_opt_or_res := g.inside_opt_or_res
314 g.inside_opt_or_res = true
315 expr_var := if expr is ast.Ident && expr.kind == .constant {
316 g.c_const_name(expr.name)
317 } else if expr is ast.Ident && expr.is_auto_heap() {
318 '(*${expr.name})'
319 } else {
320 '${expr}'
321 }
322 dot_or_ptr := if !expr_typ.has_flag(.option_mut_param_t) { '.' } else { '-> ' }
323 mut heap_line := ''
324 if in_heap {
325 // When the variable needs heap allocation, the caller has already
326 // written the partial line `TYPE *var = HEAP(TYPE, (`.
327 // We must NOT access the option's .data inside the HEAP macro
328 // before checking the state, because the option may be `none`.
329 // Pull back the partial line, emit the state check first, then
330 // write the assignment with data access after the check.
331 heap_line = g.go_before_last_stmt()
332 g.empty_line = true
333 } else {
334 g.expr_with_cast(expr, expr_typ, ret_typ)
335 g.writeln(';')
336 }
337 g.writeln('if (${c_name(expr_var)}${dot_or_ptr}state != 0) { // assign')
338 if expr is ast.Ident && expr.or_expr.kind == .propagate_option {
339 g.writeln('\tbuiltin__panic_option_not_set(_S("none"));')
340 } else {
341 g.inside_or_block = true
342 defer {
343 g.inside_or_block = false
344 }
345 or_expr := (expr as ast.Ident).or_expr
346 stmts := or_expr.stmts
347 scope := or_expr.scope
348 last_stmt := stmts.last()
349 // handles stmt block which returns something
350 // e.g. { return none }
351 if stmts.len > 0 && last_stmt is ast.ExprStmt && last_stmt.typ != ast.void_type {
352 var_expr_name := c_name(var_expr.str())
353 if last_stmt.expr is ast.Ident && last_stmt.expr.or_expr.kind != .absent {
354 g.write('${var_expr_name} = ')
355 g.expr_with_opt_or_block(ast.Expr(last_stmt.expr), last_stmt.typ, var_expr,
356 ret_typ, in_heap)
357 } else {
358 g.gen_or_block_stmts(var_expr_name, '', stmts, ret_typ, false, scope,
359 expr.pos())
360 }
361 } else {
362 // handles stmt block which doesn't returns value
363 // e.g. { return }
364 g.stmts(stmts)
365 if stmts.len > 0 && last_stmt is ast.ExprStmt {
366 g.writeln(';')
367 }
368 g.write_defer_stmts(scope, false, expr.pos())
369 }
370 }
371 g.writeln('}')
372 if in_heap {
373 // Now that the state has been checked (and we haven't returned/panicked),
374 // it is safe to access .data and do the heap allocation.
375 g.write(heap_line)
376 g.expr_with_cast(expr, expr_typ, ret_typ)
377 g.writeln('));')
378 }
379 g.inside_opt_or_res = old_inside_opt_or_res
380 } else {
381 g.expr_with_opt(expr, expr_typ, ret_typ)
382 if in_heap {
383 g.write('))')
384 }
385 }
386}
387
388// expr_opt_with_alias handles conversion from different option alias type name
389fn (mut g Gen) expr_opt_with_alias(expr ast.Expr, expr_typ ast.Type, ret_typ ast.Type) string {
390 styp := g.base_type(ret_typ)
391
392 line := g.go_before_last_stmt().trim_space()
393 g.empty_line = true
394
395 ret_var := g.new_tmp_var()
396 ret_styp := g.styp(ret_typ).replace('*', '_ptr')
397 g.writeln('${ret_styp} ${ret_var} = {.state=2, .err=_const_none__, .data={E_STRUCT}};')
398
399 if expr !is ast.None {
400 is_option_expr := expr_typ.has_flag(.option)
401 if is_option_expr {
402 g.write('builtin___option_clone((${option_name}*)')
403 } else {
404 g.write('builtin___option_ok(&(${styp}[]){ ')
405 }
406 has_addr := is_option_expr && expr !in [ast.Ident, ast.SelectorExpr]
407 if has_addr {
408 expr_styp := g.styp(expr_typ).replace('*', '_ptr')
409 g.write('ADDR(${expr_styp}, ')
410 } else if is_option_expr {
411 g.write('&')
412 }
413 g.expr(expr)
414 if has_addr {
415 g.write(')')
416 }
417 if !is_option_expr {
418 g.write(' }')
419 }
420 g.writeln(', (${option_name}*)&${ret_var}, sizeof(${styp}));')
421 }
422 g.write(line)
423 if g.inside_return {
424 g.write(' ')
425 }
426 g.write(ret_var)
427 return ret_var
428}
429
430// expr_result_with_alias handles conversion from different result alias type name
431fn (mut g Gen) expr_result_with_alias(expr ast.Expr, expr_typ ast.Type, ret_typ ast.Type) string {
432 styp := g.base_type(ret_typ)
433
434 line := g.go_before_last_stmt().trim_space()
435 g.empty_line = true
436
437 ret_var := g.new_tmp_var()
438 ret_styp := g.styp(ret_typ).replace('*', '_ptr')
439 g.writeln('${ret_styp} ${ret_var} = {.is_error=false, .err=_const_none__, .data={E_STRUCT}};')
440
441 is_result_expr := expr_typ.has_flag(.result)
442 if is_result_expr {
443 g.write('builtin___result_clone((${result_name}*)')
444 } else {
445 g.write('builtin___result_ok(&(${styp}[]){ ')
446 }
447 has_addr := is_result_expr && expr !in [ast.Ident, ast.SelectorExpr]
448 if has_addr {
449 expr_styp := g.styp(expr_typ).replace('*', '_ptr')
450 g.write('ADDR(${expr_styp}, ')
451 } else if is_result_expr {
452 g.write('&')
453 }
454 g.expr(expr)
455 if has_addr {
456 g.write(')')
457 }
458 if !is_result_expr {
459 g.write(' }')
460 }
461 g.writeln(', (${result_name}*)&${ret_var}, sizeof(${styp}));')
462 g.write(line)
463 if g.inside_return {
464 g.write(' ')
465 }
466 g.write(ret_var)
467 return ret_var
468}
469
470// expr_opt_with_cast is used in cast expr when converting compatible option types
471// e.g. ?int(?u8(0))
472fn (mut g Gen) expr_opt_with_cast(expr ast.Expr, expr_typ ast.Type, ret_typ ast.Type) string {
473 if !expr_typ.has_flag(.option) || !ret_typ.has_flag(.option) {
474 panic('cgen: expected expr_type and ret_typ to be options')
475 }
476
477 if expr_typ.idx() == ret_typ.idx() && g.table.sym(expr_typ).kind != .alias {
478 return g.expr_with_opt(expr, expr_typ, ret_typ)
479 } else {
480 if expr is ast.CallExpr && expr.return_type.has_flag(.option) {
481 return g.expr_opt_with_alias(expr, expr_typ, ret_typ)
482 } else {
483 past := g.past_tmp_var_new()
484 defer {
485 g.past_tmp_var_done(past)
486 }
487 unwrapped_ret := g.unwrap_generic(ret_typ)
488 // Unwrap type aliases to ensure sizeof uses the base type size, not the alias size
489 // This fixes the ASAN stack-buffer-overflow issue when using type aliases like MaybeInt = ?int
490 unaliased_ret := g.table.unaliased_type(unwrapped_ret)
491 styp := g.base_type(unaliased_ret)
492 decl_styp := g.styp(unwrapped_ret).replace('*', '_ptr')
493 g.writeln('${decl_styp} ${past.tmp_var};')
494 is_none := expr is ast.CastExpr && expr.expr is ast.None
495 if is_none {
496 g.write('builtin___option_none(&(${styp}[]) {')
497 } else {
498 g.write('builtin___option_ok(&(${styp}[]) {')
499 }
500 if expr is ast.CastExpr && expr_typ.has_flag(.option) {
501 ret_sym := g.table.sym(ret_typ)
502 if ret_sym.kind == .sum_type {
503 exp_sym := g.table.sym(expr_typ)
504 fname := g.get_sumtype_casting_fn(expr_typ, ret_typ)
505 g.call_cfn_for_casting_expr(fname, expr, ret_typ, expr_typ, expr_typ,
506 ret_sym.cname, expr_typ.is_ptr(), exp_sym.kind == .function,
507 g.styp(expr_typ))
508 } else {
509 g.write('*((${g.base_type(expr_typ)}*)')
510 g.expr(expr)
511 g.write('.data)')
512 }
513 } else {
514 old_inside_opt_or_res := g.inside_opt_or_res
515 g.inside_opt_or_res = false
516 g.expr_with_cast(expr, expr_typ, ret_typ)
517 g.inside_opt_or_res = old_inside_opt_or_res
518 }
519 g.writeln(' }, (${option_name}*)(&${past.tmp_var}), sizeof(${styp}));')
520 return past.tmp_var
521 }
522 }
523}
524
525// expr_with_opt is used in assigning an expression to an `option` variable
526// e.g. x = y (option lhs and rhs), mut x = ?int(123), y = none
527fn (mut g Gen) expr_with_opt(expr ast.Expr, expr_typ ast.Type, ret_typ ast.Type) string {
528 old_inside_opt_or_res := g.inside_opt_or_res
529 g.inside_opt_or_res = true
530 defer {
531 g.inside_opt_or_res = old_inside_opt_or_res
532 }
533 unwrapped_expr_typ := g.unwrap_generic(expr_typ)
534 unwrapped_ret_typ := g.unwrap_generic(ret_typ)
535 if unwrapped_expr_typ.has_flag(.option) && unwrapped_ret_typ.has_flag(.option)
536 && !g.is_arraymap_set
537 && expr in [ast.SelectorExpr, ast.DumpExpr, ast.Ident, ast.ComptimeSelector, ast.ComptimeCall, ast.AsCast, ast.CallExpr, ast.MatchExpr, ast.IfExpr, ast.IndexExpr, ast.UnsafeExpr, ast.CastExpr] {
538 if expr in [ast.Ident, ast.CastExpr] {
539 if unwrapped_expr_typ.idx() != unwrapped_ret_typ.idx()
540 && g.table.type_to_str(unwrapped_expr_typ) != g.table.type_to_str(unwrapped_ret_typ) {
541 return g.expr_opt_with_cast(expr, unwrapped_expr_typ, unwrapped_ret_typ)
542 }
543 }
544 g.expr(expr)
545 if expr is ast.ComptimeSelector {
546 return g.gen_comptime_selector(expr)
547 } else {
548 return expr.str()
549 }
550 } else {
551 tmp_out_var := g.new_tmp_var()
552 g.expr_with_tmp_var(expr, unwrapped_expr_typ, unwrapped_ret_typ, tmp_out_var, true)
553 return tmp_out_var
554 }
555 return ''
556}
557
558fn (g &Gen) static_init_guard_name(pos token.Pos) string {
559 return '_vstatic_init_${pos.pos}'
560}
561
562fn (mut g Gen) gen_static_decl_runtime_init(node ast.AssignStmt, left ast.Expr, left_type ast.Type, right ast.Expr, right_type ast.Type) bool {
563 if node.left.len != 1 || node.right.len != 1 || g.inside_ternary != 0 {
564 return false
565 }
566 if left !is ast.Ident {
567 return false
568 }
569 guard_name := g.static_init_guard_name(left.pos())
570 g.writeln(';')
571 g.writeln('static bool ${guard_name};')
572 g.writeln('if (!${guard_name}) {')
573 g.indent++
574 g.writeln('${guard_name} = true;')
575 old_is_assign_lhs := g.is_assign_lhs
576 g.is_assign_lhs = false
577 g.assign_stmt(ast.AssignStmt{
578 op: .assign
579 pos: node.pos
580 left: [left]
581 right: [right]
582 left_types: [left_type]
583 right_types: [right_type]
584 })
585 g.is_assign_lhs = old_is_assign_lhs
586 g.indent--
587 g.writeln('}')
588 return true
589}
590
591fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) {
592 mut node := unsafe { node_ }
593 if node.is_static {
594 is_defer_var := node.left[0] is ast.Ident && node.left[0].name in g.defer_vars
595 if is_defer_var && node.op == .decl_assign {
596 return
597 }
598 if !is_defer_var {
599 g.write('static ')
600 }
601 }
602 if node.is_volatile && node.left[0] is ast.Ident && node.left[0].name !in g.defer_vars {
603 g.write('volatile ')
604 }
605 mut return_type := ast.void_type
606 is_decl := node.op == .decl_assign
607 g.assign_op = node.op
608 g.inside_assign = true
609 g.arraymap_set_pos = 0
610 g.is_arraymap_set = false
611 g.is_assign_lhs = false
612 g.is_shared = false
613 defer {
614 g.assign_op = .unknown
615 g.inside_assign = false
616 g.assign_ct_type.clear()
617 g.expected_rhs_type_by_pos.clear()
618 g.arraymap_set_pos = 0
619 g.is_arraymap_set = false
620 g.is_assign_lhs = false
621 g.is_shared = false
622 }
623 op := if is_decl { token.Kind.assign } else { node.op }
624 right_expr := node.right[0]
625 match right_expr {
626 ast.CallExpr {
627 resolved_call_type := g.resolve_return_type(right_expr)
628 if resolved_call_type != ast.void_type {
629 return_type = resolved_call_type
630 } else {
631 return_type = right_expr.return_type
632 }
633 }
634 ast.LockExpr {
635 return_type = right_expr.typ
636 }
637 ast.MatchExpr {
638 return_type = right_expr.return_type
639 }
640 ast.IfExpr {
641 return_type = right_expr.typ
642 }
643 else {}
644 }
645
646 if node.right.len == 1 && node.left.len > 1 && node.left_types.len == node.left.len {
647 is_generic_context := g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0
648 concrete_left_types := if is_generic_context {
649 node.left_types.map(g.unwrap_generic(g.recheck_concrete_type(it)))
650 } else {
651 node.left_types
652 }
653 if concrete_left_types.all(it != 0 && !it.has_flag(.generic)
654 && !g.type_has_unresolved_generic_parts(it))
655 {
656 expected_multi_return := g.table.find_or_register_multi_return(concrete_left_types)
657 if return_type == ast.void_type || return_type == 0 {
658 return_type = expected_multi_return
659 } else if g.table.sym(return_type).kind == .multi_return
660 && return_type != expected_multi_return {
661 actual_return_types := if is_generic_context {
662 (g.table.sym(return_type).info as ast.MultiReturn).types.map(g.unwrap_generic(g.recheck_concrete_type(it)))
663 } else {
664 (g.table.sym(return_type).info as ast.MultiReturn).types
665 }
666 if actual_return_types.any(it == 0 || it.has_flag(.generic)
667 || g.type_has_unresolved_generic_parts(it))
668 {
669 return_type = expected_multi_return
670 }
671 }
672 }
673 }
674 // Free the old value assigned to this string var (only if it's `str = [new value]`
675 // or `x.str = [new value]` )
676 mut af := g.is_autofree && !g.is_builtin_mod && !g.is_autofree_tmp && node.left_types.len == 1
677 && node.left[0] in [ast.Ident, ast.SelectorExpr] && (node.op == .assign
678 || (node.op == .plus_assign && node.left_types[0] == ast.string_type))
679 if af && node.right.len == 1 && node.right[0] is ast.CallExpr {
680 call_expr := node.right[0] as ast.CallExpr
681 if call_expr.is_method && call_expr.left is ast.CallExpr {
682 af = false
683 }
684 }
685 mut sref_name := ''
686 mut type_to_free := ''
687 if af {
688 first_left_type := node.left_types[0]
689 first_left_sym := g.table.sym(node.left_types[0])
690 if first_left_type == ast.string_type
691 || (node.op == .assign && first_left_sym.kind == .array) {
692 type_to_free = if first_left_type == ast.string_type { 'string' } else { 'array' }
693 mut ok := true
694 left0 := node.left[0]
695 if left0 is ast.Ident {
696 if left0.name == '_' {
697 ok = false
698 }
699 }
700 if ok {
701 sref_name = '_sref${node.pos.pos}'
702 g.write('${type_to_free} ${sref_name} = (') // TODO: we are copying the entire string here, optimize
703 // we can't just do `.str` since we need the extra data from the string struct
704 // doing `&string` is also not an option since the stack memory with the data will be overwritten
705 if left0.is_auto_deref_var() && !first_left_type.has_flag(.shared_f) {
706 g.write('*')
707 }
708 g.expr(left0) // node.left[0])
709 if first_left_type.has_flag(.shared_f) {
710 g.write('->val')
711 }
712 g.writeln('); // free ${type_to_free} on re-assignment2')
713 defer(fn) {
714 if af {
715 g.writeln('builtin__${type_to_free}_free(&${sref_name});')
716 }
717 }
718 } else {
719 af = false
720 }
721 } else {
722 af = false
723 }
724 }
725 // TODO: g.gen_assign_vars_autofree(node)
726 // json_test failed w/o this check
727 if node.right.len == 1 && return_type != ast.void_type && return_type != 0 {
728 sym := g.table.sym(return_type)
729 if sym.kind == .multi_return {
730 g.gen_multi_return_assign(node, return_type, sym)
731 return
732 }
733 }
734 // TODO: non idents on left (exprs)
735 if node.has_cross_var {
736 g.gen_cross_var_assign(node)
737 }
738 // `a := 1` | `a,b := 1,2`
739 if node.right.len < node.left.len {
740 g.checker_bug('node.right.len < node.left.len', node.pos)
741 }
742 if node.right_types.len < node.left.len {
743 g.checker_bug('node.right_types.len < node.left.len', node.pos)
744 }
745 if node.left_types.len < node.left.len {
746 g.checker_bug('node.left_types.len < node.left.len', node.pos)
747 }
748
749 last_curr_var_name := g.curr_var_name.clone()
750 defer {
751 g.curr_var_name = last_curr_var_name
752 }
753 g.curr_var_name = []
754
755 for i, mut left in node.left {
756 mut is_auto_heap := false
757 mut is_fn_var := false
758 mut var_type := node.left_types[i]
759 mut val_type := node.right_types[i]
760 // Save original shared status of val_type before resolution blocks can overwrite it.
761 // This is needed because `val_type = var_type` in resolution blocks can lose the
762 // RHS shared flag (e.g., lock expr returning shared value to non-shared variable).
763 orig_val_shared := val_type.has_flag(.shared_f)
764 if g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0 {
765 resolved_left_type := g.recheck_concrete_type(var_type)
766 if resolved_left_type != 0 {
767 var_type = g.unwrap_generic(resolved_left_type)
768 }
769 resolved_right_type := g.recheck_concrete_type(val_type)
770 if resolved_right_type != 0 {
771 val_type = g.unwrap_generic(resolved_right_type)
772 }
773 }
774 mut val := node.right[i]
775 expected_lhs_type := var_type
776 if expected_lhs_type != 0 && expected_lhs_type != ast.void_type
777 && !expected_lhs_type.has_option_or_result() && val in [ast.IfExpr, ast.MatchExpr] {
778 g.expected_rhs_type_by_pos[val.pos().pos] = expected_lhs_type
779 }
780 mut str_add_rhs_tmp := ''
781 mut str_add_rhs_needs_free := false
782 mut skip_str_add_rhs_clone := false
783 if is_decl && g.cur_concrete_types.len > 0 && val is ast.CallExpr
784 && val.return_type_generic != 0 {
785 mut resolved_val_type := g.resolve_return_type(val).clear_option_and_result()
786 if resolved_val_type == ast.void_type || resolved_val_type.has_flag(.generic) {
787 resolved_val_type =
788 g.unwrap_generic(val.return_type_generic).clear_option_and_result()
789 }
790 // When unwrap_generic couldn't resolve (e.g. cur_fn.generic_names is empty
791 // for methods whose generics come from the receiver), try resolving using
792 // the receiver's generic type names from the current fn.
793 if (resolved_val_type == ast.void_type || resolved_val_type.has_flag(.generic)
794 || g.type_has_unresolved_generic_parts(resolved_val_type))
795 && g.cur_fn != unsafe { nil } && g.cur_fn.is_method
796 && g.cur_fn.receiver.typ.has_flag(.generic) {
797 receiver_generic_names := g.table.generic_type_names(g.cur_fn.receiver.typ)
798 if receiver_generic_names.len == g.cur_concrete_types.len {
799 if gen_type := g.table.convert_generic_type(val.return_type_generic,
800 receiver_generic_names, g.cur_concrete_types)
801 {
802 resolved_val_type = gen_type.clear_option_and_result()
803 }
804 }
805 }
806 if resolved_val_type != ast.void_type && !resolved_val_type.has_flag(.generic) {
807 var_type = ast.mktyp(resolved_val_type)
808 val_type = resolved_val_type
809 }
810 }
811 mut is_call := false
812 mut gen_or := false
813 mut blank_assign := false
814 mut is_va_list := false // C varargs
815 mut ident := ast.Ident{
816 scope: unsafe { nil }
817 }
818 if is_decl {
819 // Strip shared/atomic flags and pointer from the default type
820 // so that `resolved_expr_type` doesn't propagate LHS declaration
821 // properties to the resolved RHS type (e.g., for string/int literals
822 // that fall through to the default).
823 mut decl_default := var_type.clear_flag(.shared_f).clear_flag(.atomic_f)
824 if var_type.has_flag(.shared_f) && decl_default.nr_muls() > 0 {
825 decl_default = decl_default.set_nr_muls(decl_default.nr_muls() - 1)
826 }
827 // Check if this variable is auto-heap promoted early so we can
828 // skip resolved_expr_type updates that would add extra pointer
829 // levels (the HEAP macro handles the pointer promotion).
830 left_is_auto_heap := left is ast.Ident && g.resolved_ident_is_auto_heap_not_stack(left)
831 resolved_decl_type := g.resolved_expr_type(val, decl_default)
832 if resolved_decl_type != 0 && resolved_decl_type != ast.void_type && !left_is_auto_heap {
833 mut resolved_unwrapped :=
834 g.unwrap_generic(g.recheck_concrete_type(resolved_decl_type))
835 // Don't propagate pointer flag from auto-deref mut parameters.
836 // `mut e := expr` where expr is a mut param should create a value copy.
837 // Also handles the case where var_type is already a pointer (e.g.
838 // `c := head` where head is `mut &Client` → var_type is &Client
839 // but resolved_expr_type returns &&Client). Compare nr_muls to
840 // deref only when there's an extra level from auto-deref.
841 if resolved_unwrapped.is_ptr() && !var_type.is_ptr() {
842 resolved_unwrapped = resolved_unwrapped.deref()
843 } else if val is ast.Ident && val.is_auto_deref_var()
844 && resolved_unwrapped.nr_muls() > var_type.nr_muls() {
845 resolved_unwrapped = resolved_unwrapped.set_nr_muls(var_type.nr_muls())
846 }
847 // Don't propagate shared/atomic flags from the resolved RHS expression
848 // to a non-shared declaration. E.g., `sliced := shared_arr[..x]` inside
849 // a rlock block resolves to a shared type, but `sliced` is not shared.
850 if !var_type.has_flag(.shared_f) && resolved_unwrapped.has_flag(.shared_f) {
851 resolved_unwrapped = resolved_unwrapped.clear_flag(.shared_f)
852 if resolved_unwrapped.nr_muls() > 0 {
853 resolved_unwrapped =
854 resolved_unwrapped.set_nr_muls(resolved_unwrapped.nr_muls() - 1)
855 }
856 }
857 if !var_type.has_flag(.atomic_f) && resolved_unwrapped.has_flag(.atomic_f) {
858 resolved_unwrapped = resolved_unwrapped.clear_flag(.atomic_f)
859 }
860 // Skip when resolved type is a parent sumtype of a smartcast variant.
861 // This happens in match arms where the RHS uses a smartcast variable
862 // (e.g. `mut info := ts.info` inside `match ts.info { Struct { ... } }`).
863 // Resolve aggregate types (from multi-branch match arms)
864 // to the concrete variant type for the current iteration.
865 resolved_sym_ := g.table.sym(resolved_unwrapped)
866 if resolved_sym_.info is ast.Aggregate {
867 resolved_unwrapped = resolved_sym_.info.types[g.aggregate_type_idx]
868 }
869 resolved_sym := g.table.sym(resolved_unwrapped)
870 var_sym := g.table.sym(var_type)
871 is_sumtype_reversal := resolved_sym.kind == .sum_type
872 && resolved_unwrapped != var_type && var_sym.kind != .sum_type
873 // Don't downgrade a specific map/array type (e.g. map[int]SqlExpr)
874 // to the base map/array type. This happens when .clone() etc.
875 // return the base type but the checker already has a specific type.
876 is_base_container_downgrade := (resolved_unwrapped == ast.map_type
877 && var_sym.kind == .map && var_type != ast.map_type)
878 || (resolved_unwrapped == ast.array_type && var_sym.kind == .array
879 && var_type != ast.array_type)
880 // Don't introduce option/result flag when the checker already unwrapped it.
881 // E.g., `x := *var?` where var is `?&int`: checker says x is `int`,
882 // but resolved_expr_type may return `?int` because it sees the option var.
883 is_option_introduction := !var_type.has_option_or_result()
884 && resolved_unwrapped.has_option_or_result()
885 if !is_sumtype_reversal && !is_base_container_downgrade && !is_option_introduction {
886 var_type = resolved_unwrapped
887 val_type = var_type
888 node.left_types[i] = var_type
889 if i < node.right_types.len {
890 node.right_types[i] = val_type
891 }
892 if mut left is ast.Ident {
893 if mut left.obj is ast.Var {
894 left.obj.typ = var_type
895 if !var_type.has_option_or_result() {
896 left.obj.orig_type = ast.no_type
897 left.obj.smartcasts = []
898 left.obj.is_unwrapped = false
899 }
900 if left.obj.ct_type_var != .no_comptime {
901 g.type_resolver.update_ct_type(left.name, var_type)
902 }
903 }
904 if left.scope != unsafe { nil } {
905 if mut scope_var := left.scope.find_var(left.name) {
906 scope_var.typ = var_type
907 if !var_type.has_option_or_result() {
908 scope_var.orig_type = ast.no_type
909 scope_var.smartcasts = []
910 scope_var.is_unwrapped = false
911 }
912 }
913 }
914 }
915 }
916 }
917 }
918 mut cur_indexexpr := -1
919 consider_int_overflow := g.do_int_overflow_checks && g.unwrap_generic(var_type).is_int()
920 consider_int_div_mod := g.table.final_sym(g.unwrap_generic(var_type)).is_int()
921 is_safe_add_assign := node.op == .plus_assign && consider_int_overflow
922 is_safe_sub_assign := node.op == .minus_assign && consider_int_overflow
923 is_safe_mul_assign := node.op == .mult_assign && consider_int_overflow
924 is_safe_div_assign := node.op == .div_assign && consider_int_div_mod
925 is_safe_mod_assign := node.op == .mod_assign && consider_int_div_mod
926 initial_left_sym := g.table.sym(g.unwrap_generic(var_type))
927 is_va_list = initial_left_sym.language == .c && initial_left_sym.name == 'C.va_list'
928 if mut left is ast.Ident {
929 ident = left
930 g.curr_var_name << ident.name
931 // id_info := ident.var_info()
932 // var_type = id_info.typ
933 blank_assign = left.kind == .blank_ident
934 // TODO: temporary, remove this
935 left_info := left.info
936 if left_info is ast.IdentVar {
937 share := left_info.share
938 if share == .shared_t {
939 var_type = var_type.set_flag(.shared_f)
940 }
941 if share == .atomic_t {
942 var_type = var_type.set_flag(.atomic_f)
943 }
944 }
945 if mut left.obj is ast.Var {
946 if is_decl {
947 if val is ast.Ident && val.ct_expr {
948 ctyp := g.unwrap_generic(g.type_resolver.get_type(val))
949 if ctyp != ast.void_type {
950 var_type = ctyp
951 val_type = var_type
952 gen_or = val.or_expr.kind != .absent
953 if gen_or {
954 var_type = val_type.clear_flag(.option)
955 }
956 left.obj.typ = var_type
957 g.type_resolver.update_ct_type(left.name, var_type)
958 }
959 } else if val is ast.ComptimeSelector {
960 if val.typ_key != '' {
961 if is_decl {
962 var_type = g.type_resolver.get_ct_type_or_default(val.typ_key,
963 var_type)
964 val_type = var_type
965 left.obj.typ = var_type
966 g.type_resolver.update_ct_type(left.name, var_type)
967 } else {
968 val_type = g.type_resolver.get_ct_type_or_default(val.typ_key,
969 var_type)
970 }
971 }
972 } else if val is ast.ComptimeCall {
973 var_type = if val.kind in [.zero, .new] {
974 g.comptime_zero_new_result_type(val, var_type)
975 } else {
976 key_str := '${val.method_name}.return_type'
977 g.type_resolver.get_ct_type_or_default(key_str, var_type)
978 }
979 val_type = var_type
980 left.obj.typ = var_type
981 g.type_resolver.update_ct_type(left.name, var_type)
982 g.assign_ct_type[val.pos.pos] = var_type
983 } else if val is ast.Ident && val.info is ast.IdentVar {
984 val_info := val.info as ast.IdentVar
985 gen_or = val.or_expr.kind != .absent
986 if val_info.is_option && gen_or {
987 var_type = val_type.clear_flag(.option)
988 left.obj.typ = var_type
989 }
990 } else if val is ast.DumpExpr {
991 if val.expr is ast.ComptimeSelector {
992 if val.expr.typ_key != '' {
993 var_type = g.type_resolver.get_ct_type_or_default(val.expr.typ_key,
994 var_type)
995 val_type = var_type
996 left.obj.typ = var_type
997 g.type_resolver.update_ct_type(left.name, var_type)
998 }
999 }
1000 } else if val is ast.IndexExpr && (val.left is ast.Ident && val.left.ct_expr) {
1001 ctyp := g.unwrap_generic(g.type_resolver.get_type(val))
1002 if ctyp != ast.void_type {
1003 var_type = ctyp
1004 val_type = var_type
1005 left.obj.typ = var_type
1006 g.type_resolver.update_ct_type(left.name, var_type)
1007 }
1008 } else if left.obj.ct_type_var == .generic_var && val is ast.CallExpr {
1009 if val.return_type_generic != 0 {
1010 mut fn_ret_type := g.resolve_return_type(val).clear_option_and_result()
1011 if fn_ret_type == ast.void_type || fn_ret_type.has_flag(.generic) {
1012 fn_ret_type =
1013 g.unwrap_generic(val.return_type_generic).clear_option_and_result()
1014 }
1015 if fn_ret_type != ast.void_type {
1016 var_type = fn_ret_type
1017 val_type = var_type
1018 left.obj.typ = var_type
1019 g.assign_ct_type[val.pos.pos] = var_type
1020 }
1021 } else if val.is_static_method && val.left_type.has_flag(.generic) {
1022 fn_ret_type := g.resolve_return_type(val)
1023 var_type = fn_ret_type
1024 val_type = var_type
1025 left.obj.typ = var_type
1026 g.assign_ct_type[val.pos.pos] = var_type
1027 } else if val.left_type != 0 && g.table.type_kind(val.left_type) == .array
1028 && val.name == 'map' && val.args.len > 0
1029 && val.args[0].expr is ast.AsCast
1030 && val.args[0].expr.typ.has_flag(.generic) {
1031 var_type =
1032 g.table.find_or_register_array(g.unwrap_generic((val.args[0].expr as ast.AsCast).typ))
1033 val_type = var_type
1034 left.obj.typ = var_type
1035 g.assign_ct_type[val.pos.pos] = var_type
1036 }
1037 } else if val is ast.InfixExpr
1038 && val.op in [.plus, .minus, .mul, .power, .div, .mod] && val.left_ct_expr {
1039 left_ctyp := g.type_resolver.get_type_or_default(val.left, val.left_type)
1040 right_ctyp := g.type_resolver.get_type_or_default(val.right, val.right_type)
1041 ctyp := g.type_resolver.promote_type(g.unwrap_generic(left_ctyp),
1042 g.unwrap_generic(right_ctyp))
1043 if ctyp != ast.void_type {
1044 ct_type_var := g.comptime.get_ct_type_var(val.left)
1045 if ct_type_var in [.key_var, .value_var] {
1046 g.type_resolver.update_ct_type(left.name, g.unwrap_generic(ctyp))
1047 }
1048 var_type = ctyp
1049 val_type = var_type
1050 left.obj.typ = var_type
1051 }
1052 } else if val is ast.PostfixExpr && val.op == .question
1053 && (val.expr is ast.Ident && val.expr.ct_expr) {
1054 ctyp := g.unwrap_generic(g.type_resolver.get_type(val))
1055 if ctyp != ast.void_type {
1056 var_type = ctyp
1057 val_type = var_type
1058 left.obj.typ = var_type
1059
1060 ct_type_var := g.comptime.get_ct_type_var(val.expr)
1061 if ct_type_var == .field_var {
1062 g.type_resolver.update_ct_type(left.name, ctyp)
1063 }
1064 }
1065 } else if var_type.has_flag(.generic) && val is ast.StructInit
1066 && val_type.has_flag(.generic) {
1067 val_type = g.unwrap_generic(val_type)
1068 var_type = val_type
1069 }
1070 }
1071 is_auto_heap = g.resolved_ident_is_auto_heap(left)
1072 // In generic instantiations, is_auto_heap may be set by the
1073 // checker's post_process when concrete types resolve to @[heap]
1074 // structs. Reset when the variable is an option type.
1075 if is_auto_heap && g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0 {
1076 resolved_obj_typ := g.unwrap_generic(left.obj.typ)
1077 if resolved_obj_typ != 0 && resolved_obj_typ.has_flag(.option)
1078 && !resolved_obj_typ.is_ptr() {
1079 is_auto_heap = false
1080 }
1081 }
1082 if left.obj.typ != 0 && val is ast.PrefixExpr {
1083 is_fn_var = g.table.final_sym(left.obj.typ).kind == .function
1084 }
1085 }
1086 } else if mut left is ast.ComptimeSelector {
1087 if left.typ_key != '' {
1088 var_type = g.type_resolver.get_ct_type_or_default(left.typ_key, var_type)
1089 }
1090 if val is ast.ComptimeSelector {
1091 if val.typ_key != '' {
1092 val_type = g.type_resolver.get_ct_type_or_default(val.typ_key, var_type)
1093 }
1094 } else if val is ast.CallExpr && val.return_type_generic.has_flag(.generic) {
1095 g.assign_ct_type[val.pos.pos] = g.comptime.comptime_for_field_type
1096 val_type = var_type
1097 }
1098 } else if mut left is ast.IndexExpr && val is ast.ComptimeSelector {
1099 if val.typ_key != '' {
1100 val_type = g.type_resolver.get_ct_type_or_default(val.typ_key, var_type)
1101 }
1102 }
1103 if is_decl && val is ast.CallExpr && val.or_block.kind != .absent {
1104 mut resolved_call_type := g.resolve_return_type(val)
1105 if resolved_call_type == ast.void_type {
1106 resolved_call_type = val.return_type
1107 }
1108 if g.table.sym(resolved_call_type).kind == .alias {
1109 unaliased_call_type := g.table.unaliased_type(resolved_call_type)
1110 if unaliased_call_type.has_option_or_result() {
1111 resolved_call_type = g.unwrap_generic(unaliased_call_type)
1112 }
1113 }
1114 var_type = resolved_call_type.clear_option_and_result()
1115 val_type = var_type
1116 if mut left is ast.Ident && mut left.obj is ast.Var {
1117 left.obj.typ = var_type
1118 }
1119 }
1120 if is_decl && val is ast.PostfixExpr && val.op == .question {
1121 mut resolved_val_type := g.resolved_expr_type(val.expr, val_type)
1122 if g.table.sym(resolved_val_type).kind == .alias {
1123 unaliased_val_type := g.table.unaliased_type(resolved_val_type)
1124 if unaliased_val_type.has_option_or_result() {
1125 resolved_val_type = g.unwrap_generic(unaliased_val_type)
1126 }
1127 }
1128 resolved_val_type = resolved_val_type.clear_option_and_result()
1129 if resolved_val_type != 0 && resolved_val_type != ast.void_type {
1130 var_type = resolved_val_type
1131 val_type = resolved_val_type
1132 if mut left is ast.Ident && mut left.obj is ast.Var {
1133 left.obj.typ = var_type
1134 }
1135 }
1136 }
1137 if is_decl && val is ast.Ident && val.or_expr.kind != .absent && g.cur_fn != unsafe { nil }
1138 && g.cur_concrete_types.len > 0 && val.or_expr.stmts.len > 0 {
1139 last_or_stmt := val.or_expr.stmts.last()
1140 if last_or_stmt is ast.ExprStmt && last_or_stmt.typ != ast.void_type {
1141 resolved_or_type := g.resolved_expr_type(last_or_stmt.expr, last_or_stmt.typ)
1142 if resolved_or_type != 0 && resolved_or_type != ast.void_type {
1143 var_type = g.unwrap_generic(g.recheck_concrete_type(resolved_or_type))
1144 val_type = var_type
1145 if mut left is ast.Ident && mut left.obj is ast.Var {
1146 left.obj.typ = var_type
1147 }
1148 }
1149 }
1150 }
1151 if is_decl && assign_expr_unwraps_option_or_result(val) {
1152 var_type = var_type.clear_option_and_result()
1153 val_type = val_type.clear_option_and_result()
1154 if mut left is ast.Ident && mut left.obj is ast.Var {
1155 left.obj.typ = var_type
1156 }
1157 }
1158 if is_decl && var_type.has_option_or_result() {
1159 if (val is ast.CallExpr && val.or_block.kind != .absent)
1160 || (val is ast.PostfixExpr && val.op == .question) {
1161 var_type = var_type.clear_option_and_result()
1162 val_type = val_type.clear_option_and_result()
1163 if mut left is ast.Ident && mut left.obj is ast.Var {
1164 left.obj.typ = var_type
1165 }
1166 }
1167 }
1168 if is_decl && mut left is ast.Ident && mut left.obj is ast.Var {
1169 mut should_recompute_decl_type := false
1170 match val {
1171 ast.Ident {
1172 should_recompute_decl_type = val.ct_expr
1173 || (val.obj is ast.Var && val.obj.ct_type_var != .no_comptime)
1174 || (val.obj is ast.Var && (val.obj.typ.has_flag(.generic)
1175 || g.type_has_unresolved_generic_parts(val.obj.typ)))
1176 }
1177 ast.SelectorExpr {
1178 should_recompute_decl_type = val.typ.has_flag(.generic)
1179 || g.type_has_unresolved_generic_parts(val.typ)
1180 || val.expr_type.has_flag(.generic)
1181 || g.type_has_unresolved_generic_parts(val.expr_type)
1182 }
1183 ast.IndexExpr {
1184 should_recompute_decl_type = val.typ.has_flag(.generic)
1185 || g.type_has_unresolved_generic_parts(val.typ)
1186 || val.left_type.has_flag(.generic)
1187 || g.type_has_unresolved_generic_parts(val.left_type)
1188 || (val.left is ast.Ident && val.left.ct_expr)
1189 }
1190 ast.ComptimeSelector {
1191 should_recompute_decl_type = true
1192 }
1193 ast.ComptimeCall {
1194 should_recompute_decl_type = val.kind in [.zero, .new]
1195 }
1196 ast.CastExpr {
1197 should_recompute_decl_type = val.typ.has_flag(.generic)
1198 || g.type_has_unresolved_generic_parts(val.typ)
1199 }
1200 ast.PostfixExpr {
1201 should_recompute_decl_type = val.op == .question
1202 }
1203 ast.PrefixExpr {
1204 should_recompute_decl_type = val.op == .arrow
1205 }
1206 else {}
1207 }
1208
1209 if g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0 {
1210 should_recompute_decl_type = true
1211 }
1212 if val is ast.CallExpr {
1213 should_recompute_decl_type = val.return_type_generic != 0
1214 || val.is_static_method || val.concrete_types.len > 0
1215 || val.raw_concrete_types.len > 0
1216 || (g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0)
1217 || (val.is_method && (val.left_type.has_flag(.generic)
1218 || g.type_has_unresolved_generic_parts(val.left_type)
1219 || val.receiver_type.has_flag(.generic)
1220 || g.type_has_unresolved_generic_parts(val.receiver_type)))
1221 }
1222 mut resolved_val_type := if val is ast.ComptimeCall && val.kind in [.zero, .new] {
1223 g.comptime_zero_new_result_type(val, val_type)
1224 } else if val is ast.Ident && val.or_expr.kind != .absent && g.cur_fn != unsafe { nil }
1225 && g.cur_concrete_types.len > 0 {
1226 or_value_type := g.resolved_or_block_value_type(val.or_expr)
1227 if or_value_type != 0 {
1228 or_value_type
1229 } else {
1230 g.resolved_expr_type(val, val_type)
1231 }
1232 } else {
1233 g.resolved_expr_type(val, val_type)
1234 }
1235 if should_recompute_decl_type && resolved_val_type != 0
1236 && resolved_val_type != ast.void_type {
1237 if assign_expr_unwraps_option_or_result(val) {
1238 resolved_val_type = resolved_val_type.clear_option_and_result()
1239 }
1240 resolved_val_type = g.unwrap_generic(g.recheck_concrete_type(resolved_val_type))
1241 // Resolve aggregate types (from multi-branch match arms)
1242 // to the concrete variant type for the current iteration.
1243 resolved_val_sym2 := g.table.sym(resolved_val_type)
1244 if resolved_val_sym2.info is ast.Aggregate {
1245 resolved_val_type = resolved_val_sym2.info.types[g.aggregate_type_idx]
1246 }
1247 // For SelectorExpr with scope smartcast (e.g. `if w.check != none`),
1248 // the resolved field type has the option flag, but the smartcast
1249 // unwraps it. Clear the option flag in that case.
1250 if val is ast.SelectorExpr && resolved_val_type.has_flag(.option) {
1251 scope := g.file.scope.innermost(val.pos.pos)
1252 field := scope.find_struct_field(val.expr.str(), val.expr_type, val.field_name)
1253 if field != unsafe { nil } && field.smartcasts.len > 0 {
1254 resolved_val_type = resolved_val_type.clear_flag(.option)
1255 }
1256 }
1257 resolved_val_sym := g.table.final_sym(resolved_val_type)
1258 if resolved_val_sym.kind == .array && !resolved_val_type.is_ptr()
1259 && g.table.sym(resolved_val_type).kind != .alias {
1260 mut resolved_elem_type :=
1261 g.unwrap_generic(g.recheck_concrete_type(resolved_val_sym.array_info().elem_type))
1262 if resolved_elem_type == ast.int_literal_type {
1263 resolved_elem_type = ast.int_type
1264 } else if resolved_elem_type == ast.float_literal_type {
1265 resolved_elem_type = ast.f64_type
1266 }
1267 if resolved_elem_type != 0 && !resolved_elem_type.has_flag(.generic)
1268 && !g.type_has_unresolved_generic_parts(resolved_elem_type) {
1269 mut new_arr_type :=
1270 ast.idx_to_type(g.table.find_or_register_array(resolved_elem_type))
1271 // Preserve option/result flags from the original resolved type
1272 if resolved_val_type.has_flag(.option) {
1273 new_arr_type = new_arr_type.set_flag(.option)
1274 }
1275 if resolved_val_type.has_flag(.result) {
1276 new_arr_type = new_arr_type.set_flag(.result)
1277 }
1278 resolved_val_type = new_arr_type
1279 }
1280 }
1281 // When assigning from an auto-deref variable (e.g. mut ref param),
1282 // the resolved type from scope includes the extra pointer level.
1283 // Deref it to match the V value semantics.
1284 if val is ast.Ident && val.is_auto_deref_var() && resolved_val_type.is_ptr()
1285 && !g.auto_deref_source_type_is_pointer(val) {
1286 resolved_val_type = resolved_val_type.deref()
1287 }
1288 // Preserve shared/atomic flags from the original declaration.
1289 if var_type.has_flag(.shared_f) {
1290 resolved_val_type = resolved_val_type.set_flag(.shared_f)
1291 }
1292 if var_type.has_flag(.atomic_f) {
1293 resolved_val_type = resolved_val_type.set_flag(.atomic_f)
1294 }
1295 var_type = resolved_val_type
1296 // val_type represents the RHS expression type, which is NOT shared/atomic.
1297 // Only var_type (the LHS declaration type) should carry those flags.
1298 val_type = resolved_val_type.clear_flag(.shared_f).clear_flag(.atomic_f)
1299 left.obj.typ = var_type
1300 }
1301 }
1302 // Various resolution blocks above may overwrite var_type, stripping the
1303 // shared/atomic flags and nr_muls that the checker originally set.
1304 // Re-apply them based on the left-hand identifier's share attribute,
1305 // which is the authoritative source for whether the declaration is shared.
1306 if is_decl && mut left is ast.Ident {
1307 left_info := left.info
1308 if left_info is ast.IdentVar {
1309 if left_info.share == .shared_t && !var_type.has_flag(.shared_f) {
1310 var_type = var_type.set_flag(.shared_f)
1311 }
1312 if left_info.share == .atomic_t && !var_type.has_flag(.atomic_f) {
1313 var_type = var_type.set_flag(.atomic_f)
1314 }
1315 }
1316 }
1317 if is_decl && var_type.has_flag(.shared_f) && var_type.nr_muls() == 0 {
1318 var_type = var_type.set_nr_muls(1)
1319 }
1320 if is_decl && mut left is ast.Ident && mut left.obj is ast.Var {
1321 left.obj.typ = var_type
1322 if mut scope_var := left.scope.find_var(left.name) {
1323 scope_var.typ = var_type
1324 scope_var.orig_type = ast.no_type
1325 scope_var.smartcasts = []
1326 scope_var.is_unwrapped = false
1327 }
1328 }
1329 if is_decl && val is ast.CallExpr && val.kind == .clone && val.left is ast.IndexExpr {
1330 left_idx := val.left as ast.IndexExpr
1331 if left_idx.index is ast.RangeExpr && g.table.final_sym(var_type).kind == .array {
1332 is_auto_heap = false
1333 }
1334 }
1335 mut styp := g.styp(var_type)
1336 if is_decl && val is ast.CallExpr && val.kind == .clone && val.left is ast.IndexExpr {
1337 left_idx := val.left as ast.IndexExpr
1338 if left_idx.index is ast.RangeExpr && g.table.final_sym(val.return_type).kind == .array {
1339 styp = styp.trim('*')
1340 }
1341 }
1342 mut is_fixed_array_init := false
1343 mut has_val := false
1344 match val {
1345 ast.ArrayInit {
1346 is_fixed_array_init = val.is_fixed
1347 has_val = val.has_val
1348 }
1349 ast.ParExpr {
1350 if val.expr is ast.ArrayInit {
1351 array_init := val.expr as ast.ArrayInit
1352 is_fixed_array_init = array_init.is_fixed
1353 has_val = array_init.has_val
1354 }
1355 }
1356 ast.CallExpr {
1357 is_call = true
1358 if val.comptime_ret_val {
1359 return_type = g.comptime.comptime_for_field_type
1360 styp = g.styp(return_type)
1361 } else {
1362 return_type = val.return_type
1363 }
1364 }
1365 // TODO: no buffer fiddling
1366 ast.AnonFn {
1367 if !var_type.has_option_or_result() {
1368 if blank_assign {
1369 g.write('{')
1370 }
1371 // if it's a decl assign (`:=`) or a blank assignment `_ =`/`_ :=` then generate `void (*ident) (args) =`
1372 if (is_decl || blank_assign) && left is ast.Ident {
1373 sig := g.fn_var_signature(val.typ, val.decl.return_type,
1374 val.decl.params.map(it.typ), ident.name)
1375 g.write(sig + ' = ')
1376 } else {
1377 g.is_assign_lhs = true
1378 g.assign_op = node.op
1379 g.expr(left)
1380 g.is_assign_lhs = false
1381 g.is_arraymap_set = false
1382 if mut left is ast.IndexExpr {
1383 sym := g.table.final_sym(left.left_type)
1384 if sym.kind in [.map, .array] {
1385 g.expr(val)
1386 g.writeln('});')
1387 continue
1388 }
1389 }
1390 g.write(' = ')
1391 }
1392 g.expr(val)
1393 g.writeln(';')
1394 g.gen_self_recursing_anon_fn_capture_patch(left, val)
1395 if blank_assign {
1396 g.write('}')
1397 }
1398 continue
1399 }
1400 }
1401 else {}
1402 }
1403
1404 if g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0 {
1405 orig_var_option := var_type.has_flag(.option)
1406 resolved_left_type := g.resolved_expr_type(left, var_type)
1407 if resolved_left_type != 0 {
1408 var_type = g.unwrap_generic(g.recheck_concrete_type(resolved_left_type))
1409 }
1410 // Preserve the option flag when the variable was option-smartcasted.
1411 // `resolved_expr_type` returns the smartcasted (unwrapped) type for variables
1412 // inside `if x != none` blocks, but the C variable is still the option type
1413 // and needs option wrapping on assignment.
1414 if orig_var_option && !var_type.has_flag(.option) {
1415 var_type = var_type.set_flag(.option)
1416 }
1417 resolved_val_type := g.resolved_expr_type(val, val_type)
1418 if resolved_val_type != 0 {
1419 new_val_type := g.unwrap_generic(g.recheck_concrete_type(resolved_val_type))
1420 // Preserve option/result flag clearing from earlier unwrap
1421 if !val_type.has_flag(.option) && new_val_type.has_flag(.option) {
1422 val_type = new_val_type.clear_option_and_result()
1423 } else if !val_type.has_flag(.result) && new_val_type.has_flag(.result) {
1424 val_type = new_val_type.clear_option_and_result()
1425 } else {
1426 val_type = new_val_type
1427 }
1428 }
1429 }
1430 styp = g.styp(var_type)
1431 if is_decl && val is ast.CallExpr && val.kind == .clone && val.left is ast.IndexExpr {
1432 left_idx := val.left as ast.IndexExpr
1433 if left_idx.index is ast.RangeExpr && g.table.final_sym(val.return_type).kind == .array {
1434 styp = styp.trim('*')
1435 }
1436 }
1437 left_sym := g.table.sym(g.unwrap_generic(var_type))
1438 is_va_list = left_sym.language == .c && left_sym.name == 'C.va_list'
1439 unwrapped_val_type := g.unwrap_generic(val_type)
1440 right_sym := g.table.sym(unwrapped_val_type)
1441 unaliased_right_sym := g.table.final_sym(unwrapped_val_type)
1442 unaliased_left_sym := g.table.final_sym(g.unwrap_generic(var_type))
1443 is_fixed_array_var := unaliased_right_sym.kind == .array_fixed && val !is ast.ArrayInit
1444 && (val in [ast.Ident, ast.IndexExpr, ast.CallExpr, ast.SelectorExpr, ast.ComptimeSelector, ast.DumpExpr, ast.InfixExpr, ast.IfExpr, ast.MatchExpr]
1445 || (val is ast.CastExpr && val.expr !is ast.ArrayInit)
1446 || (val is ast.PrefixExpr && val.op == .arrow)
1447 || (val is ast.UnsafeExpr && val.expr in [ast.SelectorExpr, ast.Ident, ast.CallExpr]))
1448 && !((g.pref.translated || g.file.is_translated)
1449 && unaliased_left_sym.kind != .array_fixed)
1450 g.is_assign_lhs = true
1451 g.assign_op = node.op
1452
1453 g.left_is_opt = var_type.has_option_or_result()
1454 g.right_is_opt = val_type.has_option_or_result()
1455 defer(fn) {
1456 g.left_is_opt = false
1457 g.right_is_opt = false
1458 }
1459 if !is_decl && node.op == .assign && var_type.has_flag(.option_mut_param_t) {
1460 // For `mut ?T` parameters / for-loop variables, the C type is
1461 // `_option_T*` (pointer to option). Write through the pointer
1462 // by copying the entire option struct (state + data).
1463 mut target_option_type := g.resolve_current_fn_generic_param_type(left.str())
1464 if target_option_type == 0 || !target_option_type.has_flag(.option) {
1465 target_option_type = var_type.clear_flag(.option_mut_param_t)
1466 }
1467 if target_option_type.has_flag(.option) {
1468 target_inner_type := target_option_type.clear_option_and_result()
1469 if target_inner_type.is_ptr() {
1470 target_option_type = target_inner_type.deref().set_flag(.option)
1471 }
1472 }
1473 target_option_type = g.unwrap_generic(g.recheck_concrete_type(target_option_type))
1474 tmp_var := g.new_tmp_var()
1475 g.expr_with_tmp_var(val, val_type, target_option_type, tmp_var, false)
1476 left_name := c_name(left.str())
1477 is_fn_param := left is ast.Ident && left.is_auto_deref_var()
1478 if is_fn_param {
1479 // Function params use _option_T* type, copy data through pointer
1480 val_base_type := g.base_type(val_type)
1481 g.writeln('${left_name}->state = ${tmp_var}.state;')
1482 g.writeln('memcpy(&${left_name}->data, ${tmp_var}.data, sizeof(${val_base_type}));')
1483 } else {
1484 // For-loop variables use _option_T* type, copy whole struct
1485 g.writeln('*${left_name} = ${tmp_var};')
1486 }
1487 continue
1488 }
1489
1490 if blank_assign {
1491 if val is ast.IndexExpr {
1492 g.assign_op = .decl_assign
1493 }
1494 g.is_assign_lhs = false
1495 if is_call {
1496 old_is_void_expr_stmt := g.is_void_expr_stmt
1497 g.is_void_expr_stmt = true
1498 g.expr(val)
1499 g.is_void_expr_stmt = old_is_void_expr_stmt
1500 } else if g.inside_for_c_stmt {
1501 g.expr(val)
1502 } else if var_type.has_flag(.option) {
1503 // Blank ident option types can be stale across generic instantiations.
1504 // Use the actual RHS option type when it is available so `_ = val`
1505 // does not rewrap the expression into a mismatched option payload.
1506 blank_option_type := if val_type.has_flag(.option) { val_type } else { var_type }
1507 g.expr_with_opt(val, val_type, blank_option_type)
1508 } else {
1509 if left_sym.kind == .function {
1510 g.write('{void* _ = ')
1511 } else {
1512 // For blank idents, use val_type to determine the C type
1513 // instead of var_type (styp), because in generic functions
1514 // the checker's left_types[i] for blank idents can be
1515 // overwritten by a later generic instantiation.
1516 mut blank_styp := g.styp(val_type)
1517 if val is ast.Ident && val.is_auto_deref_var()
1518 && !g.auto_deref_source_type_is_pointer(val) {
1519 blank_styp = '${blank_styp}*'
1520 }
1521 if blank_styp.ends_with('*') {
1522 blank_styp = 'void*'
1523 }
1524 g.write('{${blank_styp} _ = ')
1525 }
1526 if (val in [ast.MatchExpr, ast.IfExpr, ast.ComptimeSelector] || is_fixed_array_var)
1527 && unaliased_right_sym.info is ast.ArrayFixed {
1528 tmp_var := g.expr_with_var(val, var_type, false)
1529 // When the temp var is a return wrapper struct (_v_Array_fixed_...),
1530 // access .ret_arr to get the actual C array for subscripting.
1531 init_expr := if unaliased_right_sym.info.is_fn_ret {
1532 '${tmp_var}.ret_arr'
1533 } else {
1534 tmp_var
1535 }
1536 g.fixed_array_var_init(init_expr, false, unaliased_right_sym.info.elem_type,
1537 unaliased_right_sym.info.size)
1538 } else {
1539 g.expr(val)
1540 }
1541 g.writeln(';}')
1542 }
1543 } else if node.op == .assign && (is_fixed_array_init || is_fixed_array_var
1544 || (unaliased_right_sym.kind == .array_fixed && val is ast.CastExpr)) {
1545 // Fixed arrays
1546 if unaliased_left_sym.kind != .array_fixed && unaliased_right_sym.kind == .array_fixed
1547 && (g.pref.translated || g.file.is_translated) {
1548 // translated:
1549 // arr = [5]u8{}
1550 // ptr = arr => ptr = &arr[0]
1551 g.expr(left)
1552 g.write(' = ')
1553 g.expr(val)
1554 } else if is_fixed_array_init && var_type.has_flag(.option) {
1555 g.expr(left)
1556 g.write(' = ')
1557 g.expr_with_opt(val, val_type, var_type)
1558 } else if unaliased_right_sym.kind == .array_fixed && val is ast.CastExpr {
1559 if var_type.has_flag(.option) {
1560 g.expr(left)
1561 g.writeln('.state = 0;')
1562 g.write('memcpy(')
1563 g.expr(left)
1564 g.write('.data, ')
1565 g.expr(val)
1566 g.writeln(', sizeof(${g.styp(var_type.clear_flag(.option))}));')
1567 } else {
1568 g.write('memcpy(')
1569 g.expr(left)
1570 g.write(', ')
1571 g.expr(val)
1572 g.writeln(', sizeof(${g.styp(var_type)}));')
1573 }
1574 } else {
1575 arr_typ := styp.trim('*')
1576 old_is_assign_lhs := g.is_assign_lhs
1577 // For map IndexExpr LHS, keep is_assign_lhs = true so the index
1578 // generator emits `map_get_and_set` (which inserts missing keys)
1579 // instead of `map_get` (which returns a zero-default buffer).
1580 left_is_map_index := left is ast.IndexExpr
1581 && g.table.final_sym(left.left_type).kind == .map
1582 g.is_assign_lhs = left_is_map_index
1583 left_expr := g.expr_string(left)
1584 if !is_fixed_array_init && assign_expr_unwraps_option_or_result(val) {
1585 // val has an or-block — its code generator emits unwrap
1586 // statements via go_before_last_stmt(), so we must emit
1587 // memcpy() inline. Capturing val with expr_string() would
1588 // slurp those statements into the memcpy() argument list.
1589 g.write('memcpy(${left_expr}, ')
1590 g.expr(val)
1591 g.writeln(', sizeof(${arr_typ}));')
1592 } else {
1593 mut fixed_right_expr := ''
1594 if is_fixed_array_init {
1595 right := val as ast.ArrayInit
1596 right_var := g.new_tmp_var()
1597 g.write('${arr_typ} ${right_var} = ')
1598 g.expr(right)
1599 g.writeln(';')
1600 fixed_right_expr = right_var
1601 } else {
1602 fixed_right_expr = g.expr_string(val)
1603 }
1604 g.writeln('')
1605 g.writeln('memcpy(${left_expr}, ${fixed_right_expr}, sizeof(${arr_typ}));')
1606 }
1607 g.is_assign_lhs = old_is_assign_lhs
1608 g.is_assign_lhs = false
1609 }
1610 } else {
1611 is_inside_ternary := g.inside_ternary != 0
1612 cur_line := if is_inside_ternary && is_decl {
1613 g.register_ternary_name(ident.name)
1614 g.empty_line = false
1615 g.go_before_ternary()
1616 } else {
1617 ''
1618 }
1619 if mut left is ast.IndexExpr && left.is_index_operator {
1620 g.write(cur_line)
1621 if node.op == .assign {
1622 g.index_operator_call(left.left, left.left_type, left.index, left.index_type,
1623 '[]=', val, val_type)
1624 } else {
1625 infix_op := token.assign_op_to_infix_op(node.op)
1626 op_expr := ast.InfixExpr{
1627 left: ast.Expr(left)
1628 right: val
1629 op: infix_op
1630 pos: node.pos
1631 left_type: left.typ
1632 right_type: val_type
1633 promoted_type: g.type_resolver.promote_type(left.typ, val_type)
1634 }
1635 g.index_operator_call(left.left, left.left_type, left.index, left.index_type,
1636 '[]=', ast.Expr(op_expr), left.typ)
1637 }
1638 if !g.inside_for_c_stmt {
1639 g.writeln(';')
1640 }
1641 continue
1642 }
1643 mut str_add := false
1644 mut op_overloaded := false
1645 mut op_expected_left := ast.no_type
1646 mut op_expected_right := ast.no_type
1647 is_shared_re_assign := !is_decl && node.left_types[i].has_flag(.shared_f)
1648 && left is ast.Ident && left_sym.kind in [.array, .map, .struct]
1649 mut is_mut_arg_pointer_rebind := false
1650 // Keep pointer traversal assignments on auto-deref vars as local pointer rebinds,
1651 // but only for function arguments (is_arg), not for loop iteration variables.
1652 if !is_decl && !is_shared_re_assign && !var_type.has_flag(.option)
1653 && var_type.is_any_kind_of_pointer() && unwrapped_val_type.is_any_kind_of_pointer()
1654 && left.is_auto_deref_var() && !val.is_auto_deref_var() && node.op == .assign
1655 && left.is_auto_deref_arg() {
1656 is_mut_arg_pointer_rebind = true
1657 }
1658 if node.op == .plus_assign && g.is_string_type(var_type)
1659 && g.is_string_concat_type(val_type) {
1660 if g.is_autofree && !g.is_builtin_mod && !g.is_autofree_tmp
1661 && (!g.is_string_type(val_type)
1662 || val !in [ast.Ident, ast.StringLiteral, ast.SelectorExpr, ast.ComptimeSelector]) {
1663 str_add_rhs_tmp = '_str_add_rhs_${node.pos.pos}_${i}'
1664 if g.is_string_type(val_type) {
1665 g.writeln(g.autofree_tmp_arg_init_stmt('string ${str_add_rhs_tmp} = ', val))
1666 } else {
1667 g.writeln('string ${str_add_rhs_tmp} = ${g.expr_string_with_cast(val,
1668 val_type, ast.string_type)};')
1669 val_type = ast.string_type
1670 }
1671 val = ast.Expr(ast.Ident{
1672 mod: g.cur_mod.name
1673 name: str_add_rhs_tmp
1674 })
1675 str_add_rhs_needs_free = true
1676 skip_str_add_rhs_clone = true
1677 defer(fn) {
1678 if str_add_rhs_needs_free {
1679 g.writeln('builtin__string_free(&${str_add_rhs_tmp});')
1680 }
1681 }
1682 }
1683 if mut left is ast.IndexExpr {
1684 if g.table.sym(left.left_type).kind == .array_fixed {
1685 // strs[0] += str2 => `strs[0] = _string__plus(strs[0], str2)`
1686 g.expr(left)
1687 g.write(' = builtin__string__plus(')
1688 } else {
1689 // a[0] += str => `builtin__array_set(&a, 0, &(string[]) {_string__plus(...))})`
1690 g.expr(left)
1691 g.write('builtin__string__plus(')
1692 }
1693 } else {
1694 // allow literal values to auto deref var (e.g.`for mut v in values { v += 1.0 }`)
1695 if left.is_auto_deref_var() {
1696 g.write('*')
1697 }
1698 // str += str2 => `str = builtin__string__plus(str, str2)`
1699 g.expr(left)
1700 g.write(' = builtin__string__plus(')
1701 }
1702 g.is_assign_lhs = false
1703 str_add = true
1704 }
1705 // Assignment Operator Overloading
1706 has_power_assign_overload := node.op == .power_assign
1707 && left_sym.find_method_with_generic_parent('**') != none
1708 if (((left_sym.kind == .struct && right_sym.kind == .struct)
1709 || (left_sym.kind == .alias && right_sym.kind == .alias))
1710 && node.op in [.plus_assign, .minus_assign, .div_assign, .mult_assign, .power_assign, .mod_assign])
1711 || has_power_assign_overload {
1712 extracted_op := match node.op {
1713 .plus_assign { '+' }
1714 .minus_assign { '-' }
1715 .div_assign { '/' }
1716 .mod_assign { '%' }
1717 .mult_assign { '*' }
1718 .power_assign { '**' }
1719 else { 'unknown op' }
1720 }
1721
1722 pos := g.out.len
1723 g.expr(left)
1724 struct_info := g.table.final_sym(var_type)
1725 if left_sym.info is ast.Struct && left_sym.info.generic_types.len > 0 {
1726 concrete_types := left_sym.info.concrete_types
1727 mut method_name := left_sym.cname + '_' + util.replace_op(extracted_op)
1728 specialized_suffix := g.generic_fn_name(concrete_types, '')
1729 if specialized_suffix != '' && !method_name.ends_with(specialized_suffix) {
1730 method_name = g.generic_fn_name(concrete_types, method_name)
1731 }
1732 g.write(' = ${method_name}(')
1733 g.expr(left)
1734 g.write(', ')
1735 g.expr(val)
1736 g.writeln(');')
1737 return
1738 } else if left_sym.kind == .alias
1739 && g.table.final_sym(g.unwrap_generic(var_type)).is_number()
1740 && !left_sym.has_method(extracted_op) {
1741 g.write(' = ')
1742 if node.op == .power_assign {
1743 g.gen_power_assign_expr(left, var_type, val, val_type)
1744 } else {
1745 g.expr(left)
1746 g.write(' ${extracted_op} ')
1747 g.expr(val)
1748 }
1749 if !g.inside_for_c_stmt {
1750 g.write(';')
1751 }
1752 return
1753 } else if left_sym.kind == .alias && struct_info.kind == .struct
1754 && struct_info.info is ast.Struct && struct_info.info.generic_types.len > 0 {
1755 mut method_name := struct_info.cname + '_' + util.replace_op(extracted_op)
1756 specialized_suffix := g.generic_fn_name(struct_info.info.concrete_types, '')
1757 if specialized_suffix != '' && !method_name.ends_with(specialized_suffix) {
1758 method_name = g.generic_fn_name(struct_info.info.concrete_types,
1759 method_name)
1760 }
1761 g.write(' = ${method_name}(')
1762 g.expr(left)
1763 g.write(', ')
1764 g.expr(val)
1765 g.writeln(');')
1766 return
1767 } else {
1768 if g.table.final_sym(g.unwrap_generic(var_type)).kind == .array_fixed {
1769 g.go_back_to(pos)
1770 g.empty_line = true
1771 g.write('memcpy(')
1772 g.expr(left)
1773 g.write(', ${styp}_${util.replace_op(extracted_op)}(')
1774 } else {
1775 g.write(' = ${styp}_${util.replace_op(extracted_op)}(')
1776 }
1777 method := g.table.find_method(left_sym, extracted_op) or {
1778 // the checker will most likely have found this, already...
1779 g.error('assignment operator `${extracted_op}=` used but no `${extracted_op}` method defined',
1780 node.pos)
1781 ast.Fn{}
1782 }
1783 op_expected_left = method.params[0].typ
1784 op_expected_right = method.params[1].typ
1785 op_overloaded = true
1786 }
1787 }
1788 final_left_sym := g.table.final_sym(g.unwrap_generic(var_type))
1789 final_right_sym := g.table.final_sym(unwrapped_val_type)
1790 mut aligned := 0
1791 is_safe_shift_assign := !g.pref.translated && !g.file.is_translated
1792 && node.op in [.left_shift_assign, .right_shift_assign] && final_left_sym.is_int()
1793 safe_shift_fn_name := if is_safe_shift_assign {
1794 g.safe_shift_fn_name(var_type, token.assign_op_to_infix_op(node.op))
1795 } else {
1796 ''
1797 }
1798 if final_left_sym.info is ast.Struct {
1799 if attr := final_left_sym.info.attrs.find_first('aligned') {
1800 aligned = if attr.arg == '' { 0 } else { attr.arg.int() }
1801 }
1802 }
1803
1804 if final_left_sym.kind == .bool && final_right_sym.kind == .bool
1805 && node.op in [.boolean_or_assign, .boolean_and_assign] {
1806 extracted_op := match node.op {
1807 .boolean_or_assign {
1808 '||'
1809 }
1810 .boolean_and_assign {
1811 '&&'
1812 }
1813 else {
1814 'unknown op'
1815 }
1816 }
1817
1818 g.expr(left)
1819 g.write(' = ')
1820 g.expr(left)
1821 g.write(' ${extracted_op} ')
1822 g.expr(val)
1823 g.writeln(';')
1824 return
1825 }
1826 if node.op == .power_assign && !op_overloaded {
1827 g.write_assign_target_expr(left, var_type)
1828 g.write(' = ')
1829 g.gen_power_assign_expr(left, var_type, val, val_type)
1830 g.writeln(';')
1831 return
1832 }
1833 if right_sym.info is ast.FnType && is_decl {
1834 if is_inside_ternary {
1835 g.out.write_string(util.tabs(g.indent - g.inside_ternary))
1836 }
1837 fn_name := c_fn_name(g.get_ternary_name(ident.name))
1838
1839 if val_type.has_flag(.option) {
1840 ret_styp := g.styp(g.unwrap_generic(val_type))
1841 g.write('${ret_styp} ${fn_name}')
1842 } else {
1843 g.write_fntype_decl(fn_name, right_sym.info, var_type.nr_muls())
1844 }
1845 } else {
1846 if is_decl {
1847 if is_inside_ternary {
1848 g.out.write_string(util.tabs(g.indent - g.inside_ternary))
1849 }
1850 mut is_used_var_styp := false
1851 if ident.name !in g.defer_vars {
1852 val_sym := g.table.sym(val_type)
1853 if val_sym.info is ast.Struct && val_sym.info.generic_types.len > 0 {
1854 if val is ast.StructInit {
1855 var_styp := g.styp(val.typ)
1856 if var_type.has_flag(.shared_f) {
1857 g.write('__shared__${var_styp}* ')
1858 } else {
1859 g.write('${var_styp} ')
1860 }
1861 is_used_var_styp = true
1862 } else if val is ast.PrefixExpr {
1863 if val.op == .amp && val.right is ast.StructInit {
1864 var_styp := g.styp(val.right.typ.ref())
1865 if var_type.has_flag(.shared_f) {
1866 g.write('__shared__')
1867 }
1868 g.write('${var_styp} ')
1869 is_used_var_styp = true
1870 }
1871 }
1872 }
1873 if !is_used_var_styp {
1874 if !val_type.has_flag(.option) && left_sym.is_array_fixed() {
1875 if left_sym.info is ast.Alias {
1876 parent_sym := g.table.final_sym(left_sym.info.parent_type)
1877 styp = g.styp(left_sym.info.parent_type)
1878 if !parent_sym.is_array_fixed_ret() {
1879 g.write('${styp} ')
1880 } else {
1881 g.write('${styp[3..]} ')
1882 }
1883 } else {
1884 if !left_sym.is_array_fixed_ret() {
1885 g.write('${styp} ')
1886 } else {
1887 g.write('${styp[3..]} ')
1888 }
1889 }
1890 } else {
1891 g.write('${styp} ')
1892 }
1893 }
1894 if is_auto_heap && !(val_type.is_ptr() && val_type.has_flag(.option)) {
1895 g.write('*')
1896 }
1897 }
1898 }
1899 if left in [ast.Ident, ast.SelectorExpr] && !g.is_smartcast_assign_lhs(left) {
1900 g.prevent_sum_type_unwrapping_once = true
1901 }
1902 if !is_fixed_array_var || is_decl || is_shared_re_assign {
1903 if op_overloaded {
1904 g.op_arg(left, op_expected_left, var_type)
1905 } else {
1906 if !is_decl && !is_shared_re_assign
1907 && (left.is_auto_deref_var() || is_auto_heap)
1908 && !is_mut_arg_pointer_rebind
1909 && (!var_type.has_flag(.option) || is_auto_heap) {
1910 g.write('*')
1911 }
1912 if var_type.has_flag(.option_mut_param_t) {
1913 g.expr(left)
1914 g.write(' = ')
1915 } else {
1916 g.expr(left)
1917 }
1918 if !is_decl && var_type.has_flag(.shared_f) {
1919 g.write('->val') // don't reset the mutex, just change the value
1920 }
1921 }
1922 }
1923 }
1924 if is_inside_ternary && is_decl {
1925 g.write(';\n${cur_line}')
1926 g.out.write_string(util.tabs(g.indent))
1927 g.expr(left)
1928 }
1929 if is_decl && node.is_static
1930 && g.gen_static_decl_runtime_init(node, left, var_type, val, val_type) {
1931 continue
1932 }
1933 g.is_assign_lhs = false
1934 left_expr := left
1935 if left_expr is ast.IndexExpr && g.cur_indexexpr.len > 0 {
1936 cur_indexexpr = g.cur_indexexpr.len - 1
1937 }
1938 if is_fixed_array_var || is_va_list {
1939 if is_decl {
1940 g.writeln(';')
1941 if is_va_list {
1942 continue
1943 }
1944 }
1945 } else if !var_type.has_flag(.option_mut_param_t) && cur_indexexpr == -1 && !str_add
1946 && !op_overloaded && !is_safe_add_assign && !is_safe_sub_assign
1947 && !is_safe_mul_assign && !is_safe_div_assign && !is_safe_mod_assign
1948 && !is_safe_shift_assign {
1949 g.write(' ${op} ')
1950 } else if (str_add || op_overloaded) && !is_safe_add_assign && !is_safe_sub_assign
1951 && !is_safe_mul_assign && !is_safe_div_assign && !is_safe_mod_assign {
1952 g.write(', ')
1953 } else if is_safe_shift_assign {
1954 g.write(' = ${safe_shift_fn_name}(')
1955 g.expr(left)
1956 g.write(', (u64)')
1957 } else if is_safe_add_assign || is_safe_sub_assign || is_safe_mul_assign
1958 || is_safe_div_assign || is_safe_mod_assign {
1959 overflow_styp := g.styp(get_overflow_fn_type(var_type))
1960 div_mod_styp :=
1961 g.styp(g.unwrap_generic(var_type).clear_flag(.shared_f).clear_flag(.atomic_f))
1962 vsafe_fn_name := match true {
1963 is_safe_add_assign { 'builtin__overflow__add_${overflow_styp}' }
1964 is_safe_sub_assign { 'builtin__overflow__sub_${overflow_styp}' }
1965 is_safe_mul_assign { 'builtin__overflow__mul_${overflow_styp}' }
1966 is_safe_div_assign { 'VSAFE_DIV_${div_mod_styp}' }
1967 is_safe_mod_assign { 'VSAFE_MOD_${div_mod_styp}' }
1968 else { '' }
1969 }
1970
1971 if is_safe_div_assign || is_safe_mod_assign {
1972 g.vsafe_arithmetic_ops[vsafe_fn_name] = VSafeArithmeticOp{
1973 typ: g.unwrap_generic(var_type).clear_flag(.shared_f).clear_flag(.atomic_f)
1974 op: token.assign_op_to_infix_op(node.op)
1975 }
1976 }
1977 g.write(' = ${vsafe_fn_name}(')
1978 g.expr(left)
1979 g.write(',')
1980 }
1981 mut decl_tmp_var := ''
1982 mut decl_stmt_str := ''
1983 if is_decl && g.inside_ternary == 0 && !node.is_static && !var_type.has_flag(.shared_f)
1984 && g.decl_assign_struct_init_needs_tmp(val) {
1985 decl_stmt_str = g.go_before_last_stmt().trim_space()
1986 g.empty_line = true
1987 decl_tmp_var = g.new_tmp_var()
1988 g.write('${styp} ')
1989 if is_auto_heap && !(val_type.is_ptr() && val_type.has_flag(.option)) {
1990 g.write('*')
1991 }
1992 g.write('${decl_tmp_var} ${op} ')
1993 }
1994 mut cloned := false
1995 if g.is_autofree {
1996 if right_sym.kind in [.array, .string] && !unwrapped_val_type.has_flag(.shared_f) {
1997 if !skip_str_add_rhs_clone
1998 && g.gen_clone_assignment(var_type, val, unwrapped_val_type, false) {
1999 cloned = true
2000 }
2001 } else if right_sym.info is ast.Interface && var_type != ast.error_type {
2002 g.register_free_method(var_type)
2003 }
2004 }
2005 if !cloned {
2006 if g.comptime.comptime_for_field_var == ''
2007 && ((var_type.has_flag(.option) && !val_type.has_flag(.option))
2008 || (var_type.has_flag(.result) && !val_type.has_flag(.result))) {
2009 old_inside_opt_or_res := g.inside_opt_or_res
2010 defer(fn) {
2011 g.inside_opt_or_res = old_inside_opt_or_res
2012 }
2013 g.inside_opt_or_res = true
2014 if is_decl && is_auto_heap && var_type.has_flag(.option) {
2015 g.write('&')
2016 }
2017 tmp_var := g.new_tmp_var()
2018 g.expr_with_tmp_var(val, val_type, var_type, tmp_var, true)
2019 } else if is_fixed_array_var {
2020 // TODO: Instead of the translated check, check if it's a pointer already
2021 // and don't generate memcpy &
2022 typ_str := g.styp(val_type).trim('*')
2023 final_typ_str := if is_fixed_array_var { '' } else { '(${typ_str}*)' }
2024 final_ref_str := if is_fixed_array_var {
2025 ''
2026 } else if val_type.is_ptr() {
2027 '(byte*)'
2028 } else {
2029 '(byte*)&'
2030 }
2031 if val_type.has_flag(.option) {
2032 g.expr(left)
2033 g.write(' = ')
2034 g.expr(val)
2035 } else {
2036 if op_overloaded {
2037 g.expr(left)
2038 g.write(', ')
2039 g.expr(val)
2040 g.write(').ret_arr, sizeof(${typ_str})')
2041 } else {
2042 g.write('memcpy(${final_typ_str}')
2043 g.expr(left)
2044 g.write(', ${final_ref_str}')
2045 g.expr(val)
2046 g.write(', sizeof(${typ_str}))')
2047 }
2048 }
2049 } else if is_decl {
2050 g.is_shared = var_type.has_flag(.shared_f)
2051 if is_fixed_array_init && !has_val {
2052 if val is ast.ArrayInit {
2053 g.array_init(val, g.ident_cname(ident))
2054 } else {
2055 g.write('{0}')
2056 }
2057 } else {
2058 is_option_unwrapped := assign_expr_unwraps_option_or_result(val)
2059 is_option_auto_heap := is_auto_heap && is_option_unwrapped
2060 auto_heap_uses_existing_storage := is_auto_heap && !is_fn_var
2061 && !is_option_auto_heap
2062 && g.auto_heap_assignment_uses_existing_storage(val, val_type)
2063 // For large structs (with large fixed arrays), avoid stack-allocated
2064 // compound literals which can cause stack overflow. Use vcalloc directly.
2065 mut is_large_struct_heap := false
2066 if is_auto_heap && !is_fn_var && val is ast.StructInit
2067 && g.struct_has_large_fixed_array(val.typ) {
2068 is_large_struct_heap = true
2069 }
2070 if is_auto_heap && !is_fn_var && !is_large_struct_heap
2071 && !auto_heap_uses_existing_storage {
2072 if aligned != 0 {
2073 g.write('HEAP_align(${styp}, (')
2074 } else {
2075 ptrmap_o, _ := g.vgc_ptrmap(var_type.set_nr_muls(0))
2076 if ptrmap_o.len > 0 {
2077 g.write('HEAP_vgc(${styp}, (')
2078 } else {
2079 g.write('HEAP(${styp}, (')
2080 }
2081 }
2082 }
2083 if !is_fn_var && val.is_auto_deref_var() && !is_option_unwrapped
2084 && !g.auto_deref_source_type_is_pointer(val) {
2085 g.write('*')
2086 }
2087 if (var_type.has_flag(.option) && val !in [ast.Ident, ast.SelectorExpr])
2088 || gen_or {
2089 g.expr_with_opt_or_block(val, val_type, left, var_type,
2090 is_option_auto_heap)
2091 } else if val is ast.ArrayInit {
2092 cvar_name := g.ident_cname(ident)
2093 if val.is_fixed && ident.name in g.defer_vars {
2094 g.go_before_last_stmt()
2095 g.empty_line = true
2096 g.write('memcpy(${cvar_name}, ')
2097 g.write('(${styp})')
2098 g.array_init(val, cvar_name)
2099 g.write(', sizeof(${styp}))')
2100 } else {
2101 g.array_init(val, cvar_name)
2102 }
2103 } else if val is ast.ParExpr && val.expr is ast.ArrayInit {
2104 array_init := val.expr as ast.ArrayInit
2105 cvar_name := g.ident_cname(ident)
2106 if array_init.is_fixed && ident.name in g.defer_vars {
2107 g.go_before_last_stmt()
2108 g.empty_line = true
2109 g.write('memcpy(${cvar_name}, ')
2110 g.write('(${styp})')
2111 g.array_init(array_init, cvar_name)
2112 g.write(', sizeof(${styp}))')
2113 } else {
2114 g.array_init(array_init, cvar_name)
2115 }
2116 } else if var_type.has_flag(.shared_f) && !val_type.has_flag(.shared_f)
2117 && !val_type.is_ptr() {
2118 g.expr_with_cast(val, val_type, var_type)
2119 } else if val_type.has_flag(.shared_f) || orig_val_shared {
2120 g.expr_with_cast(val, val_type.set_flag(.shared_f), var_type)
2121 } else if val in [ast.MatchExpr, ast.IfExpr]
2122 && unaliased_right_sym.info is ast.ArrayFixed {
2123 tmp_var := g.expr_with_var(val, var_type, false)
2124 // When the temp var is a return wrapper struct (_v_Array_fixed_...),
2125 // access .ret_arr to get the actual C array for subscripting.
2126 init_expr := if unaliased_right_sym.info.is_fn_ret {
2127 '${tmp_var}.ret_arr'
2128 } else {
2129 tmp_var
2130 }
2131 g.fixed_array_var_init(init_expr, false,
2132 unaliased_right_sym.info.elem_type, unaliased_right_sym.info.size)
2133 } else if is_large_struct_heap && val is ast.StructInit {
2134 // For large structs, use vcalloc directly to avoid stack overflow
2135 // from compound literals on the stack
2136 tmp_var := g.new_tmp_var()
2137 stmt_str := g.go_before_last_stmt()
2138 g.empty_line = true
2139 g.writeln('${styp}* ${tmp_var} = (${styp}*)builtin__vcalloc(sizeof(${styp}));')
2140 // Initialize non-zero fields
2141 val_sym := g.table.final_sym(val.typ)
2142 if val_sym.info is ast.Struct {
2143 for init_field in val.init_fields {
2144 if init_field.typ == 0 {
2145 continue
2146 }
2147 field_name := c_name(init_field.name)
2148 g.write('${tmp_var}->${field_name} = ')
2149 g.expr(init_field.expr)
2150 g.writeln(';')
2151 }
2152 // Handle fields with default values
2153 for field in val_sym.info.fields {
2154 mut found := false
2155 for init_field in val.init_fields {
2156 if init_field.name == field.name {
2157 found = true
2158 break
2159 }
2160 }
2161 if !found && field.has_default_expr {
2162 field_name := c_name(field.name)
2163 g.write('${tmp_var}->${field_name} = ')
2164 g.expr(field.default_expr)
2165 g.writeln(';')
2166 }
2167 }
2168 }
2169 g.empty_line = false
2170 g.write2(stmt_str, tmp_var)
2171 } else {
2172 old_inside_assign_fn_var := g.inside_assign_fn_var
2173 g.inside_assign_fn_var = val is ast.PrefixExpr && val.op == .amp
2174 && is_fn_var
2175 mut nval := val
2176 mut nval_handled := false
2177 if val is ast.PrefixExpr && val.right is ast.CallExpr {
2178 call_expr := val.right as ast.CallExpr
2179 if call_expr.name == 'new_array_from_c_array' {
2180 nval = call_expr
2181 nval_handled = true
2182 if !var_type.has_flag(.shared_f) {
2183 g.write('HEAP(${g.styp(var_type.clear_ref())}, ')
2184 }
2185 g.expr(nval)
2186 if !var_type.has_flag(.shared_f) {
2187 g.write(')')
2188 }
2189 }
2190 }
2191 if !nval_handled {
2192 if auto_heap_uses_existing_storage {
2193 g.write_auto_heap_assignment_expr(nval, val_type)
2194 } else {
2195 g.expr_in_value_context(nval, val_type, var_type)
2196 }
2197 if !is_fn_var && is_auto_heap && is_option_auto_heap
2198 && !is_large_struct_heap {
2199 if aligned != 0 {
2200 g.write('), ${aligned})')
2201 } else {
2202 g.write('))')
2203 }
2204 }
2205 }
2206 g.inside_assign_fn_var = old_inside_assign_fn_var
2207 }
2208 if !is_fn_var && is_auto_heap && !is_option_auto_heap
2209 && !auto_heap_uses_existing_storage && !is_large_struct_heap {
2210 if aligned != 0 {
2211 g.write('), ${aligned})')
2212 } else {
2213 ptrmap, nptrs := g.vgc_ptrmap(var_type.set_nr_muls(0))
2214 if ptrmap.len > 0 {
2215 g.write('), ${ptrmap}, ${nptrs})')
2216 } else {
2217 g.write('))')
2218 }
2219 }
2220 }
2221 }
2222 } else {
2223 // var = &auto_heap_var
2224 old_is_auto_heap := g.is_option_auto_heap
2225 defer(fn) {
2226 g.is_option_auto_heap = old_is_auto_heap
2227 }
2228 if val is ast.Ident && val.is_mut() && var_type.is_ptr() {
2229 if var_type.nr_muls() < val_type.nr_muls() {
2230 g.write('*'.repeat(var_type.nr_muls()))
2231 }
2232 }
2233 g.is_option_auto_heap = val_type.has_flag(.option) && val is ast.PrefixExpr
2234 && val.right is ast.Ident && (val.right as ast.Ident).is_auto_heap()
2235 if var_type.has_flag(.option) || gen_or {
2236 g.expr_with_opt_or_block(val, val_type, left, var_type, false)
2237 } else if node.has_cross_var {
2238 g.gen_cross_tmp_variable(node.left, val)
2239 } else if val is ast.None {
2240 if var_type.has_flag(.generic)
2241 && g.unwrap_generic(var_type).has_flag(.option)
2242 && var_type.nr_muls() > 0 {
2243 g.gen_option_error(var_type.set_nr_muls(0), ast.None{})
2244 } else {
2245 g.gen_option_error(var_type, ast.None{})
2246 }
2247 } else {
2248 if op_overloaded {
2249 g.op_arg(val, op_expected_right, val_type)
2250 } else {
2251 exp_type := if is_mut_arg_pointer_rebind {
2252 var_type
2253 } else if var_type.is_ptr()
2254 && (left.is_auto_deref_var() || var_type.has_flag(.shared_f)) {
2255 var_type.deref()
2256 } else {
2257 var_type
2258 }.clear_flag(.shared_f) // don't reset the mutex, just change the value
2259 mut use_heap_pointed_ident := false
2260 mut use_raw_auto_heap_ident := false
2261 if val is ast.PrefixExpr && val.op == .amp && val.right is ast.Ident {
2262 right_ident := val.right as ast.Ident
2263 mut resolved_right_type := g.resolved_expr_type(ast.Expr(right_ident),
2264 val.right_type)
2265 if resolved_right_type == 0 {
2266 resolved_right_type = val.right_type
2267 }
2268 resolved_right_type =
2269 g.unwrap_generic(g.recheck_concrete_type(resolved_right_type))
2270 rhs_sym_ := g.table.final_sym(resolved_right_type)
2271 if rhs_sym_.kind != .function {
2272 right_type_for_compare :=
2273 resolved_right_type.clear_flag(.shared_f).clear_flag(.atomic_f)
2274 exp_type_for_compare :=
2275 exp_type.clear_flag(.shared_f).clear_flag(.atomic_f)
2276 right_points_to_heap := resolved_right_type.is_ptr()
2277 && g.table.final_sym(resolved_right_type.deref()).is_heap()
2278 use_heap_pointed_ident = resolved_right_type != 0
2279 && right_points_to_heap
2280 && right_type_for_compare == exp_type_for_compare
2281 right_is_auto_heap := right_ident.is_auto_heap()
2282 || g.resolved_ident_is_auto_heap(right_ident)
2283 use_raw_auto_heap_ident = exp_type.is_ptr()
2284 && right_is_auto_heap && !use_heap_pointed_ident
2285 && resolved_right_type != 0 && !resolved_right_type.is_ptr()
2286 }
2287 }
2288 if use_heap_pointed_ident {
2289 g.expr(ast.Expr((val as ast.PrefixExpr).right))
2290 } else if use_raw_auto_heap_ident {
2291 old_inside_assign_fn_var := g.inside_assign_fn_var
2292 g.inside_assign_fn_var = true
2293 g.expr(ast.Expr((val as ast.PrefixExpr).right))
2294 g.inside_assign_fn_var = old_inside_assign_fn_var
2295 } else {
2296 g.expr_with_cast(val, val_type, exp_type)
2297 }
2298 }
2299 }
2300 }
2301 }
2302 if str_add || op_overloaded || is_safe_add_assign || is_safe_sub_assign
2303 || is_safe_mul_assign || is_safe_div_assign || is_safe_mod_assign {
2304 g.write(')')
2305 } else if is_safe_shift_assign {
2306 g.write(')')
2307 }
2308 if node_.op == .assign && var_type.has_flag(.option_mut_param_t) {
2309 g.write('.data, sizeof(${g.base_type(val_type)}))')
2310 }
2311 if cur_indexexpr != -1 {
2312 g.cur_indexexpr.delete(cur_indexexpr)
2313 g.write(' })')
2314 g.is_arraymap_set = g.cur_indexexpr.len > 0
2315 }
2316 if decl_tmp_var != '' {
2317 g.writeln(';')
2318 g.write2(decl_stmt_str, ' ')
2319 g.write(decl_tmp_var)
2320 }
2321 g.is_shared = false
2322 }
2323 g.right_is_opt = false
2324 if g.inside_ternary == 0 && (node.left.len > 1 || !node.is_simple) {
2325 g.writeln(';')
2326 }
2327 }
2328}
2329
2330fn (mut g Gen) gen_multi_return_assign(node &ast.AssignStmt, return_type ast.Type, return_sym ast.TypeSymbol) {
2331 // multi return
2332 // TODO: Handle in if_expr
2333 mut ret_type := return_type
2334 mut ret_sym := return_sym
2335 mut suffix := ''
2336 if g.comptime.inside_comptime_for && node.right[0] is ast.CallExpr {
2337 call_expr := node.right[0] as ast.CallExpr
2338 if call_expr.concrete_types.len > 0 && return_sym.info is ast.MultiReturn
2339 && g.comptime.comptime_for_field_var != '' {
2340 field_type := g.comptime.comptime_for_field_type
2341 field_sym := g.table.sym(field_type)
2342 if field_sym.info is ast.Map {
2343 map_info := field_sym.info as ast.Map
2344 ret_type =
2345 g.table.find_or_register_multi_return([map_info.key_type, map_info.value_type])
2346 ret_sym = *g.table.sym(ret_type)
2347 }
2348 }
2349 if g.comptime.comptime_for_field_var != '' {
2350 suffix = '_${g.comptime.comptime_for_field_value.name}'
2351 }
2352 }
2353 if node.right[0] is ast.CallExpr {
2354 call_expr := node.right[0] as ast.CallExpr
2355 mut fn_var_type := ast.void_type
2356 lookup_name := if call_expr.left is ast.Ident {
2357 call_expr.left.name
2358 } else {
2359 call_expr.name
2360 }
2361 resolved_current_type := g.resolve_current_fn_generic_param_type(lookup_name)
2362 if resolved_current_type != 0
2363 && g.table.final_sym(g.unwrap_generic(resolved_current_type)).kind == .function {
2364 fn_var_type = g.unwrap_generic(g.recheck_concrete_type(resolved_current_type))
2365 } else if call_expr.is_fn_var {
2366 fn_var_type = g.unwrap_generic(g.recheck_concrete_type(call_expr.fn_var_type))
2367 }
2368 if fn_var_type == 0 {
2369 if obj := call_expr.scope.find_var(lookup_name) {
2370 if g.table.final_sym(g.unwrap_generic(obj.typ)).kind == .function {
2371 fn_var_type = g.unwrap_generic(g.recheck_concrete_type(obj.typ))
2372 }
2373 }
2374 }
2375 if fn_var_type != 0 {
2376 fn_sym := g.table.final_sym(fn_var_type)
2377 if fn_sym.info is ast.FnType {
2378 resolved_ret_type :=
2379 g.unwrap_generic(g.recheck_concrete_type(fn_sym.info.func.return_type))
2380 if resolved_ret_type != 0 && g.table.sym(resolved_ret_type).kind == .multi_return {
2381 ret_type = resolved_ret_type
2382 ret_sym = *g.table.sym(ret_type)
2383 }
2384 }
2385 }
2386 }
2387 mr_var_name := 'mr_${node.pos.pos}${suffix}'
2388 mut is_option := ret_type.has_flag(.option)
2389 mut mr_styp := g.styp(ret_type.clear_flag(.result))
2390 if node.right[0] is ast.CallExpr && node.right[0].or_block.kind != .absent {
2391 is_option = false
2392 mr_styp = g.styp(ret_type.clear_option_and_result())
2393 }
2394 g.write('${mr_styp} ${mr_var_name} = ')
2395 g.expr(node.right[0])
2396 g.writeln(';')
2397 raw_mr_types := (ret_sym.info as ast.MultiReturn).types
2398 is_generic_context := g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0
2399 mr_types := if is_generic_context {
2400 raw_mr_types.map(g.unwrap_generic(g.recheck_concrete_type(it)))
2401 } else if node.right_types.len == raw_mr_types.len {
2402 node.right_types.clone()
2403 } else {
2404 raw_mr_types.clone()
2405 }
2406 mut recompute_types := node.op == .decl_assign || ret_type != return_type
2407 if g.comptime.inside_comptime_for && node.right[0] is ast.CallExpr {
2408 call_expr := node.right[0] as ast.CallExpr
2409 if call_expr.concrete_types.len > 0 && g.comptime.comptime_for_field_var != ''
2410 && return_sym.info is ast.MultiReturn {
2411 recompute_types = true
2412 for i, mut lx in &node.left {
2413 if mut lx is ast.Ident && lx.kind != .blank_ident {
2414 if mut lx.obj is ast.Var {
2415 lx.obj.typ = mr_types[i]
2416 }
2417 }
2418 }
2419 }
2420 }
2421 if recompute_types {
2422 for i, mut lx in &node.left {
2423 if mut lx is ast.Ident && lx.kind != .blank_ident {
2424 if mut lx.obj is ast.Var && i < mr_types.len {
2425 lx.obj.typ = mr_types[i]
2426 if !mr_types[i].has_option_or_result() {
2427 lx.obj.orig_type = ast.no_type
2428 lx.obj.smartcasts = []
2429 lx.obj.is_unwrapped = false
2430 }
2431 if lx.obj.ct_type_var != .no_comptime {
2432 g.type_resolver.update_ct_type(lx.name, mr_types[i])
2433 }
2434 }
2435 if i < mr_types.len && lx.scope != unsafe { nil } {
2436 if mut scope_var := lx.scope.find_var(lx.name) {
2437 scope_var.typ = mr_types[i]
2438 if !mr_types[i].has_option_or_result() {
2439 scope_var.orig_type = ast.no_type
2440 scope_var.smartcasts = []
2441 scope_var.is_unwrapped = false
2442 }
2443 }
2444 }
2445 }
2446 }
2447 }
2448 for i, lx in node.left {
2449 mut cur_indexexpr := -1
2450 mut is_auto_heap := false
2451 mut ident := ast.Ident{
2452 scope: unsafe { nil }
2453 }
2454 if lx is ast.Ident {
2455 ident = lx
2456 if lx.kind == .blank_ident {
2457 continue
2458 }
2459 if lx.obj is ast.Var {
2460 is_auto_heap = lx.obj.is_auto_heap
2461 }
2462 }
2463 if lx is ast.IndexExpr && g.cur_indexexpr.len > 0 {
2464 cur_indexexpr = g.cur_indexexpr.index(lx.pos.pos)
2465 }
2466 left_type := if recompute_types { mr_types[i] } else { node.left_types[i] }
2467 styp := if ident.name in g.defer_vars { '' } else { g.styp(left_type) }
2468 needs_auto_heap_alloc := is_auto_heap && node.op == .decl_assign
2469 if node.op == .decl_assign {
2470 g.write('${styp} ')
2471 }
2472 if lx.is_auto_deref_var() {
2473 g.write('*')
2474 }
2475 noscan := if is_auto_heap { g.check_noscan(return_type) } else { '' }
2476 mut aligned := 0
2477 sym := g.table.final_sym(left_type)
2478 if sym.info is ast.Struct {
2479 if attr := sym.info.attrs.find_first('aligned') {
2480 aligned = if attr.arg == '' { 0 } else { attr.arg.int() }
2481 }
2482 }
2483 if left_type.has_flag(.option) {
2484 base_typ := g.base_type(left_type)
2485 tmp_var := if needs_auto_heap_alloc {
2486 if aligned != 0 {
2487 'HEAP_align(${styp}, ${mr_var_name}.arg${i}, ${aligned})'
2488 } else {
2489 'HEAP${noscan}(${styp}, ${mr_var_name}.arg${i})'
2490 }
2491 } else if is_option {
2492 '(*((${g.base_type(return_type)}*)${mr_var_name}.data)).arg${i}'
2493 } else {
2494 '${mr_var_name}.arg${i}'
2495 }
2496 if mr_types[i].has_flag(.option) {
2497 old_left_is_opt := g.left_is_opt
2498 g.left_is_opt = true
2499 g.expr(lx)
2500 g.writeln(' = ${tmp_var};')
2501 g.left_is_opt = old_left_is_opt
2502 } else {
2503 g.write('builtin___option_ok(&(${base_typ}[]) { ${tmp_var} }, (${option_name}*)(&')
2504 tmp_left_is_opt := g.left_is_opt
2505 g.left_is_opt = true
2506 g.expr(lx)
2507 g.left_is_opt = tmp_left_is_opt
2508 g.writeln('), sizeof(${base_typ}));')
2509 }
2510 } else {
2511 g.expr(lx)
2512 if sym.kind == .array_fixed {
2513 g.writeln2(';',
2514 'memcpy(&${g.expr_string(lx)}, &${mr_var_name}.arg${i}, sizeof(${styp}));')
2515 } else {
2516 if cur_indexexpr != -1 {
2517 if needs_auto_heap_alloc {
2518 if aligned != 0 {
2519 g.writeln('HEAP_align(${styp}, ${mr_var_name}.arg${i}, ${aligned}) });')
2520 } else {
2521 g.writeln('HEAP${noscan}(${styp}, ${mr_var_name}.arg${i}) });')
2522 }
2523 } else if is_option {
2524 g.writeln('(*((${g.base_type(return_type)}*)${mr_var_name}.data)).arg${i} });')
2525 } else {
2526 g.writeln('${mr_var_name}.arg${i} });')
2527 }
2528 g.cur_indexexpr.delete(cur_indexexpr)
2529 } else {
2530 if needs_auto_heap_alloc {
2531 if aligned != 0 {
2532 g.writeln(' = HEAP_align(${styp}, ${mr_var_name}.arg${i}, ${aligned});')
2533 } else {
2534 g.writeln(' = HEAP${noscan}(${styp}, ${mr_var_name}.arg${i});')
2535 }
2536 } else if is_option {
2537 g.writeln(' = (*((${g.base_type(return_type)}*)${mr_var_name}.data)).arg${i};')
2538 } else {
2539 g.writeln(' = ${mr_var_name}.arg${i};')
2540 }
2541 }
2542 }
2543 }
2544 }
2545 if g.is_arraymap_set {
2546 g.is_arraymap_set = false
2547 }
2548}
2549
2550fn (mut g Gen) gen_cross_var_assign(node &ast.AssignStmt) {
2551 for i, left in node.left {
2552 left_is_auto_deref_var := left.is_auto_deref_var()
2553 match left {
2554 ast.Ident {
2555 left_typ := node.left_types[i]
2556 left_sym := g.table.sym(left_typ)
2557 mut anon_ctx := ''
2558 if g.anon_fn != unsafe { nil } {
2559 if obj := left.scope.find_var(left.name) {
2560 if obj.is_inherited {
2561 anon_ctx = '${closure_ctx}->'
2562 }
2563 }
2564 }
2565 if left_sym.info is ast.FnType {
2566 g.write_fn_ptr_decl(&left_sym.info, '_var_${left.pos.pos}')
2567 g.writeln(' = ${anon_ctx}${g.ident_cname(left)};')
2568 } else if left_is_auto_deref_var {
2569 styp := g.styp(left_typ).trim('*')
2570 if left_sym.kind == .array {
2571 g.writeln('${styp} _var_${left.pos.pos} = builtin__array_clone(${anon_ctx}${g.ident_cname(left)});')
2572 } else {
2573 g.writeln('${styp} _var_${left.pos.pos} = *${anon_ctx}${g.ident_cname(left)};')
2574 }
2575 } else {
2576 styp := g.styp(left_typ)
2577 if left_sym.kind == .array {
2578 g.writeln('${styp} _var_${left.pos.pos} = builtin__array_clone(&${anon_ctx}${g.ident_cname(left)});')
2579 } else {
2580 g.writeln('${styp} _var_${left.pos.pos} = ${anon_ctx}${g.ident_cname(left)};')
2581 }
2582 }
2583 }
2584 ast.IndexExpr {
2585 if left.is_index_operator {
2586 styp := g.styp(node.left_types[i])
2587 g.write('${styp} _var_${left.pos.pos} = ')
2588 g.expr(ast.Expr(left))
2589 g.writeln(';')
2590 continue
2591 }
2592 mut container_type := left.left_type
2593 if g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0 {
2594 resolved_container_type := g.resolved_expr_type(left.left, left.left_type)
2595 if resolved_container_type != 0 {
2596 container_type =
2597 g.unwrap_generic(g.recheck_concrete_type(resolved_container_type))
2598 }
2599 }
2600 sym := g.table.sym(g.table.unaliased_type(container_type))
2601 if sym.kind == .array {
2602 info := sym.info as ast.Array
2603 elem_sym := g.table.sym(info.elem_type)
2604 needs_clone := info.elem_type == ast.string_type && g.is_autofree
2605
2606 if elem_sym.kind == .function {
2607 left_typ := node.left_types[i]
2608 left_sym := g.table.sym(left_typ)
2609 g.write_fn_ptr_decl(left_sym.info as ast.FnType, '_var_${left.pos.pos}')
2610 g.write(' = *(voidptr*)builtin__array_get(')
2611 } else {
2612 styp := g.styp(info.elem_type)
2613 string_clone := if needs_clone { 'builtin__string_clone(' } else { '' }
2614
2615 g.write('${styp} _var_${left.pos.pos} = ${string_clone}*(${styp}*)builtin__array_get(')
2616 }
2617
2618 if left.left_type.is_ptr() || left.left.is_auto_deref_var() {
2619 g.write('*')
2620 }
2621 g.expr(left.left)
2622 g.write(', ')
2623 g.expr(left.index)
2624 if needs_clone {
2625 g.write(')')
2626 }
2627 g.writeln(');')
2628 } else if sym.kind == .array_fixed {
2629 info := sym.info as ast.ArrayFixed
2630 elem_sym := g.table.sym(info.elem_type)
2631 if elem_sym.kind == .function {
2632 left_typ := node.left_types[i]
2633 left_sym := g.table.sym(left_typ)
2634 g.write_fn_ptr_decl(left_sym.info as ast.FnType, '_var_${left.pos.pos}')
2635 g.write(' = *(voidptr*)')
2636 } else {
2637 styp := g.styp(info.elem_type)
2638 g.write('${styp} _var_${left.pos.pos} = ')
2639 }
2640 if left.left_type.is_ptr() {
2641 g.write('*')
2642 }
2643 needs_clone := info.elem_type == ast.string_type && g.is_autofree
2644 if needs_clone {
2645 g.write('builtin__string_clone(')
2646 }
2647 g.expr(left)
2648 if needs_clone {
2649 g.write(')')
2650 }
2651 g.writeln(';')
2652 } else if sym.kind == .map {
2653 info := sym.info as ast.Map
2654 styp := g.styp(info.value_type)
2655 zero := g.type_default(info.value_type)
2656 val_sym := g.table.sym(info.value_type)
2657 if val_sym.kind == .function {
2658 left_type := node.left_types[i]
2659 left_sym := g.table.sym(left_type)
2660 g.write_fn_ptr_decl(left_sym.info as ast.FnType, '_var_${left.pos.pos}')
2661 g.write(' = *(voidptr*)builtin__map_get(')
2662 } else {
2663 g.write('${styp} _var_${left.pos.pos} = *(${styp}*)builtin__map_get(')
2664 }
2665 if !left.left_type.is_ptr() {
2666 g.write('ADDR(map, ')
2667 g.expr(left.left)
2668 g.write(')')
2669 } else {
2670 g.expr(left.left)
2671 }
2672 g.write(', ')
2673 g.write_map_key_arg(left.index, info.key_type)
2674 if val_sym.kind == .function {
2675 g.writeln(', &(voidptr[]){ ${zero} });')
2676 } else {
2677 g.writeln(', &(${styp}[]){ ${zero} });')
2678 }
2679 }
2680 }
2681 ast.SelectorExpr {
2682 styp := g.styp(left.typ)
2683 g.write('${styp} _var_${left.pos.pos} = ')
2684 g.expr(left.expr)
2685 sel := g.dot_or_ptr(left.expr_type)
2686 g.writeln('${sel}${left.field_name};')
2687 }
2688 else {}
2689 }
2690 }
2691}
2692
2693fn (mut g Gen) gen_cross_tmp_variable(left []ast.Expr, val ast.Expr) {
2694 val_ := val
2695 match val {
2696 ast.Ident {
2697 mut has_var := false
2698 for lx in left {
2699 if lx is ast.Ident {
2700 if val.name == lx.name {
2701 g.write2('_var_', lx.pos.pos.str())
2702 has_var = true
2703 break
2704 }
2705 }
2706 }
2707 if !has_var {
2708 g.expr(val_)
2709 }
2710 }
2711 ast.IndexExpr {
2712 mut has_var := false
2713 for lx in left {
2714 if val_.str() == lx.str() {
2715 g.write2('_var_', lx.pos().pos.str())
2716 has_var = true
2717 break
2718 }
2719 }
2720 if !has_var {
2721 g.expr(val_)
2722 }
2723 }
2724 ast.InfixExpr {
2725 sym := g.table.sym(val.left_type)
2726 svalop := val.op.str()
2727 if _ := g.table.find_method(sym, svalop) {
2728 left_styp := g.styp(val.left_type.set_nr_muls(0))
2729 g.write2(left_styp, '_')
2730 g.write2(util.replace_op(svalop), '(')
2731 g.gen_cross_tmp_variable(left, val.left)
2732 g.write(', ')
2733 g.gen_cross_tmp_variable(left, val.right)
2734 g.write(')')
2735 } else {
2736 g.gen_cross_tmp_variable(left, val.left)
2737 g.write(svalop)
2738 g.gen_cross_tmp_variable(left, val.right)
2739 }
2740 }
2741 ast.ParExpr {
2742 g.write('(')
2743 g.gen_cross_tmp_variable(left, val.expr)
2744 g.write(')')
2745 }
2746 ast.CallExpr {
2747 if val.is_method {
2748 unwrapped_rec_type, typ_sym := g.unwrap_receiver_type(val)
2749 left_type := g.unwrap_generic(val.left_type)
2750 left_sym := g.table.sym(left_type)
2751 final_left_sym := g.table.final_sym(left_type)
2752 rec_typ_name := g.resolve_receiver_name(val, unwrapped_rec_type, final_left_sym,
2753 left_sym, typ_sym)
2754 mut fn_name := util.no_dots('${rec_typ_name}_${val.name}')
2755 if resolved_sym := g.table.find_sym(rec_typ_name) {
2756 if resolved_sym.is_builtin() && !fn_name.starts_with('builtin__') {
2757 fn_name = 'builtin__${fn_name}'
2758 }
2759 } else if rec_typ_name in ['int_literal', 'float_literal', 'vint_t'] {
2760 fn_name = 'builtin__${fn_name}'
2761 }
2762 g.write('${fn_name}(&')
2763 g.gen_cross_tmp_variable(left, val.left)
2764 for i, arg in val.args {
2765 g.gen_cross_tmp_variable(left, arg.expr)
2766 if i != val.args.len - 1 {
2767 g.write(', ')
2768 }
2769 }
2770 g.write(')')
2771 } else {
2772 mut fn_name := val.name.replace('.', '__')
2773 if val.concrete_types.len > 0 {
2774 fn_name = g.generic_fn_name(val.concrete_types, fn_name)
2775 }
2776 g.write('${fn_name}(')
2777 for i, arg in val.args {
2778 g.gen_cross_tmp_variable(left, arg.expr)
2779 if i != val.args.len - 1 {
2780 g.write(', ')
2781 }
2782 }
2783 g.write(')')
2784 }
2785 }
2786 ast.PrefixExpr {
2787 g.write(val.op.str())
2788 g.gen_cross_tmp_variable(left, val.right)
2789 }
2790 ast.PostfixExpr {
2791 g.gen_cross_tmp_variable(left, val.expr)
2792 g.write(val.op.str())
2793 }
2794 ast.SelectorExpr {
2795 mut has_var := false
2796 for lx in left {
2797 if val_.str() == lx.str() {
2798 g.write2('_var_', lx.pos().pos.str())
2799 has_var = true
2800 break
2801 }
2802 }
2803 if !has_var {
2804 g.expr(val_)
2805 }
2806 }
2807 else {
2808 g.expr(val_)
2809 }
2810 }
2811}
2812