@@ -137,6 +137,13 @@ fn test_interface_array_repeat_evaluates_receiver_once() {
137137 assert out == '1\n3'
138138}
139139
140+fn test_negative_is_return_smartcasts_following_statements() {
141+ v3_bin := build_v3_review_transform()
142+ out := run_good(v3_bin, 'negative_is_return_smartcast',
143+ 'struct MapKind {\n\tkey_type int\n\tvalue_type int\n}\nstruct OtherKind {}\ntype Kind = MapKind | OtherKind\n\nfn passthrough(k Kind) Kind {\n\treturn k\n}\n\nfn score(k Kind) int {\n\tclean := passthrough(k)\n\tif clean !is MapKind {\n\t\treturn 0\n\t}\n\treturn clean.key_type + clean.value_type\n}\n\nfn main() {\n\tprintln(int_str(score(Kind(MapKind{\n\t\tkey_type: 2\n\t\tvalue_type: 5\n\t}))))\n\tprintln(int_str(score(Kind(OtherKind{}))))\n}\n')
144+ assert out == '7\n0'
145+}
146+
140147fn test_comptime_type_conditions_handle_logical_ops() {
141148 v3_bin := build_v3_review_transform()
142149 out := run_good(v3_bin, 'comptime_type_condition_logical_ops',
@@ -357,6 +357,16 @@ fn test_fixed_array_length_checks() {
357357 assert good_ret_lit == '5'
358358}
359359
360+fn test_statement_if_branch_tails_are_not_value_checked() {
361+ v3_bin := build_v3()
362+ statement_if := run_good(v3_bin, 'statement_if_mixed_tail_exprs',
363+ "fn main() {\n\tmut n := 0\n\tmut errors := []string{}\n\tif true {\n\t\tn++\n\t} else {\n\t\terrors << 'bad'\n\t}\n\tprintln(int_str(n + errors.len))\n}\n")
364+ assert statement_if == '1'
365+ run_bad(v3_bin, 'bad_if_branch_primitive_mismatch',
366+ "fn main() {\n\tc := true\n\t_ := if c { 1 } else { 'bad' }\n}\n",
367+ 'if-expression branch type mismatch')
368+}
369+
360370// Regression tests for the post-PR review fixes around generic struct receivers
361371// and generic heap struct literals.
362372fn test_generic_struct_receiver_and_heap_init() {
@@ -1208,6 +1208,110 @@ fn (mut t Transformer) transform_if_branches_with_smartcast(id flat.NodeId, node
12081208 return new_if
12091209}
12101210
1211+fn (t &Transformer) post_if_exit_smartcasts(id flat.NodeId) []IsExprInfo {
1212+ if int(id) < 0 {
1213+ return []IsExprInfo{}
1214+ }
1215+ node := t.a.nodes[int(id)]
1216+ if node.kind != .if_expr || node.children_count < 2 {
1217+ return []IsExprInfo{}
1218+ }
1219+ cond_id := t.a.child(&node, 0)
1220+ then_id := t.a.child(&node, 1)
1221+ if info := t.negated_is_expr_info(cond_id) {
1222+ if t.stmt_definitely_exits(then_id) {
1223+ return [info]
1224+ }
1225+ }
1226+ if node.children_count >= 3 {
1227+ else_id := t.a.child(&node, 2)
1228+ if t.stmt_definitely_exits(else_id) {
1229+ return t.extract_all_is_exprs(cond_id)
1230+ }
1231+ }
1232+ return []IsExprInfo{}
1233+}
1234+
1235+fn (t &Transformer) negated_is_expr_info(cond_id flat.NodeId) ?IsExprInfo {
1236+ if int(cond_id) < 0 {
1237+ return none
1238+ }
1239+ cond := t.a.nodes[int(cond_id)]
1240+ if cond.kind != .prefix || cond.op != .not || cond.children_count == 0 {
1241+ return none
1242+ }
1243+ inner_id := t.a.child(&cond, 0)
1244+ if int(inner_id) < 0 {
1245+ return none
1246+ }
1247+ inner := t.a.nodes[int(inner_id)]
1248+ if inner.kind != .is_expr {
1249+ return none
1250+ }
1251+ info := t.extract_is_expr(inner_id)
1252+ if info.expr_name.len == 0 || info.sum_type_name.len == 0 || info.variant_name.len == 0 {
1253+ return none
1254+ }
1255+ return info
1256+}
1257+
1258+fn (t &Transformer) stmt_definitely_exits(id flat.NodeId) bool {
1259+ if int(id) < 0 {
1260+ return false
1261+ }
1262+ node := t.a.nodes[int(id)]
1263+ match node.kind {
1264+ .return_stmt {
1265+ return true
1266+ }
1267+ .block {
1268+ for i in 0 .. node.children_count {
1269+ if t.stmt_definitely_exits(t.a.child(&node, i)) {
1270+ return true
1271+ }
1272+ }
1273+ return false
1274+ }
1275+ .if_expr {
1276+ if node.children_count < 3 {
1277+ return false
1278+ }
1279+ return t.stmt_definitely_exits(t.a.child(&node, 1))
1280+ && t.stmt_definitely_exits(t.a.child(&node, 2))
1281+ }
1282+ .match_stmt {
1283+ if node.children_count < 2 {
1284+ return false
1285+ }
1286+ mut has_else := false
1287+ for i in 1 .. node.children_count {
1288+ branch := t.a.child_node(&node, i)
1289+ if branch.kind != .match_branch {
1290+ return false
1291+ }
1292+ if branch.value == 'else' {
1293+ has_else = true
1294+ }
1295+ body_start := if branch.value == 'else' { 0 } else { branch.value.int() }
1296+ mut branch_exits := false
1297+ for j in body_start .. branch.children_count {
1298+ if t.stmt_definitely_exits(t.a.child(branch, j)) {
1299+ branch_exits = true
1300+ break
1301+ }
1302+ }
1303+ if !branch_exits {
1304+ return false
1305+ }
1306+ }
1307+ return has_else
1308+ }
1309+ else {
1310+ return false
1311+ }
1312+ }
1313+}
1314+
12111315// --- helpers ---
12121316
12131317// IsExprInfo stores is expr info metadata used by transform.
@@ -1641,6 +1641,12 @@ fn (t &Transformer) fn_return_type_for_name(name string) ?string {
16411641// transform_stmts transforms transform stmts data for transform.
16421642pub fn (mut t Transformer) transform_stmts(ids []flat.NodeId) []flat.NodeId {
16431643 mut result := []flat.NodeId{cap: ids.len}
1644+ mut post_if_smartcasts := 0
1645+ defer {
1646+ for _ in 0 .. post_if_smartcasts {
1647+ t.pop_smartcast()
1648+ }
1649+ }
16441650 mut i := 0
16451651 for i < ids.len {
16461652 id := ids[i]
@@ -1688,6 +1694,10 @@ pub fn (mut t Transformer) transform_stmts(ids []flat.NodeId) []flat.NodeId {
16881694 for eid in expanded {
16891695 result << eid
16901696 }
1697+ for info in t.post_if_exit_smartcasts(id) {
1698+ t.push_smartcast(info.expr_name, info.variant_name, info.sum_type_name)
1699+ post_if_smartcasts++
1700+ }
16911701 i++
16921702 }
16931703 t.drain_pending(mut result)
@@ -220,6 +220,7 @@ pub mut:
220220 resolved_call_set []bool
221221 resolved_fn_value_names []string // node_id -> resolved function value name
222222 resolved_fn_value_set []bool
223+ statement_nodes []bool
223224 // Methods used as *values* (`recv.method` passed as a callback), recorded per enclosing
224225 // function during semantic checking — which has full scope/type info, runs before
225226 // markused, and (unlike a call) routes a value-context selector through check_selector.
@@ -297,6 +298,7 @@ pub fn TypeChecker.new(a &flat.FlatAst) TypeChecker {
297298 resolved_call_set: []bool{len: a.nodes.len}
298299 resolved_fn_value_names: []string{len: a.nodes.len}
299300 resolved_fn_value_set: []bool{len: a.nodes.len}
301+ statement_nodes: []bool{len: a.nodes.len}
300302 method_values_by_fn: map[int][]string{}
301303 method_value_locals: map[string]bool{}
302304 method_value_local_depth: map[string]int{}
@@ -341,6 +343,7 @@ fn (mut tc TypeChecker) reset_node_caches(n int) {
341343 tc.resolved_call_set = []bool{len: n}
342344 tc.resolved_fn_value_names = []string{len: n}
343345 tc.resolved_fn_value_set = []bool{len: n}
346+ tc.statement_nodes = []bool{len: n}
344347 tc.expr_type_values = []Type{len: n, init: Type(void_)}
345348 tc.expr_type_set = []bool{len: n}
346349 tc.checking_nodes = []bool{len: n}
@@ -348,13 +351,14 @@ fn (mut tc TypeChecker) reset_node_caches(n int) {
348351
349352fn (mut tc TypeChecker) extend_node_caches(n int) {
350353 if n <= tc.resolved_call_names.len && n <= tc.resolved_fn_value_names.len
351- && n <= tc.expr_type_values.len && n <= tc.checking_nodes.len {
354+ && n <= tc.statement_nodes.len && n <= tc.expr_type_values.len && n <= tc.checking_nodes.len {
352355 return
353356 }
354357 extend_string_cache(mut tc.resolved_call_names, n)
355358 extend_bool_cache(mut tc.resolved_call_set, n)
356359 extend_string_cache(mut tc.resolved_fn_value_names, n)
357360 extend_bool_cache(mut tc.resolved_fn_value_set, n)
361+ extend_bool_cache(mut tc.statement_nodes, n)
358362 extend_type_cache(mut tc.expr_type_values, n)
359363 extend_bool_cache(mut tc.expr_type_set, n)
360364 extend_bool_cache(mut tc.checking_nodes, n)
@@ -2107,13 +2111,18 @@ fn (tc &TypeChecker) fn_returns_veb_result(node flat.Node) bool {
21072111
21082112// check_fn_body validates check fn body state for types.
21092113fn (mut tc TypeChecker) check_fn_body(node flat.Node) {
2114+ saved_smartcasts := clone_smartcasts(tc.smartcasts)
2115+ defer {
2116+ tc.smartcasts = clone_smartcasts(saved_smartcasts)
2117+ }
21102118 for i in 0 .. node.children_count {
21112119 child_id := tc.a.child(&node, i)
21122120 child := tc.a.child_node(&node, i)
21132121 if child.kind == .param {
21142122 continue
21152123 }
2116- tc.check_node(child_id)
2124+ tc.check_stmt_node(child_id)
2125+ tc.apply_post_if_exit_smartcasts(child_id)
21172126 }
21182127}
21192128
@@ -3298,9 +3307,7 @@ fn (mut tc TypeChecker) check_select_stmt(node flat.Node) {
32983307 }
32993308 body_start = 2
33003309 }
3301- for j in body_start .. branch.children_count {
3302- tc.check_node(tc.a.child(&branch, j))
3303- }
3310+ tc.check_statement_sequence(branch, body_start, false)
33043311 tc.pop_scope()
33053312 }
33063313}
@@ -3339,7 +3346,7 @@ fn (mut tc TypeChecker) check_or_expr(node flat.Node) {
33393346 }
33403347 tc.push_scope()
33413348 tc.cur_scope.insert('err', tc.parse_type('IError'))
3342- tc.check_node(tc.a.child(&node, 1))
3349+ tc.check_branch_node(tc.a.child(&node, 1), true)
33433350 tc.pop_scope()
33443351}
33453352
@@ -3360,7 +3367,7 @@ fn (mut tc TypeChecker) check_fn_literal(node flat.Node) {
33603367 if child.kind == .param || child.kind == .ident {
33613368 continue
33623369 }
3363- tc.check_node(child_id)
3370+ tc.check_stmt_node(child_id)
33643371 }
33653372 tc.pop_scope()
33663373 tc.cur_fn_ret_type = saved_ret
@@ -3385,9 +3392,7 @@ fn (mut tc TypeChecker) check_lambda_expr(node flat.Node) {
33853392// check_block validates check block state for types.
33863393fn (mut tc TypeChecker) check_block(node flat.Node) {
33873394 tc.push_scope()
3388- for i in 0 .. node.children_count {
3389- tc.check_node(tc.a.child(&node, i))
3390- }
3395+ tc.check_statement_sequence(node, 0, false)
33913396 tc.pop_scope()
33923397}
33933398
@@ -3413,7 +3418,7 @@ fn (mut tc TypeChecker) check_for_stmt(node flat.Node) {
34133418 }
34143419 }
34153420 for i in 3 .. node.children_count {
3416- tc.check_node(tc.a.child(&node, i))
3421+ tc.check_stmt_node(tc.a.child(&node, i))
34173422 }
34183423 tc.pop_scope()
34193424}
@@ -3481,7 +3486,7 @@ fn (mut tc TypeChecker) check_for_in_stmt(node flat.Node) {
34813486 }
34823487 }
34833488 for i in header .. node.children_count {
3484- tc.check_node(tc.a.child(&node, i))
3489+ tc.check_stmt_node(tc.a.child(&node, i))
34853490 }
34863491 tc.pop_scope()
34873492}
@@ -6351,6 +6356,7 @@ fn (mut tc TypeChecker) check_if_expr(id flat.NodeId, node flat.Node) {
63516356 if node.children_count < 2 {
63526357 return
63536358 }
6359+ value_context := !tc.is_statement_node(id)
63546360 cond_id := tc.a.child(&node, 0)
63556361 guard_bindings := tc.check_condition(cond_id)
63566362 smartcasts := tc.extract_smartcasts(cond_id)
@@ -6365,14 +6371,20 @@ fn (mut tc TypeChecker) check_if_expr(id flat.NodeId, node flat.Node) {
63656371 for binding in guard_bindings {
63666372 tc.cur_scope.insert(binding.name, binding.typ)
63676373 }
6368- tc.check_node(then_id)
6369- then_type := tc.branch_tail_type(then_id)
6374+ tc.check_branch_node(then_id, value_context)
63706375 tc.pop_scope()
63716376 tc.smartcasts = clone_smartcasts(saved_smartcasts)
6377+ if node.children_count > 2 {
6378+ else_id := tc.a.child(&node, 2)
6379+ tc.check_branch_node(else_id, value_context)
6380+ }
6381+ if !value_context {
6382+ return
6383+ }
6384+ then_type := tc.branch_tail_type(then_id)
63726385 mut else_type := Type(void_)
63736386 if node.children_count > 2 {
63746387 else_id := tc.a.child(&node, 2)
6375- tc.check_node(else_id)
63766388 else_type = tc.branch_tail_type(else_id)
63776389 }
63786390 if then_type !is Void && else_type !is Void {
@@ -6388,6 +6400,119 @@ fn (mut tc TypeChecker) check_if_expr(id flat.NodeId, node flat.Node) {
63886400 }
63896401}
63906402
6403+fn (mut tc TypeChecker) check_stmt_node(id flat.NodeId) {
6404+ if !tc.valid_node_id(id) {
6405+ return
6406+ }
6407+ idx := int(id)
6408+ if idx >= tc.statement_nodes.len {
6409+ tc.extend_node_caches(tc.a.nodes.len)
6410+ }
6411+ if idx < tc.statement_nodes.len {
6412+ tc.statement_nodes[idx] = true
6413+ }
6414+ tc.check_node(id)
6415+}
6416+
6417+fn (tc &TypeChecker) is_statement_node(id flat.NodeId) bool {
6418+ idx := int(id)
6419+ return idx >= 0 && idx < tc.statement_nodes.len && tc.statement_nodes[idx]
6420+}
6421+
6422+fn (mut tc TypeChecker) check_statement_sequence(node flat.Node, body_start int, value_tail bool) {
6423+ saved_smartcasts := clone_smartcasts(tc.smartcasts)
6424+ defer {
6425+ tc.smartcasts = clone_smartcasts(saved_smartcasts)
6426+ }
6427+ last_idx := int(node.children_count) - 1
6428+ for i in body_start .. node.children_count {
6429+ child_id := tc.a.child(&node, i)
6430+ if value_tail && i == last_idx {
6431+ tc.check_node(child_id)
6432+ } else {
6433+ tc.check_stmt_node(child_id)
6434+ }
6435+ tc.apply_post_if_exit_smartcasts(child_id)
6436+ }
6437+}
6438+
6439+fn (mut tc TypeChecker) check_branch_node(id flat.NodeId, value_tail bool) {
6440+ if !tc.valid_node_id(id) {
6441+ return
6442+ }
6443+ node := tc.a.nodes[int(id)]
6444+ if node.kind == .block {
6445+ tc.push_scope()
6446+ tc.check_statement_sequence(node, 0, value_tail)
6447+ tc.pop_scope()
6448+ return
6449+ }
6450+ if value_tail {
6451+ tc.check_node(id)
6452+ } else {
6453+ tc.check_stmt_node(id)
6454+ }
6455+}
6456+
6457+fn (mut tc TypeChecker) apply_post_if_exit_smartcasts(id flat.NodeId) {
6458+ for binding in tc.post_if_exit_smartcasts(id) {
6459+ if valid_string_data(binding.name) {
6460+ tc.smartcasts[binding.name] = binding.typ
6461+ }
6462+ }
6463+}
6464+
6465+fn (tc &TypeChecker) post_if_exit_smartcasts(id flat.NodeId) []LocalBinding {
6466+ if !tc.valid_node_id(id) {
6467+ return []LocalBinding{}
6468+ }
6469+ node := tc.a.nodes[int(id)]
6470+ if node.kind != .if_expr || node.children_count < 2 {
6471+ return []LocalBinding{}
6472+ }
6473+ cond_id := tc.a.child(&node, 0)
6474+ then_id := tc.a.child(&node, 1)
6475+ if binding := tc.negated_is_smartcast(cond_id) {
6476+ if tc.stmt_definitely_returns(then_id) {
6477+ return [binding]
6478+ }
6479+ }
6480+ if node.children_count >= 3 {
6481+ else_id := tc.a.child(&node, 2)
6482+ if tc.stmt_definitely_returns(else_id) {
6483+ return tc.extract_smartcasts(cond_id)
6484+ }
6485+ }
6486+ return []LocalBinding{}
6487+}
6488+
6489+fn (tc &TypeChecker) negated_is_smartcast(cond_id flat.NodeId) ?LocalBinding {
6490+ if !tc.valid_node_id(cond_id) {
6491+ return none
6492+ }
6493+ cond := tc.a.nodes[int(cond_id)]
6494+ if cond.kind != .prefix || cond.op != .not || cond.children_count == 0 {
6495+ return none
6496+ }
6497+ inner_id := tc.a.child(&cond, 0)
6498+ if !tc.valid_node_id(inner_id) {
6499+ return none
6500+ }
6501+ inner := tc.a.nodes[int(inner_id)]
6502+ if inner.kind != .is_expr || inner.children_count == 0 {
6503+ return none
6504+ }
6505+ expr_id := tc.a.child(&inner, 0)
6506+ key := tc.expr_key(expr_id)
6507+ if key.len == 0 || !valid_string_data(key) || inner.value.len == 0 {
6508+ return none
6509+ }
6510+ return LocalBinding{
6511+ name: key
6512+ typ: tc.parse_type(inner.value)
6513+ }
6514+}
6515+
63916516// branch_has_value_tail converts branch has value tail data for types.
63926517fn (tc &TypeChecker) branch_has_value_tail(id flat.NodeId) bool {
63936518 if !tc.valid_node_id(id) {
@@ -6501,10 +6626,11 @@ fn (mut tc TypeChecker) check_if_guard(id flat.NodeId, node flat.Node) []LocalBi
65016626}
65026627
65036628// check_match_stmt validates check match stmt state for types.
6504-fn (mut tc TypeChecker) check_match_stmt(_id flat.NodeId, node flat.Node) {
6629+fn (mut tc TypeChecker) check_match_stmt(id flat.NodeId, node flat.Node) {
65056630 if node.children_count == 0 {
65066631 return
65076632 }
6633+ value_context := !tc.is_statement_node(id)
65086634 subject_id := tc.a.child(&node, 0)
65096635 tc.check_node(subject_id)
65106636 subject_key := tc.expr_key(subject_id)
@@ -6541,9 +6667,7 @@ fn (mut tc TypeChecker) check_match_stmt(_id flat.NodeId, node flat.Node) {
65416667 }
65426668 }
65436669 tc.push_scope()
6544- for j in n_conds .. branch.children_count {
6545- tc.check_node(tc.a.child(branch, j))
6546- }
6670+ tc.check_statement_sequence(branch, n_conds, value_context)
65476671 tc.pop_scope()
65486672 tc.smartcasts = clone_smartcasts(saved_smartcasts)
65496673 }