From fe0b4da9cf50aa9f806391202bd630a14544bb37 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Tue, 17 Feb 2026 22:12:25 +0200 Subject: [PATCH] all: restore changes that 87e0339d5 undid that were not for vlib/v2/ (#26621) --- GNUmakefile | 1 + cmd/tools/modules/testing/common.v | 2 +- cmd/tools/vtest-all.v | 2 +- vlib/v/gen/c/cgen.v | 29 +++++++++- vlib/v/gen/c/fn.v | 6 +- vlib/v/gen/c/if.v | 16 ++++- vlib/v/gen/c/str_intp.v | 22 +++++-- vlib/v/scanner/scanner.v | 58 +++++++++++++------ .../valgrind/autofree_match_sumtype_test.v | 53 +++++++++++++++++ .../slow_tests/valgrind/autofree_match_test.v | 40 +++++++++++++ .../comptime/comptime_match_assign_test.v | 24 ++++++++ 11 files changed, 222 insertions(+), 31 deletions(-) create mode 100644 vlib/v/slow_tests/valgrind/autofree_match_sumtype_test.v create mode 100644 vlib/v/slow_tests/valgrind/autofree_match_test.v diff --git a/GNUmakefile b/GNUmakefile index 4eb146055..ea88d87cd 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -41,6 +41,7 @@ MAC := 1 TCCOS := macos ifeq ($(shell expr $(shell uname -r | cut -d. -f1) \<= 15), 1) LEGACY := 1 +CFLAGS += "-I$(LEGACYLIBS)/include/LegacySupport" endif endif diff --git a/cmd/tools/modules/testing/common.v b/cmd/tools/modules/testing/common.v index 34c1d992a..4acbad5d9 100644 --- a/cmd/tools/modules/testing/common.v +++ b/cmd/tools/modules/testing/common.v @@ -541,7 +541,7 @@ fn worker_trunner(mut p pool.PoolProcessor, idx int, thread_id int) voidptr { ts.benchmark.step() tls_bench.step() - if !ts.build_tools && (!should_be_built || abs_path in ts.skip_files) { + if produces_file_output && !ts.build_tools && (!should_be_built || abs_path in ts.skip_files) { ts.benchmark.skip() tls_bench.skip() if !hide_skips { diff --git a/cmd/tools/vtest-all.v b/cmd/tools/vtest-all.v index 7f2614020..324261635 100644 --- a/cmd/tools/vtest-all.v +++ b/cmd/tools/vtest-all.v @@ -105,7 +105,7 @@ fn get_all_commands() []Command { rmfile: 'hhww.c' } res << Command{ - line: '${vexe} test vlib/builtin' + line: '${vexe} -silent test vlib/builtin' okmsg: 'V can test vlib/builtin' } res << Command{ diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index ee6ef0a0f..ae1a8af2d 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -164,6 +164,7 @@ mut: inside_global_decl bool inside_interface_deref bool inside_assign_fn_var bool + outer_tmp_var string // tmp var from outer context (e.g. from stmts_with_tmp_var) to be used by nested if/match expressions last_tmp_call_var []string last_if_option_type ast.Type // stores the expected if type on nested if expr loop_depth int @@ -2356,6 +2357,7 @@ fn (mut g Gen) stmts_with_tmp_var(stmts []ast.Stmt, tmp_var string) bool { g.set_current_pos_as_last_stmt_pos() g.skip_stmt_pos = true mut is_noreturn := false + mut is_if_expr_with_tmp := false if stmt in [ast.Return, ast.BranchStmt] { is_noreturn = true } else if stmt is ast.ExprStmt { @@ -2364,8 +2366,13 @@ fn (mut g Gen) stmts_with_tmp_var(stmts []ast.Stmt, tmp_var string) bool { is_array_fixed_init = true ret_type = stmt.expr.typ } + if stmt.expr is ast.IfExpr && g.is_autofree && !g.inside_if_option + && !g.inside_if_result { + is_if_expr_with_tmp = true + g.outer_tmp_var = tmp_var + } } - if !is_noreturn { + if !is_noreturn && !is_if_expr_with_tmp { if is_array_fixed_init { g.write('memcpy(${tmp_var}, (${g.styp(ret_type)})') } else { @@ -2373,6 +2380,10 @@ fn (mut g Gen) stmts_with_tmp_var(stmts []ast.Stmt, tmp_var string) bool { } } g.stmt(stmt) + // Reset outer_tmp_var after processing + if is_if_expr_with_tmp { + g.outer_tmp_var = '' + } if is_array_fixed_init { lines := g.go_before_last_stmt().trim_right('; \n') g.writeln('${lines}, sizeof(${tmp_var}));') @@ -4429,6 +4440,22 @@ fn (mut g Gen) selector_expr(node ast.SelectorExpr) { } } if node.expr_type == 0 { + // Handle comptime type selector T.name when inside comptime context + if node.field_name == 'name' && node.expr is ast.Ident { + ident := node.expr as ast.Ident + if g.table.cur_fn != unsafe { nil } && g.table.cur_fn.generic_names.len > 0 { + for i, gname in g.table.cur_fn.generic_names { + if gname == ident.name { + if i < g.table.cur_concrete_types.len { + g.type_name(g.table.cur_concrete_types[i]) + } else { + g.write('_S("${ident.name}")') + } + return + } + } + } + } g.checker_bug('unexpected SelectorExpr.expr_type = 0', node.pos) } diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 935ca3bd6..788eb6fca 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -1001,7 +1001,7 @@ fn (mut g Gen) call_expr(node ast.CallExpr) { } else { '' } - if gen_or || gen_keep_alive { + if (gen_or || gen_keep_alive) && node.return_type != 0 { mut ret_typ := node.return_type if g.table.sym(ret_typ).kind == .alias { unaliased_type := g.table.unaliased_type(ret_typ) @@ -1064,7 +1064,7 @@ fn (mut g Gen) call_expr(node ast.CallExpr) { } else { g.fn_call(node) } - if gen_or { + if gen_or && node.return_type != 0 { g.or_block(tmp_opt, node.or_block, node.return_type) mut unwrapped_typ := node.return_type.clear_option_and_result() if g.table.sym(unwrapped_typ).kind == .alias { @@ -1109,7 +1109,7 @@ fn (mut g Gen) call_expr(node ast.CallExpr) { } } } - } else if gen_keep_alive { + } else if gen_keep_alive && node.return_type != 0 { if node.return_type == ast.void_type { g.write('\n ${cur_line}') } else { diff --git a/vlib/v/gen/c/if.v b/vlib/v/gen/c/if.v index b9a1d8b4e..8d38fa6da 100644 --- a/vlib/v/gen/c/if.v +++ b/vlib/v/gen/c/if.v @@ -181,15 +181,24 @@ fn (mut g Gen) needs_conds_order(node ast.IfExpr) bool { } fn (mut g Gen) if_expr(node ast.IfExpr) { + use_outer_tmp := g.outer_tmp_var != '' + saved_outer_tmp_var := g.outer_tmp_var + if use_outer_tmp { + g.outer_tmp_var = '' + } + // For simple if expressions we can use C's `?:` // `if x > 0 { 1 } else { 2 }` => `(x > 0)? (1) : (2)` // For if expressions with multiple statements or another if expression inside, it's much // easier to use a temp var, than do C tricks with commas, introduce special vars etc // (as it used to be done). // Always use this in -autofree, since ?: can have tmp expressions that have to be freed. - needs_tmp_var := g.inside_if_option || g.need_tmp_var_in_if(node) + needs_tmp_var := g.inside_if_option || g.need_tmp_var_in_if(node) || use_outer_tmp needs_conds_order := g.needs_conds_order(node) - tmp := if g.inside_if_option || (node.typ != ast.void_type && needs_tmp_var) { + tmp := if use_outer_tmp { + // Use the tmp var from outer context (e.g. from stmts_with_tmp_var) + saved_outer_tmp_var + } else if g.inside_if_option || (node.typ != ast.void_type && needs_tmp_var) { g.new_tmp_var() } else { '' @@ -267,7 +276,8 @@ fn (mut g Gen) if_expr(node ast.IfExpr) { } cur_line = g.go_before_last_stmt() g.empty_line = true - if tmp != '' { + if tmp != '' && !use_outer_tmp { + // Only declare the tmp var if it's not from outer context if node.typ == ast.void_type && g.last_if_option_type != 0 { // nested if on return stmt g.write2(g.styp(g.unwrap_generic(g.last_if_option_type)), ' ') diff --git a/vlib/v/gen/c/str_intp.v b/vlib/v/gen/c/str_intp.v index 8a7b996bc..b19d35819 100644 --- a/vlib/v/gen/c/str_intp.v +++ b/vlib/v/gen/c/str_intp.v @@ -49,7 +49,11 @@ fn (mut g Gen) get_default_fmt(ftyp ast.Type, typ ast.Type) u8 { fn (mut g Gen) str_format(node ast.StringInterLiteral, i int, fmts []u8) (u64, string) { mut base := 0 // numeric base mut upper_case := false // set uppercase for the result string - mut typ := g.unwrap_generic(node.expr_types[i]) + mut typ := if i < node.expr_types.len { + g.unwrap_generic(node.expr_types[i]) + } else { + ast.string_type + } if node.exprs[i].is_auto_deref_var() { typ = typ.deref() } @@ -192,7 +196,11 @@ fn (mut g Gen) str_format(node ast.StringInterLiteral, i int, fmts []u8) (u64, s fn (mut g Gen) str_val(node ast.StringInterLiteral, i int, fmts []u8) { expr := node.exprs[i] fmt := fmts[i] - typ := g.unwrap_generic(node.expr_types[i]) + typ := if i < node.expr_types.len { + g.unwrap_generic(node.expr_types[i]) + } else { + ast.string_type + } typ_sym := g.table.sym(typ) if g.comptime.inside_comptime_for && expr is ast.SelectorExpr && expr.field_name == 'name' && expr.expr is ast.TypeOf { @@ -334,8 +342,10 @@ fn (mut g Gen) string_inter_literal(node ast.StringInterLiteral) { for i, mut expr in node_.exprs { mut field_typ := if mut expr is ast.Ident && g.is_comptime_for_var(expr) { g.comptime.comptime_for_field_type - } else { + } else if i < node_.expr_types.len { node_.expr_types[i] + } else { + ast.void_type } if g.comptime.inside_comptime_for && mut expr is ast.SelectorExpr { if expr.expr is ast.TypeOf && expr.field_name == 'name' { @@ -365,7 +375,11 @@ fn (mut g Gen) string_inter_literal(node ast.StringInterLiteral) { } } } else { - node_.expr_types[i] = field_typ + if i >= node_.expr_types.len { + node_.expr_types << field_typ + } else { + node_.expr_types[i] = field_typ + } } } g.write2('builtin__str_intp(', node.vals.len.str()) diff --git a/vlib/v/scanner/scanner.v b/vlib/v/scanner/scanner.v index 4e40c0475..47f3dd467 100644 --- a/vlib/v/scanner/scanner.v +++ b/vlib/v/scanner/scanner.v @@ -20,6 +20,26 @@ const num_sep = `_` const b_lf = 10 const b_cr = 13 const backslash = `\\` +const digit_table = get_digit_table() +const letter_table = get_letter_table() + +@[direct_array_access] +fn get_digit_table() [256]bool { + mut res := [256]bool{} + for c in 0 .. 256 { + res[c] = u8(c).is_digit() + } + return res +} + +@[direct_array_access] +fn get_letter_table() [256]bool { + mut res := [256]bool{} + for c in 0 .. 256 { + res[c] = u8(c).is_letter() + } + return res +} @[minify] pub struct Scanner { @@ -273,6 +293,7 @@ fn (s &Scanner) num_lit(start int, end int) string { } } +@[direct_array_access] fn (mut s Scanner) ident_bin_number() string { mut has_wrong_digit := false mut first_wrong_digit_pos := 0 @@ -288,7 +309,7 @@ fn (mut s Scanner) ident_bin_number() string { s.error('cannot use `_` consecutively') } if !c.is_bin_digit() && c != num_sep { - if (!c.is_digit() && !c.is_letter()) || s.is_inside_string || s.is_nested_string { + if (!digit_table[c] && !letter_table[c]) || s.is_inside_string || s.is_nested_string { break } else if !has_wrong_digit { has_wrong_digit = true @@ -332,7 +353,7 @@ fn (mut s Scanner) ident_hex_number() string { s.error('cannot use `_` consecutively') } if !c.is_hex_digit() && c != num_sep { - if !c.is_letter() || s.is_inside_string || s.is_nested_string { + if !letter_table[c] || s.is_inside_string || s.is_nested_string { break } else if !has_wrong_digit { has_wrong_digit = true @@ -357,6 +378,7 @@ fn (mut s Scanner) ident_hex_number() string { return number } +@[direct_array_access] fn (mut s Scanner) ident_oct_number() string { mut has_wrong_digit := false mut first_wrong_digit_pos := 0 @@ -372,7 +394,7 @@ fn (mut s Scanner) ident_oct_number() string { s.error('cannot use `_` consecutively') } if !c.is_oct_digit() && c != num_sep { - if (!c.is_digit() && !c.is_letter()) || s.is_inside_string || s.is_nested_string { + if (!digit_table[c] && !letter_table[c]) || s.is_inside_string || s.is_nested_string { break } else if !has_wrong_digit { has_wrong_digit = true @@ -409,8 +431,8 @@ fn (mut s Scanner) ident_dec_number() string { if c == num_sep && s.text[s.pos - 1] == num_sep { s.error('cannot use `_` consecutively') } - if !c.is_digit() && c != num_sep { - if !c.is_letter() || c in [`e`, `E`] || s.is_inside_string || s.is_nested_string { + if !digit_table[c] && c != num_sep { + if !letter_table[c] || c in [`e`, `E`] || s.is_inside_string || s.is_nested_string { break } else if !has_wrong_digit { has_wrong_digit = true @@ -431,14 +453,14 @@ fn (mut s Scanner) ident_dec_number() string { s.pos++ if s.pos < s.text.len { // 5.5, 5.5.str() - if s.text[s.pos].is_digit() { + if digit_table[s.text[s.pos]] { for s.pos < s.text.len { c := s.text[s.pos] - if !c.is_digit() { - if !c.is_letter() || c in [`e`, `E`] || s.is_inside_string + if !digit_table[c] { + if !letter_table[c] || c in [`e`, `E`] || s.is_inside_string || s.is_nested_string { // 5.5.str() - if c == `.` && s.pos + 1 < s.text.len && s.text[s.pos + 1].is_letter() { + if c == `.` && s.pos + 1 < s.text.len && letter_table[s.text[s.pos + 1]] { call_method = true } break @@ -456,14 +478,14 @@ fn (mut s Scanner) ident_dec_number() string { s.pos-- } else if s.text[s.pos] in [`e`, `E`] { // 5.e5 - } else if s.text[s.pos].is_letter() { + } else if letter_table[s.text[s.pos]] { // 5.str() call_method = true s.pos-- } else { // 5. mut symbol_length := 0 - for i := s.pos - 2; i > 0 && s.text[i - 1].is_digit(); i-- { + for i := s.pos - 2; i > 0 && digit_table[s.text[i - 1]]; i-- { symbol_length++ } float_symbol := s.text[s.pos - 2 - symbol_length..s.pos - 1] @@ -481,10 +503,10 @@ fn (mut s Scanner) ident_dec_number() string { } for s.pos < s.text.len { c := s.text[s.pos] - if !c.is_digit() { - if !c.is_letter() || s.is_inside_string || s.is_nested_string { + if !digit_table[c] { + if !letter_table[c] || s.is_inside_string || s.is_nested_string { // 5e5.str() - if c == `.` && s.pos + 1 < s.text.len && s.text[s.pos + 1].is_letter() { + if c == `.` && s.pos + 1 < s.text.len && letter_table[s.text[s.pos + 1]] { call_method = true } break @@ -713,7 +735,7 @@ pub fn (mut s Scanner) text_scan() token.Token { s.is_inter_start = false } return s.new_token(.name, name, name.len) - } else if c.is_digit() || (c == `.` && nextc.is_digit()) { + } else if digit_table[c] || (c == `.` && digit_table[nextc]) { // `123`, `.123` if !s.is_inside_string { // In C ints with `0` prefix are octal (in V they're decimal), so discarding heading zeros is needed. @@ -723,7 +745,7 @@ pub fn (mut s Scanner) text_scan() token.Token { } mut prefix_zero_num := start_pos - s.pos // how many prefix zeros should be jumped // for 0b, 0o, 0x the heading zero shouldn't be jumped - if start_pos == s.text.len || (c == `0` && !s.text[start_pos].is_digit()) { + if start_pos == s.text.len || (c == `0` && !digit_table[s.text[start_pos]]) { prefix_zero_num-- } s.pos += prefix_zero_num // jump these zeros @@ -1265,7 +1287,7 @@ pub fn (mut s Scanner) ident_string() string { s.u32_escapes_pos << s.pos - 1 } // Unknown escape sequence - if !util.is_escape_sequence(c) && !c.is_digit() && c != `\n` { + if !util.is_escape_sequence(c) && !digit_table[c] && c != `\n` { s.error('`${c.ascii_str()}` unknown escape sequence') } } @@ -1609,7 +1631,7 @@ pub fn (mut s Scanner) ident_char() string { s.error_with_pos('invalid character literal, use \`\\n\` instead', lspos) } else if c.len > len { ch := c[c.len - 1] - if !util.is_escape_sequence(ch) && !ch.is_digit() { + if !util.is_escape_sequence(ch) && !digit_table[ch] { s.error('`${ch.ascii_str()}` unknown escape sequence') } } diff --git a/vlib/v/slow_tests/valgrind/autofree_match_sumtype_test.v b/vlib/v/slow_tests/valgrind/autofree_match_sumtype_test.v new file mode 100644 index 000000000..a29a9162f --- /dev/null +++ b/vlib/v/slow_tests/valgrind/autofree_match_sumtype_test.v @@ -0,0 +1,53 @@ +// vtest vflags: -autofree +// vtest build: !sanitize-address-gcc && !sanitize-address-clang + +type Result = Success | Failure + +struct Success { + value int +} + +struct Failure { + msg string +} + +fn process(r Result) Result { + return match r { + Success { + if r.value > 10 { + Success{ + value: r.value * 2 + } + } else { + r + } + } + Failure { + r + } + } +} + +fn test_autofree_match_with_nested_if() { + r := process(Success{ + value: 15 + }) + assert r is Success + assert (r as Success).value == 30 +} + +fn test_autofree_match_with_nested_if_else() { + r := process(Success{ + value: 5 + }) + assert r is Success + assert (r as Success).value == 5 +} + +fn test_autofree_match_error_branch() { + r := process(Failure{ + msg: 'test error' + }) + assert r is Failure + assert (r as Failure).msg == 'test error' +} diff --git a/vlib/v/slow_tests/valgrind/autofree_match_test.v b/vlib/v/slow_tests/valgrind/autofree_match_test.v new file mode 100644 index 000000000..a27a90f0c --- /dev/null +++ b/vlib/v/slow_tests/valgrind/autofree_match_test.v @@ -0,0 +1,40 @@ +// vtest vflags: -autofree +// vtest build: !sanitize-address-gcc && !sanitize-address-clang + +type Tree[T] = Empty | Node[T] + +struct Empty {} + +struct Node[T] { + value T + left Tree[T] + right Tree[T] +} + +fn (tree Tree[T]) delete[T](x T) Tree[T] { + return match tree { + Empty { + tree + } + Node[T] { + if tree.left !is Empty && tree.right !is Empty { + if x < tree.value { + Node[T]{ + ...tree + left: tree.left.delete(x) + } + } else { + tree + } + } else { + tree + } + } + } +} + +fn test_autofree_match() { + mut tree := Tree[int](Empty{}) + tree = tree.delete(5) + assert tree is Empty +} diff --git a/vlib/v/tests/comptime/comptime_match_assign_test.v b/vlib/v/tests/comptime/comptime_match_assign_test.v index fa028d79d..0d9437091 100644 --- a/vlib/v/tests/comptime/comptime_match_assign_test.v +++ b/vlib/v/tests/comptime/comptime_match_assign_test.v @@ -1,3 +1,5 @@ +import strconv + fn test_comptime_match_assign() { os := 'windows' x := $match os { @@ -60,3 +62,25 @@ fn test_comptime_match_assign_reverse() { } assert z == 'unknown' } + +fn decode_number[T](str string) !T { + val := $match T.unaliased_typ { + i8 { strconv.atoi8(str)! } + i16 { strconv.atoi16(str)! } + i32 { strconv.atoi32(str)! } + i64 { strconv.atoi64(str)! } + u8 { strconv.atou8(str)! } + u16 { strconv.atou16(str)! } + u32 { strconv.atou32(str)! } + u64 { strconv.atou64(str)! } + int { strconv.atoi(str)! } + $float { T(strconv.atof_quick(str)) } + $else { return error('`decode_number` can not decode ${T.name} type') } + } + return val +} + +fn test_comptime_match_assign_generic() { + x := decode_number[f32]('1.0')! + assert x == 1.0 +} -- 2.39.5