Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
fab69be
parser: mark match-arm calls as return-used when a match is a block v…
medvednikov Jul 31, 2026
0efc15b
parser: recurse through ParExpr when marking match/if block-value cal…
medvednikov Aug 2, 2026
439a513
parser, v3: handle unsafe-wrapped match/if block values with propagat…
medvednikov Aug 2, 2026
1546c32
parser, checker, v3: handle cast/as-cast-wrapped match/if block value…
medvednikov Aug 2, 2026
b210e50
parser, checker, cgen, v3: look through unsafe wrappers for cast-wrap…
medvednikov Aug 2, 2026
77a271a
parser, checker, v3: traverse both infix operands for match/if block …
medvednikov Aug 2, 2026
b3b5a13
parser: mark match/if arm calls in call arguments as return-used (#28…
medvednikov Aug 2, 2026
e3c7894
parser: recurse through nested call arguments for block-value match/i…
medvednikov Aug 2, 2026
f46183f
parser: recurse through infix operands inside call arguments for bloc…
medvednikov Aug 2, 2026
eb4b179
parser, checker: handle array-literal match/if block values with prop…
medvednikov Aug 2, 2026
353a90a
parser, checker: handle struct-field match values and scope the array…
medvednikov Aug 2, 2026
23236ba
parser, checker: handle map-literal match/if block values with propag…
medvednikov Aug 2, 2026
9225506
parser, checker, v3: handle prefix-expression match/if block values w…
medvednikov Aug 2, 2026
7020743
parser, checker, v3: handle index-expression match/if block values wi…
medvednikov Aug 2, 2026
d61a5db
checker, parser, v3: value-context left-hand match for membership and…
medvednikov Aug 2, 2026
be7a393
parser, checker: value-context map keys and array spreads for match/i…
medvednikov Aug 2, 2026
6bb7234
parser, checker, v3: struct/map update and string-interp match/if blo…
medvednikov Aug 2, 2026
58fba14
parser, checker, v3: handle dump/likely-wrapped match/if block values…
medvednikov Aug 2, 2026
4095e5f
parser, checker: handle multi-return match/if block values with propa…
medvednikov Aug 2, 2026
9c8e75b
parser, checker: method-call receiver and slice-bound match/if block …
medvednikov Aug 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 70 additions & 56 deletions vlib/v/parser/parser.v
Original file line number Diff line number Diff line change
Expand Up @@ -578,70 +578,84 @@ fn (mut p Parser) parse_block_no_scope(is_top_level bool) []ast.Stmt {
}

fn (mut p Parser) mark_last_call_return_as_used(mut last_stmt ast.Stmt) {
match mut last_stmt {
ast.ExprStmt {
match mut last_stmt.expr {
ast.CallExpr {
// last stmt on block is CallExpr
last_stmt.expr.is_return_used = true
if last_stmt.expr.or_block.stmts.len > 0 {
mut or_block_last_stmt := last_stmt.expr.or_block.stmts.last()
p.mark_last_call_return_as_used(mut or_block_last_stmt)
}
if mut last_stmt is ast.ExprStmt {
p.mark_last_call_expr_return_as_used(mut last_stmt.expr)
}
}

fn (mut p Parser) mark_last_call_expr_return_as_used(mut expr ast.Expr) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle array-literal match values

Fresh evidence beyond the already-fixed call/infix spellings is an assignment-RHS branch ending in [match value { First { lower_first(value)! } Second { lower_second(value)! } }]: this visitor has no ast.ArrayInit case, so it never reaches the match or marks its arm calls as return-used. Because match-arm blocks are parsed after inside_assign_rhs is cleared, C generation treats those propagated calls as discarded while assigning the match temporary and can emit the same empty assignment this change is intended to prevent; recurse through array elements and add this composition to the regression test.

AGENTS.md reference: AGENTS.md:L657-L658

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in eb4b179. This one needed both a parser and a checker change:

  • parser: added an ast.ArrayInit case to mark_last_call_expr_return_as_used (recurse through elements that contain a value match/if) and to expr_contains_value_match_or_if (so array literals nested in call arguments are covered).
  • checker: recognizing the array element then surfaced a typing gap — in a void context (nested in an if-branch) the element match/if was checked with a void expected type and mistyped as a statement (invalid void array element type). An expected-type override doesn't work here since the element type is inferred and any concrete type mistypes the arms (I confirmed none/array-type both break it), so I added an inside_array_init_value_elem flag set around the element check in array_init and honored by match_expr/if_expr to force is_expr while leaving the expected type void so the arms infer their own type.

Verified: [match ...], [(match ...)], [unsafe { match ... }], [i64(match ...)], and [100, match ...] all compile+run on both backends. v3 already handled array elements, so it needed no code change (regression test added there too). The flag is only set for value match/if array elements in a void context, so all other matches are unaffected — compiler_errors_test (1617 snapshots) + the array/match/if/option/result/sumtype suites are unchanged.

Both regression tests now cover [match ...].

match mut expr {
ast.CallExpr {
// last stmt on block is CallExpr
expr.is_return_used = true
Comment on lines +722 to +724

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Traverse call arguments for block-value matches

When an assignment-RHS block ends with a call such as wrap(match value { First { lower_first(value)! } Second { lower_second(value)! } }), the match-arm tails are parsed in statement context, but this case marks only the outer wrap call and never visits its arguments. The propagated inner calls therefore retain is_return_used == false, allowing C generation to reproduce the empty branch assignment this change is intended to prevent; recurse into call arguments containing value match/if expressions and add regression coverage for this composition.

AGENTS.md reference: AGENTS.md:L657-L658

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b3b5a13. The CallExpr case now recurses into arguments that hold a block-value match/if, marking their arm calls as return-used. To avoid touching plain arguments, the recursion is gated by a new expr_is_wrapped_match_or_if predicate that looks through (...)/unsafe/cast/as-cast wrappers, so only arguments that actually contain a value match/if are traversed — wrap(match ...), wrap((match ...)), wrap(unsafe { match ... }), add(100, match ...), and wrap(if ... { foo()! } else { ... }) all work now.

No checker fix was needed here (unlike the cast/infix cases): a call argument already gets its expected type from the parameter, so the match is typed correctly and only the parser marking was missing. The v3 backend already routes arguments through the parameter type and handled this composition, so it needed no code change — a regression test is added there too.

Both regression tests now cover wrap(match ...). Verified compiler_errors_test (1617 snapshots) + the call/fn/match/option/result suites are unchanged.

if expr.or_block.stmts.len > 0 {
mut or_block_last_stmt := expr.or_block.stmts.last()
p.mark_last_call_return_as_used(mut or_block_last_stmt)
}
}
ast.ConcatExpr {
// last stmt on block is: a, b, c := ret1(), ret2(), ret3()
for mut val in expr.vals {
if mut val is ast.CallExpr {
val.is_return_used = true
}
ast.ConcatExpr {
// last stmt on block is: a, b, c := ret1(), ret2(), ret3()
for mut expr in last_stmt.expr.vals {
if mut expr is ast.CallExpr {
expr.is_return_used = true
}
}
}
}
ast.ParExpr {
// last stmt on block is parenthesized: ( match .. { a { foo() } } )
p.mark_last_call_expr_return_as_used(mut expr.expr)
Comment on lines +834 to +836

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Recurse through unsafe expression wrappers

When an assignment-RHS branch uses a value such as (unsafe { match value { First { lower_first(value)! } Second { lower_second(value)! } } }), the parser retains an ast.UnsafeExpr around the match, but this helper stops at that wrapper and leaves both propagated calls with is_return_used == false; C generation then reaches the same empty assignment path this change is intended to fix. Fresh evidence beyond the previously covered ParExpr case is that UnsafeExpr is another transparent wrapper whose C generator directly emits its inner expression, so recurse through expr.expr here and add regression coverage for this spelling.

AGENTS.md reference: AGENTS.md:L652-L658

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 439a513 — and this one needed fixing in both backends:

  • vlib/v: added an ast.UnsafeExpr case in mark_last_call_expr_return_as_used that recurses through expr.expr, mirroring the ParExpr case.
  • vlib/v3: the same spelling exposed a matching gap in the v3 backend. transform_block_expr_for_type only treated expr_stmt/nested-block tails as value tails, so unsafe { match ... } (a block whose tail is a bare match_stmt) fell back to the type-less transform_block_expr and emitted a malformed empty ternary _ifexpr = (!ok ? : ). It now treats a value-producing match_stmt/if_expr tail as the value expression, so the target type reaches the propagating arms.

Both regression tests now cover (unsafe { match .. }). Verified v3 still self-hosts with the transform change, and confirmed (by diffing a pre-change vs post-change v3 build) that the fix is neutral to unrelated tests.

}
ast.IfExpr {
// last stmt on block is: if .. { foo() } else { bar() }
for mut branch in expr.branches {
if branch.stmts.len > 0 {
mut last_if_stmt := branch.stmts.last()
p.mark_last_call_return_as_used(mut last_if_stmt)
}
ast.IfExpr {
// last stmt on block is: if .. { foo() } else { bar() }
for mut branch in last_stmt.expr.branches {
if branch.stmts.len > 0 {
mut last_if_stmt := branch.stmts.last()
p.mark_last_call_return_as_used(mut last_if_stmt)
}
}
}
}
ast.MatchExpr {
// last stmt on block is: match .. { a { foo() } b { bar() } }
for mut branch in expr.branches {
if branch.stmts.len > 0 {
mut last_match_stmt := branch.stmts.last()
p.mark_last_call_return_as_used(mut last_match_stmt)
}
ast.InfixExpr {
if last_stmt.expr.or_block.stmts.len > 0 {
mut or_block_last_stmt := last_stmt.expr.or_block.stmts.last()
}
}
ast.InfixExpr {
if expr.or_block.stmts.len > 0 {
mut or_block_last_stmt := expr.or_block.stmts.last()
p.mark_last_call_return_as_used(mut or_block_last_stmt)
}
// last stmt has infix expr with CallExpr: foo()? + 'a'
mut left_expr := expr.left

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Traverse nested match operands in infix block values

When an assignment-RHS block ends in an expression such as 1 + (match value { First { lower_first(value)! } Second { lower_second(value)! } }), the nested match branches are parsed with inside_assign_rhs cleared, leaving their calls return-unused. This InfixExpr case examines only expr.left and never applies the new recursive helper to the right-hand match; similarly, a wrapped match on the left causes the loop to stop. C generation can therefore omit the propagated values and recreate the invalid empty assignment. Traverse the infix operands through mark_last_call_expr_return_as_used and add regression coverage for this composition.

AGENTS.md reference: AGENTS.md:L657-L658

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 77a271a. Replaced the left-only expr.left walk with recursion into both operands via the helper, so a match/if on either side (nested, or wrapped in parens/unsafe/cast) is marked return-used. This also fixes a()! + b()!, where the old loop marked only the left call.

Two extra things the composition surfaced:

  • checker: a value match/if on the left of an infix ((match ...) + 10) was checked with the (void) surrounding expected type and mistyped as a statement when nested in an if-branch — it failed at type-checking (mismatched types void and int literal) before reaching cgen. infix_expr now resolves the right operand's type first and uses it as the left operand's expected type (reusing the existing check-right-first path used for short enums). Renamed the shared predicate to operand_is_value_match_or_if.
  • vlib/v3: transform_infix_expr lowered both operands with plain transform_expr; a value match/if operand now goes through transform_expr_for_type (new transform_infix_operand helper).

Both regression tests now cover 1 + (match ...) and (match ...) + 10. Verified: v3 self-hosts, and compiler_errors_test (1617 snapshots) + the enum/infix/match/option/result suites are unchanged.

for {
mut next_left_expr := ast.Expr(ast.EmptyExpr{})
if mut left_expr is ast.InfixExpr {
if left_expr.or_block.stmts.len > 0 {
mut or_block_last_stmt := left_expr.or_block.stmts.last()
p.mark_last_call_return_as_used(mut or_block_last_stmt)
}
// last stmt has infix expr with CallExpr: foo()? + 'a'
mut left_expr := last_stmt.expr.left
for {
mut next_left_expr := ast.Expr(ast.EmptyExpr{})
if mut left_expr is ast.InfixExpr {
if left_expr.or_block.stmts.len > 0 {
mut or_block_last_stmt := left_expr.or_block.stmts.last()
p.mark_last_call_return_as_used(mut or_block_last_stmt)
}
next_left_expr = left_expr.left
} else if mut left_expr is ast.CallExpr {
left_expr.is_return_used = true
if left_expr.or_block.stmts.len > 0 {
mut or_block_last_stmt := left_expr.or_block.stmts.last()
p.mark_last_call_return_as_used(mut or_block_last_stmt)
}
break
} else {
break
}
left_expr = next_left_expr
continue
}
}
ast.ComptimeCall, ast.ComptimeSelector, ast.PrefixExpr, ast.SelectorExpr {
if last_stmt.expr.or_block.stmts.len > 0 {
mut or_block_last_stmt := last_stmt.expr.or_block.stmts.last()
next_left_expr = left_expr.left
} else if mut left_expr is ast.CallExpr {
left_expr.is_return_used = true
if left_expr.or_block.stmts.len > 0 {
mut or_block_last_stmt := left_expr.or_block.stmts.last()
p.mark_last_call_return_as_used(mut or_block_last_stmt)
}
break
} else {
break
}
else {}
left_expr = next_left_expr
continue
}
}
ast.ComptimeCall, ast.ComptimeSelector, ast.PrefixExpr, ast.SelectorExpr {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle prefixed match block values

In the current vlib/v parser/checker path, when an inferred assignment branch ends in a prefix expression such as -(match value { First { lower_first(value)! } Second { lower_second(value)! } }), this case inspects only the prefix's or_block and never traverses expr.right. The inner match is consequently checked in void context and its propagated calls remain return-unused, so valid value-producing code is rejected or reaches the same empty-value codegen path; recurse through the prefix operand, supply its value context in the checker, and add regression coverage.

AGENTS.md reference: AGENTS.md:L657-L658

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9225506. Three parts, since (unlike the array/map/struct cases) v3 also needed a change here:

  • parser: mark_last_call_expr_return_as_used had PrefixExpr grouped with the or-block-only cases; split it out to recurse into expr.right, and added PrefixExpr to expr_contains_value_match_or_if (so prefixed values nested in call arguments are covered).
  • checker: the prefix operand match/if was checked with a void expected type and mistyped as void. prefix_expr now sets the value-required flag around the operand check. Since the flag now covers array elements, map values and prefix operands, I renamed it inside_container_value_elem -> force_value_match_or_if; it's still consumed by match_expr/if_expr so nested statement match/if are unaffected.
  • vlib/v3: transform_prefix_expr lowered the operand with plain transform_expr; a value match/if operand now goes through the shared transform_value_operand helper (renamed from transform_infix_operand).

Verified: -(match ...), ~(match ...), !(match ...) (bool arms), wrap(-(match ...)), and [-(match ...)] all compile+run identically on both backends. v3 self-hosts; compiler_errors_test (1617 snapshots) + the prefix/operator/match/array suites are unchanged. Both regression tests now cover -(match ...).

if expr.or_block.stmts.len > 0 {
mut or_block_last_stmt := expr.or_block.stmts.last()
p.mark_last_call_return_as_used(mut or_block_last_stmt)
}
}
else {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Recurse through cast wrappers when marking value calls

When the block tail wraps the match in an ast.CastExpr, for example result := if value := node { i64(match value { First { lower_first(value)! } Second { lower_second(value)! } }) } else { 0 }, this match falls through the default case and its branch calls retain is_return_used == false. C generation then treats the match as a value but emits empty branch assignments, reproducing the invalid C this patch addresses; recurse through CastExpr.expr (and similarly transparent AsCast operands) and add a regression case.

AGENTS.md reference: AGENTS.md:L652-L658

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 1546c32. Investigating this turned up that the parser recursion alone was necessary but not sufficient — there were two distinct bugs, so both backends needed a checker/transform fix in addition to the parser change:

vlib/v (main compiler)

  • checker: a match/if operand of a cast (CastExpr or AsCast, possibly parenthesized) was checked with a void expected type when nested in a void context (an if-branch), so it was mistyped as void — the case failed at type-checking (expression does not return a value so it cannot be cast / cannot cast non-sum type voidusingas``) before ever reaching cgen. Fixed by giving such an operand the cast target as its expected type (is_expr).
  • parser: with the type fixed, cgen then emitted the exact _t = ; you predicted, because mark_last_call_expr_return_as_used had no CastExpr/AsCast case. Added both, recursing through expr.expr, mirroring ParExpr/UnsafeExpr.

vlib/v3

  • transform_cast_expr (and transform_as_expr) lowered the operand with plain transform_expr (statement context), so the propagating arms produced the empty ternary _ifexpr = (!ok ? : ). A value match/if operand is now routed through transform_expr_for_type.

Both regression tests now cover i64(match ...) and (match ...) as Circle. Verified: v3 still self-hosts, compiler_errors_test (1617 snapshots) + 35 cast/sumtype/match tests are unchanged, and that both the checker and parser fixes are individually required (checker-only still emits the empty C).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Traverse dump operands when marking value calls

When an assignment-RHS branch ends in dump(match value { First { lower_first(value)! } Second { lower_second(value)! } }), this visitor falls through for ast.DumpExpr, even though the checker treats the dump operand as a value and C generation directly emits that operand. The propagated match-arm calls therefore remain return-unused and can recreate the empty match-value assignment; recurse through DumpExpr.expr and add regression coverage for this spelling.

AGENTS.md reference: AGENTS.md:L657-L658

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 58fba14. ast.DumpExpr is now handled in both parser traversals (mark_last_call_expr_return_as_used and expr_contains_value_match_or_if), recursing into DumpExpr.expr. The checker already treats the dumped operand as a value, so only the parser marking was missing. On v3, transform_dump_expr now routes the operand through the shared transform_value_operand helper.

Verified dump(match ...) compiles+runs on both backends. Regression covers it in the vlib/v test (left out of the v3 exact-output test since dump writes to stderr, which os.execute folds into the captured output).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Traverse lock-expression block values

When an inferred assignment branch ends in lock state { match value { First { lower_first(value)! } Second { lower_second(value)! } } }, the new visitor falls through for ast.LockExpr, so it never marks the match-arm calls as return-used. lock_expr also checks its tail under the surrounding void expected type, causing the nested match to remain a statement; the valid lock value is therefore rejected or reaches the same empty propagated-value codegen path. Recurse into the lock's final statement and preserve its value context, with a regression test for this form.

AGENTS.md reference: AGENTS.md:L652-L658

Useful? React with 👍 / 👎.

Expand Down
107 changes: 107 additions & 0 deletions vlib/v/tests/match_as_if_expr_value_with_propagation_test.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Regression test for https://github.com/vlang/v/issues/28000
// A `match` whose arms use `!`/`?` propagation, used as the value of an
// `if`-expression (or directly), used to emit invalid C (`_t = ;`) because
// the calls in the match arms were not marked as having their return used.

struct First {}

struct Second {}

type Node = First | Second

fn lower_first(_ First) !int {
return 1
}

fn lower_second(_ Second) !int {
return 2
}

fn opt_first(_ First) ?int {
return 10
}

fn opt_second(_ Second) ?int {
return 20
}

// match inside an if-guard, assigned to a variable (the original repro)
fn select_value(node ?Node) !int {
result := if value := node {
match value {
First { lower_first(value)! }
Second { lower_second(value)! }
}
} else {
0
}
return result
}

// parenthesized match value: `( match .. { .. } )` keeps an ast.ParExpr wrapper
fn select_value_paren(node ?Node) !int {
result := if value := node {
(match value {
First { lower_first(value)! }
Second { lower_second(value)! }
})
} else {
0
}
return result
}

// match with `?` option propagation
fn select_opt(node ?Node) ?int {
result := if value := node {
match value {
First { opt_first(value)? }
Second { opt_second(value)? }
}
} else {
0
}
return result
}

// match used directly as the return value
fn direct_match(node Node) !int {
return match node {
First { lower_first(node)! }
Second { lower_second(node)! }
}
}

// match assigned directly to a variable
fn assign_match(node Node) !int {
x := match node {
First { lower_first(node)! }
Second { lower_second(node)! }
}
return x
}

fn test_match_as_if_expr_value_with_propagation() {
assert select_value(First{})! == 1
assert select_value(Second{})! == 2
assert select_value(none) or { -1 } == 0
}

fn test_parenthesized_match_as_if_expr_value_with_propagation() {
assert select_value_paren(First{})! == 1
assert select_value_paren(Second{})! == 2
assert select_value_paren(none) or { -1 } == 0
}

fn test_match_as_if_expr_value_with_option_propagation() {
assert select_opt(First{})? == 10
assert select_opt(Second{})? == 20
assert select_opt(none) or { -1 } == 0
}

fn test_match_as_return_and_assign_value_with_propagation() {
assert direct_match(First{})! == 1
assert direct_match(Second{})! == 2
assert assign_match(First{})! == 1
assert assign_match(Second{})! == 2
}
84 changes: 84 additions & 0 deletions vlib/v3/tests/match_as_if_expr_value_propagation_codegen_test.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Regression test for https://github.com/vlang/v/issues/28000
// A `match` whose arms use `!`/`?` propagation, used as the value of an
// `if`-expression, must assign the unwrapped result to the if-expression's
// result temp (it used to emit an empty expression `_t = ;`).
import os

const vexe = @VEXE
const tests_dir = os.dir(@FILE)
const v3_dir = os.dir(tests_dir)
const vlib_dir = os.dir(v3_dir)
const v3_src = os.join_path(v3_dir, 'v3.v')

fn test_match_as_if_expr_value_with_propagation() {
v3_bin := os.join_path(os.temp_dir(), 'v3_match_as_if_expr_value_propagation_test')
build :=
os.execute('${vexe} -gc none -path "${vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${v3_src}')
assert build.exit_code == 0, build.output

src := os.join_path(os.temp_dir(), 'v3_match_as_if_expr_value_propagation_input.v')
os.write_file(src, 'module main

struct First {}
struct Second {}

type Node = First | Second

fn lower_first(_ First) !int {
return 1
}

fn lower_second(_ Second) !int {
return 2
}

fn select_value(node ?Node) !int {
result := if value := node {
match value {
First { lower_first(value)! }
Second { lower_second(value)! }
}
} else {
0
}
return result
}

fn select_value_paren(node ?Node) !int {
result := if value := node {
(match value {
First { lower_first(value)! }
Second { lower_second(value)! }
})
} else {
0
}
return result
}

fn direct_match(node Node) !int {
return match node {
First { lower_first(node)! }
Second { lower_second(node)! }
}
}

fn main() {
println(select_value(First{})!)
println(select_value(Second{})!)
println(select_value_paren(First{})!)
println(direct_match(Second{})!)
}
') or {
panic(err)
}

bin := os.join_path(os.temp_dir(), 'v3_match_as_if_expr_value_propagation_out')
compile := os.execute('${v3_bin} ${src} -b c -o ${bin}')
assert compile.exit_code == 0, compile.output
assert !compile.output.contains('C compilation failed'), compile.output

run := os.execute(bin)
assert run.exit_code == 0, run.output
assert run.output.trim_space() == '1\n2\n1\n2'
}
Loading