vxx2 / vlib / v / gen / c / if.v
816 lines · 804 sloc · 25.79 KB · 5ad37a5a8656c438c68dabd31c9d36467390e046
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
7
8fn (mut g Gen) resolved_if_guard_expr_type(expr ast.Expr, default_type ast.Type) ast.Type {
9 if expr is ast.IndexExpr {
10 left_type := g.resolved_map_type_from_expr(expr.left, expr.left_type)
11 left_sym := g.table.final_sym(g.unwrap_generic(left_type))
12 if left_sym.kind == .map {
13 map_info := left_sym.map_info()
14 _, mut value_type := g.resolved_map_key_value_types(left_type, map_info.key_type,
15 map_info.value_type)
16 if default_type.has_flag(.option) && !value_type.has_flag(.option) {
17 value_type = value_type.set_flag(.option)
18 } else if default_type.has_flag(.result) && !value_type.has_flag(.result) {
19 value_type = value_type.set_flag(.result)
20 }
21 return value_type
22 }
23 }
24 return g.unwrap_generic(g.recheck_concrete_type(default_type))
25}
26
27fn (mut g Gen) if_guard_var_needs_gc_pin(scope &ast.Scope, name string) bool {
28 if g.pref.gc_mode !in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt] {
29 return false
30 }
31 if name == '_' {
32 return false
33 }
34 if v := scope.find_var(name) {
35 return v.is_auto_heap || v.typ.is_any_kind_of_pointer() || g.contains_ptr(v.typ)
36 }
37 return false
38}
39
40fn (g &Gen) if_guard_else_uses_err(node ast.IfExpr, branch_idx int) bool {
41 if !node.has_else || branch_idx != node.branches.len - 2 {
42 return false
43 }
44 else_branch := node.branches[branch_idx + 1]
45 if err_var := else_branch.scope.find_var('err') {
46 return err_var.is_used
47 }
48 return false
49}
50
51fn (mut g Gen) write_if_guard_gc_pin(scope &ast.Scope, name string, cvar_name string) {
52 if g.inside_veb_tmpl {
53 return
54 }
55 if g.if_guard_var_needs_gc_pin(scope, name) {
56 g.writeln('\tGC_reachable_here(&${cvar_name});')
57 }
58}
59
60fn (mut g Gen) if_guard_error_cleanup(cvar_name string, expr_type ast.Type) {
61 if expr_type.has_flag(.option) {
62 g.writeln('\tif (${cvar_name}.state == 2 && ${cvar_name}.err._object != _const_none__._object) { builtin___v_free(${cvar_name}.err._object); }')
63 } else if expr_type.has_flag(.result) {
64 g.writeln('\tif (${cvar_name}.is_error && ${cvar_name}.err._object != _const_none__._object) { builtin___v_free(${cvar_name}.err._object); }')
65 }
66}
67
68fn (mut g Gen) need_tmp_var_in_if(node ast.IfExpr) bool {
69 if node.is_expr && (g.inside_ternary == 0 || g.is_assign_lhs) {
70 if g.is_autofree || node.typ.has_option_or_result() || node.is_comptime || g.is_assign_lhs {
71 return true
72 }
73 for branch in node.branches {
74 if branch.stmts.len > 1 {
75 return true
76 }
77 if g.need_tmp_var_in_expr(branch.cond) {
78 return true
79 }
80 if branch.stmts.len == 1 {
81 if branch.stmts[0] is ast.ExprStmt {
82 stmt := branch.stmts[0] as ast.ExprStmt
83 if stmt.expr is ast.ArrayInit && stmt.expr.is_fixed {
84 return true
85 }
86 if g.need_tmp_var_in_expr(stmt.expr) {
87 return true
88 }
89 } else if branch.stmts[0] is ast.Return {
90 return true
91 } else if branch.stmts[0] is ast.BranchStmt {
92 return true
93 }
94 }
95 }
96 }
97 return false
98}
99
100fn (mut g Gen) need_tmp_var_in_expr(expr ast.Expr) bool {
101 if is_noreturn_callexpr(expr) {
102 return true
103 }
104 match expr {
105 ast.ArrayInit {
106 elem_type := g.unwrap_generic(expr.elem_type)
107 elem_kind := if elem_type != 0 {
108 g.table.final_sym(elem_type).kind
109 } else {
110 ast.Kind.placeholder
111 }
112 if expr.has_index || (expr.has_len && (g.struct_has_array_or_map_field(elem_type)
113 || (elem_kind in [.array, .map] && !expr.has_init))) {
114 return true
115 }
116 if g.need_tmp_var_in_expr(expr.len_expr) {
117 return true
118 }
119 if g.need_tmp_var_in_expr(expr.cap_expr) {
120 return true
121 }
122 if g.need_tmp_var_in_expr(expr.init_expr) {
123 return true
124 }
125 for elem_expr in expr.exprs {
126 if g.need_tmp_var_in_expr(elem_expr) {
127 return true
128 }
129 }
130 }
131 ast.AsCast {
132 return true
133 }
134 ast.CallExpr {
135 if expr.is_method {
136 left_sym := g.table.sym(expr.receiver_type)
137 if left_sym.kind in [.array, .array_fixed, .map] {
138 return expr.name !in ['contains', 'exists', 'len', 'cap', 'first', 'last',
139 'index', 'last_index', 'get']
140 }
141 }
142 if expr.or_block.kind != .absent {
143 return true
144 }
145 if g.need_tmp_var_in_expr(expr.left) {
146 return true
147 }
148 for arg in expr.args {
149 if arg.expr is ast.ArrayDecompose {
150 return true
151 }
152 if g.need_tmp_var_in_expr(arg.expr) {
153 return true
154 }
155 }
156 return expr.expected_arg_types.any(it.has_flag(.option))
157 }
158 ast.CastExpr {
159 return g.need_tmp_var_in_expr(expr.expr)
160 }
161 ast.ConcatExpr {
162 for val in expr.vals {
163 if val is ast.CallExpr {
164 if val.return_type.has_option_or_result() {
165 return true
166 }
167 }
168 }
169 }
170 ast.Ident {
171 return expr.or_expr.kind != .absent
172 }
173 ast.IfExpr {
174 if g.need_tmp_var_in_if(expr) {
175 return true
176 }
177 }
178 ast.IfGuardExpr {
179 return true
180 }
181 ast.IndexExpr {
182 if expr.or_expr.kind != .absent {
183 return true
184 }
185 if g.need_tmp_var_in_expr(expr.left) {
186 return true
187 }
188 if g.need_tmp_var_in_expr(expr.index) {
189 return true
190 }
191 }
192 ast.InfixExpr {
193 if g.need_tmp_var_in_expr(expr.left) {
194 return true
195 }
196 if g.need_tmp_var_in_expr(expr.right) {
197 return true
198 }
199 // struct pointer equality comparisons may hoist temp vars
200 // (via gen_struct_pointer_eq_op) which breaks short-circuit
201 // evaluation when used on the right side of `&&` after an
202 // `is` check. Detect this so that infix_expr_and_or_op uses
203 // its safe short-circuit pattern instead.
204 if expr.op in [.eq, .ne] {
205 if (expr.left_type.is_ptr() && !expr.left.is_lvalue())
206 || (expr.right_type.is_ptr() && !expr.right.is_lvalue()) {
207 left_sym := g.table.sym(expr.left_type)
208 right_sym := g.table.sym(expr.right_type)
209 if left_sym.kind == .struct || right_sym.kind == .struct {
210 return true
211 }
212 }
213 }
214 }
215 ast.MapInit {
216 for key in expr.keys {
217 if g.need_tmp_var_in_expr(key) {
218 return true
219 }
220 }
221 for val in expr.vals {
222 if g.need_tmp_var_in_expr(val) {
223 return true
224 }
225 }
226 }
227 ast.MatchExpr {
228 return true
229 }
230 ast.ParExpr {
231 return g.need_tmp_var_in_expr(expr.expr)
232 }
233 ast.PrefixExpr {
234 return g.need_tmp_var_in_expr(expr.right)
235 }
236 ast.SelectorExpr {
237 if g.need_tmp_var_in_expr(expr.expr) {
238 return true
239 }
240 return expr.or_block.kind != .absent
241 }
242 ast.StringInterLiteral {
243 for e in expr.exprs {
244 if g.need_tmp_var_in_expr(e) {
245 return true
246 }
247 }
248 }
249 ast.StructInit {
250 if g.need_tmp_var_in_expr(expr.update_expr) {
251 return true
252 }
253 for init_field in expr.init_fields {
254 if g.need_tmp_var_in_expr(init_field.expr) {
255 return true
256 }
257 }
258 sym := g.table.sym(expr.typ)
259 return sym.info is ast.Struct && sym.info.has_option
260 }
261 ast.SqlExpr {
262 return true
263 }
264 ast.SpawnExpr, ast.GoExpr {
265 // `spawn`/`go` lower to statement-level setup (arg struct alloc,
266 // pthread_create, ...) that cannot live inside a C ternary operand,
267 // so an if/match expression branch that is a spawn must use the
268 // tmp-var form instead of the ternary form. See issue #27485.
269 return true
270 }
271 else {}
272 }
273
274 return false
275}
276
277fn (mut g Gen) needs_conds_order(node ast.IfExpr) bool {
278 if node.branches.len > 1 {
279 for branch in node.branches {
280 if g.need_tmp_var_in_expr(branch.cond) {
281 return true
282 }
283 }
284 }
285 return false
286}
287
288fn (mut g Gen) if_expr(node ast.IfExpr) {
289 use_outer_tmp := g.outer_tmp_var != ''
290 saved_outer_tmp_var := g.outer_tmp_var
291 if use_outer_tmp {
292 g.outer_tmp_var = ''
293 }
294 resolved_node_typ := g.infer_if_expr_type(node)
295
296 // For simple if expressions we can use C's `?:`
297 // `if x > 0 { 1 } else { 2 }` => `(x > 0)? (1) : (2)`
298 // For if expressions with multiple statements or another if expression inside, it's much
299 // easier to use a temp var, than do C tricks with commas, introduce special vars etc
300 // (as it used to be done).
301 // Always use this in -autofree, since ?: can have tmp expressions that have to be freed.
302 needs_tmp_var := g.inside_if_option || g.need_tmp_var_in_if(node) || use_outer_tmp
303 needs_conds_order := g.needs_conds_order(node)
304 tmp := if use_outer_tmp {
305 // Use the tmp var from outer context (e.g. from stmts_with_tmp_var)
306 saved_outer_tmp_var
307 } else if g.inside_if_option || (resolved_node_typ != ast.void_type && needs_tmp_var) {
308 g.new_tmp_var()
309 } else {
310 ''
311 }
312 mut cur_line := ''
313 mut raw_state := false
314 tmp_if_option_type := g.last_if_option_type
315 mut exit_label := ''
316 if needs_tmp_var {
317 exit_label = g.new_tmp_var()
318 node_typ := if g.inside_or_block {
319 resolved_node_typ.clear_option_and_result()
320 } else {
321 resolved_node_typ
322 }
323 // For generic functions, if the if-expression's type was set to a concrete type
324 // by the checker but we're generating a different generic instance, we need to
325 // use the correct concrete type from cur_concrete_types
326 resolved_typ := if g.cur_fn != unsafe { nil } && g.cur_fn.generic_names.len > 0
327 && g.cur_concrete_types.len > 0 {
328 // Try to unwrap generic, and if that doesn't work, check if we should use
329 // the function's return type
330 unwrapped := g.unwrap_generic(node_typ)
331 if g.inside_return_expr && !g.inside_struct_init && unwrapped == node_typ
332 && g.cur_fn.return_type.has_flag(.generic) {
333 // The node type didn't unwrap, but the function return type is generic
334 // Get the unwrapped function return type for this instance
335 mut fn_ret_typ := g.unwrap_generic(g.cur_fn.return_type)
336 if g.inside_or_block {
337 fn_ret_typ = fn_ret_typ.clear_option_and_result()
338 }
339 // Check if the function return type directly matches one of the concrete types
340 // If it does, the if expression type should also match that concrete type
341 fn_ret_is_direct_generic := g.cur_concrete_types.any(it == fn_ret_typ)
342 if fn_ret_is_direct_generic && node_typ != fn_ret_typ {
343 // The function returns T directly, and node_typ doesn't match the current T
344 // This means node_typ is stale from another instance
345 fn_ret_typ
346 } else {
347 // Either the function return type is wrapped (like !T or []T),
348 // or node_typ is correct for this instance
349 unwrapped
350 }
351 } else {
352 unwrapped
353 }
354 } else {
355 g.unwrap_generic(node_typ)
356 }
357 resolved_sym := g.table.final_sym(resolved_typ)
358 mut styp := g.styp(resolved_typ)
359 // When the tmp var is a fn-returned fixed array, it is a wrapper struct whose
360 // data lives in a `.ret_arr` member. Flag it so every branch writes through
361 // `.ret_arr`, even ones whose own expr type lacks the `is_fn_ret` flag (e.g. a
362 // fixed array literal mixed with a function call: `if c { fa() } else { [1]! }`).
363 prev_if_match_tmp_is_fn_ret_arr := g.if_match_tmp_is_fn_ret_arr
364 g.if_match_tmp_is_fn_ret_arr = resolved_sym.info is ast.ArrayFixed
365 && resolved_sym.info.is_fn_ret
366 defer(fn) {
367 g.if_match_tmp_is_fn_ret_arr = prev_if_match_tmp_is_fn_ret_arr
368 }
369 if (g.inside_if_option || node_typ.has_flag(.option)) && !g.inside_or_block {
370 raw_state = g.inside_if_option
371 if resolved_node_typ != ast.void_type {
372 g.last_if_option_type = resolved_node_typ
373 defer(fn) {
374 g.last_if_option_type = tmp_if_option_type
375 }
376 }
377 defer(fn) {
378 g.inside_if_option = raw_state
379 }
380 g.inside_if_option = true
381 styp = styp.replace('*', '_ptr')
382 } else if node_typ.has_flag(.result) && !g.inside_or_block {
383 raw_state = g.inside_if_result
384 defer(fn) {
385 g.inside_if_result = raw_state
386 }
387 g.inside_if_result = true
388 styp = styp.replace('*', '_ptr')
389 } else {
390 g.last_if_option_type = node_typ
391 defer(fn) {
392 g.last_if_option_type = tmp_if_option_type
393 }
394 }
395 cur_line = g.go_before_last_stmt()
396 g.empty_line = true
397 if tmp != '' && !use_outer_tmp {
398 // Only declare the tmp var if it's not from outer context
399 mut declared_tmp := false
400 if resolved_node_typ == ast.void_type && g.last_if_option_type != 0 {
401 // nested if on return stmt
402 g.write2(g.styp(g.unwrap_generic(g.last_if_option_type)), ' ')
403 } else if resolved_sym.kind == .function && resolved_sym.info is ast.FnType {
404 param_types := resolved_sym.info.func.params.map(it.typ)
405 g.writeln('${g.fn_var_signature(resolved_typ, resolved_sym.info.func.return_type,
406 param_types, tmp)}; /* if prepend */')
407 declared_tmp = true
408 } else {
409 g.write('${styp} ')
410 }
411 if !declared_tmp {
412 g.writeln('${tmp}; /* if prepend */')
413 }
414 g.set_current_pos_as_last_stmt_pos()
415 }
416 if g.infix_left_var_name.len > 0 {
417 g.writeln('if (${g.infix_left_var_name}) {')
418 g.set_current_pos_as_last_stmt_pos()
419 g.indent++
420 }
421 } else if node.is_expr || g.inside_ternary != 0 {
422 g.inside_ternary++
423 g.write('(')
424 for i, branch in node.branches {
425 if i > 0 {
426 g.write(' : ')
427 }
428 if i < node.branches.len - 1 || !node.has_else {
429 g.expr(branch.cond)
430 g.write(' ? ')
431 }
432 prev_expected_cast_type := g.expected_cast_type
433 if node.is_expr && (g.table.sym(resolved_node_typ).kind == .sum_type
434 || resolved_node_typ.has_flag(.shared_f)) {
435 g.expected_cast_type = resolved_node_typ
436 }
437 g.stmts(branch.stmts)
438 g.expected_cast_type = prev_expected_cast_type
439 }
440 if node.branches.len == 1 && !node.is_expr {
441 g.write(': 0')
442 }
443 g.write(')')
444 g.decrement_inside_ternary()
445 return
446 }
447 mut is_guard := false
448 mut guard_idx := 0
449 mut guard_vars := []string{}
450 mut guard_expr_types := []ast.Type{len: node.branches.len}
451 mut guard_else_uses_err := []bool{len: node.branches.len}
452 mut guard_owns_error := []bool{len: node.branches.len}
453 for i, branch in node.branches {
454 cond := branch.cond
455 if cond is ast.IfGuardExpr {
456 if !is_guard {
457 is_guard = true
458 guard_vars = []string{len: node.branches.len}
459 }
460 guard_idx = i // saves the last if guard index
461 guard_else_uses_err[i] = g.if_guard_else_uses_err(node, i)
462 guard_owns_error[i] = cond.expr is ast.IndexExpr
463 if cond.expr !in [ast.IndexExpr, ast.PrefixExpr] {
464 var_name := g.new_tmp_var()
465 guard_vars[i] = var_name
466 cond_expr_type := if g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0
467 && cond.expr is ast.CallExpr {
468 resolved := g.resolve_return_type(cond.expr)
469 if resolved != ast.void_type && !resolved.has_flag(.generic) {
470 if cond.expr_type.has_flag(.option) && !resolved.has_flag(.option) {
471 resolved.set_flag(.option)
472 } else if cond.expr_type.has_flag(.result) && !resolved.has_flag(.result) {
473 resolved.set_flag(.result)
474 } else {
475 resolved
476 }
477 } else {
478 cond.expr_type
479 }
480 } else {
481 cond.expr_type
482 }
483 g.writeln('${g.styp(g.unwrap_generic(cond_expr_type))} ${var_name} = {0};')
484 } else if cond.expr is ast.IndexExpr {
485 value_type := g.table.value_type(g.unwrap_generic(cond.expr.left_type))
486 if value_type.has_flag(.option) {
487 var_name := g.new_tmp_var()
488 guard_vars[i] = var_name
489 g.writeln('${g.styp(value_type)} ${var_name} = {0};')
490 } else {
491 guard_vars[i] = ''
492 }
493 } else {
494 guard_vars[i] = ''
495 }
496 }
497 }
498 mut branch_cond_var_names := []string{}
499 mut tmp_var_scope_count := 0
500 for i, branch in node.branches {
501 is_else := i == node.branches.len - 1 && node.has_else
502 if i > 0 {
503 if needs_tmp_var {
504 g.writeln('};')
505 // Open a new scope so that any variables generated by the next
506 // branch's condition evaluation (e.g. from `.any()` or `.all()`)
507 // are inside a block that the previous branch's `goto` skips over
508 // entirely, preventing gcc's -Wjump-misses-init with -cstrict.
509 if !is_else {
510 g.writeln('{')
511 tmp_var_scope_count++
512 }
513 g.set_current_pos_as_last_stmt_pos()
514 } else {
515 g.write('} else ')
516 }
517 }
518 // if last branch is `else {`
519 if is_else {
520 g.writeln('{')
521 // define `err` for the last branch after a `if val := opt {...}' guard
522 if is_guard && guard_idx == i - 1 {
523 cvar_name := guard_vars[guard_idx]
524 if guard_else_uses_err[guard_idx] {
525 if err_var := branch.scope.find_var('err') {
526 if err_var.is_used {
527 g.writeln('\tIError err = ${cvar_name}.err;')
528 }
529 }
530 } else if guard_owns_error[guard_idx] && guard_expr_types[guard_idx] != 0 {
531 g.if_guard_error_cleanup(cvar_name, guard_expr_types[guard_idx])
532 }
533 }
534 } else if branch.cond is ast.IfGuardExpr {
535 mut var_name := guard_vars[i]
536 mut short_opt := false
537 g.left_is_opt = true
538 // Resolve the expression type in generic context
539 mut guard_expr_type := branch.cond.expr_type
540 if g.cur_fn != unsafe { nil } && g.cur_concrete_types.len > 0 {
541 if branch.cond.expr is ast.CallExpr {
542 resolved := g.resolve_return_type(branch.cond.expr)
543 if resolved != ast.void_type && !resolved.has_flag(.generic) {
544 guard_expr_type = if branch.cond.expr_type.has_flag(.option)
545 && !resolved.has_flag(.option) {
546 resolved.set_flag(.option)
547 } else if branch.cond.expr_type.has_flag(.result)
548 && !resolved.has_flag(.result) {
549 resolved.set_flag(.result)
550 } else {
551 resolved
552 }
553 }
554 } else {
555 resolved := g.resolved_if_guard_expr_type(branch.cond.expr, guard_expr_type)
556 if resolved != ast.void_type {
557 guard_expr_type = resolved
558 }
559 }
560 }
561 guard_expr_types[i] = guard_expr_type
562 if var_name == '' {
563 short_opt = true // we don't need a further tmp, so use the one we'll get later
564 var_name = g.new_tmp_var()
565 guard_vars[i] = var_name // for `else`
566 g.tmp_count--
567 if guard_expr_type.has_flag(.option) {
568 g.writeln('if (${var_name}.state == 0) {')
569 } else if guard_expr_type.has_flag(.result) {
570 g.writeln('if (!${var_name}.is_error) {')
571 }
572 } else {
573 g.write('if (${var_name} = ')
574 g.expr(branch.cond.expr)
575 if guard_expr_type.has_flag(.option) {
576 dot_or_ptr := if !guard_expr_type.has_flag(.option_mut_param_t) {
577 '.'
578 } else {
579 '-> '
580 }
581 g.writeln(', ${var_name}${dot_or_ptr}state == 0) {')
582 } else if guard_expr_type.has_flag(.result) {
583 g.writeln(', !${var_name}.is_error) {')
584 }
585 }
586 if short_opt || branch.cond.vars.len > 1 || branch.cond.vars[0].name != '_' {
587 base_type := g.base_type(guard_expr_type)
588 if short_opt {
589 cond_var_name := if branch.cond.vars[0].name == '_' {
590 '_dummy_${g.tmp_count + 1}'
591 } else {
592 branch.cond.vars[0].name
593 }
594 mut short_opt_is_auto_heap := false
595 if branch.stmts.len > 0 {
596 scope := g.file.scope.innermost(ast.Node(branch.stmts.last()).pos().pos)
597 if v := scope.find_var(branch.cond.vars[0].name) {
598 short_opt_is_auto_heap = v.is_auto_heap
599 }
600 }
601 if g.table.sym(branch.cond.expr_type).kind == .array_fixed {
602 g.writeln('\t${base_type} ${cond_var_name} = {0};')
603 g.write('\tmemcpy((${base_type}*)${cond_var_name}, &')
604 g.expr(branch.cond.expr)
605 g.writeln(', sizeof(${base_type}));')
606 g.write_if_guard_gc_pin(branch.scope, branch.cond.vars[0].name,
607 cond_var_name)
608 } else if short_opt_is_auto_heap {
609 g.write('\t${base_type}* ${cond_var_name} = HEAP(${base_type}, ')
610 g.expr(branch.cond.expr)
611 g.writeln(');')
612 g.write_if_guard_gc_pin(branch.scope, branch.cond.vars[0].name,
613 cond_var_name)
614 } else {
615 g.write('\t${base_type} ${cond_var_name} = ')
616 g.expr(branch.cond.expr)
617 g.writeln(';')
618 g.write_if_guard_gc_pin(branch.scope, branch.cond.vars[0].name,
619 cond_var_name)
620 }
621 } else {
622 mut is_auto_heap := false
623 if branch.stmts.len > 0 {
624 scope := g.file.scope.innermost(ast.Node(branch.stmts.last()).pos().pos)
625 if v := scope.find_var(branch.cond.vars[0].name) {
626 is_auto_heap = v.is_auto_heap
627 }
628 }
629 if branch.cond.vars.len == 1 {
630 left_var_name := c_name(branch.cond.vars[0].name)
631 dot_or_ptr := if !branch.cond.expr_type.has_flag(.option_mut_param_t) {
632 '.'
633 } else {
634 '-> '
635 }
636 guard_typ :=
637 g.unwrap_generic(branch.cond.expr_type.clear_option_and_result())
638 guard_is_heap_obj := g.table.final_sym(guard_typ).is_heap()
639 && !guard_typ.is_ptr()
640 if guard_is_heap_obj {
641 g.writeln('\t${base_type}* ${left_var_name} = (${base_type}*)${var_name}${dot_or_ptr}data;')
642 g.write_if_guard_gc_pin(branch.scope, branch.cond.vars[0].name,
643 left_var_name)
644 } else if is_auto_heap {
645 // Non-heap structs still need a dedicated heap copy when the guard value escapes.
646 // `@[heap]` structs already live behind the option data pointer and must not be copied.
647 g.writeln('\t${base_type}* ${left_var_name} = HEAP(${base_type}, *(${base_type}*)${var_name}${dot_or_ptr}data);')
648 g.write_if_guard_gc_pin(branch.scope, branch.cond.vars[0].name,
649 left_var_name)
650 } else if base_type.starts_with('Array_fixed') {
651 g.writeln('\t${base_type} ${left_var_name} = {0};')
652 g.writeln('memcpy(${left_var_name}, (${base_type}*)${var_name}.data, sizeof(${base_type}));')
653 g.write_if_guard_gc_pin(branch.scope, branch.cond.vars[0].name,
654 left_var_name)
655 } else {
656 expr_sym := g.table.sym(branch.cond.expr_type)
657 if expr_sym.info is ast.FnType {
658 g.write_fntype_decl(left_var_name, expr_sym.info,
659 guard_expr_type.nr_muls())
660 if guard_expr_type.nr_muls() == 0 {
661 g.writeln(' = *(${base_type}*)${var_name}${dot_or_ptr}data;')
662 } else {
663 g.writeln(' = (${base_type}*)${var_name}${dot_or_ptr}data;')
664 }
665 g.write_if_guard_gc_pin(branch.scope, branch.cond.vars[0].name,
666 left_var_name)
667 } else {
668 g.write('\t${base_type} ${left_var_name}')
669 g.writeln(' = *(${base_type}*)${var_name}${dot_or_ptr}data;')
670 g.write_if_guard_gc_pin(branch.scope, branch.cond.vars[0].name,
671 left_var_name)
672 }
673 }
674 } else if branch.cond.vars.len > 1 {
675 sym := g.table.sym(guard_expr_type)
676 if sym.info is ast.MultiReturn {
677 if sym.info.types.len == branch.cond.vars.len {
678 for vi, var in branch.cond.vars {
679 if var.name == '_' {
680 continue
681 }
682 var_typ := g.styp(sym.info.types[vi])
683 left_var_name := c_name(var.name)
684 if is_auto_heap {
685 g.writeln('\t${var_typ}* ${left_var_name} = (HEAP(${base_type}, *(${base_type}*)${var_name}.data).arg${vi});')
686 g.write_if_guard_gc_pin(branch.scope, var.name,
687 left_var_name)
688 } else {
689 g.writeln('\t${var_typ} ${left_var_name} = (*(${base_type}*)${var_name}.data).arg${vi};')
690 g.write_if_guard_gc_pin(branch.scope, var.name,
691 left_var_name)
692 }
693 }
694 }
695 }
696 }
697 }
698 }
699 } else {
700 if i == 0 && node.branches.len > 1 && !needs_tmp_var && needs_conds_order {
701 cond_var_name := g.new_tmp_var()
702 line := g.go_before_last_stmt().trim_space()
703 g.empty_line = true
704 g.write('bool ${cond_var_name} = ')
705 g.expr(branch.cond)
706 g.writeln(';')
707 branch_cond_var_names << cond_var_name
708 g.set_current_pos_as_last_stmt_pos()
709 g.writeln2(line, 'if (${cond_var_name}) {')
710 } else if i > 0 && branch_cond_var_names.len > 0 && !needs_tmp_var && needs_conds_order {
711 cond_var_name := g.new_tmp_var()
712 line := g.go_before_last_stmt()
713 g.empty_line = true
714 g.writeln('bool ${cond_var_name};')
715 branch_cond := branch_cond_var_names.join(' || ')
716 g.writeln('if (!(${branch_cond})) {')
717 g.set_current_pos_as_last_stmt_pos()
718 g.indent++
719 g.write('${cond_var_name} = ')
720 prev_is_autofree := g.is_autofree
721 g.is_autofree = false
722 g.expr(branch.cond)
723 g.is_autofree = prev_is_autofree
724 g.writeln(';')
725 g.indent--
726 g.writeln('}')
727 branch_cond_var_names << cond_var_name
728 g.set_current_pos_as_last_stmt_pos()
729 g.write(line)
730 g.writeln('if (${cond_var_name}) {')
731 } else {
732 mut no_needs_par := false
733 if branch.cond is ast.InfixExpr {
734 if branch.cond.op == .key_in && branch.cond.left !is ast.InfixExpr
735 && branch.cond.right is ast.ArrayInit {
736 no_needs_par = true
737 }
738 }
739 inside_interface_deref_old := g.inside_interface_deref
740 if !g.inside_interface_deref && branch.cond is ast.Ident
741 && g.table.is_interface_var(branch.cond.obj) {
742 g.inside_interface_deref = true
743 }
744 if no_needs_par {
745 g.write('if ')
746 } else {
747 g.write('if (')
748 }
749 g.expr(branch.cond)
750 if no_needs_par {
751 g.writeln(' {')
752 } else {
753 g.writeln(') {')
754 }
755 g.inside_interface_deref = inside_interface_deref_old
756 }
757 }
758 if needs_tmp_var {
759 prev_expected_cast_type := g.expected_cast_type
760 if node.is_expr && (g.table.sym(resolved_node_typ).kind == .sum_type
761 || resolved_node_typ.has_flag(.shared_f)) {
762 g.expected_cast_type = resolved_node_typ
763 }
764 g.stmts_with_tmp_var(branch.stmts, tmp)
765 g.write_defer_stmts(branch.scope, false, node.pos)
766 g.expected_cast_type = prev_expected_cast_type
767 if !is_else
768 && (branch.stmts.len > 0 && branch.stmts.last() !in [ast.Return, ast.BranchStmt]) {
769 g.writeln('\tgoto ${exit_label};')
770 }
771 } else {
772 // restore if_expr stmt header pos
773 stmt_pos := g.nth_stmt_pos(0)
774 g.stmts(branch.stmts)
775 g.write_defer_stmts(branch.scope, false, node.pos)
776 g.stmt_path_pos << stmt_pos
777 }
778 }
779 if node.branches.len > 0 {
780 g.writeln('}')
781 if !needs_tmp_var {
782 g.set_current_pos_as_last_stmt_pos()
783 }
784 }
785 for i, var_name in guard_vars {
786 if var_name == '' || guard_expr_types[i] == 0 || guard_else_uses_err[i]
787 || !guard_owns_error[i] {
788 continue
789 }
790 if node.has_else && i == node.branches.len - 2 {
791 continue
792 }
793 g.if_guard_error_cleanup(var_name, guard_expr_types[i])
794 }
795 if needs_tmp_var {
796 // Close the extra scopes opened between branches to isolate
797 // condition-evaluation variables from earlier branches' gotos.
798 for _ in 0 .. tmp_var_scope_count {
799 g.writeln('}')
800 }
801 if g.infix_left_var_name.len > 0 {
802 g.indent--
803 g.writeln('}')
804 }
805 g.empty_line = false
806 g.writeln('\t${exit_label}: {};')
807 g.set_current_pos_as_last_stmt_pos()
808 // A fn-returned fixed array tmp var is a wrapper struct; read its data through
809 // `.ret_arr` (mirrors how match exprs emit the tmp var, see match.v).
810 if g.if_match_tmp_is_fn_ret_arr or { false } {
811 g.write('${cur_line}${tmp}.ret_arr')
812 } else {
813 g.write('${cur_line}${tmp}')
814 }
815 }
816}
817