From b9e9192b2b229c82b097911e2f64ea420a1c6c68 Mon Sep 17 00:00:00 2001 From: CreeperFace <165158232+dy-tea@users.noreply.github.com> Date: Sun, 4 Jan 2026 08:03:50 +0000 Subject: [PATCH] ast,checker,cgen,parser: fix comptime $if assign expr (fix #26061) (#26242) --- vlib/v/ast/table.v | 4 ++ vlib/v/checker/checker.v | 3 + vlib/v/checker/comptime.v | 41 ++++++++++-- vlib/v/checker/fn.v | 19 +++--- vlib/v/checker/if.v | 1 + vlib/v/checker/match.v | 35 ++++++---- vlib/v/gen/c/comptime.v | 66 ++++++++++++++----- vlib/v/parser/expr.v | 14 ++-- vlib/v/parser/if_match.v | 4 +- vlib/v/parser/parser.v | 4 +- .../tests/comptime/comptime_if_assign_test.v | 13 ++++ 11 files changed, 154 insertions(+), 50 deletions(-) create mode 100644 vlib/v/tests/comptime/comptime_if_assign_test.v diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v index 4b6a5114e..c3b7a25e5 100644 --- a/vlib/v/ast/table.v +++ b/vlib/v/ast/table.v @@ -1809,6 +1809,10 @@ pub fn (mut t Table) convert_generic_type(generic_type Type, generic_names []str if generic_names.len != to_types.len { return none } + type_idx := generic_type.idx() + if type_idx == 0 || type_idx >= t.type_symbols.len { + return none + } mut sym := t.sym(generic_type) if sym.name in generic_names { index := generic_names.index(sym.name) diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index ffec432a1..43d277f3e 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -1439,6 +1439,9 @@ fn (mut c Checker) expr_or_block_err(kind ast.OrKind, expr_name string, pos toke // return the actual type of the expression, once the result or option type is handled fn (mut c Checker) check_expr_option_or_result_call(expr ast.Expr, ret_type ast.Type) ast.Type { + if ret_type.idx() == 0 { + return ret_type + } match expr { ast.CallExpr { mut expr_ret_type := expr.return_type diff --git a/vlib/v/checker/comptime.v b/vlib/v/checker/comptime.v index 69fe866bc..29eb015a3 100644 --- a/vlib/v/checker/comptime.v +++ b/vlib/v/checker/comptime.v @@ -829,7 +829,18 @@ fn (mut c Checker) get_expr_type(cond ast.Expr) ast.Type { } else if c.is_generic_ident(cond.name) { // generic type `T` idx := c.table.cur_fn.generic_names.index(cond.name) - return c.table.cur_concrete_types[idx] + if idx >= 0 && idx < c.table.cur_concrete_types.len { + concrete_type := c.table.cur_concrete_types[idx] + if concrete_type != 0 { + return concrete_type + } + } + type_idx := c.table.find_type_idx(cond.name) + return if type_idx == 0 { + ast.void_type + } else { + ast.new_type(type_idx).set_flag(.generic) + } } else if var := cond.scope.find_var(cond.name) { // var checked_type = c.unwrap_generic(var.typ) @@ -855,11 +866,29 @@ fn (mut c Checker) get_expr_type(cond ast.Expr) ast.Type { // for `indirections` we also return the `typ` return typ } - if cond.gkind_field in [.typ, .indirections] { - // for `indirections` we also return the `typ` - return c.unwrap_generic(cond.name_type) - } else if cond.gkind_field == .unaliased_typ { - return c.table.unaliased_type(c.unwrap_generic(cond.name_type)) + if cond.gkind_field in [.typ, .indirections, .unaliased_typ] { + if cond.expr is ast.Ident { + generic_name := cond.expr.name + if c.table.cur_fn != unsafe { nil } + && generic_name in c.table.cur_fn.generic_names { + idx := c.table.cur_fn.generic_names.index(generic_name) + if idx >= 0 && idx < c.table.cur_concrete_types.len { + concrete_type := c.table.cur_concrete_types[idx] + if cond.gkind_field == .unaliased_typ { + return c.table.unaliased_type(concrete_type) + } + return concrete_type + } + } + } + unwrapped := c.unwrap_generic(cond.name_type) + if cond.gkind_field == .unaliased_typ { + if unwrapped.idx() == 0 || unwrapped.has_flag(.generic) { + return unwrapped + } + return c.table.unaliased_type(unwrapped) + } + return unwrapped } else { if cond.expr is ast.TypeOf { return c.type_resolver.typeof_field_type(c.type_resolver.typeof_type(cond.expr.expr, diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 4ef57d2b6..1d586fc92 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -2952,24 +2952,23 @@ fn (mut c Checker) post_process_generic_fns() ! { // Loop thru each generic function concrete type. // Check each specific fn instantiation. for i in 0 .. c.file.generic_fns.len { - mut node := c.file.generic_fns[i] - c.mod = node.mod - fkey := node.fkey() + c.mod = c.file.generic_fns[i].mod + fkey := c.file.generic_fns[i].fkey() all_generic_fns[fkey]++ if all_generic_fns[fkey] > generic_fn_cutoff_limit_per_fn { c.error('${fkey} generic function visited more than ${generic_fn_cutoff_limit_per_fn} times', - node.pos) + c.file.generic_fns[i].pos) return error('fkey: ${fkey}') } gtypes := c.table.fn_generic_types[fkey] $if trace_post_process_generic_fns ? { - eprintln('> post_process_generic_fns ${node.mod} | ${node.name} | fkey: ${fkey} | gtypes: ${gtypes} | c.file.generic_fns.len: ${c.file.generic_fns.len}') + eprintln('> post_process_generic_fns ${c.file.generic_fns[i].mod} | ${c.file.generic_fns[i].name} | fkey: ${fkey} | gtypes: ${gtypes} | c.file.generic_fns.len: ${c.file.generic_fns.len}') } for concrete_types in gtypes { c.table.cur_concrete_types = concrete_types - c.fn_decl(mut node) - if node.name in ['veb.run', 'veb.run_at', 'x.vweb.run', 'x.vweb.run_at', 'vweb.run', - 'vweb.run_at'] { + c.fn_decl(mut c.file.generic_fns[i]) + if c.file.generic_fns[i].name in ['veb.run', 'veb.run_at', 'x.vweb.run', 'x.vweb.run_at', + 'vweb.run', 'vweb.run_at'] { for ct in concrete_types { if ct !in c.vweb_gen_types { c.vweb_gen_types << ct @@ -2979,8 +2978,8 @@ fn (mut c Checker) post_process_generic_fns() ! { } c.table.cur_concrete_types = [] $if trace_post_process_generic_fns ? { - if node.generic_names.len > 0 { - eprintln(' > fn_decl node.name: ${node.name} | generic_names: ${node.generic_names} | ninstances: ${node.ninstances}') + if c.file.generic_fns[i].generic_names.len > 0 { + eprintln(' > fn_decl node.name: ${c.file.generic_fns[i].name} | generic_names: ${c.file.generic_fns[i].generic_names} | ninstances: ${c.file.generic_fns[i].ninstances}') } } } diff --git a/vlib/v/checker/if.v b/vlib/v/checker/if.v index 0ef8b1e01..d9471773c 100644 --- a/vlib/v/checker/if.v +++ b/vlib/v/checker/if.v @@ -74,6 +74,7 @@ fn (mut c Checker) if_expr(mut node ast.IfExpr) ast.Type { c.expected_type = c.expected_or_type } expr_required := c.expected_type != ast.void_type + || (node.is_comptime && node.is_expr && node.has_else && c.fn_level > 0) former_expected_type := c.expected_type if node_is_expr { c.expected_expr_type = c.expected_type diff --git a/vlib/v/checker/match.v b/vlib/v/checker/match.v index 2dd1d8e56..623c7b2f5 100644 --- a/vlib/v/checker/match.v +++ b/vlib/v/checker/match.v @@ -6,13 +6,17 @@ import v.token import strings fn (mut c Checker) match_expr(mut node ast.MatchExpr) ast.Type { - node.is_expr = c.expected_type != ast.void_type + if !node.is_comptime { + node.is_expr = c.expected_type != ast.void_type + } node.expected_type = c.expected_type if mut node.cond is ast.ParExpr && !c.pref.translated && !c.file.is_translated { c.warn('unnecessary `()` in `match` condition, use `match expr {` instead of `match (expr) {`.', node.cond.pos) } - if node.is_expr { + expr_required := c.expected_type != ast.void_type + || (node.is_comptime && node.is_expr && c.fn_level > 0) + if node.is_expr || expr_required { c.expected_expr_type = c.expected_type defer(fn) { c.expected_expr_type = ast.void_type @@ -46,12 +50,15 @@ fn (mut c Checker) match_expr(mut node ast.MatchExpr) ast.Type { if !c.ensure_type_exists(node.cond_type, node.pos) { return ast.void_type } - c.check_expr_option_or_result_call(node.cond, cond_type) - cond_type_sym := c.table.sym(cond_type) - cond_is_option := cond_type.has_flag(.option) + if node.cond_type == 0 { + return ast.void_type + } + c.check_expr_option_or_result_call(node.cond, node.cond_type) + cond_type_sym := c.table.sym(node.cond_type) + cond_is_option := node.cond_type.has_flag(.option) node.is_sum_type = cond_type_sym.kind in [.interface, .sum_type] c.match_exprs(mut node, cond_type_sym) - c.expected_type = cond_type + c.expected_type = node.cond_type mut first_iteration := true mut infer_cast_type := ast.void_type mut need_explicit_cast := false @@ -64,8 +71,9 @@ fn (mut c Checker) match_expr(mut node ast.MatchExpr) ast.Type { mut comptime_match_found_branch := false mut comptime_match_cond_value := '' if node.is_comptime { - if node.cond in [ast.ComptimeType, ast.TypeNode] - || (node.cond is ast.Ident && (c.is_generic_ident(node.cond.name))) { + if node.cond in [ast.ComptimeType, ast.TypeNode] || (node.cond is ast.Ident + && (c.is_generic_ident(node.cond.name))) + || (node.cond is ast.SelectorExpr && node.cond.gkind_field in [.typ, .unaliased_typ]) { // must be a type `$match` c.inside_x_matches_type = true } else { @@ -284,8 +292,8 @@ fn (mut c Checker) match_expr(mut node ast.MatchExpr) ast.Type { if cond_type_sym.kind == .none { c.error('`none` cannot be a match condition', node.pos) } - // If the last statement is an expression, return its type - if branch.stmts.len > 0 && node.is_expr { + if branch.stmts.len > 0 && node.is_expr + && (!node.is_comptime || (node.is_comptime && comptime_match_branch_result)) { mut stmt := branch.stmts.last() if mut stmt is ast.ExprStmt { c.expected_type = if c.expected_expr_type != ast.void_type { @@ -471,7 +479,9 @@ fn (mut c Checker) match_expr(mut node ast.MatchExpr) ast.Type { ret_type = if stmt.types.len > 0 { stmt.types[0] } else { c.expected_type } } } - first_iteration = false + if !node.is_comptime || (node.is_comptime && comptime_match_branch_result) { + first_iteration = false + } if node.is_comptime { // branches may not have been processed by c.stmts() if has_top_return(branch.stmts) { @@ -573,6 +583,9 @@ fn (mut c Checker) get_comptime_number_value(mut expr ast.Expr) ?i64 { fn (mut c Checker) match_exprs(mut node ast.MatchExpr, cond_type_sym ast.TypeSymbol) { c.expected_type = node.expected_type + if node.cond_type.idx() == 0 { + return + } cond_sym := c.table.sym(node.cond_type) mut enum_ref_checked := false mut is_comptime_value_match := false diff --git a/vlib/v/gen/c/comptime.v b/vlib/v/gen/c/comptime.v index bd820015f..2fb157875 100644 --- a/vlib/v/gen/c/comptime.v +++ b/vlib/v/gen/c/comptime.v @@ -44,6 +44,10 @@ fn (mut g Gen) gen_comptime_selector(expr ast.ComptimeSelector) string { } fn (mut g Gen) comptime_call(mut node ast.ComptimeCall) { + if node.kind == .compile_error || node.kind == .compile_warn { + // handled by checker, this branch was not taken + return + } if node.kind == .embed_file { // $embed_file('/path/to/file') g.gen_embed_file_init(mut node) @@ -368,12 +372,27 @@ fn (mut g Gen) gen_branch_context_string() string { fn (mut g Gen) comptime_if(node ast.IfExpr) { tmp_var := g.new_tmp_var() - is_opt_or_result := node.typ.has_option_or_result() - is_array_fixed := g.table.final_sym(node.typ).kind == .array_fixed - line := if node.is_expr { + mut inferred_typ := node.typ + if node.is_expr && node.typ == ast.void_type && node.branches.len > 0 { + for branch in node.branches { + if branch.stmts.len > 0 { + last_stmt := branch.stmts.last() + if last_stmt is ast.ExprStmt { + expr_typ := g.type_resolver.get_type_or_default(last_stmt.expr, last_stmt.typ) + if expr_typ != ast.void_type && !expr_typ.has_flag(.generic) { + inferred_typ = expr_typ + break + } + } + } + } + } + is_opt_or_result := inferred_typ.has_option_or_result() + is_array_fixed := g.table.final_sym(inferred_typ).kind == .array_fixed + line := if node.is_expr && inferred_typ != ast.void_type { stmt_str := g.go_before_last_stmt() g.write(util.tabs(g.indent)) - styp := g.styp(node.typ) + styp := g.styp(inferred_typ) g.writeln('${styp} ${tmp_var};') stmt_str } else { @@ -446,9 +465,9 @@ fn (mut g Gen) comptime_if(node ast.IfExpr) { g.skip_stmt_pos = true if is_opt_or_result { tmp_var2 := g.new_tmp_var() - g.write('{ ${g.base_type(node.typ)} ${tmp_var2} = ') + g.write('{ ${g.base_type(inferred_typ)} ${tmp_var2} = ') g.stmt(last) - g.writeln('builtin___result_ok(&(${g.base_type(node.typ)}[]) { ${tmp_var2} }, (_result*)(&${tmp_var}), sizeof(${g.base_type(node.typ)}));') + 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} = ') @@ -465,14 +484,14 @@ fn (mut g Gen) comptime_if(node ast.IfExpr) { g.skip_stmt_pos = true if is_opt_or_result { tmp_var2 := g.new_tmp_var() - base_styp := g.base_type(node.typ) + 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(node.typ) + base_styp := g.base_type(inferred_typ) g.write('{ ${base_styp} ${tmp_var2} = ') g.stmt(last) if g.out.last_n(2).contains(';') { @@ -976,11 +995,28 @@ fn (mut g Gen) comptime_selector_type(node ast.SelectorExpr) ast.Type { fn (mut g Gen) comptime_match(node ast.MatchExpr) { tmp_var := g.new_tmp_var() - is_opt_or_result := node.return_type.has_option_or_result() - line := if node.is_expr { + mut inferred_typ := node.return_type + if node.is_expr && (node.return_type == ast.void_type || node.return_type.idx() == 0) + && node.branches.len > 0 { + for branch in node.branches { + if branch.stmts.len > 0 { + last_stmt := branch.stmts.last() + if last_stmt is ast.ExprStmt { + expr_typ := g.type_resolver.get_type_or_default(last_stmt.expr, last_stmt.typ) + if expr_typ != ast.void_type && expr_typ.idx() != 0 + && !expr_typ.has_flag(.generic) { + inferred_typ = expr_typ + break + } + } + } + } + } + is_opt_or_result := inferred_typ.has_option_or_result() + line := if node.is_expr && inferred_typ != ast.void_type && inferred_typ.idx() != 0 { stmt_str := g.go_before_last_stmt() g.write(util.tabs(g.indent)) - styp := g.styp(node.return_type) + styp := g.styp(inferred_typ) g.writeln('${styp} ${tmp_var};') stmt_str } else { @@ -1034,9 +1070,9 @@ fn (mut g Gen) comptime_match(node ast.MatchExpr) { g.skip_stmt_pos = true if is_opt_or_result { tmp_var2 := g.new_tmp_var() - g.write('{ ${g.base_type(node.return_type)} ${tmp_var2} = ') + g.write('{ ${g.base_type(inferred_typ)} ${tmp_var2} = ') g.stmt(last) - g.writeln('builtin___result_ok(&(${g.base_type(node.return_type)}[]) { ${tmp_var2} }, (_result*)(&${tmp_var}), sizeof(${g.base_type(node.return_type)}));') + 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} = ') @@ -1054,9 +1090,9 @@ fn (mut g Gen) comptime_match(node ast.MatchExpr) { g.skip_stmt_pos = true if is_opt_or_result { tmp_var2 := g.new_tmp_var() - g.write('{ ${g.base_type(node.return_type)} ${tmp_var2} = ') + g.write('{ ${g.base_type(inferred_typ)} ${tmp_var2} = ') g.stmt(last) - g.writeln('builtin___result_ok(&(${g.base_type(node.return_type)}[]) { ${tmp_var2} }, (_result*)(&${tmp_var}), sizeof(${g.base_type(node.return_type)}));') + 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('${tmp_var} = ') diff --git a/vlib/v/parser/expr.v b/vlib/v/parser/expr.v index cf02e5e80..86845959a 100644 --- a/vlib/v/parser/expr.v +++ b/vlib/v/parser/expr.v @@ -126,10 +126,14 @@ fn (mut p Parser) check_expr(precedence int) !ast.Expr { p.is_stmt_ident = is_stmt_ident } .key_if { - return p.if_expr(true, false) + mut is_expr := false + if p.prev_tok.kind.is_assign() { + is_expr = true + } + return p.if_expr(true, is_expr) } .key_match { - return p.match_expr(true) + return p.match_expr(true, p.prev_tok.kind.is_assign()) } else { return p.unexpected_with_pos(p.peek_tok.pos(), @@ -184,10 +188,10 @@ fn (mut p Parser) check_expr(precedence int) !ast.Expr { } } .key_match { - if p.peek_tok.kind in [.lpar, .lsbr] && p.peek_tok.is_next_to(p.tok) { - node = p.call_expr(p.language, p.mod) + node = if p.peek_tok.kind in [.lpar, .lsbr] && p.peek_tok.is_next_to(p.tok) { + p.call_expr(p.language, p.mod) } else { - node = p.match_expr(false) + p.match_expr(false, false) } } .key_select { diff --git a/vlib/v/parser/if_match.v b/vlib/v/parser/if_match.v index 1dad04891..7b3c22681 100644 --- a/vlib/v/parser/if_match.v +++ b/vlib/v/parser/if_match.v @@ -305,9 +305,10 @@ fn (mut p Parser) resolve_at_expr(expr ast.AtExpr) !string { return '' } -fn (mut p Parser) match_expr(is_comptime bool) ast.MatchExpr { +fn (mut p Parser) match_expr(is_comptime bool, is_expr bool) ast.MatchExpr { mut match_first_pos := p.tok.pos() old_inside_ct_match := p.inside_ct_match + is_expr_ := p.prev_tok.kind == .key_return || is_expr if is_comptime { p.next() // `$` match_first_pos = p.prev_tok.pos().extend(p.tok.pos()) @@ -506,6 +507,7 @@ fn (mut p Parser) match_expr(is_comptime bool) ast.MatchExpr { pos.update_last_line(p.prev_tok.line_nr) return ast.MatchExpr{ is_comptime: is_comptime + is_expr: is_expr_ branches: branches cond: cond is_sum_type: is_sum_type diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 98ec676ff..969cc4a6c 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -801,7 +801,7 @@ fn (mut p Parser) top_stmt() ast.Stmt { } .key_match { mut pos := p.tok.pos() - expr := p.match_expr(true) + expr := p.match_expr(true, false) pos.update_last_line(p.prev_tok.line_nr) return ast.ExprStmt{ expr: expr @@ -1112,7 +1112,7 @@ fn (mut p Parser) stmt(is_top_level bool) ast.Stmt { } .key_match { mut pos := p.tok.pos() - expr := p.match_expr(true) + expr := p.match_expr(true, false) pos.update_last_line(p.prev_tok.line_nr) return ast.ExprStmt{ expr: expr diff --git a/vlib/v/tests/comptime/comptime_if_assign_test.v b/vlib/v/tests/comptime/comptime_if_assign_test.v new file mode 100644 index 000000000..00b914e8e --- /dev/null +++ b/vlib/v/tests/comptime/comptime_if_assign_test.v @@ -0,0 +1,13 @@ +fn ret[T](str string) !T { + val := $if T is i8 { + T(123) + } $else { + T(123) + } + return val +} + +fn test_main() { + x := ret[f32]('1.0')! + assert x == 123.0 +} -- 2.39.5