From 4fab05f62db39b5fecdcc1b25b5f361220c3e98c Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 25 Mar 2026 16:42:24 +0300 Subject: [PATCH] checker: fix dereference of T after $if T is $pointer (fixes #25619) --- vlib/v/checker/checker.v | 3 + vlib/v/checker/comptime.v | 5 + vlib/v/checker/if.v | 6 + vlib/v/gen/c/comptime.v | 150 ++++++++++++------ vlib/v/gen/c/utils.v | 3 + .../comptime_if_pointer_binding_test.v | 20 +++ vlib/v/type_resolver/type_resolver.v | 69 ++++++++ 7 files changed, 205 insertions(+), 51 deletions(-) create mode 100644 vlib/v/tests/comptime/comptime_if_pointer_binding_test.v diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 80d04dcba..cfd064894 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -4005,6 +4005,9 @@ fn (mut c Checker) unwrap_generic(typ ast.Type) ast.Type { } } } + if t_typ := c.type_resolver.resolve_bound_generic_type(typ) { + return t_typ + } } return typ } diff --git a/vlib/v/checker/comptime.v b/vlib/v/checker/comptime.v index 5ee4f34f5..73f9a1a80 100644 --- a/vlib/v/checker/comptime.v +++ b/vlib/v/checker/comptime.v @@ -1087,6 +1087,8 @@ fn (mut c Checker) get_expr_type(cond ast.Expr) ast.Type { } else { ast.new_type(type_idx).set_flag(.generic) } + } else if cond.name in c.type_resolver.type_map { + return c.type_resolver.get_ct_type_or_default(cond.name, ast.void_type) } else if var := cond.scope.find_var(cond.name) { // var checked_type = c.unwrap_generic(var.typ) @@ -1176,6 +1178,9 @@ fn (mut c Checker) check_compatible_types(left_type ast.Type, left_name string, } else if expr is ast.TypeNode { typ := c.get_expr_type(expr) right_type := c.unwrap_generic(typ) + if c.type_resolver.bind_matching_generic_type(left_type, right_type) { + return true + } right_sym := c.table.sym(right_type) if right_sym.kind == .placeholder || right_type.has_flag(.generic) { c.error('unknown type `${right_sym.name}`', expr.pos) diff --git a/vlib/v/checker/if.v b/vlib/v/checker/if.v index 284d98c85..b414dfd5e 100644 --- a/vlib/v/checker/if.v +++ b/vlib/v/checker/if.v @@ -140,6 +140,9 @@ fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type { comptime_branch_context_str := if node.is_comptime { c.gen_branch_context_string() } else { '' } for i, mut branch in node.branches { + if node.is_comptime { + c.push_new_comptime_info() + } orig_branch_cond := branch.cond mut comptime_remove_curr_branch_stmts := false if branch.cond is ast.ParExpr && !c.pref.translated && !c.file.is_translated { @@ -562,6 +565,9 @@ fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type { // remove the branch statements since they may contain OS-specific code. branch.stmts = [] } + if node.is_comptime { + c.pop_comptime_info() + } } if nbranches_with_return > 0 { if nbranches_with_return == node.branches.len { diff --git a/vlib/v/gen/c/comptime.v b/vlib/v/gen/c/comptime.v index 897af3276..eef5f3680 100644 --- a/vlib/v/gen/c/comptime.v +++ b/vlib/v/gen/c/comptime.v @@ -644,61 +644,64 @@ fn (mut g Gen) comptime_if(node ast.IfExpr) { g.defer_ifdef += expr_str } if node.is_expr { - len := branch.stmts.len - if len > 0 { - last := branch.stmts.last() as ast.ExprStmt - if len > 1 { - g.indent++ - g.writeln('{') - g.stmts(branch.stmts[..len - 1]) - g.set_current_pos_as_last_stmt_pos() - prev_skip_stmt_pos := g.skip_stmt_pos - g.skip_stmt_pos = true - if is_opt_or_result { - tmp_var2 := g.new_tmp_var() - g.write('{ ${g.base_type(inferred_typ)} ${tmp_var2} = ') - g.stmt(last) - g.writeln('builtin___result_ok(&(${g.base_type(inferred_typ)}[]) { ${tmp_var2} }, (_result*)(&${tmp_var}), sizeof(${g.base_type(inferred_typ)}));') - g.writeln('}') + if is_true.val { + g.bind_comptime_if_generic_types(branch.cond) + len := branch.stmts.len + if len > 0 { + last := branch.stmts.last() as ast.ExprStmt + if len > 1 { + g.indent++ + g.writeln('{') + g.stmts(branch.stmts[..len - 1]) + g.set_current_pos_as_last_stmt_pos() + prev_skip_stmt_pos := g.skip_stmt_pos + g.skip_stmt_pos = true + if is_opt_or_result { + tmp_var2 := g.new_tmp_var() + g.write('{ ${g.base_type(inferred_typ)} ${tmp_var2} = ') + g.stmt(last) + g.writeln('builtin___result_ok(&(${g.base_type(inferred_typ)}[]) { ${tmp_var2} }, (_result*)(&${tmp_var}), sizeof(${g.base_type(inferred_typ)}));') + g.writeln('}') + } else { + g.write('\t${tmp_var} = ') + g.stmt(last) + } + g.skip_stmt_pos = prev_skip_stmt_pos + g.writeln2(';', '}') + g.indent-- + g.write_defer_stmts(branch.scope, false, branch.pos) } else { - g.write('\t${tmp_var} = ') - g.stmt(last) - } - g.skip_stmt_pos = prev_skip_stmt_pos - g.writeln2(';', '}') - g.indent-- - g.write_defer_stmts(branch.scope, false, branch.pos) - } else { - g.indent++ - g.set_current_pos_as_last_stmt_pos() - prev_skip_stmt_pos := g.skip_stmt_pos - g.skip_stmt_pos = true - if is_opt_or_result { - tmp_var2 := g.new_tmp_var() - base_styp := g.base_type(inferred_typ) - g.write('{ ${base_styp} ${tmp_var2} = ') - g.stmt(last) - g.writeln('builtin___result_ok(&(${base_styp}[]) { ${tmp_var2} }, (_result*)(&${tmp_var}), sizeof(${base_styp}));') - g.writeln('}') - } else if is_array_fixed { - tmp_var2 := g.new_tmp_var() - base_styp := g.base_type(inferred_typ) - g.write('{ ${base_styp} ${tmp_var2} = ') - g.stmt(last) - if g.out.last_n(2).contains(';') { - g.go_back(2) + g.indent++ + g.set_current_pos_as_last_stmt_pos() + prev_skip_stmt_pos := g.skip_stmt_pos + g.skip_stmt_pos = true + if is_opt_or_result { + tmp_var2 := g.new_tmp_var() + base_styp := g.base_type(inferred_typ) + g.write('{ ${base_styp} ${tmp_var2} = ') + g.stmt(last) + g.writeln('builtin___result_ok(&(${base_styp}[]) { ${tmp_var2} }, (_result*)(&${tmp_var}), sizeof(${base_styp}));') + g.writeln('}') + } else if is_array_fixed { + tmp_var2 := g.new_tmp_var() + base_styp := g.base_type(inferred_typ) + g.write('{ ${base_styp} ${tmp_var2} = ') + g.stmt(last) + if g.out.last_n(2).contains(';') { + g.go_back(2) + } + g.writeln(';') + g.writeln2('memcpy(&${tmp_var}, &${tmp_var2}, sizeof(${base_styp}));', + '}') + } else { + g.write('${tmp_var} = ') + g.stmt(last) } + g.skip_stmt_pos = prev_skip_stmt_pos g.writeln(';') - g.writeln2('memcpy(&${tmp_var}, &${tmp_var2}, sizeof(${base_styp}));', - '}') - } else { - g.write('${tmp_var} = ') - g.stmt(last) + g.indent-- + g.write_defer_stmts(branch.scope, false, branch.pos) } - g.skip_stmt_pos = prev_skip_stmt_pos - g.writeln(';') - g.indent-- - g.write_defer_stmts(branch.scope, false, branch.pos) } } } else { @@ -708,6 +711,7 @@ fn (mut g Gen) comptime_if(node ast.IfExpr) { g.writeln('{') } if is_true.val || g.pref.output_cross_c { + g.bind_comptime_if_generic_types(branch.cond) g.stmts(branch.stmts) } if should_create_scope { @@ -724,9 +728,53 @@ fn (mut g Gen) comptime_if(node ast.IfExpr) { } } +fn (mut g Gen) bind_comptime_if_generic_types(cond ast.Expr) { + match cond { + ast.ParExpr { + g.bind_comptime_if_generic_types(cond.expr) + } + ast.PrefixExpr { + g.bind_comptime_if_generic_types(cond.right) + } + ast.Likely { + g.bind_comptime_if_generic_types(cond.expr) + } + ast.InfixExpr { + match cond.op { + .and, .logical_or { + g.bind_comptime_if_generic_types(cond.left) + g.bind_comptime_if_generic_types(cond.right) + } + .key_is { + if cond.right is ast.TypeNode { + g.type_resolver.bind_matching_generic_type(g.get_expr_type(cond.left), + cond.right.typ) + } + } + .key_in { + if cond.right is ast.ArrayInit { + left_type := g.get_expr_type(cond.left) + for expr in cond.right.exprs { + if expr is ast.TypeNode + && g.type_resolver.bind_matching_generic_type(left_type, expr.typ) { + break + } + } + } + } + else {} + } + } + else {} + } +} + fn (mut g Gen) get_expr_type(cond ast.Expr) ast.Type { match cond { ast.Ident { + if cond.name in g.type_resolver.type_map { + return g.type_resolver.get_ct_type_or_default(cond.name, ast.void_type) + } return g.unwrap_generic(g.type_resolver.get_type_or_default(cond, cond.obj.typ)) } ast.TypeNode { diff --git a/vlib/v/gen/c/utils.v b/vlib/v/gen/c/utils.v index 63d2e2a38..726e54384 100644 --- a/vlib/v/gen/c/utils.v +++ b/vlib/v/gen/c/utils.v @@ -70,6 +70,9 @@ fn (mut g Gen) unwrap_generic(typ ast.Type) ast.Type { } } } + if t_typ := g.type_resolver.resolve_bound_generic_type(typ) { + return t_typ + } } return resolved_typ } diff --git a/vlib/v/tests/comptime/comptime_if_pointer_binding_test.v b/vlib/v/tests/comptime/comptime_if_pointer_binding_test.v new file mode 100644 index 000000000..65fc9199a --- /dev/null +++ b/vlib/v/tests/comptime/comptime_if_pointer_binding_test.v @@ -0,0 +1,20 @@ +fn type_name_of[U]() string { + return typeof[U]().name +} + +fn pointee_name[T](val T) string { + _ = val + $if T is &V { + return type_name_of[V]() + } $else { + return typeof[T]().name + } +} + +fn test_comptime_if_pointer_binding() { + x := 123 + px := &x + ppx := &px + assert pointee_name(px) == 'int' + assert pointee_name(ppx) == '&int' +} diff --git a/vlib/v/type_resolver/type_resolver.v b/vlib/v/type_resolver/type_resolver.v index 1763f721b..16e234d65 100644 --- a/vlib/v/type_resolver/type_resolver.v +++ b/vlib/v/type_resolver/type_resolver.v @@ -88,6 +88,75 @@ pub fn (t &TypeResolver) get_ct_type_or_default(key string, default_type ast.Typ return t.type_map[resolved_key] or { default_type } } +// resolve_bound_generic_type returns the branch-local concrete type for a bound comptime generic. +pub fn (t &TypeResolver) resolve_bound_generic_type(typ ast.Type) ?ast.Type { + if !typ.has_flag(.generic) { + return none + } + generic_name := t.table.sym(typ).name + if generic_name !in t.type_map { + return none + } + mut resolved := t.type_map[generic_name] + if typ.has_flag(.option) { + resolved = resolved.set_flag(.option) + } + if typ.has_flag(.result) { + resolved = resolved.set_flag(.result) + } + if typ.has_flag(.shared_f) { + resolved = resolved.set_flag(.shared_f) + } + if typ.has_flag(.atomic_f) { + resolved = resolved.set_flag(.atomic_f) + } + if typ.nr_muls() > 0 { + resolved = resolved.set_nr_muls(resolved.nr_muls() + typ.nr_muls()) + } + return resolved +} + +// bind_matching_generic_type binds a pointer-pattern generic like `&V` to the matching pointee type. +pub fn (mut t TypeResolver) bind_matching_generic_type(left_type ast.Type, pattern_type ast.Type) bool { + if !pattern_type.has_flag(.generic) || pattern_type.nr_muls() == 0 { + return false + } + mut concrete_type := t.resolver.unwrap_generic(left_type) + if concrete_type == ast.no_type || concrete_type.has_flag(.generic) { + return false + } + if concrete_type.nr_muls() < pattern_type.nr_muls() { + return false + } + if concrete_type.has_flag(.option) != pattern_type.has_flag(.option) + || concrete_type.has_flag(.result) != pattern_type.has_flag(.result) + || concrete_type.has_flag(.shared_f) != pattern_type.has_flag(.shared_f) + || concrete_type.has_flag(.atomic_f) != pattern_type.has_flag(.atomic_f) { + return false + } + for _ in 0 .. pattern_type.nr_muls() { + concrete_type = concrete_type.deref() + } + if pattern_type.has_flag(.option) { + concrete_type = concrete_type.clear_flag(.option) + } + if pattern_type.has_flag(.result) { + concrete_type = concrete_type.clear_flag(.result) + } + if pattern_type.has_flag(.shared_f) { + concrete_type = concrete_type.clear_flag(.shared_f) + } + if pattern_type.has_flag(.atomic_f) { + concrete_type = concrete_type.clear_flag(.atomic_f) + } + generic_name := t.table.sym(pattern_type).name + if existing_type := t.type_map[generic_name] { + return existing_type == concrete_type + } + t.update_ct_type(generic_name, concrete_type) + return true +} + @[noreturn] fn (t &TypeResolver) error(s string, pos token.Pos) { util.show_compiler_message('cgen error:', pos: pos, file_path: t.resolver.file.path, message: s) -- 2.39.5