From 8739cbd7fc28231971854e7a7606cfeff0e26ea8 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Mon, 29 Jun 2026 16:06:27 +0300 Subject: [PATCH] v3: fix self host --- .../tests/review_transform_regressions_test.v | 7 + vlib/v3/tests/type_checker_errors_test.v | 10 ++ vlib/v3/transform/if.v | 104 +++++++++++ vlib/v3/transform/transform.v | 10 ++ vlib/v3/types/checker.v | 162 ++++++++++++++++-- 5 files changed, 274 insertions(+), 19 deletions(-) diff --git a/vlib/v3/tests/review_transform_regressions_test.v b/vlib/v3/tests/review_transform_regressions_test.v index a7aacbb85..70e6d67c8 100644 --- a/vlib/v3/tests/review_transform_regressions_test.v +++ b/vlib/v3/tests/review_transform_regressions_test.v @@ -137,6 +137,13 @@ fn test_interface_array_repeat_evaluates_receiver_once() { assert out == '1\n3' } +fn test_negative_is_return_smartcasts_following_statements() { + v3_bin := build_v3_review_transform() + out := run_good(v3_bin, 'negative_is_return_smartcast', + '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') + assert out == '7\n0' +} + fn test_comptime_type_conditions_handle_logical_ops() { v3_bin := build_v3_review_transform() out := run_good(v3_bin, 'comptime_type_condition_logical_ops', diff --git a/vlib/v3/tests/type_checker_errors_test.v b/vlib/v3/tests/type_checker_errors_test.v index 5a98624c6..0236b69fc 100644 --- a/vlib/v3/tests/type_checker_errors_test.v +++ b/vlib/v3/tests/type_checker_errors_test.v @@ -357,6 +357,16 @@ fn test_fixed_array_length_checks() { assert good_ret_lit == '5' } +fn test_statement_if_branch_tails_are_not_value_checked() { + v3_bin := build_v3() + statement_if := run_good(v3_bin, 'statement_if_mixed_tail_exprs', + "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") + assert statement_if == '1' + run_bad(v3_bin, 'bad_if_branch_primitive_mismatch', + "fn main() {\n\tc := true\n\t_ := if c { 1 } else { 'bad' }\n}\n", + 'if-expression branch type mismatch') +} + // Regression tests for the post-PR review fixes around generic struct receivers // and generic heap struct literals. fn test_generic_struct_receiver_and_heap_init() { diff --git a/vlib/v3/transform/if.v b/vlib/v3/transform/if.v index a099c966f..49fd67c42 100644 --- a/vlib/v3/transform/if.v +++ b/vlib/v3/transform/if.v @@ -1208,6 +1208,110 @@ fn (mut t Transformer) transform_if_branches_with_smartcast(id flat.NodeId, node return new_if } +fn (t &Transformer) post_if_exit_smartcasts(id flat.NodeId) []IsExprInfo { + if int(id) < 0 { + return []IsExprInfo{} + } + node := t.a.nodes[int(id)] + if node.kind != .if_expr || node.children_count < 2 { + return []IsExprInfo{} + } + cond_id := t.a.child(&node, 0) + then_id := t.a.child(&node, 1) + if info := t.negated_is_expr_info(cond_id) { + if t.stmt_definitely_exits(then_id) { + return [info] + } + } + if node.children_count >= 3 { + else_id := t.a.child(&node, 2) + if t.stmt_definitely_exits(else_id) { + return t.extract_all_is_exprs(cond_id) + } + } + return []IsExprInfo{} +} + +fn (t &Transformer) negated_is_expr_info(cond_id flat.NodeId) ?IsExprInfo { + if int(cond_id) < 0 { + return none + } + cond := t.a.nodes[int(cond_id)] + if cond.kind != .prefix || cond.op != .not || cond.children_count == 0 { + return none + } + inner_id := t.a.child(&cond, 0) + if int(inner_id) < 0 { + return none + } + inner := t.a.nodes[int(inner_id)] + if inner.kind != .is_expr { + return none + } + info := t.extract_is_expr(inner_id) + if info.expr_name.len == 0 || info.sum_type_name.len == 0 || info.variant_name.len == 0 { + return none + } + return info +} + +fn (t &Transformer) stmt_definitely_exits(id flat.NodeId) bool { + if int(id) < 0 { + return false + } + node := t.a.nodes[int(id)] + match node.kind { + .return_stmt { + return true + } + .block { + for i in 0 .. node.children_count { + if t.stmt_definitely_exits(t.a.child(&node, i)) { + return true + } + } + return false + } + .if_expr { + if node.children_count < 3 { + return false + } + return t.stmt_definitely_exits(t.a.child(&node, 1)) + && t.stmt_definitely_exits(t.a.child(&node, 2)) + } + .match_stmt { + if node.children_count < 2 { + return false + } + mut has_else := false + for i in 1 .. node.children_count { + branch := t.a.child_node(&node, i) + if branch.kind != .match_branch { + return false + } + if branch.value == 'else' { + has_else = true + } + body_start := if branch.value == 'else' { 0 } else { branch.value.int() } + mut branch_exits := false + for j in body_start .. branch.children_count { + if t.stmt_definitely_exits(t.a.child(branch, j)) { + branch_exits = true + break + } + } + if !branch_exits { + return false + } + } + return has_else + } + else { + return false + } + } +} + // --- helpers --- // IsExprInfo stores is expr info metadata used by transform. diff --git a/vlib/v3/transform/transform.v b/vlib/v3/transform/transform.v index f8c1917d4..290393077 100644 --- a/vlib/v3/transform/transform.v +++ b/vlib/v3/transform/transform.v @@ -1641,6 +1641,12 @@ fn (t &Transformer) fn_return_type_for_name(name string) ?string { // transform_stmts transforms transform stmts data for transform. pub fn (mut t Transformer) transform_stmts(ids []flat.NodeId) []flat.NodeId { mut result := []flat.NodeId{cap: ids.len} + mut post_if_smartcasts := 0 + defer { + for _ in 0 .. post_if_smartcasts { + t.pop_smartcast() + } + } mut i := 0 for i < ids.len { id := ids[i] @@ -1688,6 +1694,10 @@ pub fn (mut t Transformer) transform_stmts(ids []flat.NodeId) []flat.NodeId { for eid in expanded { result << eid } + for info in t.post_if_exit_smartcasts(id) { + t.push_smartcast(info.expr_name, info.variant_name, info.sum_type_name) + post_if_smartcasts++ + } i++ } t.drain_pending(mut result) diff --git a/vlib/v3/types/checker.v b/vlib/v3/types/checker.v index 57158d623..2f426cb0b 100644 --- a/vlib/v3/types/checker.v +++ b/vlib/v3/types/checker.v @@ -220,6 +220,7 @@ pub mut: resolved_call_set []bool resolved_fn_value_names []string // node_id -> resolved function value name resolved_fn_value_set []bool + statement_nodes []bool // Methods used as *values* (`recv.method` passed as a callback), recorded per enclosing // function during semantic checking — which has full scope/type info, runs before // 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 { resolved_call_set: []bool{len: a.nodes.len} resolved_fn_value_names: []string{len: a.nodes.len} resolved_fn_value_set: []bool{len: a.nodes.len} + statement_nodes: []bool{len: a.nodes.len} method_values_by_fn: map[int][]string{} method_value_locals: map[string]bool{} method_value_local_depth: map[string]int{} @@ -341,6 +343,7 @@ fn (mut tc TypeChecker) reset_node_caches(n int) { tc.resolved_call_set = []bool{len: n} tc.resolved_fn_value_names = []string{len: n} tc.resolved_fn_value_set = []bool{len: n} + tc.statement_nodes = []bool{len: n} tc.expr_type_values = []Type{len: n, init: Type(void_)} tc.expr_type_set = []bool{len: n} tc.checking_nodes = []bool{len: n} @@ -348,13 +351,14 @@ fn (mut tc TypeChecker) reset_node_caches(n int) { fn (mut tc TypeChecker) extend_node_caches(n int) { if n <= tc.resolved_call_names.len && n <= tc.resolved_fn_value_names.len - && n <= tc.expr_type_values.len && n <= tc.checking_nodes.len { + && n <= tc.statement_nodes.len && n <= tc.expr_type_values.len && n <= tc.checking_nodes.len { return } extend_string_cache(mut tc.resolved_call_names, n) extend_bool_cache(mut tc.resolved_call_set, n) extend_string_cache(mut tc.resolved_fn_value_names, n) extend_bool_cache(mut tc.resolved_fn_value_set, n) + extend_bool_cache(mut tc.statement_nodes, n) extend_type_cache(mut tc.expr_type_values, n) extend_bool_cache(mut tc.expr_type_set, n) extend_bool_cache(mut tc.checking_nodes, n) @@ -2107,13 +2111,18 @@ fn (tc &TypeChecker) fn_returns_veb_result(node flat.Node) bool { // check_fn_body validates check fn body state for types. fn (mut tc TypeChecker) check_fn_body(node flat.Node) { + saved_smartcasts := clone_smartcasts(tc.smartcasts) + defer { + tc.smartcasts = clone_smartcasts(saved_smartcasts) + } for i in 0 .. node.children_count { child_id := tc.a.child(&node, i) child := tc.a.child_node(&node, i) if child.kind == .param { continue } - tc.check_node(child_id) + tc.check_stmt_node(child_id) + tc.apply_post_if_exit_smartcasts(child_id) } } @@ -3298,9 +3307,7 @@ fn (mut tc TypeChecker) check_select_stmt(node flat.Node) { } body_start = 2 } - for j in body_start .. branch.children_count { - tc.check_node(tc.a.child(&branch, j)) - } + tc.check_statement_sequence(branch, body_start, false) tc.pop_scope() } } @@ -3339,7 +3346,7 @@ fn (mut tc TypeChecker) check_or_expr(node flat.Node) { } tc.push_scope() tc.cur_scope.insert('err', tc.parse_type('IError')) - tc.check_node(tc.a.child(&node, 1)) + tc.check_branch_node(tc.a.child(&node, 1), true) tc.pop_scope() } @@ -3360,7 +3367,7 @@ fn (mut tc TypeChecker) check_fn_literal(node flat.Node) { if child.kind == .param || child.kind == .ident { continue } - tc.check_node(child_id) + tc.check_stmt_node(child_id) } tc.pop_scope() tc.cur_fn_ret_type = saved_ret @@ -3385,9 +3392,7 @@ fn (mut tc TypeChecker) check_lambda_expr(node flat.Node) { // check_block validates check block state for types. fn (mut tc TypeChecker) check_block(node flat.Node) { tc.push_scope() - for i in 0 .. node.children_count { - tc.check_node(tc.a.child(&node, i)) - } + tc.check_statement_sequence(node, 0, false) tc.pop_scope() } @@ -3413,7 +3418,7 @@ fn (mut tc TypeChecker) check_for_stmt(node flat.Node) { } } for i in 3 .. node.children_count { - tc.check_node(tc.a.child(&node, i)) + tc.check_stmt_node(tc.a.child(&node, i)) } tc.pop_scope() } @@ -3481,7 +3486,7 @@ fn (mut tc TypeChecker) check_for_in_stmt(node flat.Node) { } } for i in header .. node.children_count { - tc.check_node(tc.a.child(&node, i)) + tc.check_stmt_node(tc.a.child(&node, i)) } tc.pop_scope() } @@ -6351,6 +6356,7 @@ fn (mut tc TypeChecker) check_if_expr(id flat.NodeId, node flat.Node) { if node.children_count < 2 { return } + value_context := !tc.is_statement_node(id) cond_id := tc.a.child(&node, 0) guard_bindings := tc.check_condition(cond_id) smartcasts := tc.extract_smartcasts(cond_id) @@ -6365,14 +6371,20 @@ fn (mut tc TypeChecker) check_if_expr(id flat.NodeId, node flat.Node) { for binding in guard_bindings { tc.cur_scope.insert(binding.name, binding.typ) } - tc.check_node(then_id) - then_type := tc.branch_tail_type(then_id) + tc.check_branch_node(then_id, value_context) tc.pop_scope() tc.smartcasts = clone_smartcasts(saved_smartcasts) + if node.children_count > 2 { + else_id := tc.a.child(&node, 2) + tc.check_branch_node(else_id, value_context) + } + if !value_context { + return + } + then_type := tc.branch_tail_type(then_id) mut else_type := Type(void_) if node.children_count > 2 { else_id := tc.a.child(&node, 2) - tc.check_node(else_id) else_type = tc.branch_tail_type(else_id) } 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) { } } +fn (mut tc TypeChecker) check_stmt_node(id flat.NodeId) { + if !tc.valid_node_id(id) { + return + } + idx := int(id) + if idx >= tc.statement_nodes.len { + tc.extend_node_caches(tc.a.nodes.len) + } + if idx < tc.statement_nodes.len { + tc.statement_nodes[idx] = true + } + tc.check_node(id) +} + +fn (tc &TypeChecker) is_statement_node(id flat.NodeId) bool { + idx := int(id) + return idx >= 0 && idx < tc.statement_nodes.len && tc.statement_nodes[idx] +} + +fn (mut tc TypeChecker) check_statement_sequence(node flat.Node, body_start int, value_tail bool) { + saved_smartcasts := clone_smartcasts(tc.smartcasts) + defer { + tc.smartcasts = clone_smartcasts(saved_smartcasts) + } + last_idx := int(node.children_count) - 1 + for i in body_start .. node.children_count { + child_id := tc.a.child(&node, i) + if value_tail && i == last_idx { + tc.check_node(child_id) + } else { + tc.check_stmt_node(child_id) + } + tc.apply_post_if_exit_smartcasts(child_id) + } +} + +fn (mut tc TypeChecker) check_branch_node(id flat.NodeId, value_tail bool) { + if !tc.valid_node_id(id) { + return + } + node := tc.a.nodes[int(id)] + if node.kind == .block { + tc.push_scope() + tc.check_statement_sequence(node, 0, value_tail) + tc.pop_scope() + return + } + if value_tail { + tc.check_node(id) + } else { + tc.check_stmt_node(id) + } +} + +fn (mut tc TypeChecker) apply_post_if_exit_smartcasts(id flat.NodeId) { + for binding in tc.post_if_exit_smartcasts(id) { + if valid_string_data(binding.name) { + tc.smartcasts[binding.name] = binding.typ + } + } +} + +fn (tc &TypeChecker) post_if_exit_smartcasts(id flat.NodeId) []LocalBinding { + if !tc.valid_node_id(id) { + return []LocalBinding{} + } + node := tc.a.nodes[int(id)] + if node.kind != .if_expr || node.children_count < 2 { + return []LocalBinding{} + } + cond_id := tc.a.child(&node, 0) + then_id := tc.a.child(&node, 1) + if binding := tc.negated_is_smartcast(cond_id) { + if tc.stmt_definitely_returns(then_id) { + return [binding] + } + } + if node.children_count >= 3 { + else_id := tc.a.child(&node, 2) + if tc.stmt_definitely_returns(else_id) { + return tc.extract_smartcasts(cond_id) + } + } + return []LocalBinding{} +} + +fn (tc &TypeChecker) negated_is_smartcast(cond_id flat.NodeId) ?LocalBinding { + if !tc.valid_node_id(cond_id) { + return none + } + cond := tc.a.nodes[int(cond_id)] + if cond.kind != .prefix || cond.op != .not || cond.children_count == 0 { + return none + } + inner_id := tc.a.child(&cond, 0) + if !tc.valid_node_id(inner_id) { + return none + } + inner := tc.a.nodes[int(inner_id)] + if inner.kind != .is_expr || inner.children_count == 0 { + return none + } + expr_id := tc.a.child(&inner, 0) + key := tc.expr_key(expr_id) + if key.len == 0 || !valid_string_data(key) || inner.value.len == 0 { + return none + } + return LocalBinding{ + name: key + typ: tc.parse_type(inner.value) + } +} + // branch_has_value_tail converts branch has value tail data for types. fn (tc &TypeChecker) branch_has_value_tail(id flat.NodeId) bool { if !tc.valid_node_id(id) { @@ -6501,10 +6626,11 @@ fn (mut tc TypeChecker) check_if_guard(id flat.NodeId, node flat.Node) []LocalBi } // check_match_stmt validates check match stmt state for types. -fn (mut tc TypeChecker) check_match_stmt(_id flat.NodeId, node flat.Node) { +fn (mut tc TypeChecker) check_match_stmt(id flat.NodeId, node flat.Node) { if node.children_count == 0 { return } + value_context := !tc.is_statement_node(id) subject_id := tc.a.child(&node, 0) tc.check_node(subject_id) subject_key := tc.expr_key(subject_id) @@ -6541,9 +6667,7 @@ fn (mut tc TypeChecker) check_match_stmt(_id flat.NodeId, node flat.Node) { } } tc.push_scope() - for j in n_conds .. branch.children_count { - tc.check_node(tc.a.child(branch, j)) - } + tc.check_statement_sequence(branch, n_conds, value_context) tc.pop_scope() tc.smartcasts = clone_smartcasts(saved_smartcasts) } -- 2.39.5