From fd479a2bcdeee6083e38ffb0238b5a6417c582d6 Mon Sep 17 00:00:00 2001 From: "Eliyaan (Nopana)" <103932369+Eliyaan@users.noreply.github.com> Date: Thu, 11 Sep 2025 15:12:28 +0200 Subject: [PATCH 1/5] native: support more assign ops, reduce code duplication (#25283) --- vlib/v/gen/native/amd64.v | 262 +++++++++++++----------------- vlib/v/gen/native/tests/assign.vv | 82 ++++++++++ 2 files changed, 191 insertions(+), 153 deletions(-) diff --git a/vlib/v/gen/native/amd64.v b/vlib/v/gen/native/amd64.v index 43606579b3..0370e80ea8 100644 --- a/vlib/v/gen/native/amd64.v +++ b/vlib/v/gen/native/amd64.v @@ -1753,10 +1753,8 @@ fn (mut c Amd64) mov(r Register, val i32) { } } -fn (mut c Amd64) mul_reg(a Amd64Register, b Amd64Register) { - if a != .rax { - c.g.n_error('mul always operates on rax') - } +// rax times b +fn (mut c Amd64) mul_reg_rax(b Amd64Register) { match b { .rax { c.g.write8(0x48) @@ -1799,10 +1797,8 @@ fn (mut c Amd64) imul_reg(r Amd64Register) { } } -fn (mut c Amd64) div_reg(a Amd64Register, b Amd64Register) { - if a != .rax { - c.g.n_error('div always operates on rax') - } +// rax divided by b +fn (mut c Amd64) div_reg_rax(b Amd64Register) { match b { .rax { c.g.write8(0x48) @@ -1831,8 +1827,9 @@ fn (mut c Amd64) div_reg(a Amd64Register, b Amd64Register) { c.g.println('div ${b}') } -fn (mut c Amd64) mod_reg(a Amd64Register, b Amd64Register) { - c.div_reg(a, b) +// rax % b +fn (mut c Amd64) mod_reg_rax(b Amd64Register) { + c.div_reg_rax(b) c.mov_reg(Amd64Register.rdx, Amd64Register.rax) } @@ -2287,36 +2284,6 @@ fn (mut c Amd64) assign_var(var IdentVar, raw_type ast.Type) { // TODO: may have a problem if the literal is bigger than max_i64: needs u64 fn (mut c Amd64) assign_ident_int_lit(node ast.AssignStmt, i i32, int_lit ast.IntegerLiteral, left ast.Ident) { match node.op { - .plus_assign { - c.mov_var_to_reg(Amd64Register.rax, left) - c.mov64(Amd64Register.rdx, i64(int_lit.val.int())) - c.add_reg(Amd64Register.rax, Amd64Register.rdx) - c.mov_reg_to_var(left, Amd64Register.rax) - } - .minus_assign { - c.mov_var_to_reg(Amd64Register.rax, left) - c.mov64(Amd64Register.rdx, i64(int_lit.val.int())) - c.sub_reg(Amd64Register.rax, Amd64Register.rdx) - c.mov_reg_to_var(left, Amd64Register.rax) - } - .mult_assign { - c.mov_var_to_reg(Amd64Register.rax, left) - c.mov64(Amd64Register.rdx, i64(int_lit.val.int())) - c.mul_reg(.rax, .rdx) - c.mov_reg_to_var(left, Amd64Register.rax) - } - .div_assign { - c.mov_var_to_reg(Amd64Register.rax, left) - c.mov64(Amd64Register.rdx, i64(int_lit.val.int())) - c.div_reg(.rax, .rdx) - c.mov_reg_to_var(left, Amd64Register.rax) - } - .mod_assign { - c.mov_var_to_reg(Amd64Register.rax, left) - c.mov64(Amd64Register.rdx, i64(int_lit.val.int())) - c.mod_reg(.rax, .rdx) - c.mov_reg_to_var(left, Amd64Register.rax) - } .decl_assign { c.allocate_var(left.name, 8, i64(int_lit.val.int())) } @@ -2324,56 +2291,23 @@ fn (mut c Amd64) assign_ident_int_lit(node ast.AssignStmt, i i32, int_lit ast.In c.mov64(Amd64Register.rax, i64(int_lit.val.int())) c.mov_reg_to_var(left, Amd64Register.rax) } - .left_shift_assign { - c.mov_var_to_reg(Amd64Register.rax, left) - c.mov64(Amd64Register.rcx, i64(int_lit.val.int())) - c.shl_reg(.rax, .rcx) - c.mov_reg_to_var(left, Amd64Register.rax) - } - .right_shift_assign { - c.mov_var_to_reg(Amd64Register.rax, left) - c.mov64(Amd64Register.rcx, i64(int_lit.val.int())) - c.sar_reg(.rax, .rcx) - c.mov_reg_to_var(left, Amd64Register.rax) - } - .unsigned_right_shift_assign { - c.mov_var_to_reg(Amd64Register.rax, left) - c.mov64(Amd64Register.rcx, i64(int_lit.val.int())) - c.shr_reg(.rax, .rcx) - c.mov_reg_to_var(left, Amd64Register.rax) - } - .xor_assign { - c.mov_var_to_reg(Amd64Register.rax, left) - c.mov64(Amd64Register.rcx, i64(int_lit.val.int())) - c.bitxor_reg(.rax, .rcx) - c.mov_reg_to_var(left, Amd64Register.rax) - } - .or_assign { - c.mov_var_to_reg(Amd64Register.rax, left) - c.mov64(Amd64Register.rcx, i64(int_lit.val.int())) - c.bitor_reg(.rax, .rcx) - c.mov_reg_to_var(left, Amd64Register.rax) - } - .and_assign { - c.mov_var_to_reg(Amd64Register.rax, left) - c.mov64(Amd64Register.rcx, i64(int_lit.val.int())) - c.bitand_reg(.rax, .rcx) - c.mov_reg_to_var(left, Amd64Register.rax) - } .boolean_and_assign { c.mov_var_to_reg(Amd64Register.rax, left) - c.mov64(Amd64Register.rcx, i64(int_lit.val.int())) - c.bitand_reg(.rax, .rcx) + c.mov64(Amd64Register.rbx, i64(int_lit.val.int())) + c.bitand_reg(.rax, .rbx) c.mov_reg_to_var(left, Amd64Register.rax) } .boolean_or_assign { c.mov_var_to_reg(Amd64Register.rax, left) - c.mov64(Amd64Register.rcx, i64(int_lit.val.int())) - c.bitor_reg(.rax, .rcx) + c.mov64(Amd64Register.rbx, i64(int_lit.val.int())) + c.bitor_reg(.rax, .rbx) c.mov_reg_to_var(left, Amd64Register.rax) } else { - c.g.n_error('${@LOCATION} unexpected assignment op ${node.op}') + c.mov_var_to_reg(Amd64Register.rax, left) + c.mov64(Amd64Register.rbx, i64(int_lit.val.int())) + c.apply_op_int(.rax, .rbx, node.op) + c.mov_reg_to_var(left, Amd64Register.rax) } } } @@ -2591,19 +2525,26 @@ fn (mut c Amd64) assign_ident_right_expr(node ast.AssignStmt, i i32, right ast.E c.mov_ssereg_to_var(ident, .xmm1) } else if left_type.is_int() { - c.mov_var_to_reg(Amd64Register.rbx, ident) - - match node.op { - .plus_assign { c.add_reg(.rbx, .rax) } - .minus_assign { c.sub_reg(.rbx, .rax) } - .div_assign { c.div_reg(.rbx, .rax) } - .mult_assign { c.mul_reg(.rbx, .rax) } - else { c.g.n_error('${@LOCATION} unexpected assignment operator ${node.op} for i32') } - } - - c.mov_reg_to_var(ident, Amd64Register.rbx) + c.mov_reg(Amd64Register.rbx, Amd64Register.rax) + c.mov_var_to_reg(Amd64Register.rax, ident) + c.apply_op_int(.rax, .rbx, node.op) + c.mov_reg_to_var(ident, Amd64Register.rax) } else { - c.g.n_error('${@LOCATION} assignment arithmetic not implemented for type ${node.left_types[i]}') + match node.op { + .boolean_and_assign { + c.mov_var_to_reg(Amd64Register.rbx, ident) + c.bitand_reg(.rbx, .rax) + c.mov_reg_to_var(ident, Amd64Register.rbx) + } + .boolean_or_assign { + c.mov_var_to_reg(Amd64Register.rbx, ident) + c.bitor_reg(.rbx, .rax) + c.mov_reg_to_var(ident, Amd64Register.rbx) + } + else { + c.g.n_error('${@LOCATION} assignment arithmetic not implemented for type ${node.left_types[i]}') + } + } } } } @@ -2631,19 +2572,26 @@ fn (mut c Amd64) assign_ident_right_expr(node ast.AssignStmt, i i32, right ast.E c.mov_ssereg_to_var(ident, .xmm1) } else if left_type.is_int() { - c.mov_var_to_reg(Amd64Register.rbx, ident) - - match node.op { - .plus_assign { c.add_reg(.rbx, .rax) } - .minus_assign { c.sub_reg(.rbx, .rax) } - .div_assign { c.div_reg(.rbx, .rax) } - .mult_assign { c.mul_reg(.rbx, .rax) } - else { c.g.n_error('${@LOCATION} unexpected assignment operator ${node.op} for i32') } - } - - c.mov_reg_to_var(ident, Amd64Register.rbx) + c.mov_reg(Amd64Register.rbx, Amd64Register.rax) + c.mov_var_to_reg(Amd64Register.rax, ident) + c.apply_op_int(.rax, .rbx, node.op) + c.mov_reg_to_var(ident, Amd64Register.rax) } else { - c.g.n_error('${@LOCATION} assignment arithmetic not implemented for type ${node.left_types[i]}') + match node.op { + .boolean_and_assign { + c.mov_var_to_reg(Amd64Register.rbx, ident) + c.bitand_reg(.rbx, .rax) + c.mov_reg_to_var(ident, Amd64Register.rbx) + } + .boolean_or_assign { + c.mov_var_to_reg(Amd64Register.rbx, ident) + c.bitor_reg(.rbx, .rax) + c.mov_reg_to_var(ident, Amd64Register.rbx) + } + else { + c.g.n_error('${@LOCATION} assignment arithmetic not implemented for type ${node.left_types[i]}') + } + } } } } @@ -2680,6 +2628,59 @@ fn (mut c Amd64) assign_ident_right_expr(node ast.AssignStmt, i i32, right ast.E }*/ } +// /!\ for div, mul, mod the left value should always be .rax +fn (mut c Amd64) apply_op_int(left_value Amd64Register, right_value Amd64Register, op token.Kind) { + match op { + .plus_assign { + c.add_reg(left_value, right_value) + } + .minus_assign { + c.sub_reg(left_value, right_value) + } + .div_assign { + if left_value != .rax { + c.g.n_error('@{LOCATION} div always operates on rax') + } + c.mov(Amd64Register.rdx, i32(0)) // 64bits IDIV uses RDX:RAX + c.div_reg_rax(right_value) + } + .mult_assign { + if left_value != .rax { + c.g.n_error('@{LOCATION} mul always operates on rax') + } + c.mul_reg_rax(right_value) + } + .xor_assign { + c.bitxor_reg(left_value, right_value) + } + .mod_assign { + if left_value != .rax { + c.g.n_error('@{LOCATION} mod always operates on rax') + } + c.mov(Amd64Register.rdx, i32(0)) // 64bits IDIV uses RDX:RAX + c.mod_reg_rax(right_value) + } + .or_assign { + c.bitor_reg(left_value, right_value) + } + .and_assign { + c.bitand_reg(left_value, right_value) + } + .right_shift_assign { + c.shr_reg(left_value, right_value) + } + .left_shift_assign { + c.shl_reg(left_value, right_value) + } + .unsigned_right_shift_assign { + c.sar_reg(left_value, right_value) + } + else { + c.g.n_error('${@LOCATION} unexpected operator ${op} for int') + } + } +} + fn (mut c Amd64) gen_type_promotion(from ast.Type, to ast.Type, option Amd64RegisterOption) { if !to.is_pure_float() { return @@ -3077,52 +3078,6 @@ fn (mut c Amd64) assign_stmt(node ast.AssignStmt) { .decl_assign, .assign { c.mov_store(.rbx, .rcx, size) } - .plus_assign { - c.add_reg(.rax, .rcx) - c.mov_store(.rbx, .rax, size) - } - .minus_assign { - c.sub_reg(.rax, .rcx) - c.mov_store(.rbx, .rax, size) - } - .and_assign { - c.bitand_reg(.rax, .rcx) - c.mov_store(.rbx, .rax, size) - } - .mod_assign { - c.mov(Amd64Register.rdx, i32(0)) // 64bits IDIV uses RDX:RAX - c.mod_reg(.rax, .rcx) - c.mov_store(.rbx, .rax, size) - } - .mult_assign { - c.mul_reg(.rax, .rcx) - c.mov_store(.rbx, .rax, size) - } - .div_assign { - c.mov(Amd64Register.rdx, i32(0)) // 64bits IDIV uses RDX:RAX - c.div_reg(.rax, .rcx) - c.mov_store(.rbx, .rax, size) - } - .xor_assign { - c.bitxor_reg(.rax, .rcx) - c.mov_store(.rbx, .rax, size) - } - .or_assign { - c.bitor_reg(.rax, .rcx) - c.mov_store(.rbx, .rax, size) - } - .right_shift_assign { - c.shr_reg(.rax, .rcx) - c.mov_store(.rbx, .rax, size) - } - .left_shift_assign { - c.shl_reg(.rax, .rcx) - c.mov_store(.rbx, .rax, size) - } - .unsigned_right_shift_assign { - c.sar_reg(.rax, .rcx) - c.mov_store(.rbx, .rax, size) - } .boolean_and_assign { c.bitand_reg(.rax, .rcx) c.mov_store(.rbx, .rax, size) @@ -3132,7 +3087,8 @@ fn (mut c Amd64) assign_stmt(node ast.AssignStmt) { c.mov_store(.rbx, .rax, size) } else { - c.g.n_error('${@LOCATION} Unsupported assign instruction (${node.op})') + c.apply_op_int(.rax, .rcx, node.op) + c.mov_store(.rbx, .rax, size) } } } else if var_type.is_pure_float() { @@ -4289,7 +4245,7 @@ fn (mut c Amd64) convert_int_to_string(a Register, b Register) { c.mov(Amd64Register.rdx, 0) // upperhalf of the dividend c.mov(Amd64Register.rbx, 10) - c.div_reg(.rax, .rbx) // rax will be the result of the division + c.div_reg_rax(.rbx) // rax will be the result of the division c.add8(.rdx, i32(`0`)) // rdx is the remainder, add 48 to convert it into it's ascii representation c.mov_store(.rdi, .rdx, ._8) diff --git a/vlib/v/gen/native/tests/assign.vv b/vlib/v/gen/native/tests/assign.vv index 6c6e353d07..a45425ce8b 100644 --- a/vlib/v/gen/native/tests/assign.vv +++ b/vlib/v/gen/native/tests/assign.vv @@ -7,6 +7,88 @@ fn main() { test_alias(100, 9) test_plus_assign() test_minus_assign() + op_assigns_test() + if_expr_op_assigns_test() +} + +fn if_expr_op_assigns_test() { + mut b := 1 + one := 1 + two := 2 + three := 3 + four := 4 + five := 5 + ten := 10 + b += if false {0} else {one} + b -= if false {0} else {one} + assert b == 1 + b |= if false {0} else {two} + assert b == 3 + b &= if false {0} else {one} + assert b == 1 + b ^= if false {0} else {five} + assert b == 4 + b %= if false {0} else {three} + assert b == 1 + b *= if false {0} else {ten} + b /= if false {0} else {ten} + assert b == 1 + b <<= if false {0} else {four} + b >>>= if false {0} else {two} + b >>= if false {0} else {two} + assert b == 1 + + mut var := true + t := true + f := false + var &&= if false {f} else {t} + assert var == true + var &&= if false {t} else {f} + assert var == false + var ||= if false {f} else {t} + assert var == true + var ||= if false {t} else {f} + assert var == true +} + +fn op_assigns_test() { + mut b := 1 + one := 1 + two := 2 + three := 3 + four := 4 + five := 5 + ten := 10 + b += one + b -= one + assert b == 1 + b |= two + assert b == 3 + b &= one + assert b == 1 + b ^= five + assert b == 4 + b %= three + assert b == 1 + b *= ten + b /= ten + assert b == 1 + b <<= four + b >>>= two + b >>= two + assert b == 1 + + mut var := true + t := true + f := false + var &&= t + assert var == true + var &&= f + assert var == false + var ||= t + assert var == true + var ||= f + assert var == true } fn test_minus_assign() { From d73ebc1aca582d13af2d8708aab2088d7921de12 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Thu, 11 Sep 2025 13:18:56 +0300 Subject: [PATCH 2/5] v.ast: add a `mod` field to ast.FnTypeDecl too --- cmd/tools/vast/vast.v | 1 + vlib/v/ast/ast.v | 1 + vlib/v/parser/parser.v | 1 + 3 files changed, 3 insertions(+) diff --git a/cmd/tools/vast/vast.v b/cmd/tools/vast/vast.v index d38a95accb..67394cf1f9 100644 --- a/cmd/tools/vast/vast.v +++ b/cmd/tools/vast/vast.v @@ -897,6 +897,7 @@ fn (t Tree) fn_type_decl(node ast.FnTypeDecl) &Node { mut obj := create_object() obj.add_terse('ast_type', t.string_node('FnTypeDecl')) obj.add_terse('name', t.string_node(node.name)) + obj.add_terse('mod', t.string_node(node.mod)) obj.add_terse('is_pub', t.bool_node(node.is_pub)) obj.add_terse('typ', t.type_node(node.typ)) obj.add('pos', t.pos(node.pos)) diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index cdf72c96f7..b215349290 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -1522,6 +1522,7 @@ pub mut: pub struct FnTypeDecl { pub: name string + mod string is_pub bool typ Type pos token.Pos diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index c762ce7f10..24c7fda1fc 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -2782,6 +2782,7 @@ fn (mut p Parser) type_decl() ast.TypeDecl { p.attrs = [] return ast.FnTypeDecl{ name: fn_name + mod: p.mod is_pub: is_pub typ: fn_type pos: decl_pos From 04e79e7b2a96bed2592d7939258ff455be82db16 Mon Sep 17 00:00:00 2001 From: CreeperFace <165158232+dy-tea@users.noreply.github.com> Date: Thu, 11 Sep 2025 21:33:43 +0100 Subject: [PATCH 3/5] checker: prevent usage of imported module name prefix as identifier names, to avoid cgen collisions (#25280) --- vlib/v/checker/assign.v | 1 + vlib/v/checker/checker.v | 35 ++++++++++++++----- vlib/v/checker/fn.v | 1 + .../tests/clash_ident_module_name_prefix.out | 28 +++++++++++++++ .../tests/clash_ident_module_name_prefix.vv | 23 ++++++++++++ 5 files changed, 80 insertions(+), 8 deletions(-) create mode 100644 vlib/v/checker/tests/clash_ident_module_name_prefix.out create mode 100644 vlib/v/checker/tests/clash_ident_module_name_prefix.vv diff --git a/vlib/v/checker/assign.v b/vlib/v/checker/assign.v index e9fc1495ab..af0b4b2633 100644 --- a/vlib/v/checker/assign.v +++ b/vlib/v/checker/assign.v @@ -444,6 +444,7 @@ fn (mut c Checker) assign_stmt(mut node ast.AssignStmt) { if c.check_import_sym_conflict(left.name) { c.error('duplicate of an import symbol `${left.name}`', left.pos) } + c.check_module_name_conflict(left.name, left.pos) } if node.op == .assign && left_type.has_flag(.option) && right is ast.UnsafeExpr && right.expr.is_nil() { diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 34b2cb8fc2..79b6a2cdd9 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -137,14 +137,15 @@ mut: inside_assign bool // doing_line_info int // a quick single file run when called with v -line-info (contains line nr to inspect) // doing_line_path string // same, but stores the path being parsed - is_index_assign bool - comptime_call_pos int // needed for correctly checking use before decl for templates - goto_labels map[string]ast.GotoLabel // to check for unused goto labels - enum_data_type ast.Type - field_data_type ast.Type - variant_data_type ast.Type - fn_return_type ast.Type - orm_table_fields map[string][]ast.StructField // known table structs + is_index_assign bool + comptime_call_pos int // needed for correctly checking use before decl for templates + goto_labels map[string]ast.GotoLabel // to check for unused goto labels + enum_data_type ast.Type + field_data_type ast.Type + variant_data_type ast.Type + fn_return_type ast.Type + orm_table_fields map[string][]ast.StructField // known table structs + short_module_names []string // to check for function names colliding with module functions v_current_commit_hash string // same as old C.V_CURRENT_COMMIT_HASH assign_stmt_attr string // for `x := [1,2,3] @[freed]` @@ -331,6 +332,14 @@ pub fn (mut c Checker) change_current_file(file &ast.File) { c.vmod_file_content = '' c.mod = file.mod.name c.is_generated = file.is_generated + c.short_module_names = ['builtin'] + for import_sym in c.file.imports { + c.short_module_names << if import_sym.alias == '' { + import_sym.mod.all_after_last('.') + } else { + import_sym.alias + } + } } pub fn (mut c Checker) check_files(ast_files []&ast.File) { @@ -5790,6 +5799,16 @@ fn (c &Checker) check_import_sym_conflict(ident string) bool { return false } +fn (mut c Checker) check_module_name_conflict(ident string, pos token.Pos) { + if ident.contains('__') { + prefix := ident.all_before('__') + if prefix in c.short_module_names { + c.error('identifier cannot use prefix `${prefix}__` of imported module `${prefix}`', + pos) + } + } +} + // update_unresolved_fixed_sizes updates the unresolved type symbols for array fixed return type and alias type. pub fn (mut c Checker) update_unresolved_fixed_sizes() { for mut stmt in c.unresolved_fixed_sizes { diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 12b2d8a357..0c2b338fa4 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -104,6 +104,7 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) { if !node.is_method && node.mod == 'main' && node.short_name in c.table.builtin_pub_fns { c.error('cannot redefine builtin public function `${node.short_name}`', node.pos) } + c.check_module_name_conflict(node.short_name, node.pos) } if node.name == 'main.main' { c.main_fn_decl_node = *node diff --git a/vlib/v/checker/tests/clash_ident_module_name_prefix.out b/vlib/v/checker/tests/clash_ident_module_name_prefix.out new file mode 100644 index 0000000000..4a756ca522 --- /dev/null +++ b/vlib/v/checker/tests/clash_ident_module_name_prefix.out @@ -0,0 +1,28 @@ +vlib/v/checker/tests/clash_ident_module_name_prefix.vv:7:1: error: identifier cannot use prefix `builtin__` of imported module `builtin` + 5 | // os is not imported so the os__ prefix should not produce an error + 6 | + 7 | fn builtin__string_str() { + | ~~~~~~~~~~~~~~~~~~~~~~~~ + 8 | } + 9 | +vlib/v/checker/tests/clash_ident_module_name_prefix.vv:10:1: error: identifier cannot use prefix `time__` of imported module `time` + 8 | } + 9 | + 10 | fn time__utc() { + | ~~~~~~~~~~~~~~ + 11 | } + 12 | +vlib/v/checker/tests/clash_ident_module_name_prefix.vv:17:2: error: identifier cannot use prefix `builtin__` of imported module `builtin` + 15 | + 16 | fn main() { + 17 | builtin__string_str := 'Hello V!'.str() + | ~~~~~~~~~~~~~~~~~~~ + 18 | time__now := time.now() + 19 | os__log := 'Hello V!' +vlib/v/checker/tests/clash_ident_module_name_prefix.vv:18:2: error: identifier cannot use prefix `time__` of imported module `time` + 16 | fn main() { + 17 | builtin__string_str := 'Hello V!'.str() + 18 | time__now := time.now() + | ~~~~~~~~~ + 19 | os__log := 'Hello V!' + 20 | println(builtin__string_str) \ No newline at end of file diff --git a/vlib/v/checker/tests/clash_ident_module_name_prefix.vv b/vlib/v/checker/tests/clash_ident_module_name_prefix.vv new file mode 100644 index 0000000000..738dfde11f --- /dev/null +++ b/vlib/v/checker/tests/clash_ident_module_name_prefix.vv @@ -0,0 +1,23 @@ +import time + +// builtin__ prefix should always produce an error +// time is imported so the time__ prefix should always produce an error +// os is not imported so the os__ prefix should not produce an error + +fn builtin__string_str() { +} + +fn time__utc() { +} + +fn os__getwd() { +} + +fn main() { + builtin__string_str := 'Hello V!'.str() + time__now := time.now() + os__log := 'Hello V!' + println(builtin__string_str) + println(time__now) + println(os__log) +} From 56f20d1ff8ce8c1f68bb3cee1bb241e7e2f45bb3 Mon Sep 17 00:00:00 2001 From: blackshirt Date: Fri, 12 Sep 2025 03:36:11 +0700 Subject: [PATCH 4/5] x.crypto.ascon: small cleanups and optimization (#25284) --- vlib/x/crypto/ascon/ascon.v | 19 +++++++++++++------ vlib/x/crypto/ascon/digest.v | 33 ++++++++++++++++++++++----------- vlib/x/crypto/ascon/util.v | 16 +++++++++++++--- 3 files changed, 48 insertions(+), 20 deletions(-) diff --git a/vlib/x/crypto/ascon/ascon.v b/vlib/x/crypto/ascon/ascon.v index 8bb1b1977a..1ebd5bab84 100644 --- a/vlib/x/crypto/ascon/ascon.v +++ b/vlib/x/crypto/ascon/ascon.v @@ -41,6 +41,9 @@ fn ascon_pnr(mut s State, nr int) { if nr < 1 || nr > 16 { panic('Invalid round number') } + // Allocate temporary vars to reduce allocation within loop + mut x0 := u64(0) + mut y0 := u64(0) // Ascon permutation routine for i := max_nr_perm - nr; i < max_nr_perm; i++ { // 3.2 Constant-Addition Layer step @@ -56,18 +59,22 @@ fn ascon_pnr(mut s State, nr int) { s.e0 ^= s.e4 s.e4 ^= s.e3 s.e2 ^= s.e1 - + // Set temp vars to values + x0 = s.e0 + y0 = s.e4 ^ (~s.e0 & s.e1) + /* t0 := s.e4 ^ (~s.e0 & s.e1) t1 := s.e0 ^ (~s.e1 & s.e2) t2 := s.e1 ^ (~s.e2 & s.e3) t3 := s.e2 ^ (~s.e3 & s.e4) t4 := s.e3 ^ (~s.e4 & s.e0) + */ - s.e0 = t1 - s.e1 = t2 - s.e2 = t3 - s.e3 = t4 - s.e4 = t0 + s.e0 = s.e0 ^ (~s.e1 & s.e2) // t1 + s.e1 = s.e1 ^ (~s.e2 & s.e3) // t2 + s.e2 = s.e2 ^ (~s.e3 & s.e4) // t3 + s.e3 = s.e3 ^ (~s.e4 & x0) // t4, change s.e0 to x0 + s.e4 = y0 s.e1 ^= s.e0 s.e0 ^= s.e4 diff --git a/vlib/x/crypto/ascon/digest.v b/vlib/x/crypto/ascon/digest.v index 89a7dac7e1..07ee9ad303 100644 --- a/vlib/x/crypto/ascon/digest.v +++ b/vlib/x/crypto/ascon/digest.v @@ -135,20 +135,32 @@ fn (mut d Digest) squeeze(mut dst []u8) int { } @[direct_array_access; inline] -fn ascon_generic_hash(mut s State, msg_ []u8, size int) []u8 { +fn ascon_generic_hash(mut s State, msg []u8, size int) []u8 { // Assumed state was correctly initialized // Absorbing the message - mut msg := msg_.clone() - for msg.len >= block_size { - s.e0 ^= binary.little_endian_u64(msg[0..block_size]) - unsafe { - msg = msg[block_size..] + mut pos := 0 + // Check if msg has non-null length, if yes, absorb it. + // Otherwise, just pad it + if _likely_(msg.len > 0) { + mut msg_len := msg.len + for msg_len >= block_size { + s.e0 ^= binary.little_endian_u64(msg[pos..pos + block_size]) + pos += block_size + msg_len -= block_size + ascon_pnr(mut s, ascon_prnd_12) } - ascon_pnr(mut s, ascon_prnd_12) + // Absorb the last partial message block + last_block := unsafe { msg[pos..] } + s.e0 ^= u64(0x01) << (8 * last_block.len) // pad(last_block.len) + if last_block.len > 0 { + s.e0 ^= load_bytes(last_block, last_block.len) + } + } else { + // Otherwise, just pad it + s.e0 ^= u64(0x01) } - // Absorb the last partial message block - s.e0 ^= load_bytes(msg, msg.len) - s.e0 ^= pad(msg.len) + // reset pos + pos = 0 // Squeezing phase // @@ -156,7 +168,6 @@ fn ascon_generic_hash(mut s State, msg_ []u8, size int) []u8 { // permutation 𝐴𝑠𝑐𝑜𝑛-𝑝[12] to the state: ascon_pnr(mut s, ascon_prnd_12) mut out := []u8{len: size} - mut pos := 0 mut clen := out.len for clen >= block_size { binary.little_endian_put_u64(mut out[pos..pos + 8], s.e0) diff --git a/vlib/x/crypto/ascon/util.v b/vlib/x/crypto/ascon/util.v index 345683ea66..97e70d5e98 100644 --- a/vlib/x/crypto/ascon/util.v +++ b/vlib/x/crypto/ascon/util.v @@ -83,13 +83,23 @@ fn set_byte(b u8, i int) u64 { fn load_bytes(bytes []u8, n int) u64 { mut x := u64(0) for i := 0; i < n; i++ { - x |= set_byte(bytes[i], i) + // This is the same way to store bytes in little-endian way + // x |= u64(bytes[0]) << 8*0 // LSB at lowest index + // x |= u64(bytes[1]) << 8*1 + // x |= u64(bytes[2]) << 8*2 + // x |= u64(bytes[3]) << 8*3 + // ...etc + // x |= u64(bytes[7]) << 8*7 // MSB at highest index + x |= u64(bytes[i]) << (8 * i) } - return u64le(x) + // No need to cast with u64le, its alread le + return x } +@[direct_array_access] fn store_bytes(mut out []u8, x u64, n int) { for i := 0; i < n; i++ { - out[i] = get_byte(x, i) + // use underlying get_byte directly + out[i] = u8(x >> (8 * i)) } } From a8d200ac0ecdb264f78c4a6626ca4533cad5dddd Mon Sep 17 00:00:00 2001 From: Larsimusrex Date: Fri, 12 Sep 2025 09:57:15 +0200 Subject: [PATCH 5/5] decoder2: improve enum decoding; fix handling of required fields at the end of a json string (#25289) --- vlib/x/json2/decoder2/attributes_test.v | 10 + vlib/x/json2/decoder2/decode.v | 272 ++++++++++-------- .../x/json2/decoder2/tests/decode_enum_test.v | 95 ++++++ .../tests/decode_escaped_string_test.v | 2 +- 4 files changed, 258 insertions(+), 121 deletions(-) create mode 100644 vlib/x/json2/decoder2/tests/decode_enum_test.v diff --git a/vlib/x/json2/decoder2/attributes_test.v b/vlib/x/json2/decoder2/attributes_test.v index 8458c63759..8fe9d9ce0a 100644 --- a/vlib/x/json2/decoder2/attributes_test.v +++ b/vlib/x/json2/decoder2/attributes_test.v @@ -38,6 +38,16 @@ struct StruWithRequiredAttribute { b int } +struct Foo { + a int @[required] +} + +fn test_last_field_requiered() { + assert json.decode[Foo]('{"a":0}')! == Foo{ + a: 0 + } +} + fn test_skip_and_rename_attributes() { assert json.decode[StruWithJsonAttribute]('{"name": "hola1", "a": 2, "b": 3}')! == StruWithJsonAttribute{ a: 2 diff --git a/vlib/x/json2/decoder2/decode.v b/vlib/x/json2/decoder2/decode.v index d0a5d1b752..f5e61a50d7 100644 --- a/vlib/x/json2/decoder2/decode.v +++ b/vlib/x/json2/decoder2/decode.v @@ -38,7 +38,7 @@ struct StructFieldInfo { is_required bool is_raw bool mut: - decoded_with_value_info_node &Node[ValueInfo] = unsafe { nil } + is_decoded bool } // Decoder represents a JSON decoder. @@ -368,105 +368,7 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! { } } $if T.unaliased_typ is string { - string_info := decoder.current_node.value - - if string_info.value_kind == .string { - mut string_buffer := []u8{cap: string_info.length} // might be too long but most json strings don't contain many escape characters anyways - - mut buffer_index := 1 - mut string_index := 1 - - for string_index < string_info.length - 1 { - current_byte := decoder.json[string_info.position + string_index] - - if current_byte == `\\` { - // push all characters up to this point - unsafe { - string_buffer.push_many(decoder.json.str + string_info.position + - buffer_index, string_index - buffer_index) - } - - string_index++ - - escaped_char := decoder.json[string_info.position + string_index] - - string_index++ - - match escaped_char { - `/`, `"`, `\\` { - string_buffer << escaped_char - } - `b` { - string_buffer << `\b` - } - `f` { - string_buffer << `\f` - } - `n` { - string_buffer << `\n` - } - `r` { - string_buffer << `\r` - } - `t` { - string_buffer << `\t` - } - `u` { - unicode_point := rune(strconv.parse_uint(decoder.json[ - string_info.position + string_index..string_info.position + - string_index + 4], 16, 32)!) - - string_index += 4 - - if unicode_point < 0xD800 { // normal utf-8 - string_buffer << unicode_point.bytes() - } else if unicode_point >= 0xDC00 { // trail surrogate -> invalid - decoder.decode_error('Got trail surrogate: ${u32(unicode_point):04X} before head surrogate.')! - } else { // head surrogate -> treat as utf-16 - if string_index > string_info.length - 6 { - decoder.decode_error('Expected a trail surrogate after a head surrogate, but got no valid escape sequence.')! - } - if decoder.json[string_info.position + string_index.. - string_info.position + string_index + 2] != '\\u' { - decoder.decode_error('Expected a trail surrogate after a head surrogate, but got no valid escape sequence.')! - } - - string_index += 2 - - unicode_point2 := rune(strconv.parse_uint(decoder.json[ - string_info.position + string_index..string_info.position + - string_index + 4], 16, 32)!) - - string_index += 4 - - if unicode_point2 < 0xDC00 { - decoder.decode_error('Expected a trail surrogate after a head surrogate, but got ${u32(unicode_point):04X}.')! - } - - final_unicode_point := (unicode_point2 & 0x3FF) + - ((unicode_point & 0x3FF) << 10) + 0x10000 - string_buffer << final_unicode_point.bytes() - } - } - else {} // has already been checked - } - - buffer_index = string_index - } else { - string_index++ - } - } - - // push the rest - unsafe { - string_buffer.push_many(decoder.json.str + string_info.position + buffer_index, - string_index - buffer_index) - } - - val = string_buffer.bytestr() - } else { - return decoder.decode_error('Expected string, but got ${string_info.value_kind}') - } + decoder.decode_string(mut val)! } $else $if T.unaliased_typ is $sumtype { decoder.decode_sumtype(mut val)! return @@ -509,7 +411,7 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! { for attr in field.attrs { if attr.starts_with('json:') { if attr.len <= 6 { - return decoder.decode_error('`json` attribute must have an argument') + decoder.decode_error('`json` attribute must have an argument')! } json_name_str = unsafe { attr.str + 6 } json_name_len = attr.len - 6 @@ -615,9 +517,9 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! { if current_field_info.value.is_skip { if current_field_info.value.is_required == false { - return decoder.decode_error('This should not happen. Please, file a bug. `skip` field should not be processed here without a `required` attribute') + decoder.decode_error('This should not happen. Please, file a bug. `skip` field should not be processed here without a `required` attribute')! } - current_field_info.value.decoded_with_value_info_node = decoder.current_node + current_field_info.value.is_decoded = true if decoder.current_node != unsafe { nil } { decoder.current_node = decoder.current_node.next } @@ -627,7 +529,7 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! { if current_field_info.value.is_raw { $if field.unaliased_typ is $enum { // workaround to avoid the error: enums can only be assigned `int` values - return decoder.decode_error('`raw` attribute cannot be used with enum fields') + decoder.decode_error('`raw` attribute cannot be used with enum fields')! } $else $if field.typ is ?string { position := decoder.current_node.value.position end := position + decoder.current_node.value.length @@ -659,7 +561,7 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! { decoder.current_node = decoder.current_node.next } } $else { - return decoder.decode_error('`raw` attribute can only be used with string fields') + decoder.decode_error('`raw` attribute can only be used with string fields')! } } else { $if field.typ is $option { @@ -682,7 +584,7 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! { decoder.decode_value(mut val.$(field.name))! } } - current_field_info.value.decoded_with_value_info_node = decoder.current_node + current_field_info.value.is_decoded = true break } } @@ -705,15 +607,15 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! { current_field_info = current_field_info.next continue } - if current_field_info.value.decoded_with_value_info_node == unsafe { nil } { - return decoder.decode_error('missing required field `${unsafe { + if !current_field_info.value.is_decoded { + decoder.decode_error('missing required field `${unsafe { tos(current_field_info.value.field_name_str, current_field_info.value.field_name_len) - }}`') + }}`')! } current_field_info = current_field_info.next } } else { - return decoder.decode_error('Expected object, but got ${struct_info.value_kind}') + decoder.decode_error('Expected object, but got ${struct_info.value_kind}')! } unsafe { struct_fields_info.free() @@ -723,14 +625,14 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! { value_info := decoder.current_node.value if value_info.value_kind != .boolean { - return decoder.decode_error('Expected boolean, but got ${value_info.value_kind}') + decoder.decode_error('Expected boolean, but got ${value_info.value_kind}')! } unsafe { val = vmemcmp(decoder.json.str + value_info.position, true_in_string.str, true_in_string.len) == 0 } - } $else $if T.unaliased_typ is $float || T.unaliased_typ is $int || T.unaliased_typ is $enum { + } $else $if T.unaliased_typ is $float || T.unaliased_typ is $int { value_info := decoder.current_node.value if value_info.value_kind == .number { @@ -742,10 +644,12 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! { unsafe { decoder.decode_number(&val)! } } else { - return decoder.decode_error('Expected number, but got ${value_info.value_kind}') + decoder.decode_error('Expected number, but got ${value_info.value_kind}')! } + } $else $if T.unaliased_typ is $enum { + decoder.decode_enum(mut val)! } $else { - return decoder.decode_error('cannot decode value with ${typeof(val).name} type') + decoder.decode_error('cannot decode value with ${typeof(val).name} type')! } if decoder.current_node != unsafe { nil } { @@ -753,6 +657,108 @@ fn (mut decoder Decoder) decode_value[T](mut val T) ! { } } +fn (mut decoder Decoder) decode_string[T](mut val T) ! { + string_info := decoder.current_node.value + + if string_info.value_kind == .string { + mut string_buffer := []u8{cap: string_info.length} // might be too long but most json strings don't contain many escape characters anyways + + mut buffer_index := 1 + mut string_index := 1 + + for string_index < string_info.length - 1 { + current_byte := decoder.json[string_info.position + string_index] + + if current_byte == `\\` { + // push all characters up to this point + unsafe { + string_buffer.push_many(decoder.json.str + string_info.position + buffer_index, + string_index - buffer_index) + } + + string_index++ + + escaped_char := decoder.json[string_info.position + string_index] + + string_index++ + + match escaped_char { + `/`, `"`, `\\` { + string_buffer << escaped_char + } + `b` { + string_buffer << `\b` + } + `f` { + string_buffer << `\f` + } + `n` { + string_buffer << `\n` + } + `r` { + string_buffer << `\r` + } + `t` { + string_buffer << `\t` + } + `u` { + unicode_point := rune(strconv.parse_uint(decoder.json[ + string_info.position + string_index..string_info.position + + string_index + 4], 16, 32)!) + + string_index += 4 + + if unicode_point < 0xD800 || unicode_point > 0xDFFF { // normal utf-8 + string_buffer << unicode_point.bytes() + } else if unicode_point >= 0xDC00 { // trail surrogate -> invalid + decoder.decode_error('Got trail surrogate: ${u32(unicode_point):04X} before head surrogate.')! + } else { // head surrogate -> treat as utf-16 + if string_index > string_info.length - 6 { + decoder.decode_error('Expected a trail surrogate after a head surrogate, but got no valid escape sequence.')! + } + if decoder.json[string_info.position + string_index.. + string_info.position + string_index + 2] != '\\u' { + decoder.decode_error('Expected a trail surrogate after a head surrogate, but got no valid escape sequence.')! + } + + string_index += 2 + + unicode_point2 := rune(strconv.parse_uint(decoder.json[ + string_info.position + string_index..string_info.position + + string_index + 4], 16, 32)!) + + string_index += 4 + + if unicode_point2 < 0xDC00 { + decoder.decode_error('Expected a trail surrogate after a head surrogate, but got ${u32(unicode_point):04X}.')! + } + + final_unicode_point := (unicode_point2 & 0x3FF) + + ((unicode_point & 0x3FF) << 10) + 0x10000 + string_buffer << final_unicode_point.bytes() + } + } + else {} // has already been checked + } + + buffer_index = string_index + } else { + string_index++ + } + } + + // push the rest + unsafe { + string_buffer.push_many(decoder.json.str + string_info.position + buffer_index, + string_index - buffer_index) + } + + val = string_buffer.bytestr() + } else { + decoder.decode_error('Expected string, but got ${string_info.value_kind}')! + } +} + fn (mut decoder Decoder) decode_array[T](mut val []T) ! { array_info := decoder.current_node.value @@ -775,7 +781,7 @@ fn (mut decoder Decoder) decode_array[T](mut val []T) ! { val << array_element } } else { - return decoder.decode_error('Expected array, but got ${array_info.value_kind}') + decoder.decode_error('Expected array, but got ${array_info.value_kind}')! } } @@ -819,7 +825,7 @@ fn (mut decoder Decoder) decode_map[K, V](mut val map[K]V) ! { decoder.decode_value(mut val[key])! } } else { - return decoder.decode_error('Expected object, but got ${map_info.value_kind}') + decoder.decode_error('Expected object, but got ${map_info.value_kind}')! } } @@ -889,6 +895,36 @@ fn get_number_digits[T](num T) int { } } +fn (mut decoder Decoder) decode_enum[T](mut val T) ! { + enum_info := decoder.current_node.value + + if enum_info.value_kind == .number { + mut result := 0 + unsafe { decoder.decode_number(&result)! } + + $for value in T.values { + if int(value.value) == result { + val = value.value + return + } + } + decoder.decode_error('Number value: `${result}` does not match any field in enum: ${typeof(val).name}')! + } else if enum_info.value_kind == .string { + mut result := '' + unsafe { decoder.decode_value(mut result)! } + + $for value in T.values { + if value.name == result { + val = value.value + return + } + } + decoder.decode_error('String value: `${result}` does not match any field in enum: ${typeof(val).name}')! + } + + decoder.decode_error('Expected number or string value for enum, got: ${enum_info.value_kind}')! +} + // use pointer instead of mut so enum cast works @[unsafe] fn (mut decoder Decoder) decode_number[T](val &T) ! { @@ -904,10 +940,6 @@ fn (mut decoder Decoder) decode_number[T](val &T) ! { $if T.unaliased_typ is $float { *val = T(strconv.atof_quick(decoder.json[number_info.position..number_info.position + number_info.length])) - } $else $if T.unaliased_typ is $enum { - mut result := 0 - decoder.decode_number(&result)! - *val = T(result) } $else { // this part is a minefield mut is_negative := false mut index := 0 diff --git a/vlib/x/json2/decoder2/tests/decode_enum_test.v b/vlib/x/json2/decoder2/tests/decode_enum_test.v new file mode 100644 index 0000000000..9473ce1a7e --- /dev/null +++ b/vlib/x/json2/decoder2/tests/decode_enum_test.v @@ -0,0 +1,95 @@ +import x.json2.decoder2 as json + +enum Bar { + a + b + c = 10 +} + +type BarAlias = Bar + +fn test_number_decode() { + assert json.decode[Bar]('0')! == Bar.a + assert json.decode[Bar]('1')! == Bar.b + assert json.decode[Bar]('10')! == Bar.c + + assert json.decode[BarAlias]('0')! == Bar.a + assert json.decode[BarAlias]('1')! == Bar.b + assert json.decode[BarAlias]('10')! == Bar.c +} + +fn test_number_decode_fails() { + if _ := json.decode[Bar]('2') { + assert false + } else { + if err is json.JsonDecodeError { + assert err.line == 1 + assert err.character == 1 + assert err.message == 'Data: Number value: `2` does not match any field in enum: &Bar' + } + } + + if _ := json.decode[BarAlias]('2') { + assert false + } else { + if err is json.JsonDecodeError { + assert err.line == 1 + assert err.character == 1 + assert err.message == 'Data: Number value: `2` does not match any field in enum: &BarAlias' + } + } +} + +fn test_string_decode() { + assert json.decode[Bar]('"a"')! == Bar.a + assert json.decode[Bar]('"b"')! == Bar.b + assert json.decode[Bar]('"c"')! == Bar.c + + assert json.decode[BarAlias]('"a"')! == Bar.a + assert json.decode[BarAlias]('"b"')! == Bar.b + assert json.decode[BarAlias]('"c"')! == Bar.c +} + +fn test_string_decode_fails() { + if _ := json.decode[Bar]('"d"') { + assert false + } else { + if err is json.JsonDecodeError { + assert err.line == 1 + assert err.character == 1 + assert err.message == 'Data: String value: `d` does not match any field in enum: &Bar' + } + } + + if _ := json.decode[BarAlias]('"d"') { + assert false + } else { + if err is json.JsonDecodeError { + assert err.line == 1 + assert err.character == 1 + assert err.message == 'Data: String value: `d` does not match any field in enum: &BarAlias' + } + } +} + +fn test_invalid_decode_fails() { + if _ := json.decode[Bar]('true') { + assert false + } else { + if err is json.JsonDecodeError { + assert err.line == 1 + assert err.character == 1 + assert err.message == 'Data: Expected number or string value for enum, got: boolean' + } + } + + if _ := json.decode[BarAlias]('true') { + assert false + } else { + if err is json.JsonDecodeError { + assert err.line == 1 + assert err.character == 1 + assert err.message == 'Data: Expected number or string value for enum, got: boolean' + } + } +} diff --git a/vlib/x/json2/decoder2/tests/decode_escaped_string_test.v b/vlib/x/json2/decoder2/tests/decode_escaped_string_test.v index d3e437895a..59a65a8e94 100644 --- a/vlib/x/json2/decoder2/tests/decode_escaped_string_test.v +++ b/vlib/x/json2/decoder2/tests/decode_escaped_string_test.v @@ -3,7 +3,7 @@ import x.json2.decoder2 fn test_decode_escaped_string() { escaped_strings := ['test', 'test\\sd', 'test\nsd', '\ntest', 'test\\"', 'test\\', 'test\u1234ps', - 'test\u1234', '\u1234\\\t"', ''] + 'test\u1234', '\u1234\\\t"', '', '\uff0f', 'test \uff0f test', '😀', 'text 😀 text'] json_string := json2.encode[[]string](escaped_strings) decoded_strings := decoder2.decode[[]string](json_string)!