From 20aefee830843de2de4ace07092ff71a22bb24c7 Mon Sep 17 00:00:00 2001 From: CreeperFace <165158232+dy-tea@users.noreply.github.com> Date: Thu, 20 Nov 2025 11:20:43 +0000 Subject: [PATCH] checker,cgen,type_resolver: prevent stale type cast on comptime `$for`, handle in dumpexpr (fix #25781) (#25784) --- vlib/v/checker/checker.v | 12 +++++ vlib/v/checker/if.v | 54 +++++++++++++++---- vlib/v/checker/match.v | 5 +- vlib/v/gen/c/cgen.v | 19 ++++--- vlib/v/gen/c/comptime.v | 10 +++- vlib/v/gen/c/dumpexpr.v | 36 ++++++++++++- .../comptime_for_in_options_struct_test.v | 28 ++++++++++ vlib/v/type_resolver/type_resolver.v | 9 +++- 8 files changed, 149 insertions(+), 24 deletions(-) diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index c10b89618..a54212da7 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -3179,6 +3179,18 @@ pub fn (mut c Checker) expr(mut node ast.Expr) ast.Type { } else if (node.expr as ast.Ident).name in c.type_resolver.type_map { node.expr_type = c.type_resolver.get_ct_type_or_default((node.expr as ast.Ident).name, node.expr_type) + } else if node.expr.obj is ast.Var { + var_obj := node.expr.obj as ast.Var + if var_obj.smartcasts.len > 0 { + node.expr_type = c.unwrap_generic(var_obj.smartcasts.last()) + } + } + } else if mut node.expr is ast.Ident { + if node.expr.obj is ast.Var { + var_obj := node.expr.obj as ast.Var + if var_obj.smartcasts.len > 0 { + node.expr_type = c.unwrap_generic(var_obj.smartcasts.last()) + } } } c.check_expr_option_or_result_call(node.expr, node.expr_type) diff --git a/vlib/v/checker/if.v b/vlib/v/checker/if.v index 52b54b526..0ef8b1e01 100644 --- a/vlib/v/checker/if.v +++ b/vlib/v/checker/if.v @@ -114,7 +114,10 @@ fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type { c.cur_ct_id++ branch.id = c.cur_ct_id } - idx_str := comptime_branch_context_str + '|id=${branch.id}|' + mut idx_str := comptime_branch_context_str + '|id=${branch.id}|' + if c.comptime.inside_comptime_for && c.comptime.comptime_for_field_var != '' { + idx_str += '|field_type=${c.comptime.comptime_for_field_type}|' + } c.comptime.inside_comptime_if = true mut sb := strings.new_builder(256) comptime_if_result, comptime_if_multi_pass_branch = c.comptime_if_cond(mut branch.cond, mut @@ -186,7 +189,10 @@ fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type { c.cur_ct_id++ branch.id = c.cur_ct_id } - idx_str := comptime_branch_context_str + '|id=${branch.id}|' + mut idx_str := comptime_branch_context_str + '|id=${branch.id}|' + if c.comptime.inside_comptime_for && c.comptime.comptime_for_field_var != '' { + idx_str += '|field_type=${c.comptime.comptime_for_field_type}|' + } c.table.comptime_is_true[idx_str] = ast.ComptTimeCondResult{ val: comptime_if_result c_str: '' @@ -517,13 +523,35 @@ fn (mut c Checker) smartcast_if_conds(mut node ast.Expr, mut scope ast.Scope, co || (node.left is ast.SelectorExpr && node.left.is_mut) { c.fail_if_immutable(mut node.left) } - if node.left is ast.Ident && c.comptime.get_ct_type_var(node.left) == .smartcast { - node.left_type = c.type_resolver.get_type(node.left) - c.smartcast(mut node.left, node.left_type, node.left_type.clear_flag(.option), mut - scope, true, true) + if c.comptime.inside_comptime_for && c.comptime.comptime_for_field_var != '' + && node.left is ast.Ident { + if mut node.left is ast.Ident { + if mut node.left.obj is ast.Var { + if node.left.obj.ct_type_var == .field_var { + scope.register(ast.Var{ + name: node.left.name + typ: node.left_type + pos: node.left.pos + is_used: true + is_mut: node.left.is_mut + is_inherited: node.left.obj.is_inherited + is_unwrapped: true + orig_type: node.left_type + ct_type_var: .field_var + ct_type_unwrapped: true + }) + } + } + } } else { - c.smartcast(mut node.left, node.left_type, node.left_type.clear_flag(.option), mut - scope, false, true) + if node.left is ast.Ident && c.comptime.get_ct_type_var(node.left) == .smartcast { + node.left_type = c.type_resolver.get_type(node.left) + c.smartcast(mut node.left, node.left_type, node.left_type.clear_flag(.option), mut + scope, true, true) + } else { + c.smartcast(mut node.left, node.left_type, node.left_type.clear_flag(.option), mut + scope, false, true) + } } } else if node.op == .key_is { if node.left is ast.Ident && node.left.ct_expr { @@ -593,9 +621,15 @@ fn (mut c Checker) smartcast_if_conds(mut node ast.Expr, mut scope ast.Scope, co node.left.pos) } } - if left_final_sym.kind in [.interface, .sum_type] { + is_option_unwrap := node.left_type.has_flag(.option) + && !right_type.has_flag(.option) + skip_smartcast := c.comptime.inside_comptime_for + && c.comptime.comptime_for_field_var != '' && node.left is ast.Ident + && (node.left as ast.Ident).name == c.comptime.comptime_for_field_var + if !skip_smartcast + && (left_final_sym.kind in [.interface, .sum_type] || is_option_unwrap) { c.smartcast(mut node.left, node.left_type, right_type, mut - scope, is_comptime, false) + scope, is_comptime, is_option_unwrap) } } } diff --git a/vlib/v/checker/match.v b/vlib/v/checker/match.v index ced8201e8..c6c26989e 100644 --- a/vlib/v/checker/match.v +++ b/vlib/v/checker/match.v @@ -137,7 +137,10 @@ fn (mut c Checker) match_expr(mut node ast.MatchExpr) ast.Type { c.cur_ct_id++ branch.id = c.cur_ct_id } - idx_str := comptime_branch_context_str + '|id=${branch.id}|' + mut idx_str := comptime_branch_context_str + '|id=${branch.id}|' + if c.comptime.inside_comptime_for && c.comptime.comptime_for_field_var != '' { + idx_str += '|field_type=${c.comptime.comptime_for_field_type}|' + } mut c_str := '' if !branch.is_else { if c.inside_x_matches_type { diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 27ee1ef57..f4e9a83b9 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -5353,7 +5353,12 @@ fn (mut g Gen) ident(node ast.Ident) { if !g.is_assign_lhs && node.obj.ct_type_var !in [.smartcast, .generic_param, .no_comptime, .aggregate] { comptime_type := g.type_resolver.get_type(node) - if comptime_type.has_flag(.option) { + orig_has_option := node.obj.typ.has_flag(.option) + if orig_has_option && !comptime_type.has_flag(.option) { + styp := g.base_type(comptime_type) + ptr := if is_auto_heap { '->' } else { '.' } + g.write('(*(${styp}*)${name}${ptr}data)') + } else if comptime_type.has_flag(.option) { if (g.inside_opt_or_res || g.left_is_opt) && node.or_expr.kind == .absent { if !g.is_assign_lhs && is_auto_heap { g.write('(*${name})') @@ -5362,11 +5367,8 @@ fn (mut g Gen) ident(node ast.Ident) { } } else { styp := g.base_type(comptime_type) - if is_auto_heap { - g.write('(*(${styp}*)${name}->data)') - } else { - g.write('(*(${styp}*)${name}.data)') - } + ptr := if is_auto_heap { '->' } else { '.' } + g.write('(*(${styp}*)${name}${ptr}data)') } } else { if is_auto_heap { @@ -5872,7 +5874,10 @@ fn (mut g Gen) gen_hash_stmts(mut sb strings.Builder, node &ast.HashStmtNode, se mut comptime_branch_context_str := g.gen_branch_context_string() mut is_true := ast.ComptTimeCondResult{} for i, branch in node.branches { - idx_str := comptime_branch_context_str + '|id=${branch.id}|' + mut idx_str := comptime_branch_context_str + '|id=${branch.id}|' + if g.comptime.inside_comptime_for && g.comptime.comptime_for_field_var != '' { + idx_str += '|field_type=${g.comptime.comptime_for_field_type}|' + } if comptime_is_true := g.table.comptime_is_true[idx_str] { // `g.table.comptime_is_true` are the branch condition results set by `checker` is_true = comptime_is_true diff --git a/vlib/v/gen/c/comptime.v b/vlib/v/gen/c/comptime.v index 02b7efec1..d3cc6e73a 100644 --- a/vlib/v/gen/c/comptime.v +++ b/vlib/v/gen/c/comptime.v @@ -423,7 +423,10 @@ fn (mut g Gen) comptime_if(node ast.IfExpr) { // The first part represents the current context of the branch statement, `comptime_branch_context_str`, formatted like `T=int,X=string,method.name=json` // The second part is the branch's id. // This format must match what is in `checker`. - idx_str := comptime_branch_context_str + '|id=${branch.id}|' + mut idx_str := comptime_branch_context_str + '|id=${branch.id}|' + if g.comptime.inside_comptime_for && g.comptime.comptime_for_field_var != '' { + idx_str += '|field_type=${g.comptime.comptime_for_field_type}|' + } if comptime_is_true := g.table.comptime_is_true[idx_str] { // `g.table.comptime_is_true` are the branch condition results set by `checker` is_true = comptime_is_true @@ -1013,7 +1016,10 @@ fn (mut g Gen) comptime_match(node ast.MatchExpr) { // The first part represents the current context of the branch statement, `comptime_branch_context_str`, formatted like `T=int,X=string,method.name=json` // The second part is the branch's id. // This format must match what is in `checker`. - idx_str := comptime_branch_context_str + '|id=${branch.id}|' + mut idx_str := comptime_branch_context_str + '|id=${branch.id}|' + if g.comptime.inside_comptime_for && g.comptime.comptime_for_field_var != '' { + idx_str += '|field_type=${g.comptime.comptime_for_field_type}|' + } if comptime_is_true := g.table.comptime_is_true[idx_str] { // `g.table.comptime_is_true` are the branch condition results set by `checker` is_true = comptime_is_true diff --git a/vlib/v/gen/c/dumpexpr.v b/vlib/v/gen/c/dumpexpr.v index e7ad84e59..304866047 100644 --- a/vlib/v/gen/c/dumpexpr.v +++ b/vlib/v/gen/c/dumpexpr.v @@ -42,7 +42,21 @@ fn (mut g Gen) dump_expr(node ast.DumpExpr) { } } } else if node.expr is ast.Ident && node.expr.ct_expr { - expr_type = g.type_resolver.get_type(node.expr) + for { + if node.expr.obj is ast.Var { + if node.expr.obj.ct_type_var == .field_var && g.comptime.inside_comptime_for + && g.comptime.comptime_for_field_var != '' { + expr_type = if node.expr.obj.ct_type_unwrapped || node.expr.obj.is_unwrapped { + g.comptime.comptime_for_field_type.clear_flag(.option) + } else { + g.comptime.comptime_for_field_type + } + break + } + } + expr_type = g.type_resolver.get_type(node.expr) + break + } name = g.styp(g.unwrap_generic(expr_type.clear_flags(.shared_f, .result))).replace('*', '') } else if node.expr is ast.SelectorExpr && node.expr.expr is ast.Ident @@ -83,7 +97,25 @@ fn (mut g Gen) dump_expr(node ast.DumpExpr) { if expr_type.has_flag(.option_mut_param_t) { g.write('*') } - g.expr(node.expr) + for { + if node.expr is ast.Ident { + if node.expr.obj is ast.Var { + if node.expr.obj.ct_type_var == .field_var && g.comptime.inside_comptime_for + && (node.expr.obj.ct_type_unwrapped || node.expr.obj.is_unwrapped) { + field_type := g.comptime.comptime_for_field_type + if field_type.has_flag(.option) { + styp := g.base_type(field_type.clear_flag(.option)) + is_auto_heap := node.expr.is_auto_heap() + ptr := if is_auto_heap { '->' } else { '.' } + g.write('(*(${styp}*)${c_name(node.expr.name)}${ptr}data)') + break + } + } + } + } + g.expr(node.expr) + break + } g.inside_opt_or_res = old_inside_opt_or_res } g.write(')') diff --git a/vlib/v/tests/comptime/comptime_for_in_options_struct_test.v b/vlib/v/tests/comptime/comptime_for_in_options_struct_test.v index 1e9792c15..447713e0b 100644 --- a/vlib/v/tests/comptime/comptime_for_in_options_struct_test.v +++ b/vlib/v/tests/comptime/comptime_for_in_options_struct_test.v @@ -11,12 +11,40 @@ fn unwrap_not_none_field_types[T](t T) []string { $if f is $option { if v != none { arr << '${typeof(v).name}:${f.name}=`${v}`' + + w := v // assign + + $if w is string { + t_string(w) // fn call with string value + } + + t_generic(w) // fn call with generic value } } } return arr } +fn t_string(s string) { + assert s == 'x' +} + +fn t_generic[T](t T) { + $if t is int { + assert t == 1 + return + } + $if t is f64 { + assert t == 2.3 + return + } + $if t is string { + t_string(t) + return + } + assert false +} + fn test_main() { arr := unwrap_not_none_field_types(Options{ a: 'x' diff --git a/vlib/v/type_resolver/type_resolver.v b/vlib/v/type_resolver/type_resolver.v index efd4f241c..134471376 100644 --- a/vlib/v/type_resolver/type_resolver.v +++ b/vlib/v/type_resolver/type_resolver.v @@ -207,8 +207,13 @@ pub fn (mut t TypeResolver) get_type(node ast.Expr) ast.Type { } .field_var { // field var from $for loop - if node.obj.ct_type_unwrapped { - t.info.comptime_for_field_type.clear_flag(.option) + unwrapped_field_type := t.info.comptime_for_field_type.clear_flag(.option) + if t.info.comptime_for_field_type.has_flag(.option) + && node.obj.typ != ast.void_type && !node.obj.typ.has_flag(.option) + && node.obj.typ == unwrapped_field_type { + node.obj.typ + } else if node.obj.ct_type_unwrapped { + unwrapped_field_type } else { t.info.comptime_for_field_type } -- 2.39.5