From 682db6685219d63d808fda329fb16224a682394e Mon Sep 17 00:00:00 2001 From: Larsimusrex Date: Thu, 4 Sep 2025 10:39:24 +0200 Subject: [PATCH 01/15] builtin, checker, cgen: expose is_embed in FieldData (#25232) --- vlib/builtin/builtin.v | 7 ++++--- vlib/v/checker/comptime.v | 2 +- vlib/v/gen/c/comptime.v | 1 + vlib/v/tests/comptime/comptime_for_test.v | 12 +++++++++--- vlib/v/type_resolver/comptime_resolver.v | 1 + 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/vlib/builtin/builtin.v b/vlib/builtin/builtin.v index 73a8a93371..3a8347f815 100644 --- a/vlib/builtin/builtin.v +++ b/vlib/builtin/builtin.v @@ -124,9 +124,10 @@ pub: typ int // the internal TypeID of the field f, unaliased_typ int // if f's type was an alias of int, this will be TypeID(int) - attrs []string // the attributes of the field f - is_pub bool // f is in a `pub:` section - is_mut bool // f is in a `mut:` section + attrs []string // the attributes of the field f + is_pub bool // f is in a `pub:` section + is_mut bool // f is in a `mut:` section + is_embed bool // f is a embedded struct is_shared bool // `f shared Abc` is_atomic bool // `f atomic int` , TODO diff --git a/vlib/v/checker/comptime.v b/vlib/v/checker/comptime.v index 6d855fbe0c..e8d99be0df 100644 --- a/vlib/v/checker/comptime.v +++ b/vlib/v/checker/comptime.v @@ -1398,7 +1398,7 @@ fn (mut c Checker) comptime_if_cond(mut cond ast.Expr, mut sb strings.Builder) ( } ast.SelectorExpr { if c.comptime.comptime_for_field_var != '' && cond.expr is ast.Ident { - if (cond.expr as ast.Ident).name == c.comptime.comptime_for_field_var && cond.field_name in ['is_mut', 'is_pub', 'is_shared', 'is_atomic', 'is_option', 'is_array', 'is_map', 'is_chan', 'is_struct', 'is_alias', 'is_enum'] { + if (cond.expr as ast.Ident).name == c.comptime.comptime_for_field_var && cond.field_name in ['is_mut', 'is_pub', 'is_embed', 'is_shared', 'is_atomic', 'is_option', 'is_array', 'is_map', 'is_chan', 'is_struct', 'is_alias', 'is_enum'] { is_true = c.type_resolver.get_comptime_selector_bool_field(cond.field_name) sb.write_string('${is_true}') return is_true, true diff --git a/vlib/v/gen/c/comptime.v b/vlib/v/gen/c/comptime.v index 88571cd09a..cb55c5841f 100644 --- a/vlib/v/gen/c/comptime.v +++ b/vlib/v/gen/c/comptime.v @@ -740,6 +740,7 @@ fn (mut g Gen) comptime_for(node ast.ComptimeFor) { g.writeln('\t${node.val_var}.unaliased_typ = ${int(unaliased_styp.idx())};\t// ${g.table.type_to_str(unaliased_styp)}') g.writeln('\t${node.val_var}.is_pub = ${field.is_pub};') g.writeln('\t${node.val_var}.is_mut = ${field.is_mut};') + g.writeln('\t${node.val_var}.is_embed = ${field.is_embed};') g.writeln('\t${node.val_var}.is_shared = ${field.typ.has_flag(.shared_f)};') g.writeln('\t${node.val_var}.is_atomic = ${field.typ.has_flag(.atomic_f)};') diff --git a/vlib/v/tests/comptime/comptime_for_test.v b/vlib/v/tests/comptime/comptime_for_test.v index 7a02f16b9c..ee2482ec14 100644 --- a/vlib/v/tests/comptime/comptime_for_test.v +++ b/vlib/v/tests/comptime/comptime_for_test.v @@ -1,4 +1,5 @@ struct App { + Inner a string b string mut: @@ -12,6 +13,8 @@ pub mut: h u8 } +struct Inner {} + @['foo/bar/three'] fn (mut app App) run() { } @@ -85,13 +88,16 @@ fn test_comptime_for_fields() { assert field.name in ['d', 'e'] } if field.is_mut { - assert field.name in ['c', 'd', 'g', 'h'] + assert field.name in ['c', 'd', 'g', 'h', 'Inner'] } if field.is_pub { - assert field.name in ['e', 'f', 'g', 'h'] + assert field.name in ['e', 'f', 'g', 'h', 'Inner'] } if field.is_pub && field.is_mut { - assert field.name in ['g', 'h'] + assert field.name in ['g', 'h', 'Inner'] + } + if field.is_embed { + assert field.name == 'Inner' } if field.name == 'f' { assert sizeof(field) == 8 diff --git a/vlib/v/type_resolver/comptime_resolver.v b/vlib/v/type_resolver/comptime_resolver.v index 79f296e19d..c33cff5365 100644 --- a/vlib/v/type_resolver/comptime_resolver.v +++ b/vlib/v/type_resolver/comptime_resolver.v @@ -224,6 +224,7 @@ pub fn (mut t TypeResolver) get_comptime_selector_bool_field(field_name string) match field_name { 'is_pub' { return field.is_pub } 'is_mut' { return field.is_mut } + 'is_embed' { return field.is_embed } 'is_shared' { return field_typ.has_flag(.shared_f) } 'is_atomic' { return field_typ.has_flag(.atomic_f) } 'is_option' { return field.typ.has_flag(.option) } From 88686960174103fd7755fb736869f500d36a78fc Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Thu, 4 Sep 2025 11:46:16 +0300 Subject: [PATCH 02/15] repl: fix handling of lines with comments like `math.pi // comment` (fix #25229) --- cmd/tools/vrepl.v | 2 +- vlib/v/slow_tests/repl/line_comment.repl | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 vlib/v/slow_tests/repl/line_comment.repl diff --git a/cmd/tools/vrepl.v b/cmd/tools/vrepl.v index 4c5eb36239..ada995bda0 100644 --- a/cmd/tools/vrepl.v +++ b/cmd/tools/vrepl.v @@ -441,7 +441,7 @@ fn run_repl(workdir string, vrepl_prefix string) int { prompt = '... ' } oline := r.get_one_line(prompt) or { break } - line := oline.trim_space() + line := oline.all_before('//').trim_space() if line == '' { continue } diff --git a/vlib/v/slow_tests/repl/line_comment.repl b/vlib/v/slow_tests/repl/line_comment.repl new file mode 100644 index 0000000000..9218e2c41a --- /dev/null +++ b/vlib/v/slow_tests/repl/line_comment.repl @@ -0,0 +1,5 @@ +math.pi +math.pi // some comment +===output=== +3.141592653589793 +3.141592653589793 From dabc08b6ee58157be58e5a1c06df83ddb2a7c47e Mon Sep 17 00:00:00 2001 From: Swastik Baranwal Date: Thu, 4 Sep 2025 15:27:25 +0530 Subject: [PATCH 03/15] cgen: fix alias enum used in comptime `$for` (fix #25211) (#25212) --- vlib/v/gen/c/comptime.v | 7 ++++++- .../comptime/comptime_enum_values_test.v | 20 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/vlib/v/gen/c/comptime.v b/vlib/v/gen/c/comptime.v index cb55c5841f..c44b33b04d 100644 --- a/vlib/v/gen/c/comptime.v +++ b/vlib/v/gen/c/comptime.v @@ -781,7 +781,12 @@ fn (mut g Gen) comptime_for(node ast.ComptimeFor) { if g.pref.translated && node.typ.is_number() { g.writeln('_const_main__${val};') } else { - g.writeln('${g.styp(node.typ)}__${val};') + node_sym := g.table.sym(node.typ) + if node_sym.info is ast.Alias { + g.writeln('${g.styp(node_sym.info.parent_type)}__${val};') + } else { + g.writeln('${g.styp(node.typ)}__${val};') + } } enum_attrs := sym.info.attrs[val] if enum_attrs.len == 0 { diff --git a/vlib/v/tests/comptime/comptime_enum_values_test.v b/vlib/v/tests/comptime/comptime_enum_values_test.v index 912fee9e73..a652798bd3 100644 --- a/vlib/v/tests/comptime/comptime_enum_values_test.v +++ b/vlib/v/tests/comptime/comptime_enum_values_test.v @@ -5,6 +5,8 @@ enum CharacterGroup { special } +type AnotherCharGroup = CharacterGroup + fn (self CharacterGroup) value() string { return match self { .chars { 'first' } @@ -33,3 +35,21 @@ fn test_main() { assert values == [CharacterGroup.chars, CharacterGroup.alphanumerics, CharacterGroup.numeric, CharacterGroup.special] } + +fn test_alias_enum() { + mut values := []EnumData{} + $for entry in AnotherCharGroup.values { + values << entry + } + assert values[0].value == int(CharacterGroup.chars) + assert values[0].name == CharacterGroup.chars.str() + + assert values[1].value == int(CharacterGroup.alphanumerics) + assert values[1].name == CharacterGroup.alphanumerics.str() + + assert values[2].value == int(CharacterGroup.numeric) + assert values[2].name == CharacterGroup.numeric.str() + + assert values[3].value == int(CharacterGroup.special) + assert values[3].name == CharacterGroup.special.str() +} From f17e0fd52b12f3d4b81f79a14253fb2e1d2d3a7d Mon Sep 17 00:00:00 2001 From: CreeperFace <165158232+dy-tea@users.noreply.github.com> Date: Thu, 4 Sep 2025 11:04:57 +0100 Subject: [PATCH 04/15] cgen: ensure variable names do not conflict with builtin methods (fix #25063) (#25178) --- vlib/v/gen/c/assign.v | 128 ++++++++++++ vlib/v/gen/c/autofree.v | 3 +- vlib/v/gen/c/cgen.v | 2 + vlib/v/tests/clash_var_fn_name_test.v | 284 ++++++++++++++++++++++++++ 4 files changed, 416 insertions(+), 1 deletion(-) create mode 100644 vlib/v/tests/clash_var_fn_name_test.v diff --git a/vlib/v/gen/c/assign.v b/vlib/v/gen/c/assign.v index 2b4a158437..31c9749de9 100644 --- a/vlib/v/gen/c/assign.v +++ b/vlib/v/gen/c/assign.v @@ -7,6 +7,126 @@ import v.ast import v.util import v.token +fn (mut g Gen) resolve_var_method_conflict(var_name string, val ast.Expr) string { + mut method_call := ast.CallExpr{} + mut has_method_call := false + mut receiver_type := ast.Type(0) + mut method_name := '' + match val { + ast.ArrayInit { + if var_name == 'new_array_from_c_array_noscan' { + return '_var_${var_name}' + } + } + ast.MapInit { + if var_name == 'new_map_noscan_key_value' { + return '_var_${var_name}' + } + } + ast.CallExpr { + if val.is_method { + method_call = val + has_method_call = true + } + } + ast.UnsafeExpr { + if val.expr is ast.CallExpr { + call_expr := val.expr as ast.CallExpr + if call_expr.is_method { + method_call = call_expr + has_method_call = true + } + } else if val.expr is ast.IndexExpr { + index_expr := val.expr as ast.IndexExpr + left_type := g.unwrap_generic(index_expr.left_type) + left_sym := g.table.sym(left_type) + type_name := left_sym.kind.str() + if (index_expr.index is ast.RangeExpr && var_name == '${type_name}_slice') + || var_name in ['${type_name}_get', '${type_name}_at'] { + return '_var_${var_name}' + } + } + } + ast.InfixExpr { + receiver_type = val.left_type + method_name = match val.op { + .eq { '_eq' } + .ne { '_ne' } + .lt { '_lt' } + .le { '_le' } + .gt { '_gt' } + .ge { '_ge' } + .plus { '_plus' } + .minus { '_minus' } + .mul { '_mul' } + .div { '_div' } + .mod { '_mod' } + else { '' } + } + has_method_call = method_name != '' && receiver_type != 0 + } + ast.IndexExpr { + left_type := g.unwrap_generic(val.left_type) + left_sym := g.table.sym(left_type) + type_name := left_sym.kind.str() + if (val.index is ast.RangeExpr && var_name == '${type_name}_slice') + || var_name in ['${type_name}_get', '${type_name}_at'] { + return '_var_${var_name}' + } + } + else {} + } + if has_method_call { + mut left_type := ast.Type(0) + if method_call.left_type != 0 { + left_type = g.unwrap_generic(method_call.left_type) + method_name = method_call.name + } else if receiver_type != 0 { + left_type = g.unwrap_generic(receiver_type) + } + if left_type != 0 { + left_sym := g.table.sym(left_type) + final_left_sym := g.table.final_sym(left_type) + if var_name == '${left_sym.cname}_${method_name}' { + return '_var_${var_name}' + } + if final_left_sym.kind == .array && !(left_sym.kind == .alias + && left_sym.has_method(method_name)) { + actual_method_name := match method_name { + 'repeat' { + 'repeat_to_depth' + } + 'clone' { + 'clone_to_depth' + } + 'pop_left' { + 'pop_left_noscan' + } + 'pop' { + 'pop_noscan' + } + else { + method_name + } + } + if var_name == 'array_${actual_method_name}' { + return '_var_${var_name}' + } + } + if final_left_sym.kind == .map && !(left_sym.kind == .alias + && left_sym.has_method(method_name)) { + if method_name in ['clone', 'move'] && var_name == 'map_${method_name}' { + return '_var_${var_name}' + } + if method_name in ['keys', 'values'] && var_name == 'map_${method_name}' { + return '_var_${var_name}' + } + } + } + } + return var_name +} + fn (mut g Gen) expr_with_opt_or_block(expr ast.Expr, expr_typ ast.Type, var_expr ast.Expr, ret_typ ast.Type, in_heap bool) { gen_or := expr is ast.Ident && expr.or_expr.kind != .absent @@ -305,6 +425,14 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { is_va_list = left_sym.language == .c && left_sym.name == 'C.va_list' if mut left is ast.Ident { ident = left + if is_decl && i < node.right.len { + resolved_name := g.resolve_var_method_conflict(ident.name, node.right[i]) + if resolved_name != ident.name { + g.transformed_var_names[ident.name] = resolved_name + ident.name = resolved_name + node.left[i] = ident + } + } g.curr_var_name << ident.name // id_info := ident.var_info() // var_type = id_info.typ diff --git a/vlib/v/gen/c/autofree.v b/vlib/v/gen/c/autofree.v index a4f88ae12a..941934b280 100644 --- a/vlib/v/gen/c/autofree.v +++ b/vlib/v/gen/c/autofree.v @@ -242,7 +242,8 @@ fn (mut g Gen) autofree_var_call(free_fn_name string, v ast.Var) { af.writeln('\t\t${free_fn_name}((${base_type}*)${c_name(v.name)}.data); // autofreed option var ${g.cur_mod.name} ${g.is_builtin_mod}') af.writeln('\t}') } else if v.typ.idx() != ast.u8_type_idx { - af.writeln('\t${free_fn_name}(&${c_name(v.name)}); // autofreed var ${g.cur_mod.name} ${g.is_builtin_mod}') + var_name := g.transformed_var_names[v.name] or { v.name } + af.writeln('\t${free_fn_name}(&${c_name(var_name)}); // autofreed var ${g.cur_mod.name} ${g.is_builtin_mod}') } } g.autofree_scope_stmts << af.str() diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index c1e0368510..00dc97b7ff 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -258,6 +258,7 @@ mut: autofree_methods map[ast.Type]string generated_free_methods map[ast.Type]bool autofree_scope_stmts []string + transformed_var_names map[string]string use_segfault_handler bool = true test_function_names []string ///////// @@ -5261,6 +5262,7 @@ fn (mut g Gen) ident(node ast.Ident) { return } mut name := if node.kind == .function { c_fn_name(node.name) } else { c_name(node.name) } + name = g.transformed_var_names[node.name] or { name } if node.kind == .constant { if g.pref.translated && !g.is_builtin_mod && !util.module_is_builtin(node.name.all_before_last('.')) { diff --git a/vlib/v/tests/clash_var_fn_name_test.v b/vlib/v/tests/clash_var_fn_name_test.v new file mode 100644 index 0000000000..3b1b249bd6 --- /dev/null +++ b/vlib/v/tests/clash_var_fn_name_test.v @@ -0,0 +1,284 @@ +fn test_bool() { + b := bool(false) + bool_str := b.str() +} + +fn test_byteptr() { + b0 := byteptr(c'a') + b1 := byteptr(c'b') + b2 := byteptr(c'c') + b3 := byteptr(c'd') + b4 := byteptr(c'e') + byteptr_str := b0.str() + byteptr_vbytes := unsafe { b0.vbytes(1) } + byteptr_vstring := unsafe { b1.vstring() } + byteptr_vstring_literal := unsafe { b2.vstring_literal() } + byteptr_vstring_literal_with_len := unsafe { b3.vstring_literal_with_len(1) } + byteptr_vstring_with_len := unsafe { b4.vstring_with_len(1) } +} + +fn test_string() { + s := 'vstring' + s1 := ' other' + string_after := s.after('v') + string_after_char := s.after_char(`v`) + string_all_after := s.all_after('v') + string_all_after_first := s.all_after_first('v') + string_all_after_last := s.all_after_last('v') + string_all_before := s.all_before('v') + string_all_before_last := s.all_before_last('v') + string_before := s.before('v') + string_bool := s.bool() + string_bytes := s.bytes() + string_camel_to_snake := s.camel_to_snake() + string_capitalize := s.capitalize() + string_clone := s.clone() + string_compare := s.compare(s1) + string_contains := s.contains('v') + string_contains_any := s.contains_any('vs') + string_contains_any_substr := s.contains_any_substr(['v', 'ing']) + string_contains_only := s.contains_only('s') + string_contains_u8 := s.contains_u8(`g`) + string_count := s.count('i') + string_ends_with := s.ends_with('ing') + string_expand_tabs := s.expand_tabs(4) + string_f32 := s.f32() + string_f64 := s.f64() + string_fields := s.fields() + string_find_between := s.find_between('v', 'g') + string_hash := s.hash() + string_i16 := s.i16() + string_i32 := s.i32() + string_i64 := s.i64() + string_i8 := s.i8() + string_indent_width := s.indent_width() + string_index := s.index('g') + string_index_after_ := s.index_after_('n', 3) + string_index_any := s.index_any('g') + string_index_u8 := s.index_u8(`g`) + string_int := s.int() + string_is_ascii := s.is_ascii() + string_is_bin := s.is_bin() + string_is_blank := s.is_blank() + string_is_capital := s.is_capital() + string_is_hex := s.is_hex() + string_is_identifier := s.is_identifier() + string_is_int := s.is_int() + string_is_lower := s.is_lower() + string_is_oct := s.is_oct() + string_is_pure_ascii := s.is_pure_ascii() + string_is_title := s.is_title() + string_is_upper := s.is_upper() + string_last_index := s.last_index('g') + string_last_index_u8 := s.last_index_u8(`g`) + string_len_utf8 := s.len_utf8() + string_limit := s.limit(5) + string_match_glob := s.match_glob('*') + string_normalize_tabs := s.normalize_tabs(2) + string_parse_int := s.parse_int(10, 32) or { 0 } + string_parse_uint := s.parse_uint(10, 32) or { 0 } + string_repeat := s.repeat(2) + string_replace := s.replace('v', 'V') + string_replace_char := s.replace_char(`v`, `V`, 1) + string_replace_each := s.replace_each(['v', 'V']) + string_replace_once := s.replace_once('v', 'V') + string_reverse := s.reverse() + string_rsplit := s.rsplit('g') + string_rsplit_any := s.rsplit_any('g') + string_rsplit_nth := s.rsplit_nth('g', 1) + string_rsplit_once, tmp := s.rsplit_once('g') or { '', '' } + string_runes := s.runes() + string_runes_iterator := s.runes_iterator() + string_snake_to_camel := s.snake_to_camel() + string_split := s.split('r') + string_split_any := s.split_any('r') + string_split_by_space := s.split_by_space() + string_split_into_lines := s.split_into_lines() + string_split_n := s.split_n('g', 2) + string_split_nth := s.split_nth('ri', 2) + string_split_once, tmp1 := s.split_once('g') or { '', '' } + string_starts_with := s.starts_with('v') + string_starts_with_captial := s.starts_with_capital() + string_str := s.str() + string_strip_margin := s.strip_margin() + string_strip_margin_custom := s.strip_margin_custom(`v`) + string_substr := s.substr(1, 3) + string_substr_ni := s.substr_ni(0, 1) + string_substr_unsafe := unsafe { s[0..2] } + string_substr_with_check := s.substr_with_check(0, 1) or { '' } + string_title := s.title() + string_to_lower := s.to_lower() + string_to_lower_ascii := s.to_lower_ascii() + string_to_upper := s.to_upper() + string_to_upper_ascii := s.to_upper_ascii() + string_to_wide := s.to_wide() + string_trim_chars := s.trim('string') + string_trim_indent := s.trim_indent() + string_trim_indexes, tmp3 := s.trim_indexes('in') + string_trim_left := s.trim_left('g') + string_trim_right := s.trim_right('g') + string_trim_space := s.trim_space() + string_trim_space_left := s.trim_space_left() + string_trim_space_right := s.trim_space_right() + string_trim_string_left := s.trim_string_left('v') + string_trim_string_right := s.trim_string_right('g') + string_u16 := s.u16() + string_u32 := s.u32() + string_u64 := s.u64() + string_u8 := s.u8() + string_u8_array := s.u8_array() + string_uncapitalize := s.uncapitalize() + string_utf32_code := s.utf32_code() + string_wrap := s.wrap(width: 20) + string__eq := s == s1 + string__lt := s < s1 + string__plus := s + s1 + string_at := s[3] +} + +fn test_i8() { + i8_ := i8(0) + i8_str := i8_.str() +} + +fn test_i16() { + i16_ := i16(0) + i16_str := i16_.str() +} + +fn test_i32() { + i32_ := i32(0) + i32_str := i32_.str() +} + +fn test_int() { + int_ := int(0) + int_str := int_.str() + int_literal_str := 0.str() +} + +fn test_i64() { + i64_ := i64(0) + i64_str := i64_.str() +} + +fn test_u8() { + u8_ := u8(0) + u8_str := u8_.str() + u8_ascii_str := u8_.ascii_str() +} + +fn test_u16() { + u16_ := u16(0) + u16_str := u16_.str() +} + +fn test_u32() { + u32_ := u32(0) + u32_str := u32_.str() +} + +fn test_u64() { + u64_ := u64(0) + u64_str := u64_.str() + u64_hex := u64_.hex() +} + +fn test_isize() { + isize_ := isize(0) + isize_str := isize_.str() +} + +fn test_usize() { + usize_ := usize(0) + usize_str := usize_.str() +} + +fn test_f32() { + f32_ := f32(0) + f32_str := f32_.str() + f32_strg := f32_.strg() + f32_strsci := f32_.strsci(2) + f32_strlong := f32_.strlong() + f32_eq_epsilon := f32_.eq_epsilon(0.000001) +} + +fn test_f64() { + f64_ := f64(0) + f64_str := f64_.str() + f64_strg := f64_.strg() + f64_strsci := f64_.strsci(2) + f64_strlong := f64_.strlong() + f64_eq_epsilon := f64_.eq_epsilon(0.000001) +} + +fn test_float() { + float_literal_str := 0.1.str() +} + +fn test_rune() { + r := rune(0) + rune_bytes := r.bytes() + rune_str := r.str() + rune_to_upper := r.to_upper() +} + +fn test_ptr() { + ptr := unsafe { voidptr(nil) } + voidptr_hex_full := ptr.hex_full() + voidptr_str := ptr.str() + voidptr_vbytes := unsafe { ptr.vbytes(1) } +} + +fn test_char() { + char_ := unsafe { &char(c'a') } + char_str := char_.str() + char_vstring := unsafe { char_.vstring() } + char_vstring_literal_with_len := unsafe { char_.vstring_with_len(1) } + char_vstring_with_len := unsafe { char_.vstring_with_len(1) } +} + +fn test_cstring() { + cstring := c'cstring' + u8_vstring := unsafe { cstring.vstring() } +} + +fn test_array() { + new_array_from_c_array_noscan := [1, 2, 3] + mut a := [1, 2, 3] + array_repeat_to_depth := a.repeat(2) + array_first := a.first() + array_last := a.last() + array_pop_left_noscan := a.pop_left() + array_pop_noscan := a.pop() + array_get := a[0] + array_clone_to_depth := a.clone() + array_reverse := a.reverse() + array_filter := a.filter(it < 2) + array_any := a.any(it % 2 == 1) + array_count := a.count(it > 1) + array_all := a.all(it > 0) + array_slice := unsafe { a[0..1] } +} + +fn test_map() { + new_map_noscan_key_value := map[int]int{} + new_map_noscan_value := { + 'test': 10 + } + mut m := { + 'test': 10 + } + map_clone := m.clone() + map_keys := m.keys() + map_values := m.values() + map_get := m['test'] + map_move := m.move() +} + +fn test_assign() { + r := rune(0) + mut rune_bytes := r.bytes() + rb := rune_bytes + _ = rune_bytes +} From e15d8fcf493613f1c75d4b9c21ed7851f58308e0 Mon Sep 17 00:00:00 2001 From: kbkpbot Date: Thu, 4 Sep 2025 18:33:39 +0800 Subject: [PATCH 05/15] checker: comptime match only eval true branch (fix #25223) (#25225) --- vlib/v/checker/match.v | 11 +++++++---- .../comptime_match_eval_only_true_branch_test.v | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 vlib/v/tests/comptime/comptime_match_eval_only_true_branch_test.v diff --git a/vlib/v/checker/match.v b/vlib/v/checker/match.v index 6d3530d00d..3fb651d6d9 100644 --- a/vlib/v/checker/match.v +++ b/vlib/v/checker/match.v @@ -221,10 +221,13 @@ fn (mut c Checker) match_expr(mut node ast.MatchExpr) ast.Type { } } } - if node.is_expr { - c.stmts_ending_with_expression(mut branch.stmts, c.expected_or_type) - } else { - c.stmts(mut branch.stmts) + + if !node.is_comptime || (node.is_comptime && comptime_match_branch_result) { + if node.is_expr { + c.stmts_ending_with_expression(mut branch.stmts, c.expected_or_type) + } else { + c.stmts(mut branch.stmts) + } } c.smartcast_mut_pos = token.Pos{} c.smartcast_cond_pos = token.Pos{} diff --git a/vlib/v/tests/comptime/comptime_match_eval_only_true_branch_test.v b/vlib/v/tests/comptime/comptime_match_eval_only_true_branch_test.v new file mode 100644 index 0000000000..74ca289ed2 --- /dev/null +++ b/vlib/v/tests/comptime/comptime_match_eval_only_true_branch_test.v @@ -0,0 +1,17 @@ +module main + +fn func[T]() bool { + $match T { + u8, u16 { + return true + } + $else { + // return false + $compile_error('fail') + } + } +} + +fn test_comptime_match_eval_only_true_branch() { + assert func[u8]() +} From dbd5b5f56cb7451949eaa72ec8534eac7b6a6cd1 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Thu, 4 Sep 2025 13:47:53 +0300 Subject: [PATCH 06/15] Revert "cgen: ensure variable names do not conflict with builtin methods (fix #25063) (#25178)" This reverts commit f17e0fd52b12f3d4b81f79a14253fb2e1d2d3a7d. --- vlib/v/gen/c/assign.v | 128 ------------ vlib/v/gen/c/autofree.v | 3 +- vlib/v/gen/c/cgen.v | 2 - vlib/v/tests/clash_var_fn_name_test.v | 284 -------------------------- 4 files changed, 1 insertion(+), 416 deletions(-) delete mode 100644 vlib/v/tests/clash_var_fn_name_test.v diff --git a/vlib/v/gen/c/assign.v b/vlib/v/gen/c/assign.v index 31c9749de9..2b4a158437 100644 --- a/vlib/v/gen/c/assign.v +++ b/vlib/v/gen/c/assign.v @@ -7,126 +7,6 @@ import v.ast import v.util import v.token -fn (mut g Gen) resolve_var_method_conflict(var_name string, val ast.Expr) string { - mut method_call := ast.CallExpr{} - mut has_method_call := false - mut receiver_type := ast.Type(0) - mut method_name := '' - match val { - ast.ArrayInit { - if var_name == 'new_array_from_c_array_noscan' { - return '_var_${var_name}' - } - } - ast.MapInit { - if var_name == 'new_map_noscan_key_value' { - return '_var_${var_name}' - } - } - ast.CallExpr { - if val.is_method { - method_call = val - has_method_call = true - } - } - ast.UnsafeExpr { - if val.expr is ast.CallExpr { - call_expr := val.expr as ast.CallExpr - if call_expr.is_method { - method_call = call_expr - has_method_call = true - } - } else if val.expr is ast.IndexExpr { - index_expr := val.expr as ast.IndexExpr - left_type := g.unwrap_generic(index_expr.left_type) - left_sym := g.table.sym(left_type) - type_name := left_sym.kind.str() - if (index_expr.index is ast.RangeExpr && var_name == '${type_name}_slice') - || var_name in ['${type_name}_get', '${type_name}_at'] { - return '_var_${var_name}' - } - } - } - ast.InfixExpr { - receiver_type = val.left_type - method_name = match val.op { - .eq { '_eq' } - .ne { '_ne' } - .lt { '_lt' } - .le { '_le' } - .gt { '_gt' } - .ge { '_ge' } - .plus { '_plus' } - .minus { '_minus' } - .mul { '_mul' } - .div { '_div' } - .mod { '_mod' } - else { '' } - } - has_method_call = method_name != '' && receiver_type != 0 - } - ast.IndexExpr { - left_type := g.unwrap_generic(val.left_type) - left_sym := g.table.sym(left_type) - type_name := left_sym.kind.str() - if (val.index is ast.RangeExpr && var_name == '${type_name}_slice') - || var_name in ['${type_name}_get', '${type_name}_at'] { - return '_var_${var_name}' - } - } - else {} - } - if has_method_call { - mut left_type := ast.Type(0) - if method_call.left_type != 0 { - left_type = g.unwrap_generic(method_call.left_type) - method_name = method_call.name - } else if receiver_type != 0 { - left_type = g.unwrap_generic(receiver_type) - } - if left_type != 0 { - left_sym := g.table.sym(left_type) - final_left_sym := g.table.final_sym(left_type) - if var_name == '${left_sym.cname}_${method_name}' { - return '_var_${var_name}' - } - if final_left_sym.kind == .array && !(left_sym.kind == .alias - && left_sym.has_method(method_name)) { - actual_method_name := match method_name { - 'repeat' { - 'repeat_to_depth' - } - 'clone' { - 'clone_to_depth' - } - 'pop_left' { - 'pop_left_noscan' - } - 'pop' { - 'pop_noscan' - } - else { - method_name - } - } - if var_name == 'array_${actual_method_name}' { - return '_var_${var_name}' - } - } - if final_left_sym.kind == .map && !(left_sym.kind == .alias - && left_sym.has_method(method_name)) { - if method_name in ['clone', 'move'] && var_name == 'map_${method_name}' { - return '_var_${var_name}' - } - if method_name in ['keys', 'values'] && var_name == 'map_${method_name}' { - return '_var_${var_name}' - } - } - } - } - return var_name -} - fn (mut g Gen) expr_with_opt_or_block(expr ast.Expr, expr_typ ast.Type, var_expr ast.Expr, ret_typ ast.Type, in_heap bool) { gen_or := expr is ast.Ident && expr.or_expr.kind != .absent @@ -425,14 +305,6 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { is_va_list = left_sym.language == .c && left_sym.name == 'C.va_list' if mut left is ast.Ident { ident = left - if is_decl && i < node.right.len { - resolved_name := g.resolve_var_method_conflict(ident.name, node.right[i]) - if resolved_name != ident.name { - g.transformed_var_names[ident.name] = resolved_name - ident.name = resolved_name - node.left[i] = ident - } - } g.curr_var_name << ident.name // id_info := ident.var_info() // var_type = id_info.typ diff --git a/vlib/v/gen/c/autofree.v b/vlib/v/gen/c/autofree.v index 941934b280..a4f88ae12a 100644 --- a/vlib/v/gen/c/autofree.v +++ b/vlib/v/gen/c/autofree.v @@ -242,8 +242,7 @@ fn (mut g Gen) autofree_var_call(free_fn_name string, v ast.Var) { af.writeln('\t\t${free_fn_name}((${base_type}*)${c_name(v.name)}.data); // autofreed option var ${g.cur_mod.name} ${g.is_builtin_mod}') af.writeln('\t}') } else if v.typ.idx() != ast.u8_type_idx { - var_name := g.transformed_var_names[v.name] or { v.name } - af.writeln('\t${free_fn_name}(&${c_name(var_name)}); // autofreed var ${g.cur_mod.name} ${g.is_builtin_mod}') + af.writeln('\t${free_fn_name}(&${c_name(v.name)}); // autofreed var ${g.cur_mod.name} ${g.is_builtin_mod}') } } g.autofree_scope_stmts << af.str() diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 00dc97b7ff..c1e0368510 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -258,7 +258,6 @@ mut: autofree_methods map[ast.Type]string generated_free_methods map[ast.Type]bool autofree_scope_stmts []string - transformed_var_names map[string]string use_segfault_handler bool = true test_function_names []string ///////// @@ -5262,7 +5261,6 @@ fn (mut g Gen) ident(node ast.Ident) { return } mut name := if node.kind == .function { c_fn_name(node.name) } else { c_name(node.name) } - name = g.transformed_var_names[node.name] or { name } if node.kind == .constant { if g.pref.translated && !g.is_builtin_mod && !util.module_is_builtin(node.name.all_before_last('.')) { diff --git a/vlib/v/tests/clash_var_fn_name_test.v b/vlib/v/tests/clash_var_fn_name_test.v deleted file mode 100644 index 3b1b249bd6..0000000000 --- a/vlib/v/tests/clash_var_fn_name_test.v +++ /dev/null @@ -1,284 +0,0 @@ -fn test_bool() { - b := bool(false) - bool_str := b.str() -} - -fn test_byteptr() { - b0 := byteptr(c'a') - b1 := byteptr(c'b') - b2 := byteptr(c'c') - b3 := byteptr(c'd') - b4 := byteptr(c'e') - byteptr_str := b0.str() - byteptr_vbytes := unsafe { b0.vbytes(1) } - byteptr_vstring := unsafe { b1.vstring() } - byteptr_vstring_literal := unsafe { b2.vstring_literal() } - byteptr_vstring_literal_with_len := unsafe { b3.vstring_literal_with_len(1) } - byteptr_vstring_with_len := unsafe { b4.vstring_with_len(1) } -} - -fn test_string() { - s := 'vstring' - s1 := ' other' - string_after := s.after('v') - string_after_char := s.after_char(`v`) - string_all_after := s.all_after('v') - string_all_after_first := s.all_after_first('v') - string_all_after_last := s.all_after_last('v') - string_all_before := s.all_before('v') - string_all_before_last := s.all_before_last('v') - string_before := s.before('v') - string_bool := s.bool() - string_bytes := s.bytes() - string_camel_to_snake := s.camel_to_snake() - string_capitalize := s.capitalize() - string_clone := s.clone() - string_compare := s.compare(s1) - string_contains := s.contains('v') - string_contains_any := s.contains_any('vs') - string_contains_any_substr := s.contains_any_substr(['v', 'ing']) - string_contains_only := s.contains_only('s') - string_contains_u8 := s.contains_u8(`g`) - string_count := s.count('i') - string_ends_with := s.ends_with('ing') - string_expand_tabs := s.expand_tabs(4) - string_f32 := s.f32() - string_f64 := s.f64() - string_fields := s.fields() - string_find_between := s.find_between('v', 'g') - string_hash := s.hash() - string_i16 := s.i16() - string_i32 := s.i32() - string_i64 := s.i64() - string_i8 := s.i8() - string_indent_width := s.indent_width() - string_index := s.index('g') - string_index_after_ := s.index_after_('n', 3) - string_index_any := s.index_any('g') - string_index_u8 := s.index_u8(`g`) - string_int := s.int() - string_is_ascii := s.is_ascii() - string_is_bin := s.is_bin() - string_is_blank := s.is_blank() - string_is_capital := s.is_capital() - string_is_hex := s.is_hex() - string_is_identifier := s.is_identifier() - string_is_int := s.is_int() - string_is_lower := s.is_lower() - string_is_oct := s.is_oct() - string_is_pure_ascii := s.is_pure_ascii() - string_is_title := s.is_title() - string_is_upper := s.is_upper() - string_last_index := s.last_index('g') - string_last_index_u8 := s.last_index_u8(`g`) - string_len_utf8 := s.len_utf8() - string_limit := s.limit(5) - string_match_glob := s.match_glob('*') - string_normalize_tabs := s.normalize_tabs(2) - string_parse_int := s.parse_int(10, 32) or { 0 } - string_parse_uint := s.parse_uint(10, 32) or { 0 } - string_repeat := s.repeat(2) - string_replace := s.replace('v', 'V') - string_replace_char := s.replace_char(`v`, `V`, 1) - string_replace_each := s.replace_each(['v', 'V']) - string_replace_once := s.replace_once('v', 'V') - string_reverse := s.reverse() - string_rsplit := s.rsplit('g') - string_rsplit_any := s.rsplit_any('g') - string_rsplit_nth := s.rsplit_nth('g', 1) - string_rsplit_once, tmp := s.rsplit_once('g') or { '', '' } - string_runes := s.runes() - string_runes_iterator := s.runes_iterator() - string_snake_to_camel := s.snake_to_camel() - string_split := s.split('r') - string_split_any := s.split_any('r') - string_split_by_space := s.split_by_space() - string_split_into_lines := s.split_into_lines() - string_split_n := s.split_n('g', 2) - string_split_nth := s.split_nth('ri', 2) - string_split_once, tmp1 := s.split_once('g') or { '', '' } - string_starts_with := s.starts_with('v') - string_starts_with_captial := s.starts_with_capital() - string_str := s.str() - string_strip_margin := s.strip_margin() - string_strip_margin_custom := s.strip_margin_custom(`v`) - string_substr := s.substr(1, 3) - string_substr_ni := s.substr_ni(0, 1) - string_substr_unsafe := unsafe { s[0..2] } - string_substr_with_check := s.substr_with_check(0, 1) or { '' } - string_title := s.title() - string_to_lower := s.to_lower() - string_to_lower_ascii := s.to_lower_ascii() - string_to_upper := s.to_upper() - string_to_upper_ascii := s.to_upper_ascii() - string_to_wide := s.to_wide() - string_trim_chars := s.trim('string') - string_trim_indent := s.trim_indent() - string_trim_indexes, tmp3 := s.trim_indexes('in') - string_trim_left := s.trim_left('g') - string_trim_right := s.trim_right('g') - string_trim_space := s.trim_space() - string_trim_space_left := s.trim_space_left() - string_trim_space_right := s.trim_space_right() - string_trim_string_left := s.trim_string_left('v') - string_trim_string_right := s.trim_string_right('g') - string_u16 := s.u16() - string_u32 := s.u32() - string_u64 := s.u64() - string_u8 := s.u8() - string_u8_array := s.u8_array() - string_uncapitalize := s.uncapitalize() - string_utf32_code := s.utf32_code() - string_wrap := s.wrap(width: 20) - string__eq := s == s1 - string__lt := s < s1 - string__plus := s + s1 - string_at := s[3] -} - -fn test_i8() { - i8_ := i8(0) - i8_str := i8_.str() -} - -fn test_i16() { - i16_ := i16(0) - i16_str := i16_.str() -} - -fn test_i32() { - i32_ := i32(0) - i32_str := i32_.str() -} - -fn test_int() { - int_ := int(0) - int_str := int_.str() - int_literal_str := 0.str() -} - -fn test_i64() { - i64_ := i64(0) - i64_str := i64_.str() -} - -fn test_u8() { - u8_ := u8(0) - u8_str := u8_.str() - u8_ascii_str := u8_.ascii_str() -} - -fn test_u16() { - u16_ := u16(0) - u16_str := u16_.str() -} - -fn test_u32() { - u32_ := u32(0) - u32_str := u32_.str() -} - -fn test_u64() { - u64_ := u64(0) - u64_str := u64_.str() - u64_hex := u64_.hex() -} - -fn test_isize() { - isize_ := isize(0) - isize_str := isize_.str() -} - -fn test_usize() { - usize_ := usize(0) - usize_str := usize_.str() -} - -fn test_f32() { - f32_ := f32(0) - f32_str := f32_.str() - f32_strg := f32_.strg() - f32_strsci := f32_.strsci(2) - f32_strlong := f32_.strlong() - f32_eq_epsilon := f32_.eq_epsilon(0.000001) -} - -fn test_f64() { - f64_ := f64(0) - f64_str := f64_.str() - f64_strg := f64_.strg() - f64_strsci := f64_.strsci(2) - f64_strlong := f64_.strlong() - f64_eq_epsilon := f64_.eq_epsilon(0.000001) -} - -fn test_float() { - float_literal_str := 0.1.str() -} - -fn test_rune() { - r := rune(0) - rune_bytes := r.bytes() - rune_str := r.str() - rune_to_upper := r.to_upper() -} - -fn test_ptr() { - ptr := unsafe { voidptr(nil) } - voidptr_hex_full := ptr.hex_full() - voidptr_str := ptr.str() - voidptr_vbytes := unsafe { ptr.vbytes(1) } -} - -fn test_char() { - char_ := unsafe { &char(c'a') } - char_str := char_.str() - char_vstring := unsafe { char_.vstring() } - char_vstring_literal_with_len := unsafe { char_.vstring_with_len(1) } - char_vstring_with_len := unsafe { char_.vstring_with_len(1) } -} - -fn test_cstring() { - cstring := c'cstring' - u8_vstring := unsafe { cstring.vstring() } -} - -fn test_array() { - new_array_from_c_array_noscan := [1, 2, 3] - mut a := [1, 2, 3] - array_repeat_to_depth := a.repeat(2) - array_first := a.first() - array_last := a.last() - array_pop_left_noscan := a.pop_left() - array_pop_noscan := a.pop() - array_get := a[0] - array_clone_to_depth := a.clone() - array_reverse := a.reverse() - array_filter := a.filter(it < 2) - array_any := a.any(it % 2 == 1) - array_count := a.count(it > 1) - array_all := a.all(it > 0) - array_slice := unsafe { a[0..1] } -} - -fn test_map() { - new_map_noscan_key_value := map[int]int{} - new_map_noscan_value := { - 'test': 10 - } - mut m := { - 'test': 10 - } - map_clone := m.clone() - map_keys := m.keys() - map_values := m.values() - map_get := m['test'] - map_move := m.move() -} - -fn test_assign() { - r := rune(0) - mut rune_bytes := r.bytes() - rb := rune_bytes - _ = rune_bytes -} From 2b4253caf9e6846b88310575768449191d8a9ba9 Mon Sep 17 00:00:00 2001 From: Leo Developer Date: Thu, 4 Sep 2025 23:22:52 +0200 Subject: [PATCH 07/15] time: always return utc() timezone for Time.unix/0 (fix #17784) (#25233) --- vlib/orm/orm_test.v | 4 +++- vlib/time/operator.v | 7 +++++-- vlib/time/private_test.c.v | 14 -------------- vlib/time/time.v | 30 ++++++++++++++++++++++++------ vlib/time/time_format_test.v | 2 +- vlib/time/time_test.c.v | 2 +- vlib/time/time_test.v | 17 ----------------- vlib/v/gen/c/json.v | 8 ++++---- 8 files changed, 38 insertions(+), 46 deletions(-) delete mode 100644 vlib/time/private_test.c.v diff --git a/vlib/orm/orm_test.v b/vlib/orm/orm_test.v index c6eebc747a..5b5be2180f 100644 --- a/vlib/orm/orm_test.v +++ b/vlib/orm/orm_test.v @@ -393,7 +393,9 @@ fn test_orm() { // Note: usually updated_time_mod.created != t, because t has // its microseconds set, while the value retrieved from the DB // has them zeroed, because the db field resolution is seconds. - assert modules.first().created.format_ss() == t.format_ss() + // Note: the database also stores the time in UTC, so the + // comparison must be done on the unix timestamp. + assert modules.first().created.unix() == t.unix() users = sql db { select from User where (name == 'Sam' && is_customer == true) || id == 1 diff --git a/vlib/time/operator.v b/vlib/time/operator.v index 8c5fab8e89..8242e3444e 100644 --- a/vlib/time/operator.v +++ b/vlib/time/operator.v @@ -3,13 +3,16 @@ module time // operator `==` returns true if provided time is equal to time @[inline] pub fn (t1 Time) == (t2 Time) bool { - return t1.unix() == t2.unix() && t1.nanosecond == t2.nanosecond + return t1.is_local == t2.is_local && t1.local_unix() == t2.local_unix() + && t1.nanosecond == t2.nanosecond } // operator `<` returns true if provided time is less than time @[inline] pub fn (t1 Time) < (t2 Time) bool { - return t1.unix() < t2.unix() || (t1.unix() == t2.unix() && t1.nanosecond < t2.nanosecond) + t1u := t1.unix() + t2u := t2.unix() + return t1u < t2u || (t1u == t2u && t1.nanosecond < t2.nanosecond) } // Time subtract using operator overloading. diff --git a/vlib/time/private_test.c.v b/vlib/time/private_test.c.v deleted file mode 100644 index 7e69c40374..0000000000 --- a/vlib/time/private_test.c.v +++ /dev/null @@ -1,14 +0,0 @@ -// tests that use and test private functions -module time - -// test the old behavior is same as new, the unix time should always be local time -fn test_new_is_same_as_old_for_all_platforms() { - t := C.time(0) - tm := C.localtime(&t) - old_time := convert_ctime(tm, 0) - new_time := now() - diff := new_time.unix - old_time.unix - // could in very rare cases be that the second changed between calls - dump(diff) - assert (diff >= -2 && diff <= 2) == true -} diff --git a/vlib/time/time.v b/vlib/time/time.v index 9e18c8ef52..c9b1059a4d 100644 --- a/vlib/time/time.v +++ b/vlib/time/time.v @@ -106,6 +106,12 @@ pub fn (t Time) smonth() string { // unix returns the UNIX time with second resolution. @[inline] pub fn (t Time) unix() i64 { + return time_with_unix(t.local_to_utc()).unix +} + +// local_unix returns the UNIX local time with second resolution. +@[inline] +pub fn (t Time) local_unix() i64 { return time_with_unix(t).unix } @@ -135,14 +141,26 @@ pub fn (t Time) add(duration_in_nanosecond Duration) Time { // ... so instead, handle the addition manually in parts ¯\_(ツ)_/¯ mut increased_time_nanosecond := i64(t.nanosecond) + duration_in_nanosecond.nanoseconds() // increased_time_second - mut increased_time_second := t.unix() + (increased_time_nanosecond / second) + mut increased_time_second := t.local_unix() + (increased_time_nanosecond / second) increased_time_nanosecond = increased_time_nanosecond % second if increased_time_nanosecond < 0 { increased_time_second-- increased_time_nanosecond += second } res := unix_nanosecond(increased_time_second, int(increased_time_nanosecond)) - return if t.is_local { res.as_local() } else { res } + + if t.is_local { + // we need to reset unix to 0, because we don't know the offset + // and we can't calculate it without it without causing infinite recursion + // so unfortunately we need to recalculate unix next time it is needed + return Time{ + ...res + is_local: true + unix: 0 + } + } + + return res } // add_seconds returns a new time struct with an added number of seconds. @@ -177,7 +195,7 @@ pub fn since(t Time) Duration { // ``` pub fn (t Time) relative() string { znow := now() - mut secs := znow.unix - t.unix() + mut secs := znow.unix() - t.unix() mut prefix := '' mut suffix := '' if secs < 0 { @@ -239,7 +257,7 @@ pub fn (t Time) relative() string { // ``` pub fn (t Time) relative_short() string { znow := now() - mut secs := znow.unix - t.unix() + mut secs := znow.unix() - t.unix() mut prefix := '' mut suffix := '' if secs < 0 { @@ -364,9 +382,9 @@ pub fn days_in_month(month int, year int) !int { return res } -// debug returns detailed breakdown of time (`Time{ year: YYYY month: MM day: dd hour: HH: minute: mm second: ss nanosecond: nanos unix: unix }`). +// debug returns detailed breakdown of time (`Time{ year: YYYY month: MM day: dd hour: HH: minute: mm second: ss nanosecond: nanos unix: unix is_local: false }`). pub fn (t Time) debug() string { - return 'Time{ year: ${t.year:04} month: ${t.month:02} day: ${t.day:02} hour: ${t.hour:02} minute: ${t.minute:02} second: ${t.second:02} nanosecond: ${t.nanosecond:09} unix: ${t.unix:07} }' + return 'Time{ year: ${t.year:04} month: ${t.month:02} day: ${t.day:02} hour: ${t.hour:02} minute: ${t.minute:02} second: ${t.second:02} nanosecond: ${t.nanosecond:09} unix: ${t.unix:07} is_local: ${t.is_local} }' } // offset returns time zone UTC offset in seconds. diff --git a/vlib/time/time_format_test.v b/vlib/time/time_format_test.v index 8ca07ac5d4..f21be4061a 100644 --- a/vlib/time/time_format_test.v +++ b/vlib/time/time_format_test.v @@ -12,7 +12,7 @@ const time_to_test = time.Time{ fn test_now_format() { t := time.now() u := t.unix() - assert t.format() == time.unix(int(u)).format() + assert t.format() == time.unix(int(u)).utc_to_local().format() } fn test_format() { diff --git a/vlib/time/time_test.c.v b/vlib/time/time_test.c.v index 2222e1e2d6..7bf896608d 100644 --- a/vlib/time/time_test.c.v +++ b/vlib/time/time_test.c.v @@ -15,7 +15,7 @@ fn test_tm_gmtoff() { dump(t2) dump(t1.nanosecond) dump(t2.nanosecond) - diff := int(t1.unix() - t2.unix()) + diff := int(t1.local_unix() - t2.unix()) dump(diff) dump(info.tm_gmtoff) assert diff in [info.tm_gmtoff - 1, info.tm_gmtoff, info.tm_gmtoff + 1] diff --git a/vlib/time/time_test.v b/vlib/time/time_test.v index 34554cab82..e33b0e5c2e 100644 --- a/vlib/time/time_test.v +++ b/vlib/time/time_test.v @@ -90,17 +90,10 @@ fn test_unix() { assert t6.hour == 6 assert t6.minute == 9 assert t6.second == 29 - assert local_time_to_test.unix() == 332198622 assert utc_time_to_test.unix() == 332198622 } fn test_format_rfc3339() { - // assert '1980-07-11T19:23:42.123Z' - res := local_time_to_test.format_rfc3339() - assert res.ends_with('23:42.123Z') - assert res.starts_with('1980-07-1') - assert res.contains('T') - // assert '1980-07-11T19:23:42.123Z' utc_res := utc_time_to_test.format_rfc3339() assert utc_res.ends_with('23:42.123Z') @@ -109,11 +102,6 @@ fn test_format_rfc3339() { } fn test_format_rfc3339_micro() { - res := local_time_to_test.format_rfc3339_micro() - assert res.ends_with('23:42.123456Z') - assert res.starts_with('1980-07-1') - assert res.contains('T') - utc_res := utc_time_to_test.format_rfc3339_micro() assert utc_res.ends_with('23:42.123456Z') assert utc_res.starts_with('1980-07-1') @@ -121,11 +109,6 @@ fn test_format_rfc3339_micro() { } fn test_format_rfc3339_nano() { - res := local_time_to_test.format_rfc3339_nano() - assert res.ends_with('23:42.123456789Z') - assert res.starts_with('1980-07-1') - assert res.contains('T') - utc_res := utc_time_to_test.format_rfc3339_nano() assert utc_res.ends_with('23:42.123456789Z') assert utc_res.starts_with('1980-07-1') diff --git a/vlib/v/gen/c/json.v b/vlib/v/gen/c/json.v index db30ad49bd..7b102074aa 100644 --- a/vlib/v/gen/c/json.v +++ b/vlib/v/gen/c/json.v @@ -417,7 +417,7 @@ fn (mut g Gen) gen_sumtype_enc_dec(utyp ast.Type, sym ast.TypeSymbol, mut enc st if variant_sym.kind == .enum { enc.writeln('\t\tcJSON_AddItemToObject(o, "${unmangled_variant_name}", ${js_enc_name('u64')}(*${var_data}${field_op}_${variant_typ}));') } else if variant_sym.name == 'time.Time' { - enc.writeln('\t\tcJSON_AddItemToObject(o, "${unmangled_variant_name}", ${js_enc_name('i64')}(${var_data}${field_op}_${variant_typ}->__v_unix));') + enc.writeln('\t\tcJSON_AddItemToObject(o, "${unmangled_variant_name}", ${js_enc_name('i64')}(time__Time_unix(*${var_data}${field_op}_${variant_typ})));') } else { enc.writeln('\t\tcJSON_AddItemToObject(o, "${unmangled_variant_name}", ${js_enc_name(variant_typ)}(*${var_data}${field_op}_${variant_typ}));') } @@ -442,7 +442,7 @@ fn (mut g Gen) gen_sumtype_enc_dec(utyp ast.Type, sym ast.TypeSymbol, mut enc st } } else if variant_sym.name == 'time.Time' { enc.writeln('\t\tcJSON_AddItemToObject(o, "_type", cJSON_CreateString("${unmangled_variant_name}"));') - enc.writeln('\t\tcJSON_AddItemToObject(o, "value", ${js_enc_name('i64')}(${var_data}${field_op}_${variant_typ}->__v_unix));') + enc.writeln('\t\tcJSON_AddItemToObject(o, "value", ${js_enc_name('i64')}(time__Time_unix(*${var_data}${field_op}_${variant_typ})));') } else { enc.writeln('\t\tcJSON_free(o);') enc.writeln('\t\to = ${js_enc_name(variant_typ)}(*${var_data}${field_op}_${variant_typ});') @@ -954,9 +954,9 @@ fn (mut g Gen) gen_struct_enc_dec(utyp ast.Type, type_info ast.TypeInfo, styp st // time struct requires special treatment // it has to be encoded as a unix timestamp number if is_option { - enc.writeln('${indent}cJSON_AddItemToObject(o, "${name}", json__encode_u64((*(${g.base_type(field.typ)}*)(${prefix_enc}${op}${c_name(field.name)}.data)).__v_unix));') + enc.writeln('${indent}cJSON_AddItemToObject(o, "${name}", json__encode_u64(time__Time_unix(*(${g.base_type(field.typ)}*)(${prefix_enc}${op}${c_name(field.name)}.data))));') } else { - enc.writeln('${indent}cJSON_AddItemToObject(o, "${name}", json__encode_u64(${prefix_enc}${op}${c_name(field.name)}.__v_unix));') + enc.writeln('${indent}cJSON_AddItemToObject(o, "${name}", json__encode_u64(time__Time_unix(${prefix_enc}${op}${c_name(field.name)})));') } } else { if !field.typ.is_any_kind_of_pointer() { From 9a0166701c2fc7095bed34299b976167ce03b791 Mon Sep 17 00:00:00 2001 From: Felipe Pena Date: Fri, 5 Sep 2025 01:44:41 -0300 Subject: [PATCH 08/15] cgen: add `@[reused]` attribute to mark methods, reusing the receiver memory on return (needed for autofree) (fix #25221) (#25235) --- vlib/builtin/string_charptr_byteptr_helpers.v | 18 +++++++++--------- vlib/v/gen/c/autofree.v | 15 +++++++++++++++ .../gen/c/testdata/autofree_reused.c.must_have | 10 ++++++++++ vlib/v/gen/c/testdata/autofree_reused.out | 2 ++ vlib/v/gen/c/testdata/autofree_reused.vv | 10 ++++++++++ 5 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 vlib/v/gen/c/testdata/autofree_reused.c.must_have create mode 100644 vlib/v/gen/c/testdata/autofree_reused.out create mode 100644 vlib/v/gen/c/testdata/autofree_reused.vv diff --git a/vlib/builtin/string_charptr_byteptr_helpers.v b/vlib/builtin/string_charptr_byteptr_helpers.v index 8094db2a6c..a1a1226a8c 100644 --- a/vlib/builtin/string_charptr_byteptr_helpers.v +++ b/vlib/builtin/string_charptr_byteptr_helpers.v @@ -3,7 +3,7 @@ module builtin // Note: this file will be removed soon // byteptr.vbytes() - makes a V []u8 structure from a C style memory buffer. Note: the data is reused, NOT copied! -@[unsafe] +@[reused; unsafe] pub fn (data byteptr) vbytes(len int) []u8 { return unsafe { voidptr(data).vbytes(len) } } @@ -11,7 +11,7 @@ pub fn (data byteptr) vbytes(len int) []u8 { // vstring converts a C style string to a V string. Note: the string data is reused, NOT copied. // strings returned from this function will be normal V strings beside that (i.e. they would be // freed by V's -autofree mechanism, when they are no longer used). -@[unsafe] +@[reused; unsafe] pub fn (bp byteptr) vstring() string { return string{ str: bp @@ -21,7 +21,7 @@ pub fn (bp byteptr) vstring() string { // vstring_with_len converts a C style string to a V string. // Note: the string data is reused, NOT copied. -@[unsafe] +@[reused; unsafe] pub fn (bp byteptr) vstring_with_len(len int) string { return string{ str: bp @@ -32,7 +32,7 @@ pub fn (bp byteptr) vstring_with_len(len int) string { // vstring converts C char* to V string. // Note: the string data is reused, NOT copied. -@[unsafe] +@[reused; unsafe] pub fn (cp charptr) vstring() string { return string{ str: byteptr(cp) @@ -43,7 +43,7 @@ pub fn (cp charptr) vstring() string { // vstring_with_len converts C char* to V string. // Note: the string data is reused, NOT copied. -@[unsafe] +@[reused; unsafe] pub fn (cp charptr) vstring_with_len(len int) string { return string{ str: byteptr(cp) @@ -59,7 +59,7 @@ pub fn (cp charptr) vstring_with_len(len int) string { // This is suitable for readonly strings, C string literals etc, // that can be read by the V program, but that should not be // managed by it, for example `os.args` is implemented using it. -@[unsafe] +@[reused; unsafe] pub fn (bp byteptr) vstring_literal() string { return string{ str: bp @@ -70,7 +70,7 @@ pub fn (bp byteptr) vstring_literal() string { // vstring_with_len converts a C style string to a V string. // Note: the string data is reused, NOT copied. -@[unsafe] +@[reused; unsafe] pub fn (bp byteptr) vstring_literal_with_len(len int) string { return string{ str: bp @@ -82,7 +82,7 @@ pub fn (bp byteptr) vstring_literal_with_len(len int) string { // vstring_literal converts C char* to V string. // See also vstring_literal defined on byteptr for more details. // Note: the string data is reused, NOT copied. -@[unsafe] +@[reused; unsafe] pub fn (cp charptr) vstring_literal() string { return string{ str: byteptr(cp) @@ -94,7 +94,7 @@ pub fn (cp charptr) vstring_literal() string { // vstring_literal_with_len converts C char* to V string. // See also vstring_literal_with_len defined on byteptr. // Note: the string data is reused, NOT copied. -@[unsafe] +@[reused; unsafe] pub fn (cp charptr) vstring_literal_with_len(len int) string { return string{ str: byteptr(cp) diff --git a/vlib/v/gen/c/autofree.v b/vlib/v/gen/c/autofree.v index a4f88ae12a..21da98b50b 100644 --- a/vlib/v/gen/c/autofree.v +++ b/vlib/v/gen/c/autofree.v @@ -98,6 +98,21 @@ fn (mut g Gen) autofree_scope_vars2(scope &ast.Scope, start_pos int, end_pos int if obj.expr is ast.IfGuardExpr { continue } + if obj.expr is ast.UnsafeExpr && obj.expr.expr is ast.CallExpr + && (obj.expr.expr as ast.CallExpr).is_method { + if left_var := scope.objects[obj.expr.expr.left.str()] { + if func := g.table.find_method(g.table.final_sym(left_var.typ), + obj.expr.expr.name) + { + if func.attrs.contains('reused') && left_var is ast.Var + && left_var.expr is ast.CastExpr { + if left_var.expr.expr.is_literal() { + continue + } + } + } + } + } g.autofree_variable(obj) } else {} diff --git a/vlib/v/gen/c/testdata/autofree_reused.c.must_have b/vlib/v/gen/c/testdata/autofree_reused.c.must_have new file mode 100644 index 0000000000..aa0d7004e1 --- /dev/null +++ b/vlib/v/gen/c/testdata/autofree_reused.c.must_have @@ -0,0 +1,10 @@ +VV_LOC void main__main(void) { + byteptr b = ((byteptr)("a")); + Array_u8 s = byteptr_vbytes(b, 1); + string _t1 = Array_u8_str(s); println(_t1); string_free(&_t1); + ; + byteptr bb = ((byteptr)("a")); + string ss = byteptr_vstring(bb); + println(ss); +} + diff --git a/vlib/v/gen/c/testdata/autofree_reused.out b/vlib/v/gen/c/testdata/autofree_reused.out new file mode 100644 index 0000000000..30ec3724e2 --- /dev/null +++ b/vlib/v/gen/c/testdata/autofree_reused.out @@ -0,0 +1,2 @@ +[97] +a diff --git a/vlib/v/gen/c/testdata/autofree_reused.vv b/vlib/v/gen/c/testdata/autofree_reused.vv new file mode 100644 index 0000000000..a4593f05db --- /dev/null +++ b/vlib/v/gen/c/testdata/autofree_reused.vv @@ -0,0 +1,10 @@ +// vtest vflags: -autofree +fn main() { + b := byteptr(c'a') + s := unsafe { b.vbytes(1) } + println(s) + + bb := byteptr(c'a') + ss := unsafe { bb.vstring() } + println(ss) +} From cf61f4fa1b6e2d382952ee71687758780c03af9c Mon Sep 17 00:00:00 2001 From: kbkpbot Date: Fri, 5 Sep 2025 17:23:24 +0800 Subject: [PATCH 09/15] builtin: fix C prefix for proc_pidpath() (#25239) --- vlib/builtin/cfns.c.v | 2 +- vlib/os/os.c.v | 2 +- vlib/os/os_darwin.c.v | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/vlib/builtin/cfns.c.v b/vlib/builtin/cfns.c.v index c30a2945fc..5147a29c76 100644 --- a/vlib/builtin/cfns.c.v +++ b/vlib/builtin/cfns.c.v @@ -54,7 +54,7 @@ fn C.isdigit(c int) bool fn C.popen(c &char, t &char) voidptr // -pub fn proc_pidpath(int, voidptr, int) int +fn C.proc_pidpath(int, voidptr, int) int fn C.realpath(const_path &char, resolved_path &char) &char diff --git a/vlib/os/os.c.v b/vlib/os/os.c.v index a2bef3f227..8c37461146 100644 --- a/vlib/os/os.c.v +++ b/vlib/os/os.c.v @@ -715,7 +715,7 @@ pub fn executable() string { } $if macos { pid := C.getpid() - ret := proc_pidpath(pid, &result[0], max_path_len) + ret := C.proc_pidpath(pid, &result[0], max_path_len) if ret <= 0 { eprintln('os.executable() failed at calling proc_pidpath with pid: ${pid} . proc_pidpath returned ${ret} ') return executable_fallback() diff --git a/vlib/os/os_darwin.c.v b/vlib/os/os_darwin.c.v index 010044a82f..d161a43f73 100644 --- a/vlib/os/os_darwin.c.v +++ b/vlib/os/os_darwin.c.v @@ -3,6 +3,8 @@ // that can be found in the LICENSE file. module os +#include + pub const sys_write = 4 pub const sys_open = 5 pub const sys_close = 6 From 35510bc65936f9188283c12eb382dc18eb32b501 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Fri, 5 Sep 2025 12:39:02 +0300 Subject: [PATCH 10/15] builtin: add input_rune/0 and input_rune_iterator/0 + tests (#25238) --- .gitattributes | 1 + vlib/builtin/input_rune_iterator.v | 40 +++++++++++++++++++ vlib/builtin/input_rune_iterator_test.v | 51 +++++++++++++++++++++++++ vlib/v/tests/runes.txt | 1 + 4 files changed, 93 insertions(+) create mode 100644 vlib/builtin/input_rune_iterator.v create mode 100644 vlib/builtin/input_rune_iterator_test.v create mode 100644 vlib/v/tests/runes.txt diff --git a/.gitattributes b/.gitattributes index 47a6d90d27..ef7f2882b7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8,3 +8,4 @@ v.mod linguist-language=V .vdocignore linguist-language=ignore Dockerfile.* linguist-language=Dockerfile +vlib/v/tests/runes.txt binary diff --git a/vlib/builtin/input_rune_iterator.v b/vlib/builtin/input_rune_iterator.v new file mode 100644 index 0000000000..3091986879 --- /dev/null +++ b/vlib/builtin/input_rune_iterator.v @@ -0,0 +1,40 @@ +module builtin + +// input_rune returns a single rune from the standart input (an unicode codepoint). +// It expects, that the input is utf8 encoded. +// It will return `none` on EOF. +pub fn input_rune() ?rune { + x := input_character() + if x <= 0 { + return none + } + char_len := utf8_char_len(u8(x)) + if char_len == 1 { + return x + } + mut b := u8(x) + b = b << char_len + mut res := rune(b) + mut shift := 6 - char_len + for i := 1; i < char_len; i++ { + c := rune(input_character()) + res = rune(res) << shift + res |= c & 63 // 0x3f + shift = 6 + } + return res +} + +// InputRuneIterator is an iterator over the input runes. +pub struct InputRuneIterator {} + +// next returns the next rune from the input stream. +pub fn (mut self InputRuneIterator) next() ?rune { + return input_rune() +} + +// input_rune_iterator returns an iterator to allow for `for i, r in input_rune_iterator() {`. +// When the input stream is closed, the loop will break. +pub fn input_rune_iterator() InputRuneIterator { + return InputRuneIterator{} +} diff --git a/vlib/builtin/input_rune_iterator_test.v b/vlib/builtin/input_rune_iterator_test.v new file mode 100644 index 0000000000..f5667b0262 --- /dev/null +++ b/vlib/builtin/input_rune_iterator_test.v @@ -0,0 +1,51 @@ +// vtest build: !windows +import os +import time + +fn test_input_rune_iterator_with_unicode_input() { + mut p := os.new_process(@VEXE) + p.set_args(['-e', 'for i, r in input_rune_iterator() { println("> i: \${i:04} | r: `\${r}`") }']) + p.set_redirect_stdio() + p.run() + spawn fn [mut p] () { + time.sleep(10 * time.millisecond) + dump(p.pid) + p.stdin_write('Проба Abc 你好 🌍 123') + time.sleep(10 * time.millisecond) + p.stdin_write('\0x00') // 0 should break the input stream + time.sleep(10 * time.millisecond) + eprintln('>>> done') + }() + mut olines := []string{} + for p.is_alive() { + if oline := p.pipe_read(.stdout) { + olines << oline + } + time.sleep(1 * time.millisecond) + } + p.close() + p.wait() + assert p.code == 0 + eprintln('done') + solines := olines.join('\n').trim_space().replace('\r', '') + eprintln('solines.len: ${solines.len} | solines: ${solines}') + assert solines.len > 100 + assert solines == '> i: 0000 | r: `П` +> i: 0001 | r: `р` +> i: 0002 | r: `о` +> i: 0003 | r: `б` +> i: 0004 | r: `а` +> i: 0005 | r: ` ` +> i: 0006 | r: `A` +> i: 0007 | r: `b` +> i: 0008 | r: `c` +> i: 0009 | r: ` ` +> i: 0010 | r: `你` +> i: 0011 | r: `好` +> i: 0012 | r: ` ` +> i: 0013 | r: `🌍` +> i: 0014 | r: ` ` +> i: 0015 | r: `1` +> i: 0016 | r: `2` +> i: 0017 | r: `3`' +} diff --git a/vlib/v/tests/runes.txt b/vlib/v/tests/runes.txt new file mode 100644 index 0000000000..6ab946b5e6 --- /dev/null +++ b/vlib/v/tests/runes.txt @@ -0,0 +1 @@ +Проба Abc 你好 🌍 123 From 4dcf79e388ceeb0baede3757d1f0b8e7e502d2e7 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Fri, 5 Sep 2025 12:50:22 +0300 Subject: [PATCH 11/15] os,ci: use a conditional include, instead of a platform file, to include libproc.h on macos only --- vlib/os/os.c.v | 4 ++++ vlib/os/os_darwin.c.v | 2 -- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/vlib/os/os.c.v b/vlib/os/os.c.v index 8c37461146..dceb76f5be 100644 --- a/vlib/os/os.c.v +++ b/vlib/os/os.c.v @@ -5,6 +5,10 @@ import strings #include // #include #include +$if macos { + #include +} + $if freebsd || openbsd { #include } diff --git a/vlib/os/os_darwin.c.v b/vlib/os/os_darwin.c.v index d161a43f73..010044a82f 100644 --- a/vlib/os/os_darwin.c.v +++ b/vlib/os/os_darwin.c.v @@ -3,8 +3,6 @@ // that can be found in the LICENSE file. module os -#include - pub const sys_write = 4 pub const sys_open = 5 pub const sys_close = 6 From 1820a5584b9bc606d0b5d12f6a60dfc62e98fe96 Mon Sep 17 00:00:00 2001 From: Mike <45243121+tankf33der@users.noreply.github.com> Date: Fri, 5 Sep 2025 16:47:25 +0300 Subject: [PATCH 12/15] math.big: replace division with Knuth, improve performance (#25242) --- vlib/math/big/array_ops.v | 35 ++-- vlib/math/big/array_ops_test.v | 20 +-- vlib/math/big/division_array_ops.v | 217 +++++++++++------------- vlib/math/big/division_array_ops_test.v | 97 +---------- vlib/math/big/integer.v | 4 +- 5 files changed, 135 insertions(+), 238 deletions(-) diff --git a/vlib/math/big/array_ops.v b/vlib/math/big/array_ops.v index dc5f535d1a..105faa0bf6 100644 --- a/vlib/math/big/array_ops.v +++ b/vlib/math/big/array_ops.v @@ -181,10 +181,7 @@ fn divide_digit_array(operand_a []u64, operand_b []u64, mut quotient []u64, mut cmp_result := compare_digit_array(operand_a, operand_b) // a == b => q, r = 1, 0 if cmp_result == 0 { - quotient << 1 - for quotient.len > 1 { - quotient.delete_last() - } + quotient[0] = 1 remainder.clear() return } @@ -192,7 +189,9 @@ fn divide_digit_array(operand_a []u64, operand_b []u64, mut quotient []u64, mut // a < b => q, r = 0, a if cmp_result < 0 { quotient.clear() - remainder << operand_a + for i in 0 .. operand_a.len { + remainder[i] = operand_a[i] + } return } if operand_b.len == 1 { @@ -210,38 +209,36 @@ fn divide_array_by_digit(operand_a []u64, divisor u64, mut quotient []u64, mut r // 1 digit for both dividend and divisor dividend := operand_a[0] q := dividend / divisor - if q != 0 { - quotient << q - } + quotient[0] = q + rem := dividend % divisor - if rem != 0 { - remainder << rem - } + remainder[0] = rem + + shrink_tail_zeros(mut quotient) + shrink_tail_zeros(mut remainder) return } // Dividend has more digits mut rem := u64(0) mut quo := u64(0) - mut qtemp := []u64{len: quotient.cap} - divisor64 := u64(divisor) // Perform division step by step for index := operand_a.len - 1; index >= 0; index-- { hi := rem >> (64 - digit_bits) lo := rem << digit_bits | operand_a[index] - quo, rem = bits.div_64(hi, lo, divisor64) - qtemp[index] = quo & max_digit + quo, rem = bits.div_64(hi, lo, divisor) + quotient[index] = quo & max_digit } + // Remove leading zeros from quotient - shrink_tail_zeros(mut qtemp) - quotient << qtemp - remainder << rem + shrink_tail_zeros(mut quotient) + remainder[0] = rem shrink_tail_zeros(mut remainder) } @[inline] fn divide_array_by_array(operand_a []u64, operand_b []u64, mut quotient []u64, mut remainder []u64) { - binary_divide_array_by_array(operand_a, operand_b, mut quotient, mut remainder) + knuth_divide_array_by_array(operand_a, operand_b, mut quotient, mut remainder) } // Shifts the contents of the original array by the given amount of bits to the left. diff --git a/vlib/math/big/array_ops_test.v b/vlib/math/big/array_ops_test.v index 88441a6103..e6a0fbbc1a 100644 --- a/vlib/math/big/array_ops_test.v +++ b/vlib/math/big/array_ops_test.v @@ -136,8 +136,8 @@ fn test_compare_digit_array_02() { fn test_divide_digit_array_01() { a := [u64(14)] b := [u64(2)] - mut q := []u64{cap: 1} - mut r := []u64{cap: 1} + mut q := []u64{len: 1} + mut r := []u64{len: 1} divide_digit_array(a, b, mut q, mut r) assert q == [u64(7)] @@ -147,8 +147,8 @@ fn test_divide_digit_array_01() { fn test_divide_digit_array_02() { a := [u64(14)] b := [u64(15)] - mut q := []u64{cap: 1} - mut r := []u64{cap: 1} + mut q := []u64{len: 1} + mut r := []u64{len: 1} divide_digit_array(a, b, mut q, mut r) assert q == []u64{len: 0} @@ -158,8 +158,8 @@ fn test_divide_digit_array_02() { fn test_divide_digit_array_03() { a := [u64(0), 4] b := [u64(0), 1] - mut q := []u64{cap: a.len - b.len + 1} - mut r := []u64{cap: a.len} + mut q := []u64{len: a.len - b.len + 1} + mut r := []u64{len: a.len} divide_digit_array(a, b, mut q, mut r) assert q == [u64(4)] @@ -169,8 +169,8 @@ fn test_divide_digit_array_03() { fn test_divide_digit_array_04() { a := [u64(2), 4] b := [u64(0), 1] - mut q := []u64{cap: a.len - b.len + 1} - mut r := []u64{cap: a.len} + mut q := []u64{len: a.len - b.len + 1} + mut r := []u64{len: a.len} divide_digit_array(a, b, mut q, mut r) assert q == [u64(4)] @@ -180,8 +180,8 @@ fn test_divide_digit_array_04() { fn test_divide_digit_array_05() { a := [u64(3)] b := [u64(2)] - mut q := []u64{cap: a.len - b.len + 1} - mut r := []u64{cap: a.len} + mut q := []u64{len: a.len - b.len + 1} + mut r := []u64{len: a.len} divide_digit_array(a, b, mut q, mut r) assert q == [u64(1)] diff --git a/vlib/math/big/division_array_ops.v b/vlib/math/big/division_array_ops.v index 81802f9ed5..66912ca35b 100644 --- a/vlib/math/big/division_array_ops.v +++ b/vlib/math/big/division_array_ops.v @@ -2,57 +2,6 @@ module big import math.bits -// suppose operand_a bigger than operand_b and both not null. -// Both quotient and remaider are allocated but of length 0 -@[direct_array_access] -fn binary_divide_array_by_array(operand_a []u64, operand_b []u64, mut quotient []u64, mut remainder []u64) { - remainder << operand_a - - len_diff := operand_a.len - operand_b.len - $if debug { - assert len_diff >= 0 - } - - // we must do in place shift and operations. - mut divisor := []u64{cap: operand_b.len} - for _ in 0 .. len_diff { - divisor << u64(0) - } - divisor << operand_b - for _ in 0 .. len_diff + 1 { - quotient << u64(0) - } - - lead_zer_remainder := u32(bits.leading_zeros_64(remainder.last()) - (64 - digit_bits)) - lead_zer_divisor := u32(bits.leading_zeros_64(divisor.last()) - (64 - digit_bits)) - bit_offset := (u32(digit_bits) * u32(len_diff)) + (lead_zer_divisor - lead_zer_remainder) - - // align - if lead_zer_remainder < lead_zer_divisor { - left_shift_in_place(mut divisor, lead_zer_divisor - lead_zer_remainder) - } else if lead_zer_remainder > lead_zer_divisor { - left_shift_in_place(mut remainder, lead_zer_remainder - lead_zer_divisor) - } - - $if debug { - assert left_align_p(divisor[divisor.len - 1], remainder[remainder.len - 1]) - } - for bit_idx := int(bit_offset); bit_idx >= 0; bit_idx-- { - if greater_equal_from_end(remainder, divisor) { - bit_set(mut quotient, bit_idx) - subtract_align_last_byte_in_place(mut remainder, divisor) - } - right_shift_in_place(mut divisor, 1) - } - - // adjust - if lead_zer_remainder > lead_zer_divisor { - right_shift_in_place(mut remainder, lead_zer_remainder - lead_zer_divisor) - } - shrink_tail_zeros(mut remainder) - shrink_tail_zeros(mut quotient) -} - // help routines for cleaner code but inline for performance // quicker than BitField.set_bit @[direct_array_access; inline] @@ -65,80 +14,112 @@ fn bit_set(mut a []u64, n int) { a[byte_offset] |= mask } -// a.len is greater or equal to b.len -// returns true if a >= b (completed with zeroes) -@[direct_array_access; inline] -fn greater_equal_from_end(a []u64, b []u64) bool { - $if debug { - assert a.len >= b.len - } - offset := a.len - b.len - for index := a.len - 1; index >= offset; index-- { - if a[index] > b[index - offset] { - return true - } else if a[index] < b[index - offset] { - return false +@[direct_array_access] +fn knuth_divide_array_by_array(operand_a []u64, operand_b []u64, mut quotient []u64, mut remainder []u64) { + m := operand_a.len - operand_b.len + n := operand_b.len + mut u := []u64{len: operand_a.len + 1} + mut v := []u64{len: n} + leading_zeros := bits.leading_zeros_64(operand_b.last()) - (64 - digit_bits) + + if leading_zeros > 0 { + mut carry := u64(0) + amount := digit_bits - leading_zeros + for i in 0 .. operand_a.len { + temp := (operand_a[i] << leading_zeros) | carry + u[i] = temp & max_digit + carry = operand_a[i] >> amount + } + u[operand_a.len] = carry + carry = 0 + for i in 0 .. operand_b.len { + temp := (operand_b[i] << leading_zeros) | carry + v[i] = temp & max_digit + carry = operand_b[i] >> amount + } + } else { + for i in 0 .. operand_a.len { + u[i] = operand_a[i] + } + for i in 0 .. operand_b.len { + v[i] = operand_b[i] } } - return true -} -// a := a - b supposed a >= b -// attention the b operand is align with the a operand before the subtraction -@[direct_array_access; inline] -fn subtract_align_last_byte_in_place(mut a []u64, b []u64) { - mut carry := u64(0) - mut new_carry := u64(0) - offset := a.len - b.len - for index := a.len - b.len; index < a.len; index++ { - if a[index] < (b[index - offset] + carry) || (b[index - offset] == max_digit && carry > 0) { - new_carry = 1 - } else { - new_carry = 0 + if remainder.len >= (n + 1) { + remainder.trim(n + 1) + } else { + remainder = []u64{len: n + 1} + } + + v_n_1 := v[n - 1] + v_n_2 := v[n - 2] + for j := m; j >= 0; j-- { + u_j_n := u[j + n] + u_j_n_1 := u[j + n - 1] + u_j_n_2 := u[j + n - 2] + + mut qhat, mut rhat := bits.div_64(u_j_n >> (64 - digit_bits), (u_j_n << digit_bits) | u_j_n_1, + v_n_1) + mut x1, mut x2 := bits.mul_64(qhat, v_n_2) + x2 = x2 & max_digit + x1 = (x1 << (64 - digit_bits)) | (x2 >> digit_bits) + for greater_than(x1, x2, rhat, u_j_n_2) { + qhat-- + prev := rhat + rhat += v_n_1 + if rhat < prev { + break + } + x1, x2 = bits.mul_64(qhat, v_n_2) + x2 = x2 & max_digit + x1 = (x1 << (64 - digit_bits)) | (x2 >> digit_bits) } - a[index] -= (b[index - offset] + carry) - a[index] = a[index] & max_digit - carry = new_carry + mut carry := u64(0) + for i in 0 .. n { + hi, lo := bits.mul_add_64(v[i], qhat, carry) + remainder[i] = lo & max_digit + carry = (hi << (64 - digit_bits)) | (lo >> digit_bits) + } + remainder[n] = carry + + mut borrow := u64(0) + for i in 0 .. n + 1 { + result := u[j + i] - remainder[i] - borrow + u[j + i] = result & max_digit + borrow = (result >> digit_bits) & 1 + } + if borrow == 1 { + qhat-- + carry = u64(0) + for i in 0 .. n { + sum := u[j + i] + v[i] + carry + u[j + i] = sum & max_digit + carry = sum >> digit_bits + } + } + quotient[j] = qhat } - $if debug { - assert carry == 0 + + remainder.delete_last() + if leading_zeros > 0 { + mut carry := u64(0) + max_leading_digit := (u64(1) << leading_zeros) - 1 + for i := n - 1; i >= 0; i-- { + current_limb := u[i] + remainder[i] = (current_limb >> leading_zeros) | carry + carry = (current_limb & max_leading_digit) << (digit_bits - leading_zeros) + } + } else { + for i in 0 .. n { + remainder[i] = u[i] + } } + shrink_tail_zeros(mut quotient) + shrink_tail_zeros(mut remainder) } -// logical left shift -// there is no overflow. We know that the last bits are zero -// and that n <= `digit_bits` -@[direct_array_access; inline] -fn left_shift_in_place(mut a []u64, n u32) { - mut carry := u64(0) - mut prec_carry := u64(0) - mask := ((u64(1) << n) - 1) << (digit_bits - n) - for index in 0 .. a.len { - prec_carry = carry >> (digit_bits - n) - carry = a[index] & mask - a[index] <<= n - a[index] = a[index] & max_digit - a[index] |= prec_carry - } -} - -// logical right shift without control because these digits have already been -// shift left before -@[direct_array_access; inline] -fn right_shift_in_place(mut a []u64, n u32) { - mut carry := u64(0) - mut prec_carry := u64(0) - mask := (u64(1) << n) - 1 - for index := a.len - 1; index >= 0; index-- { - carry = a[index] & mask - a[index] >>= n - a[index] |= prec_carry << (digit_bits - n) - prec_carry = carry - } -} - -// for assert @[inline] -fn left_align_p(a u64, b u64) bool { - return bits.leading_zeros_64(a) == bits.leading_zeros_64(b) +fn greater_than(x1 u64, x2 u64, y1 u64, y2 u64) bool { + return x1 > y1 || (x1 == y1 && x2 > y2) } diff --git a/vlib/math/big/division_array_ops_test.v b/vlib/math/big/division_array_ops_test.v index 6f81c9427c..7eced22c85 100644 --- a/vlib/math/big/division_array_ops_test.v +++ b/vlib/math/big/division_array_ops_test.v @@ -2,92 +2,11 @@ module big import rand -fn test_left_shift_in_place() { - mut a := [u64(1), 1, 1, 1, 1] - left_shift_in_place(mut a, 1) - assert a == [u64(2), 2, 2, 2, 2] - left_shift_in_place(mut a, 7) - assert a == [u64(256), 256, 256, 256, 256] - mut b := [u64(0x08000000_00000001), 0x0c000000_00000000, 0x08000000_00000000, 0x07ffffff_ffffffff] - left_shift_in_place(mut b, 1) - assert b == [u64(2), 0x08000000_00000001, 1, 0x0fffffff_ffffffff] - mut c := [u64(0x00ffffff_ffffffff), 0x00f0f0f0_f0f0f0f0, 1, 0x03ffffff_ffffffff, 1] - left_shift_in_place(mut c, 2) - assert c == [u64(0x03ffffff_fffffffc), 0x03c3c3c3_c3c3c3c0, 4, 0x0fffffff_fffffffc, 4] -} - -fn test_right_shift_in_place() { - mut a := [u64(2), 2, 2, 2, 2] - right_shift_in_place(mut a, 1) - assert a == [u64(1), 1, 1, 1, 1] - a = [u64(256), 256, 256, 256, 256] - right_shift_in_place(mut a, 7) - assert a == [u64(2), 2, 2, 2, 2] - a = [u64(0), 0, 1] - right_shift_in_place(mut a, 1) - assert a == [u64(0), 0x08000000_00000000, 0] - mut b := [u64(3), 0x08000000_00000001, 1, 0x0fffffff_ffffffff] - right_shift_in_place(mut b, 1) - assert b == [u64(0x08000000_00000001), 0x0c000000_00000000, 0x08000000_00000000, - 0x07ffffff_ffffffff] - mut c := [u64(0x03ffffff), 0x03c3c3c3_c3c3c3c0, 7, 0xfffffffc, 4] - right_shift_in_place(mut c, 2) - assert c == [u64(0x00ffffff), 0x0cf0f0f0_f0f0f0f0, 1, 0x3fffffff, 1] -} - -fn test_subtract_align_last_byte_in_place() { - mut a := [u64(2), 2, 2, 2, 2] - mut b := [u64(1), 1, 2, 1, 1] - subtract_align_last_byte_in_place(mut a, b) - assert a == [u64(1), 1, 0, 1, 1] - - a = [u64(0), 0, 0, 0, 1] - b = [u64(0), 0, 1] - subtract_align_last_byte_in_place(mut a, b) - assert a == [u64(0), 0, 0, 0, 0] - - a = [u64(0), 0, 0, 0, 1, 13] - b = [u64(1), 0, 1] - mut c := []u64{len: a.len} - mut d := [u64(0), 0, 0] - d << b // to have same length - subtract_digit_array(a, d, mut c) - subtract_align_last_byte_in_place(mut a, b) - assert a == [u64(0), 0, 0, u64(-1) & max_digit, 0, 12] - assert c == a -} - -fn test_greater_equal_from_end() { - mut a := [u64(1), 2, 3, 4, 5, 6] - mut b := [u64(3), 4, 5, 6] - assert greater_equal_from_end(a, b) == true - - a = [u64(1), 2, 3, 4, 5, 6] - b = [u64(1), 2, 3, 4, 5, 6] - assert greater_equal_from_end(a, b) == true - - a = [u64(1), 2, 3, 4, 5, 6] - b = [u64(2), 2, 3, 4, 5, 6] - assert greater_equal_from_end(a, b) == false - - a = [u64(0), 0, 0, 4, 5, 6] - b = [u64(4), 5, 6] - assert greater_equal_from_end(a, b) == true - - a = [u64(0), 0, 0, 4, 5, 6] - b = [u64(4), 6, 6] - assert greater_equal_from_end(a, b) == false - - a = [u64(0), 0, 0, 4, 5, 5] - b = [u64(4), 5, 6] - assert greater_equal_from_end(a, b) == false -} - fn test_divide_digit_array_03() { a := [u64(0), 4] b := [u64(0), 1] - mut q := []u64{cap: a.len - b.len + 1} - mut r := []u64{cap: a.len} + mut q := []u64{len: a.len - b.len + 1} + mut r := []u64{len: a.len} divide_digit_array(a, b, mut q, mut r) assert q == [u64(4)] @@ -97,8 +16,8 @@ fn test_divide_digit_array_03() { fn test_divide_digit_array_04() { a := [u64(2), 4] b := [u64(0), 1] - mut q := []u64{cap: a.len - b.len + 1} - mut r := []u64{cap: a.len} + mut q := []u64{len: a.len - b.len + 1} + mut r := []u64{len: a.len} divide_digit_array(a, b, mut q, mut r) assert q == [u64(4)] @@ -108,8 +27,8 @@ fn test_divide_digit_array_04() { fn test_divide_digit_array_05() { a := [u64(2), 4, 5] b := [u64(0), 1] - mut q := []u64{cap: a.len - b.len + 1} - mut r := []u64{cap: a.len} + mut q := []u64{len: a.len - b.len + 1} + mut r := []u64{len: a.len} divide_digit_array(a, b, mut q, mut r) assert q == [u64(4), 5] @@ -119,8 +38,8 @@ fn test_divide_digit_array_05() { fn test_divide_digit_array_06() { a := [u64(2), 4, 5, 3] b := [u64(0), 0x8000] - mut q := []u64{cap: a.len - b.len + 1} - mut r := []u64{cap: a.len} + mut q := []u64{len: a.len - b.len + 1} + mut r := []u64{len: a.len} divide_digit_array(a, b, mut q, mut r) assert q == [u64(0xa000_00000000), 0x6000_00000000] diff --git a/vlib/math/big/integer.v b/vlib/math/big/integer.v index 565398c40d..85f96a058c 100644 --- a/vlib/math/big/integer.v +++ b/vlib/math/big/integer.v @@ -393,8 +393,8 @@ pub fn (multiplicand Integer) * (multiplier Integer) Integer { // // DO NOT use this method if the divisor has any chance of being 0. fn (dividend Integer) div_mod_internal(divisor Integer) (Integer, Integer) { - mut q := []u64{cap: int_max(1, dividend.digits.len - divisor.digits.len + 1)} - mut r := []u64{cap: dividend.digits.len} + mut q := []u64{len: int_max(1, dividend.digits.len - divisor.digits.len + 1)} + mut r := []u64{len: dividend.digits.len} mut q_signum := 0 mut r_signum := 0 From 08a739d79379ad9fbb1e21227ad0f994edea3cf2 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Fri, 5 Sep 2025 18:34:13 +0300 Subject: [PATCH 13/15] ci: cleanup .gitattributes, runes.txt and input_rune_iterator_test.v --- .gitattributes | 1 - vlib/builtin/input_rune_iterator_test.v | 1 + vlib/v/tests/runes.txt | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 vlib/v/tests/runes.txt diff --git a/.gitattributes b/.gitattributes index ef7f2882b7..47a6d90d27 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8,4 +8,3 @@ v.mod linguist-language=V .vdocignore linguist-language=ignore Dockerfile.* linguist-language=Dockerfile -vlib/v/tests/runes.txt binary diff --git a/vlib/builtin/input_rune_iterator_test.v b/vlib/builtin/input_rune_iterator_test.v index f5667b0262..e121ead2ba 100644 --- a/vlib/builtin/input_rune_iterator_test.v +++ b/vlib/builtin/input_rune_iterator_test.v @@ -1,4 +1,5 @@ // vtest build: !windows +// vtest retry: 2 import os import time diff --git a/vlib/v/tests/runes.txt b/vlib/v/tests/runes.txt deleted file mode 100644 index 6ab946b5e6..0000000000 --- a/vlib/v/tests/runes.txt +++ /dev/null @@ -1 +0,0 @@ -Проба Abc 你好 🌍 123 From 083d3dba3866b7fa6408172a648e4dc8e2d959e5 Mon Sep 17 00:00:00 2001 From: kfont Date: Fri, 5 Sep 2025 09:24:59 -0700 Subject: [PATCH 14/15] net.http: Use a full url when using a proxy, instead of only the path (#25228) --- vlib/net/http/http_proxy.v | 4 +++- vlib/net/http/http_proxy_test.v | 28 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/vlib/net/http/http_proxy.v b/vlib/net/http/http_proxy.v index f175d64414..7e420c62f5 100644 --- a/vlib/net/http/http_proxy.v +++ b/vlib/net/http/http_proxy.v @@ -87,7 +87,9 @@ fn (pr &HttpProxy) build_proxy_headers(host string) string { fn (pr &HttpProxy) http_do(host urllib.URL, method Method, path string, req &Request) !Response { host_name, port := net.split_address(host.hostname())! - s := req.build_request_headers(req.method, host_name, port, path) + port_part := if port == 80 || port == 0 { '' } else { ':${port}' } + + s := req.build_request_headers(req.method, host_name, port, '${host.scheme}://${host_name}${port_part}${path}') if host.scheme == 'https' { mut client := pr.ssl_dial('${host.host}:443')! diff --git a/vlib/net/http/http_proxy_test.v b/vlib/net/http/http_proxy_test.v index 01660f16af..af47bf3b82 100644 --- a/vlib/net/http/http_proxy_test.v +++ b/vlib/net/http/http_proxy_test.v @@ -1,6 +1,8 @@ module http import encoding.base64 +import net.urllib +import os const sample_proxy_url = 'https://localhost' const sample_auth_proxy_url = 'http://user:pass@localhost:8888' @@ -46,3 +48,29 @@ fn test_proxy_headers_authenticated() ? { assert headers == 'CONNECT 127.0.0.1:1337 HTTP/1.1\r\n' + 'Host: 127.0.0.1\r\n' + 'Proxy-Connection: Keep-Alive\r\nProxy-Authorization: Basic ${auth_token}\r\n\r\n' } + +fn test_http_proxy_do() { + env := os.environ() + mut env_proxy := '' + + for envvar in ['http_proxy', 'HTTP_PROXY', 'https_proxy', 'HTTPS_PROXY'] { + prox_val := env[envvar] or { continue } + if prox_val != '' { + env_proxy = env[envvar] + } + } + if env_proxy != '' { + println('Has usable proxy env vars') + proxy := new_http_proxy(env_proxy)! + mut header := new_header(key: .user_agent, value: 'vlib') + header.add_custom('X-Vlang-Test', 'proxied')! + res := proxy.http_do(urllib.parse('http://httpbin.org/headers')!, Method.get, + '/headers', &Request{ proxy: proxy, header: header })! + println(res.status_code) + println('he4aders ${res.header}') + assert res.status_code == 200 + // assert res.header.data['X-Vlang-Test'] == 'proxied' + } else { + println('Proxy env vars (HTTP_PROXY or HTTPS_PROXY) not set. Skipping test.') + } +} From 21c46f4ae5132ef01ded0b9eda90d25e08a01bca Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Fri, 5 Sep 2025 19:28:29 +0300 Subject: [PATCH 15/15] time: move the nanosecond comparison before the rest in the Time == Time implementation --- vlib/time/operator.v | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vlib/time/operator.v b/vlib/time/operator.v index 8242e3444e..66dcdb0bd4 100644 --- a/vlib/time/operator.v +++ b/vlib/time/operator.v @@ -3,8 +3,8 @@ module time // operator `==` returns true if provided time is equal to time @[inline] pub fn (t1 Time) == (t2 Time) bool { - return t1.is_local == t2.is_local && t1.local_unix() == t2.local_unix() - && t1.nanosecond == t2.nanosecond + return t1.nanosecond == t2.nanosecond && t1.is_local == t2.is_local + && t1.local_unix() == t2.local_unix() } // operator `<` returns true if provided time is less than time