v / vlib / v2 / transformer / if.v
1543 lines · 1455 sloc · 43.28 KB · ddb021b9866c3b4523b746fa2f4c16a594f8bd89
Raw
1// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4
5module transformer
6
7import v2.ast
8import v2.pref
9import v2.token
10import v2.types
11
12fn (mut t Transformer) register_if_guard_lhs_payload_type(lhs []ast.Expr, payload_type types.Type) {
13 for expr in lhs {
14 match expr {
15 ast.Ident {
16 if expr.name != '_' {
17 t.remember_local_decl_type(expr.name, payload_type)
18 t.register_local_var_type(expr.name, payload_type)
19 if expr.pos.is_valid() {
20 t.register_synth_type(expr.pos, payload_type)
21 }
22 }
23 }
24 ast.ModifierExpr {
25 if expr.expr is ast.Ident {
26 ident := expr.expr as ast.Ident
27 if ident.name != '_' {
28 t.remember_local_decl_type(ident.name, payload_type)
29 t.register_local_var_type(ident.name, payload_type)
30 if ident.pos.is_valid() {
31 t.register_synth_type(ident.pos, payload_type)
32 }
33 }
34 }
35 }
36 else {}
37 }
38 }
39}
40
41// try_expand_if_guard_assign_stmts expands an if-guard assignment to multiple statements.
42// Transforms: x := if r := map[key] { r } else { default }
43// Into (for maps):
44// x := if key in map { map[key] } else { default }
45// Into (for other cases):
46// r := expr
47// x := if r { r } else { default }
48fn (mut t Transformer) try_expand_if_guard_assign_stmts(stmt ast.AssignStmt) ?[]ast.Stmt {
49 // Check for single assignment with IfExpr RHS
50 if stmt.rhs.len != 1 || stmt.lhs.len != 1 {
51 return none
52 }
53 rhs_expr := stmt.rhs[0]
54 // Check if RHS is an IfExpr with IfGuardExpr condition
55 if rhs_expr !is ast.IfExpr {
56 return none
57 }
58 if_expr := rhs_expr as ast.IfExpr
59 if if_expr.cond !is ast.IfGuardExpr {
60 return none
61 }
62 guard := if_expr.cond as ast.IfGuardExpr
63
64 // Extract guard variable name from LHS of the guard assignment
65 mut guard_var_name := ''
66 for lhs_expr in guard.stmt.lhs {
67 if lhs_expr is ast.Ident {
68 guard_var_name = lhs_expr.name
69 break
70 } else if lhs_expr is ast.ModifierExpr {
71 if lhs_expr.expr is ast.Ident {
72 guard_var_name = lhs_expr.expr.name
73 break
74 }
75 }
76 }
77 if guard_var_name == '' || guard.stmt.rhs.len == 0 {
78 return none
79 }
80
81 guard_rhs := guard.stmt.rhs[0]
82 if t.expr_wrapper_type_for_or(guard_rhs) != none || t.expr_returns_result(guard_rhs)
83 || t.expr_returns_option(guard_rhs) {
84 return none
85 }
86 synth_pos := t.next_synth_pos()
87
88 // Check if RHS is a map index expression - use "key in map" condition
89 if guard_rhs is ast.IndexExpr {
90 if _ := t.get_map_type_for_expr(guard_rhs.lhs) {
91 // This is a map access - transform using "key in map" check
92 // x := if key in map { map[key] } else { default }
93 key_in_map := t.make_infix_expr_at(.key_in, guard_rhs.expr, guard_rhs.lhs,
94 guard_rhs.pos)
95
96 // Build new stmts for the then-branch: guard_var := map[key]; <original stmts>
97 mut new_then_stmts := []ast.Stmt{cap: if_expr.stmts.len + 1}
98 new_then_stmts << ast.AssignStmt{
99 op: .decl_assign
100 lhs: guard.stmt.lhs
101 rhs: guard.stmt.rhs
102 pos: guard.stmt.pos
103 }
104 for s in if_expr.stmts {
105 new_then_stmts << s
106 }
107
108 modified_if := ast.IfExpr{
109 cond: key_in_map
110 stmts: new_then_stmts
111 else_expr: if_expr.else_expr
112 pos: synth_pos
113 }
114 // Propagate the original IfExpr type to the synthesized node
115 if orig_type := t.get_expr_type(ast.Expr(if_expr)) {
116 t.register_synth_type(synth_pos, orig_type)
117 }
118
119 return [
120 ast.Stmt(ast.AssignStmt{
121 op: stmt.op
122 lhs: stmt.lhs
123 rhs: [ast.Expr(modified_if)]
124 pos: stmt.pos
125 }),
126 ]
127 }
128 }
129
130 // Non-map case: use original approach
131 mut stmts := []ast.Stmt{}
132
133 // 1. Guard variable declaration: r := expr
134 stmts << ast.AssignStmt{
135 op: .decl_assign
136 lhs: guard.stmt.lhs
137 rhs: guard.stmt.rhs
138 pos: guard.stmt.pos
139 }
140
141 // 2. Modified if expression with guard variable as condition
142 // x := if r { r } else { default }
143 // Use synthesized position to avoid inheriting wrong type from original IfGuardExpr
144 guard_ident := ast.Ident{
145 name: guard_var_name
146 pos: synth_pos
147 }
148 modified_if := ast.IfExpr{
149 cond: guard_ident
150 stmts: if_expr.stmts
151 else_expr: if_expr.else_expr
152 pos: synth_pos
153 }
154 // Propagate the original IfExpr type to the synthesized node
155 if orig_type := t.get_expr_type(ast.Expr(if_expr)) {
156 t.register_synth_type(synth_pos, orig_type)
157 }
158 stmts << ast.AssignStmt{
159 op: stmt.op
160 lhs: stmt.lhs
161 rhs: [ast.Expr(modified_if)]
162 pos: stmt.pos
163 }
164
165 return stmts
166}
167
168// try_expand_if_guard_stmt expands a statement-level if-guard.
169// Transforms: if attr := table[name] { use(attr) }
170// Into: if (table[name]) { attr := table[name]; use(attr) }
171// For Result types: if attr := fn_call() { use(attr) }
172// Into: { _tmp := fn_call(); if (!_tmp.is_error) { attr := *(_tmp.data); use(attr) } else { else_body } }
173fn (mut t Transformer) if_guard_else_expr_with_err(else_expr ast.Expr, temp_ident ast.Ident) ast.Expr {
174 if else_expr is ast.IfExpr {
175 else_if := else_expr as ast.IfExpr
176 if else_if.cond is ast.EmptyExpr {
177 mut else_stmts := []ast.Stmt{}
178 else_stmts << ast.AssignStmt{
179 op: .decl_assign
180 lhs: [ast.Expr(ast.Ident{
181 name: 'err'
182 })]
183 rhs: [
184 t.synth_selector(temp_ident, 'err', types.Type(types.Struct{
185 name: 'IError'
186 })),
187 ]
188 }
189 else_stmts << else_if.stmts
190 return ast.IfExpr{
191 cond: else_if.cond
192 stmts: t.transform_stmts(else_stmts)
193 else_expr: t.transform_expr(else_if.else_expr)
194 pos: else_if.pos
195 }
196 }
197 }
198 return t.transform_expr(else_expr)
199}
200
201fn (mut t Transformer) try_expand_if_guard_stmt(stmt ast.ExprStmt) ?[]ast.Stmt {
202 // Check if this is an IfExpr with IfGuardExpr condition
203 if stmt.expr !is ast.IfExpr {
204 return none
205 }
206 if_expr := stmt.expr as ast.IfExpr
207 if if_expr.cond !is ast.IfGuardExpr {
208 return none
209 }
210 guard := if_expr.cond as ast.IfGuardExpr
211
212 // The entire if-guard expansion is at statement position. Suppress value-
213 // position IfExpr hoisting (`_if_t<N> := if ...`) for nested else-if chains
214 // so they don't generate mistyped temps for what is really a statement.
215 saved_skip := t.skip_if_value_lowering
216 t.skip_if_value_lowering = true
217 defer {
218 t.skip_if_value_lowering = saved_skip
219 }
220
221 if guard.stmt.rhs.len == 0 {
222 return none
223 }
224
225 mut rhs := guard.stmt.rhs[0]
226 mut guard_prefix_stmts := []ast.Stmt{}
227 if t.expr_has_or_expr(rhs) {
228 rhs = t.extract_or_expr(rhs, mut guard_prefix_stmts)
229 }
230 synth_pos := t.next_synth_pos()
231
232 // Check if RHS is a call that returns Result/Option
233 // First try expression-based lookup (works for both function and method calls)
234 mut is_result := t.expr_returns_result(rhs)
235 mut is_option := t.expr_returns_option(rhs)
236
237 // Fallback to function name lookup for simple function calls
238 if !is_result && !is_option {
239 fn_name := t.get_call_fn_name(rhs)
240 is_result = fn_name != '' && t.fn_returns_result(fn_name)
241 is_option = fn_name != '' && t.fn_returns_option(fn_name)
242 }
243 if !is_result && !is_option {
244 if ret_type := t.fn_pointer_call_return_type(rhs) {
245 is_result = ret_type is types.ResultType || ret_type.name().starts_with('!')
246 is_option = ret_type is types.OptionType || ret_type.name().starts_with('?')
247 }
248 }
249
250 if is_result || is_option {
251 // Handle Result/Option if-guard
252 // Generate: { _tmp := call(); if (!_tmp.is_error) { attr := extractValue(_tmp); body } else { else } }
253 temp_name := t.gen_temp_name()
254 temp_pos := synth_pos
255 if_pos := t.next_synth_pos()
256 temp_ident := ast.Ident{
257 name: temp_name
258 pos: temp_pos
259 }
260
261 mut stmts := []ast.Stmt{}
262 mut data_type := types.Type(types.voidptr_)
263 for prefix_stmt in guard_prefix_stmts {
264 stmts << prefix_stmt
265 }
266
267 // Register temp variable type so cleanc can look up its type for .data unwrapping
268 if wrapper_type := t.expr_wrapper_type_for_or(rhs) {
269 t.register_temp_var(temp_name, wrapper_type)
270 t.register_synth_type(temp_pos, wrapper_type)
271 if wrapper_type is types.ResultType {
272 data_type = wrapper_type.base_type
273 is_result = true
274 is_option = false
275 } else if wrapper_type is types.OptionType {
276 data_type = wrapper_type.base_type
277 is_result = false
278 is_option = true
279 }
280 } else if wrapper_type := t.get_expr_type(rhs) {
281 t.register_temp_var(temp_name, wrapper_type)
282 t.register_synth_type(temp_pos, wrapper_type)
283 if wrapper_type is types.ResultType {
284 data_type = wrapper_type.base_type
285 } else if wrapper_type is types.OptionType {
286 data_type = wrapper_type.base_type
287 }
288 } else if wrapper_type := t.fn_pointer_call_return_type(rhs) {
289 t.register_temp_var(temp_name, wrapper_type)
290 t.register_synth_type(temp_pos, wrapper_type)
291 if wrapper_type is types.ResultType {
292 data_type = wrapper_type.base_type
293 } else if wrapper_type is types.OptionType {
294 data_type = wrapper_type.base_type
295 }
296 }
297
298 // 1. _tmp := call()
299 transformed_rhs := t.transform_expr(rhs)
300 if t.pending_stmts.len > 0 {
301 for ps in t.pending_stmts {
302 stmts << ps
303 }
304 t.pending_stmts.clear()
305 }
306 stmts << ast.AssignStmt{
307 op: .decl_assign
308 lhs: [ast.Expr(temp_ident)]
309 rhs: [transformed_rhs]
310 pos: temp_pos
311 }
312
313 // 2. Build condition: !_tmp.is_error (for Result) or _tmp.state == 0 (for Option)
314 success_cond := if is_result {
315 ast.Expr(ast.PrefixExpr{
316 op: .not
317 expr: t.synth_selector(temp_ident, 'is_error', types.Type(types.bool_))
318 })
319 } else {
320 t.make_infix_expr(.eq, t.synth_selector(temp_ident, 'state', types.Type(types.int_)),
321 t.make_number_expr('0'))
322 }
323
324 // 3. Build if-body: attr := _tmp.data; original_body
325 // cleanc handles the cast and dereference when it sees .data on Result type
326 mut if_stmts := []ast.Stmt{}
327 data_access := t.synth_selector(temp_ident, 'data', data_type)
328 t.register_if_guard_lhs_payload_type(guard.stmt.lhs, data_type)
329 if_stmts << ast.AssignStmt{
330 op: .decl_assign
331 lhs: guard.stmt.lhs
332 rhs: [data_access]
333 pos: guard.stmt.pos
334 }
335 for s in if_expr.stmts {
336 if_stmts << s
337 }
338
339 // 4. Build the if expression
340 transformed_if_stmts := t.transform_stmts(if_stmts)
341 modified_if := ast.IfExpr{
342 cond: success_cond
343 stmts: transformed_if_stmts
344 else_expr: t.if_guard_else_expr_with_err(if_expr.else_expr, temp_ident)
345 pos: if_pos
346 }
347 // Propagate the original IfExpr type to the synthesized node
348 if orig_type := t.get_expr_type(ast.Expr(if_expr)) {
349 t.register_synth_type(if_pos, orig_type)
350 }
351 stmts << ast.ExprStmt{
352 expr: modified_if
353 }
354
355 return stmts
356 }
357
358 // Non-Result/Option if-guard
359 // Check if RHS is an index expression (map or array lookup)
360 // For map lookups: if x := map[key] { use(x) }
361 // Transform to: { _tmp := map__get_check(&map, &key); if (_tmp != nil) { x := *_tmp; use(x) } }
362 // For array lookups: if x := arr[i] { use(x) }
363 // Transform to: if (i < arr.len) { x := arr[i]; use(x) }
364 if rhs is ast.IndexExpr {
365 if map_expr_typ := t.get_expr_type(rhs.lhs) {
366 if map_type := t.unwrap_map_type(map_expr_typ) {
367 // This is a map lookup - use map__get_check pattern
368 temp_pos := synth_pos
369 if_pos := t.next_synth_pos()
370 temp_name := t.gen_temp_name()
371 temp_ident := ast.Ident{
372 name: temp_name
373 pos: temp_pos
374 }
375
376 // Register temp variable type: map__get_check returns pointer to value type
377 pointer_type := types.Type(types.Pointer{
378 base_type: map_type.value_type
379 })
380 t.register_temp_var(temp_name, pointer_type)
381 t.register_synth_type(temp_pos, pointer_type)
382
383 mut prefix_stmts := []ast.Stmt{}
384 map_arg := if t.is_pointer_type(map_expr_typ) {
385 t.transform_expr(rhs.lhs)
386 } else {
387 t.addr_of_with_prefix_temp(rhs.lhs, map_expr_typ, mut prefix_stmts)
388 }
389 key_arg := t.addr_of_with_prefix_temp(rhs.expr, map_type.key_type, mut prefix_stmts)
390
391 get_check_call := ast.CallExpr{
392 lhs: ast.Ident{
393 name: 'map__get_check'
394 }
395 args: [
396 map_arg,
397 t.voidptr_cast(key_arg),
398 ]
399 }
400 temp_assign := ast.AssignStmt{
401 op: .decl_assign
402 lhs: [ast.Expr(temp_ident)]
403 rhs: [ast.Expr(get_check_call)]
404 pos: synth_pos
405 }
406
407 // Build if body: guard_var := *_tmp; original_body
408 // Use typed_deref to cast voidptr to correct pointer type
409 // before dereference (map__get_check returns voidptr)
410 mut if_stmts := []ast.Stmt{}
411 deref_tmp := t.typed_deref(temp_ident, map_type.value_type)
412 if_stmts << ast.AssignStmt{
413 op: .decl_assign
414 lhs: guard.stmt.lhs
415 rhs: [deref_tmp]
416 pos: guard.stmt.pos
417 }
418 for s in if_expr.stmts {
419 if_stmts << s
420 }
421
422 // Build condition: _tmp != nil
423 null_check := t.make_infix_expr(.ne, ast.Expr(temp_ident), ast.Expr(ast.Ident{
424 name: 'nil'
425 }))
426
427 // Build the if expression
428 modified_if := ast.IfExpr{
429 cond: null_check
430 stmts: t.transform_stmts(if_stmts)
431 else_expr: t.transform_expr(if_expr.else_expr)
432 pos: if_pos
433 }
434 // Propagate the original IfExpr type to the synthesized node
435 if orig_type := t.get_expr_type(ast.Expr(if_expr)) {
436 t.register_synth_type(if_pos, orig_type)
437 }
438
439 prefix_stmts << ast.Stmt(temp_assign)
440 prefix_stmts << ast.Stmt(ast.ExprStmt{
441 expr: modified_if
442 })
443 return prefix_stmts
444 }
445 }
446
447 // This is an array lookup - generate bounds check: index < array.len
448 bounds_check := t.make_infix_expr_at(.lt, t.transform_expr(rhs.expr), t.synth_selector(t.transform_expr(rhs.lhs),
449 'len', types.Type(types.int_)), rhs.pos)
450
451 // Build if body: guard_var := arr[i]; original_body
452 mut if_stmts := []ast.Stmt{}
453 if_stmts << ast.AssignStmt{
454 op: .decl_assign
455 lhs: guard.stmt.lhs
456 rhs: guard.stmt.rhs
457 pos: guard.stmt.pos
458 }
459 for s in if_expr.stmts {
460 if_stmts << s
461 }
462
463 // Build the if expression
464 modified_if := ast.IfExpr{
465 cond: bounds_check
466 stmts: t.transform_stmts(if_stmts)
467 else_expr: t.transform_expr(if_expr.else_expr)
468 pos: synth_pos
469 }
470 // Propagate the original IfExpr type to the synthesized node
471 if orig_type := t.get_expr_type(ast.Expr(if_expr)) {
472 t.register_synth_type(synth_pos, orig_type)
473 }
474
475 return [
476 ast.Stmt(ast.ExprStmt{
477 expr: modified_if
478 }),
479 ]
480 }
481
482 rhs_expr := t.transform_expr(rhs)
483
484 // For map lookups returning arrays, generate:
485 // { arr := map[key]; if (arr.data != nil) { ... } }
486 // This handles the case where "key exists" = "non-nil data"
487 map_returns_array := t.is_map_lookup_returning_array(rhs)
488
489 // Prepend guard variable assignment to stmts (inside the if body)
490 guard_assign := ast.AssignStmt{
491 op: .decl_assign
492 lhs: guard.stmt.lhs
493 rhs: guard.stmt.rhs
494 pos: guard.stmt.pos
495 }
496 mut new_stmts := []ast.Stmt{cap: if_expr.stmts.len + 1}
497 new_stmts << guard_assign
498 for s in if_expr.stmts {
499 new_stmts << s
500 }
501
502 // Determine the condition to use
503 mut cond_expr := ast.Expr(rhs_expr)
504 if map_returns_array {
505 // For arrays, check .data != nil (indicates key existed in map)
506 // Extract guard variable name
507 mut guard_var_name := ''
508 for lhs_expr in guard.stmt.lhs {
509 if lhs_expr is ast.Ident {
510 guard_var_name = lhs_expr.name
511 break
512 }
513 }
514 // Check if it's a blank identifier - if so, use a temp variable
515 is_blank := guard_var_name == '_'
516 if is_blank {
517 guard_var_name = t.gen_temp_name()
518 }
519 if guard_var_name != '' {
520 // Generate: guard_var.data != nil
521 // But we need to declare the variable first, so we generate:
522 // { arr := map[key]; if (arr.data) { ... } }
523 // Put assignment before the if, then use arr.data as condition
524 // When blank, use temp variable instead of _
525 temp_lhs := if is_blank {
526 [
527 ast.Expr(ast.Ident{
528 name: guard_var_name
529 pos: synth_pos
530 }),
531 ]
532 } else {
533 guard.stmt.lhs
534 }
535 temp_assign := ast.AssignStmt{
536 op: .decl_assign
537 lhs: temp_lhs
538 rhs: guard.stmt.rhs
539 pos: guard.stmt.pos
540 }
541 // Remove the guard_assign from new_stmts since we're putting it before the if
542 new_stmts = []ast.Stmt{cap: if_expr.stmts.len}
543 for s in if_expr.stmts {
544 new_stmts << s
545 }
546 // Use arr.data as condition
547 cond_expr = t.synth_selector(ast.Ident{
548 name: guard_var_name
549 pos: synth_pos
550 }, 'data', types.Type(types.voidptr_))
551 modified_if := ast.IfExpr{
552 cond: cond_expr
553 stmts: t.transform_stmts(new_stmts)
554 else_expr: t.transform_expr(if_expr.else_expr)
555 pos: synth_pos
556 }
557 // Propagate the original IfExpr type to the synthesized node
558 if orig_type := t.get_expr_type(ast.Expr(if_expr)) {
559 t.register_synth_type(synth_pos, orig_type)
560 }
561 return [
562 ast.Stmt(temp_assign),
563 ast.Stmt(ast.ExprStmt{
564 expr: modified_if
565 }),
566 ]
567 }
568 }
569
570 modified_if := ast.IfExpr{
571 cond: cond_expr
572 stmts: t.transform_stmts(new_stmts)
573 else_expr: t.transform_expr(if_expr.else_expr)
574 pos: synth_pos
575 }
576 // Propagate the original IfExpr type to the synthesized node
577 if orig_type := t.get_expr_type(ast.Expr(if_expr)) {
578 t.register_synth_type(synth_pos, orig_type)
579 }
580
581 return [ast.Stmt(ast.ExprStmt{
582 expr: modified_if
583 })]
584}
585
586// try_expand_return_if_expr handles IfExpr in return statements
587// Transforms: return if cond { a } else { b }
588// Into: if cond { return a } else { return b }
589fn (mut t Transformer) try_expand_return_if_expr(stmt ast.ReturnStmt) ?[]ast.Stmt {
590 // Only handle single-expression returns with IfExpr
591 if stmt.exprs.len != 1 {
592 return none
593 }
594 if_expr := stmt.exprs[0]
595 if if_expr !is ast.IfExpr {
596 return none
597 }
598 ie := if_expr as ast.IfExpr
599 // Must have an else branch to be a valid expression form
600 if ie.else_expr is ast.EmptyExpr {
601 return none
602 }
603 // Transform into if-statement with return in each branch
604 return t.expand_return_if_expr(ie)
605}
606
607// try_expand_return_match_expr handles MatchExpr in return statements.
608// It first lowers the match to an if-expression, then emits branch-local returns so
609// optional/result and aggregate return ABI handling is applied independently per branch.
610fn (mut t Transformer) try_expand_return_match_expr(stmt ast.ReturnStmt) ?[]ast.Stmt {
611 if stmt.exprs.len != 1 {
612 return none
613 }
614 match_expr := stmt.exprs[0]
615 if match_expr !is ast.MatchExpr {
616 return none
617 }
618 match_expr_ast := match_expr as ast.MatchExpr
619 return_sumtype_info := t.current_return_sumtype_wrap_info() or { ConcreteSumtypeWrapInfo{} }
620 should_wrap_return_sumtype := return_sumtype_info.name != ''
621 skip_return_sumtype_wrap := (t.cur_fn_returns_option || t.cur_fn_returns_result)
622 && t.return_expr_should_skip_sumtype_wrap(match_expr)
623 old_wrap := t.sumtype_return_wrap
624 if should_wrap_return_sumtype && !skip_return_sumtype_wrap {
625 t.sumtype_return_wrap = return_sumtype_info.name
626 }
627 old_preserve_match_branch_value := t.preserve_match_branch_value
628 t.preserve_match_branch_value = true
629 transformed := t.transform_match_expr(match_expr_ast)
630 t.preserve_match_branch_value = old_preserve_match_branch_value
631 t.sumtype_return_wrap = old_wrap
632 if transformed is ast.IfExpr {
633 ie := transformed as ast.IfExpr
634 return t.expand_return_match_if_expr(ie)
635 }
636 return none
637}
638
639// expand_return_if_expr recursively expands an if-expression into if-statements with returns
640fn (mut t Transformer) expand_return_if_expr(ie ast.IfExpr) []ast.Stmt {
641 return t.expand_return_if_expr_with_options(ie, false)
642}
643
644fn (mut t Transformer) expand_return_match_if_expr(ie ast.IfExpr) []ast.Stmt {
645 return t.expand_return_if_expr_with_options(ie, true)
646}
647
648fn (mut t Transformer) return_stmts_for_branch_expr(expr ast.Expr, allow_empty_else bool) []ast.Stmt {
649 if expr is ast.IfExpr {
650 return t.expand_return_if_expr_with_options(expr, allow_empty_else)
651 }
652 if t.is_void_call_expr(expr) {
653 return [
654 ast.Stmt(ast.ExprStmt{
655 expr: expr
656 }),
657 ]
658 }
659 if expr is ast.UnsafeExpr && expr.stmts.len > 0 {
660 last := expr.stmts[expr.stmts.len - 1]
661 if last is ast.ExprStmt && last.expr is ast.IfExpr {
662 mut stmts := []ast.Stmt{cap: expr.stmts.len}
663 if expr.stmts.len > 1 {
664 for stmt in expr.stmts[..expr.stmts.len - 1] {
665 stmts << stmt
666 }
667 }
668 stmts << t.expand_return_if_expr_with_options(last.expr, allow_empty_else)
669 return stmts
670 }
671 }
672 return_expr := if info := t.current_return_sumtype_wrap_info() {
673 t.wrap_sumtype_value_transformed_with_variants(expr, info.name, info.variants) or { expr }
674 } else {
675 expr
676 }
677 return [
678 ast.Stmt(ast.ReturnStmt{
679 exprs: [return_expr]
680 }),
681 ]
682}
683
684fn (mut t Transformer) expand_return_if_expr_with_options(ie ast.IfExpr, allow_empty_else bool) []ast.Stmt {
685 // Build the then-branch statements with return
686 mut then_stmts := []ast.Stmt{}
687 for i, s in ie.stmts {
688 if i == ie.stmts.len - 1 {
689 // Last statement - wrap in return if it's an expression
690 if s is ast.ExprStmt {
691 then_stmts << t.return_stmts_for_branch_expr(s.expr, allow_empty_else)
692 } else {
693 then_stmts << s
694 }
695 } else {
696 then_stmts << s
697 }
698 }
699
700 // Build the else-branch
701 mut else_expr := ast.empty_expr
702 if ie.else_expr is ast.IfExpr {
703 else_ie := ie.else_expr as ast.IfExpr
704 // Check if this is a pure else block (no condition)
705 if else_ie.cond is ast.EmptyExpr {
706 // Pure else - create an if block with just the else stmts that include return
707 mut else_stmts := []ast.Stmt{}
708 for i, s in else_ie.stmts {
709 if i == else_ie.stmts.len - 1 {
710 if s is ast.ExprStmt {
711 else_stmts << t.return_stmts_for_branch_expr(s.expr, allow_empty_else)
712 } else {
713 else_stmts << s
714 }
715 } else {
716 else_stmts << s
717 }
718 }
719 else_expr = ast.Expr(ast.IfExpr{
720 cond: ast.empty_expr
721 stmts: else_stmts
722 else_expr: ast.empty_expr
723 })
724 } else {
725 // else-if chain - recursively expand
726 expanded_else := t.expand_return_if_expr_with_options(else_ie, allow_empty_else)
727 // The expanded result should be an ExprStmt containing an IfExpr
728 if expanded_else.len > 0 && expanded_else[0] is ast.ExprStmt {
729 expr_stmt := expanded_else[0] as ast.ExprStmt
730 else_expr = expr_stmt.expr
731 } else {
732 // Fallback: wrap in else block
733 else_expr = ast.Expr(ast.IfExpr{
734 cond: ast.empty_expr
735 stmts: expanded_else
736 else_expr: ast.empty_expr
737 })
738 }
739 }
740 } else if ie.else_expr is ast.EmptyExpr && allow_empty_else {
741 else_expr = ast.empty_expr
742 } else {
743 // Simple else - wrap the expression in return
744 else_expr = ast.Expr(ast.IfExpr{
745 cond: ast.empty_expr
746 stmts: t.return_stmts_for_branch_expr(ie.else_expr, allow_empty_else)
747 else_expr: ast.empty_expr
748 })
749 }
750
751 // Create the transformed if expression (used as statement)
752 transformed_if := ast.IfExpr{
753 cond: ie.cond
754 stmts: then_stmts
755 else_expr: else_expr
756 }
757 // Wrap in ExprStmt to make it a valid statement
758 return [ast.Stmt(ast.ExprStmt{
759 expr: transformed_if
760 })]
761}
762
763// try_expand_if_expr_assign_stmts handles assignment with IfExpr RHS.
764// Transforms: lhs = if cond { a } else { b }
765// Into: if cond { lhs = a } else { lhs = b }
766fn (mut t Transformer) try_expand_if_expr_assign_stmts(stmt ast.AssignStmt) ?[]ast.Stmt {
767 // Only handle simple assignment for now.
768 if stmt.op != .assign || stmt.lhs.len != 1 || stmt.rhs.len != 1 {
769 return none
770 }
771 rhs := stmt.rhs[0]
772 if rhs !is ast.IfExpr {
773 return none
774 }
775 ie := rhs as ast.IfExpr
776 // Expression-form if must have else branch.
777 if ie.else_expr is ast.EmptyExpr {
778 return none
779 }
780 return t.expand_assign_if_expr(stmt.lhs[0], ie)
781}
782
783// expand_assign_if_expr recursively expands an if-expression into if-statements
784// that perform assignment in each branch.
785fn (mut t Transformer) expand_assign_if_expr(lhs ast.Expr, ie ast.IfExpr) []ast.Stmt {
786 mut then_stmts := []ast.Stmt{}
787 for i, s in ie.stmts {
788 if i == ie.stmts.len - 1 && s is ast.ExprStmt {
789 then_stmts << ast.AssignStmt{
790 op: .assign
791 lhs: [lhs]
792 rhs: [s.expr]
793 }
794 } else {
795 then_stmts << s
796 }
797 }
798
799 mut else_expr := ast.empty_expr
800 if ie.else_expr is ast.IfExpr {
801 else_ie := ie.else_expr as ast.IfExpr
802 // else { ... }
803 if else_ie.cond is ast.EmptyExpr {
804 mut else_stmts := []ast.Stmt{}
805 for i, s in else_ie.stmts {
806 if i == else_ie.stmts.len - 1 && s is ast.ExprStmt {
807 else_stmts << ast.AssignStmt{
808 op: .assign
809 lhs: [lhs]
810 rhs: [s.expr]
811 }
812 } else {
813 else_stmts << s
814 }
815 }
816 else_expr = ast.Expr(ast.IfExpr{
817 cond: ast.empty_expr
818 stmts: else_stmts
819 else_expr: ast.empty_expr
820 })
821 } else {
822 expanded_else := t.expand_assign_if_expr(lhs, else_ie)
823 if expanded_else.len > 0 {
824 first_else_stmt := expanded_else[0]
825 if first_else_stmt is ast.ExprStmt {
826 else_expr = (first_else_stmt as ast.ExprStmt).expr
827 } else {
828 else_expr = ast.Expr(ast.IfExpr{
829 cond: ast.empty_expr
830 stmts: expanded_else
831 else_expr: ast.empty_expr
832 })
833 }
834 } else {
835 else_expr = ast.Expr(ast.IfExpr{
836 cond: ast.empty_expr
837 stmts: expanded_else
838 else_expr: ast.empty_expr
839 })
840 }
841 }
842 } else {
843 else_expr = ast.Expr(ast.IfExpr{
844 cond: ast.empty_expr
845 stmts: [
846 ast.Stmt(ast.AssignStmt{
847 op: .assign
848 lhs: [lhs]
849 rhs: [ie.else_expr]
850 }),
851 ]
852 else_expr: ast.empty_expr
853 })
854 }
855
856 transformed_if := ast.IfExpr{
857 cond: ie.cond
858 stmts: then_stmts
859 else_expr: else_expr
860 }
861 return [ast.Stmt(ast.ExprStmt{
862 expr: transformed_if
863 })]
864}
865
866// if_expr_is_value returns true if the IfExpr produces a value (i.e., the body's
867// last statement is an ExprStmt whose expression has a non-void type).
868// This distinguishes value-position ifs from statement-position ifs that happen
869// to have an else branch.
870fn (t &Transformer) if_expr_is_value(ie ast.IfExpr) bool {
871 if ie.stmts.len == 0 {
872 return false
873 }
874 last := ie.stmts[ie.stmts.len - 1]
875 if last !is ast.ExprStmt {
876 return false
877 }
878 // Check that the expression actually produces a non-void value.
879 // Statement-form ifs may end with void function calls or postfix ops
880 // used for side effects (i++, println(...), etc).
881 last_expr := (last as ast.ExprStmt).expr
882 if typ := t.get_expr_type(last_expr) {
883 type_name := t.type_to_c_name(typ)
884 if type_name == '' || type_name == 'void' {
885 return false
886 }
887 }
888 return true
889}
890
891// try_expand_tuple_if_assign_stmts expands `x, y, w, h := if cond { a, b, c, d } else { e, f, g, h }`
892// into individual declarations + if-statement with assignments:
893// x := 0; y := 0; w := 0; h := 0;
894// if cond { x = a; y = b; w = c; h = d } else { x = e; y = f; w = g; h = h2 }
895fn (mut t Transformer) try_expand_tuple_if_assign_stmts(stmt ast.AssignStmt) ?[]ast.Stmt {
896 if stmt.op != .decl_assign {
897 return none
898 }
899 // Must have tuple LHS
900 is_tuple_lhs := stmt.lhs.len > 1 || (stmt.lhs.len == 1 && stmt.lhs[0] is ast.Tuple)
901 if !is_tuple_lhs {
902 return none
903 }
904 // Must have single IfExpr RHS
905 if stmt.rhs.len != 1 {
906 return none
907 }
908 if stmt.rhs[0] !is ast.IfExpr {
909 // Check if RHS might be Tuple containing an IfExpr
910 return none
911 }
912 tuple_lhs := if stmt.lhs.len > 1 {
913 stmt.lhs
914 } else if stmt.lhs[0] is ast.Tuple {
915 (stmt.lhs[0] as ast.Tuple).exprs
916 } else {
917 stmt.lhs
918 }
919 n := tuple_lhs.len
920 if n == 0 {
921 return none
922 }
923 if_expr := stmt.rhs[0] as ast.IfExpr
924 // Extract tuple values from then branch
925 then_stmts := t.build_tuple_branch_assigns(if_expr.stmts, tuple_lhs, n, stmt.pos) or {
926 return none
927 }
928 // Process else branch
929 mut else_expr := ast.Expr(ast.empty_expr)
930 if if_expr.else_expr is ast.IfExpr {
931 else_if := if_expr.else_expr as ast.IfExpr
932 if else_if.cond is ast.EmptyExpr {
933 // Plain else block
934 else_stmts := t.build_tuple_branch_assigns(else_if.stmts, tuple_lhs, n, stmt.pos) or {
935 return none
936 }
937 else_expr = ast.IfExpr{
938 stmts: else_stmts
939 pos: else_if.pos
940 }
941 } else {
942 // else-if chain
943 else_if_stmts := t.build_tuple_branch_assigns(else_if.stmts, tuple_lhs, n, stmt.pos) or {
944 return none
945 }
946 else_expr = ast.IfExpr{
947 cond: else_if.cond
948 stmts: else_if_stmts
949 else_expr: else_if.else_expr
950 pos: else_if.pos
951 }
952 }
953 }
954 // Build result: declarations for each variable, then the if-statement
955 mut result := []ast.Stmt{cap: n + 1}
956 for lhs_expr in tuple_lhs {
957 result << ast.Stmt(ast.AssignStmt{
958 op: .decl_assign
959 lhs: [lhs_expr]
960 rhs: [ast.Expr(ast.BasicLiteral{
961 kind: .number
962 value: '0'
963 })]
964 pos: stmt.pos
965 })
966 }
967 result << ast.Stmt(ast.ExprStmt{
968 expr: ast.IfExpr{
969 cond: if_expr.cond
970 stmts: then_stmts
971 else_expr: else_expr
972 pos: if_expr.pos
973 }
974 })
975 return result
976}
977
978// extract_branch_tuple_values extracts N values from a branch's last statement.
979// The last statement should be an ExprStmt containing either a Tuple or a single expression.
980fn (t &Transformer) extract_branch_tuple_values(stmts []ast.Stmt, n int) ?[]ast.Expr {
981 if stmts.len == 0 {
982 return none
983 }
984 // Find last non-empty statement (skip trailing EmptyStmt)
985 mut last_idx := stmts.len - 1
986 for last_idx >= 0 && stmts[last_idx] is ast.EmptyStmt {
987 last_idx--
988 }
989 if last_idx < 0 {
990 return none
991 }
992 last := stmts[last_idx]
993 if last !is ast.ExprStmt {
994 return none
995 }
996 last_expr := (last as ast.ExprStmt).expr
997 if last_expr is ast.Tuple {
998 if last_expr.exprs.len == n {
999 return last_expr.exprs
1000 }
1001 return none
1002 }
1003 // Single expression, only valid for n == 1
1004 if n == 1 {
1005 return [last_expr]
1006 }
1007 return none
1008}
1009
1010// build_tuple_branch_assigns builds assignment statements for a tuple if-expression branch.
1011// If the branch ends with a Tuple literal (a, b, c), it assigns each element directly.
1012// If the branch ends with a single call expression returning a tuple, it assigns the call
1013// to a temp variable and extracts .arg0, .arg1, etc.
1014fn (mut t Transformer) build_tuple_branch_assigns(stmts []ast.Stmt, tuple_lhs []ast.Expr, n int, pos token.Pos) ?[]ast.Stmt {
1015 if tuple_values := t.extract_branch_tuple_values(stmts, n) {
1016 // Branch has explicit tuple values — assign each one directly
1017 mut result := []ast.Stmt{cap: n}
1018 for i in 0 .. n {
1019 result << ast.Stmt(ast.AssignStmt{
1020 op: .assign
1021 lhs: [tuple_lhs[i]]
1022 rhs: [tuple_values[i]]
1023 pos: pos
1024 })
1025 }
1026 return result
1027 }
1028 // Branch has a single non-tuple expression (e.g. a function call returning a tuple).
1029 // Extract it and assign via temp: _tuple_tN = call(); x = _tuple_tN.arg0; y = _tuple_tN.arg1
1030 if stmts.len == 0 {
1031 return none
1032 }
1033 mut last_idx := stmts.len - 1
1034 for last_idx >= 0 && stmts[last_idx] is ast.EmptyStmt {
1035 last_idx--
1036 }
1037 if last_idx < 0 {
1038 return none
1039 }
1040 last := stmts[last_idx]
1041 if last !is ast.ExprStmt {
1042 return none
1043 }
1044 last_expr := (last as ast.ExprStmt).expr
1045 if last_expr is ast.Tuple {
1046 return none // Tuple case was already handled above
1047 }
1048 // Single expression returning a tuple (e.g. function call)
1049 t.temp_counter++
1050 tmp_name := '_tuple_t${t.temp_counter}'
1051 tmp_ident := ast.Ident{
1052 name: tmp_name
1053 }
1054 mut result := []ast.Stmt{cap: n + 1}
1055 // Include any preceding statements from the branch
1056 for i in 0 .. last_idx {
1057 if stmts[i] !is ast.EmptyStmt {
1058 result << stmts[i]
1059 }
1060 }
1061 // Assign call result to temp
1062 result << ast.Stmt(ast.AssignStmt{
1063 op: .decl_assign
1064 lhs: [ast.Expr(tmp_ident)]
1065 rhs: [last_expr]
1066 pos: pos
1067 })
1068 // Extract .arg0, .arg1, etc.
1069 for i in 0 .. n {
1070 result << ast.Stmt(ast.AssignStmt{
1071 op: .assign
1072 lhs: [tuple_lhs[i]]
1073 rhs: [
1074 ast.Expr(ast.SelectorExpr{
1075 lhs: tmp_ident
1076 rhs: ast.Ident{
1077 name: 'arg${i}'
1078 }
1079 }),
1080 ]
1081 pos: pos
1082 })
1083 }
1084 return result
1085}
1086
1087// lower_if_expr_value lowers a value-position IfExpr into a temp variable + statement-form if.
1088// Generates: _if_t<N> := if cond { a } else { b }
1089// Hoists the decl_assign via pending_stmts and returns the temp ident as replacement.
1090fn (mut t Transformer) lower_if_expr_value(ie ast.IfExpr) ast.Expr {
1091 t.temp_counter++
1092 tmp_name := '_if_t${t.temp_counter}'
1093 tmp_ident := ast.Ident{
1094 name: tmp_name
1095 }
1096 // Register temp variable type so cleanc can resolve it from scope
1097 if typ := t.get_expr_type(ast.Expr(ie)) {
1098 t.register_temp_var(tmp_name, typ)
1099 }
1100 // Hoist the decl_assign with the IfExpr as RHS.
1101 // cleanc's gen_assign_stmt already handles decl_assign + IfExpr RHS
1102 // by emitting: Type tmp; if (cond) { tmp = a; } else { tmp = b; }
1103 t.pending_stmts << ast.Stmt(ast.AssignStmt{
1104 op: .decl_assign
1105 lhs: [ast.Expr(tmp_ident)]
1106 rhs: [ast.Expr(ie)]
1107 pos: ie.pos
1108 })
1109 return ast.Expr(tmp_ident)
1110}
1111
1112fn (mut t Transformer) collect_defers_in_if(node ast.IfExpr, mut defer_bodies [][]ast.Stmt) ast.Expr {
1113 new_else := t.collect_defers_in_else(node.else_expr, mut defer_bodies)
1114 return ast.IfExpr{
1115 cond: node.cond
1116 stmts: t.collect_and_remove_defers(node.stmts, mut defer_bodies)
1117 else_expr: new_else
1118 }
1119}
1120
1121fn (mut t Transformer) collect_defers_in_else(else_expr ast.Expr, mut defer_bodies [][]ast.Stmt) ast.Expr {
1122 if else_expr is ast.IfExpr {
1123 return t.collect_defers_in_if(else_expr, mut defer_bodies)
1124 }
1125 return else_expr
1126}
1127
1128// inject_defer_before_returns walks the statement list and replaces return statements
1129// with: { defer_body; return expr; } — saving the return value in a temp var if needed.
1130fn (mut t Transformer) inject_defer_before_returns(stmts []ast.Stmt, defer_stmts []ast.Stmt, has_return_type bool) []ast.Stmt {
1131 mut result := []ast.Stmt{cap: stmts.len}
1132 for stmt in stmts {
1133 match stmt {
1134 ast.ReturnStmt {
1135 if has_return_type && stmt.exprs.len > 0 {
1136 // Save return value to temp, run defers, return temp
1137 t.temp_counter++
1138 temp_name := '_defer_t${t.temp_counter}'
1139 if expr_type := t.get_expr_type(stmt.exprs[0]) {
1140 t.register_temp_var(temp_name, expr_type)
1141 }
1142 ret_expr := ast.Expr(stmt.exprs[0])
1143 result << ast.Stmt(ast.AssignStmt{
1144 op: .decl_assign
1145 lhs: [ast.Expr(ast.Ident{
1146 name: temp_name
1147 })]
1148 rhs: [ret_expr]
1149 })
1150 result << defer_stmts
1151 result << ast.Stmt(ast.ReturnStmt{
1152 exprs: [ast.Expr(ast.Ident{
1153 name: temp_name
1154 })]
1155 })
1156 } else {
1157 result << defer_stmts
1158 result << ast.Stmt(stmt)
1159 }
1160 }
1161 ast.ExprStmt {
1162 expr := stmt.expr
1163 if expr is ast.IfExpr {
1164 result << ast.Stmt(ast.ExprStmt{
1165 expr: t.inject_defer_in_if_expr(expr, defer_stmts, has_return_type)
1166 })
1167 } else {
1168 result << ast.Stmt(stmt)
1169 }
1170 }
1171 ast.ForStmt {
1172 result << ast.Stmt(ast.ForStmt{
1173 init: stmt.init
1174 cond: stmt.cond
1175 post: stmt.post
1176 stmts: t.inject_defer_before_returns(stmt.stmts, defer_stmts, has_return_type)
1177 })
1178 }
1179 ast.BlockStmt {
1180 result << ast.Stmt(ast.BlockStmt{
1181 stmts: t.inject_defer_before_returns(stmt.stmts, defer_stmts, has_return_type)
1182 })
1183 }
1184 else {
1185 result << stmt
1186 }
1187 }
1188 }
1189 return result
1190}
1191
1192fn (mut t Transformer) inject_defer_in_if_expr(node ast.IfExpr, defer_stmts []ast.Stmt, has_return_type bool) ast.Expr {
1193 new_else := t.inject_defer_in_else(node.else_expr, defer_stmts, has_return_type)
1194 return ast.IfExpr{
1195 cond: node.cond
1196 stmts: t.inject_defer_before_returns(node.stmts, defer_stmts, has_return_type)
1197 else_expr: new_else
1198 }
1199}
1200
1201fn (mut t Transformer) inject_defer_in_else(else_expr ast.Expr, defer_stmts []ast.Stmt, has_return_type bool) ast.Expr {
1202 match else_expr {
1203 ast.IfExpr {
1204 // else if: recurse
1205 return t.inject_defer_in_if_expr(else_expr, defer_stmts, has_return_type)
1206 }
1207 ast.EmptyExpr {
1208 return else_expr
1209 }
1210 else {
1211 return else_expr
1212 }
1213 }
1214}
1215
1216// transform_comptime_expr evaluates compile-time conditionals and returns the selected branch
1217fn (mut t Transformer) eval_comptime_if(node ast.IfExpr) ast.Expr {
1218 cond_result := t.eval_comptime_cond(node.cond)
1219
1220 if cond_result {
1221 // Condition is true - return the then branch with transformed statements
1222 if node.stmts.len == 1 {
1223 stmt := node.stmts[0]
1224 if stmt is ast.ExprStmt {
1225 return t.transform_expr(stmt.expr)
1226 }
1227 }
1228 // Multi-statement branch at expression level can't be represented;
1229 // statement-level expansion handles these properly
1230 return ast.empty_expr
1231 } else {
1232 // Condition is false - evaluate else branch
1233 else_e := node.else_expr
1234 if else_e !is ast.EmptyExpr {
1235 if else_e is ast.IfExpr {
1236 if else_e.cond is ast.EmptyExpr {
1237 // Plain $else block
1238 if else_e.stmts.len == 1 {
1239 stmt := else_e.stmts[0]
1240 if stmt is ast.ExprStmt {
1241 return t.transform_expr(stmt.expr)
1242 }
1243 }
1244 // Multi-statement $else at expression level
1245 return ast.empty_expr
1246 } else {
1247 // $else $if - recursive evaluation
1248 return t.eval_comptime_if(else_e)
1249 }
1250 }
1251 }
1252 }
1253 // Condition is false and no else branch - return empty (comptime block is skipped)
1254 return ast.empty_expr
1255}
1256
1257// resolve_comptime_if_stmts evaluates a compile-time $if condition and returns
1258// the selected branch's statements, fully resolving the comptime at statement level.
1259fn (mut t Transformer) resolve_comptime_if_stmts(node ast.IfExpr) []ast.Stmt {
1260 cond_result := t.eval_comptime_cond(node.cond)
1261 if cond_result {
1262 return node.stmts
1263 }
1264 // Condition is false - evaluate else branch
1265 else_e := node.else_expr
1266 if else_e is ast.IfExpr {
1267 if else_e.cond is ast.EmptyExpr {
1268 // Plain $else block
1269 return else_e.stmts
1270 }
1271 // $else $if - recursive evaluation
1272 return t.resolve_comptime_if_stmts(else_e)
1273 }
1274 return []
1275}
1276
1277// can_eval_comptime_cond returns true if the transformer can evaluate this
1278// comptime condition. Returns false for conditions involving generic type
1279// checks (key_is/not_is) that need to be resolved by the backend.
1280fn (t &Transformer) can_eval_comptime_cond(cond ast.Expr) bool {
1281 match cond {
1282 ast.Ident {
1283 return true
1284 }
1285 ast.ComptimeExpr {
1286 return t.can_eval_comptime_cond(cond.expr)
1287 }
1288 ast.CallExpr {
1289 return transformer_pkgconfig_call_name(cond) != none
1290 }
1291 ast.CallOrCastExpr {
1292 return transformer_pkgconfig_call_name(cond) != none
1293 }
1294 ast.PrefixExpr {
1295 return t.can_eval_comptime_cond(cond.expr)
1296 }
1297 ast.InfixExpr {
1298 if cond.op == .key_is || cond.op == .not_is {
1299 return false
1300 }
1301 return t.can_eval_comptime_cond(cond.lhs) && t.can_eval_comptime_cond(cond.rhs)
1302 }
1303 ast.PostfixExpr {
1304 return true
1305 }
1306 ast.ParenExpr {
1307 return t.can_eval_comptime_cond(cond.expr)
1308 }
1309 else {
1310 return false
1311 }
1312 }
1313}
1314
1315fn (t &Transformer) can_eval_comptime_cond_cursor(cond ast.Cursor) bool {
1316 if !cond.is_valid() {
1317 return false
1318 }
1319 match cond.kind() {
1320 .expr_ident {
1321 return true
1322 }
1323 .expr_comptime, .expr_paren {
1324 return t.can_eval_comptime_cond_cursor(cond.edge(0))
1325 }
1326 .expr_call, .expr_call_or_cast {
1327 return transformer_pkgconfig_call_name_cursor(cond) != none
1328 }
1329 .expr_prefix {
1330 return t.can_eval_comptime_cond_cursor(cond.edge(0))
1331 }
1332 .expr_infix {
1333 op := unsafe { token.Token(int(cond.aux())) }
1334 if op == .key_is || op == .not_is {
1335 return false
1336 }
1337 return t.can_eval_comptime_cond_cursor(cond.edge(0))
1338 && t.can_eval_comptime_cond_cursor(cond.edge(1))
1339 }
1340 .expr_postfix {
1341 return true
1342 }
1343 else {
1344 return false
1345 }
1346 }
1347}
1348
1349// transform_comptime_if_bodies recursively transforms the body stmts of each
1350// branch in a comptime $if, without evaluating the condition. This is used when
1351// the condition can't be evaluated at transform time (e.g., generic type checks).
1352fn (mut t Transformer) transform_comptime_if_bodies(node ast.IfExpr) ast.IfExpr {
1353 transformed_stmts := t.transform_stmts(node.stmts)
1354 mut transformed_else := node.else_expr
1355 if node.else_expr is ast.IfExpr {
1356 else_if := node.else_expr as ast.IfExpr
1357 transformed_else_if := t.transform_comptime_if_bodies(else_if)
1358 transformed_else = ast.Expr(transformed_else_if)
1359 }
1360 return ast.IfExpr{
1361 cond: node.cond
1362 stmts: transformed_stmts
1363 else_expr: transformed_else
1364 }
1365}
1366
1367// eval_comptime_cond evaluates a compile-time condition expression
1368fn (t &Transformer) eval_comptime_cond(cond ast.Expr) bool {
1369 match cond {
1370 ast.Ident {
1371 return t.eval_comptime_flag(cond.name)
1372 }
1373 ast.ComptimeExpr {
1374 return t.eval_comptime_cond(cond.expr)
1375 }
1376 ast.CallExpr {
1377 if pkg_name := transformer_pkgconfig_call_name(cond) {
1378 return t.eval_pkgconfig_cond(pkg_name)
1379 }
1380 }
1381 ast.CallOrCastExpr {
1382 if pkg_name := transformer_pkgconfig_call_name(cond) {
1383 return t.eval_pkgconfig_cond(pkg_name)
1384 }
1385 }
1386 ast.PrefixExpr {
1387 if cond.op == .not {
1388 return !t.eval_comptime_cond(cond.expr)
1389 }
1390 }
1391 ast.InfixExpr {
1392 if cond.op == .and {
1393 return t.eval_comptime_cond(cond.lhs) && t.eval_comptime_cond(cond.rhs)
1394 }
1395 if cond.op == .logical_or {
1396 return t.eval_comptime_cond(cond.lhs) || t.eval_comptime_cond(cond.rhs)
1397 }
1398 }
1399 ast.PostfixExpr {
1400 // Handle optional feature check: feature?
1401 if cond.op == .question {
1402 inner := cond.expr
1403 if inner is ast.Ident {
1404 return pref.comptime_optional_flag_value(t.pref, inner.name)
1405 }
1406 }
1407 }
1408 ast.ParenExpr {
1409 return t.eval_comptime_cond(cond.expr)
1410 }
1411 else {}
1412 }
1413
1414 return false
1415}
1416
1417fn (t &Transformer) eval_comptime_cond_cursor(cond ast.Cursor) bool {
1418 if !cond.is_valid() {
1419 return false
1420 }
1421 match cond.kind() {
1422 .expr_ident {
1423 return t.eval_comptime_flag(cond.name())
1424 }
1425 .expr_comptime {
1426 return t.eval_comptime_cond_cursor(cond.edge(0))
1427 }
1428 .expr_call, .expr_call_or_cast {
1429 if pkg_name := transformer_pkgconfig_call_name_cursor(cond) {
1430 return t.eval_pkgconfig_cond(pkg_name)
1431 }
1432 }
1433 .expr_prefix {
1434 op := unsafe { token.Token(int(cond.aux())) }
1435 if op == .not {
1436 return !t.eval_comptime_cond_cursor(cond.edge(0))
1437 }
1438 }
1439 .expr_infix {
1440 op := unsafe { token.Token(int(cond.aux())) }
1441 if op == .and {
1442 return t.eval_comptime_cond_cursor(cond.edge(0))
1443 && t.eval_comptime_cond_cursor(cond.edge(1))
1444 }
1445 if op == .logical_or {
1446 return t.eval_comptime_cond_cursor(cond.edge(0))
1447 || t.eval_comptime_cond_cursor(cond.edge(1))
1448 }
1449 }
1450 .expr_postfix {
1451 op := unsafe { token.Token(int(cond.aux())) }
1452 inner := cond.edge(0)
1453 if op == .question && inner.is_valid() && inner.kind() == .expr_ident {
1454 return pref.comptime_optional_flag_value(t.pref, inner.name())
1455 }
1456 }
1457 .expr_paren {
1458 return t.eval_comptime_cond_cursor(cond.edge(0))
1459 }
1460 else {}
1461 }
1462
1463 return false
1464}
1465
1466fn (t &Transformer) eval_pkgconfig_cond(pkg_name string) bool {
1467 if t.pref.is_cross_target() {
1468 return false
1469 }
1470 return pref.comptime_pkgconfig_value(pkg_name)
1471}
1472
1473fn transformer_pkgconfig_call_name(expr ast.Expr) ?string {
1474 match expr {
1475 ast.CallExpr {
1476 if expr.lhs is ast.Ident && expr.lhs.name == 'pkgconfig' && expr.args.len == 1 {
1477 return transformer_string_literal_value(expr.args[0])
1478 }
1479 }
1480 ast.CallOrCastExpr {
1481 if expr.lhs is ast.Ident && expr.lhs.name == 'pkgconfig' {
1482 return transformer_string_literal_value(expr.expr)
1483 }
1484 }
1485 else {}
1486 }
1487
1488 return none
1489}
1490
1491fn transformer_pkgconfig_call_name_cursor(expr ast.Cursor) ?string {
1492 match expr.kind() {
1493 .expr_call {
1494 lhs := expr.edge(0)
1495 if lhs.is_valid() && lhs.kind() == .expr_ident && lhs.name() == 'pkgconfig'
1496 && expr.edge_count() == 2 {
1497 return transformer_string_literal_value_cursor(expr.edge(1))
1498 }
1499 }
1500 .expr_call_or_cast {
1501 lhs := expr.edge(0)
1502 if lhs.is_valid() && lhs.kind() == .expr_ident && lhs.name() == 'pkgconfig' {
1503 return transformer_string_literal_value_cursor(expr.edge(1))
1504 }
1505 }
1506 else {}
1507 }
1508
1509 return none
1510}
1511
1512fn transformer_string_literal_value(expr ast.Expr) ?string {
1513 if expr is ast.StringLiteral {
1514 return transformer_unquote_string_literal_value(expr.value)
1515 }
1516 return none
1517}
1518
1519fn transformer_string_literal_value_cursor(expr ast.Cursor) ?string {
1520 if !expr.is_valid() || expr.kind() != .expr_string {
1521 return none
1522 }
1523 return transformer_unquote_string_literal_value(expr.name())
1524}
1525
1526fn transformer_unquote_string_literal_value(value string) string {
1527 if value.len >= 2 && ((value[0] == `"` && value[value.len - 1] == `"`)
1528 || (value[0] == `'` && value[value.len - 1] == `'`)) {
1529 return value[1..value.len - 1]
1530 }
1531 return value
1532}
1533
1534// eval_comptime_flag delegates to the shared `pref.comptime_flag_value` so
1535// the parser and the transformer recognize exactly the same flag names.
1536fn (t &Transformer) eval_comptime_flag(name string) bool {
1537 // Fast path: the three native-backend gated flags hit the cached bool
1538 // rather than the helper's repeated pref/backend comparisons.
1539 if name == 'native' || name == 'builtin_write_buf_to_fd_should_use_c_write' || name == 'tinyc' {
1540 return t.is_native_be
1541 }
1542 return pref.comptime_flag_value(t.pref, name)
1543}
1544