From 1b7486a19a5dede45e2e4a1121cb41bc5054af6c Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Fri, 24 Apr 2026 08:06:28 +0300 Subject: [PATCH] checker: relax the array int index rule, copy Go's behavior --- vlib/builtin/array.v | 66 +++++++++++++++ vlib/builtin/builtin.c.v | 22 +++++ vlib/builtin/js/map_test.js.v | 4 +- vlib/builtin/string.v | 41 +++++++++ vlib/v/checker/checker.v | 7 +- .../index_expr_implicit_int_downcast_err.out | 24 ++---- .../index_expr_implicit_int_downcast_err.vv | 8 -- vlib/v/gen/c/index.v | 83 +++++++++++++++++-- vlib/v/markused/walker.v | 19 +++++ 9 files changed, 235 insertions(+), 39 deletions(-) diff --git a/vlib/builtin/array.v b/vlib/builtin/array.v index e435415f5..8de7cf82c 100644 --- a/vlib/builtin/array.v +++ b/vlib/builtin/array.v @@ -731,6 +731,31 @@ fn (a array) get(i int) voidptr { } } +@[markused] +fn (a array) get_i64(i i64) voidptr { + $if !no_bounds_checking { + if i < 0 || i >= i64(a.len) { + panic_n2('array.get: index out of range (i,a.len):', i, a.len) + } + } + unsafe { + return &u8(a.data) + u64(i) * u64(a.element_size) + } +} + +@[markused] +fn (a array) get_u64(i u64) voidptr { + $if !no_bounds_checking { + if i >= u64(a.len) { + panic('array.get: index out of range (i,a.len): ' + i.str() + ', ' + + impl_i64_to_string(a.len)) + } + } + unsafe { + return &u8(a.data) + i * u64(a.element_size) + } +} + @[markused] fn (a array) get_ni(i int) voidptr { return a.get(v_ni_index(i, a.len)) @@ -746,6 +771,26 @@ fn (a array) get_with_check(i int) voidptr { } } +@[markused] +fn (a array) get_with_check_i64(i i64) voidptr { + if i < 0 || i >= i64(a.len) { + return 0 + } + unsafe { + return &u8(a.data) + u64(i) * u64(a.element_size) + } +} + +@[markused] +fn (a array) get_with_check_u64(i u64) voidptr { + if i >= u64(a.len) { + return 0 + } + unsafe { + return &u8(a.data) + i * u64(a.element_size) + } +} + @[markused] fn (a array) get_with_check_ni(i int) voidptr { return a.get_with_check(v_ni_index(i, a.len)) @@ -1044,6 +1089,27 @@ fn (mut a array) set(i int, val voidptr) { unsafe { vmemcpy(&u8(a.data) + u64(a.element_size) * u64(i), val, a.element_size) } } +@[markused] +fn (mut a array) set_i64(i i64, val voidptr) { + $if !no_bounds_checking { + if i < 0 || i >= i64(a.len) { + panic_n2('array.set: index out of range (i,a.len):', i, a.len) + } + } + unsafe { vmemcpy(&u8(a.data) + u64(a.element_size) * u64(i), val, a.element_size) } +} + +@[markused] +fn (mut a array) set_u64(i u64, val voidptr) { + $if !no_bounds_checking { + if i >= u64(a.len) { + panic('array.set: index out of range (i,a.len): ' + i.str() + ', ' + + impl_i64_to_string(a.len)) + } + } + unsafe { vmemcpy(&u8(a.data) + u64(a.element_size) * i, val, a.element_size) } +} + @[markused] fn (mut a array) set_ni(i int, val voidptr) { a.set(v_ni_index(i, a.len), val) diff --git a/vlib/builtin/builtin.c.v b/vlib/builtin/builtin.c.v index 34030f051..b8471d48b 100644 --- a/vlib/builtin/builtin.c.v +++ b/vlib/builtin/builtin.c.v @@ -79,6 +79,28 @@ fn v_fixed_index(i int, len int) int { return i } +@[inline; markused] +fn v_fixed_index_i64(i i64, len int) int { + $if !no_bounds_checking { + if i < 0 || i >= i64(len) { + panic('fixed array index out of range (index: ' + i.str() + ', len: ' + i64(len).str() + + ')') + } + } + return int(i) +} + +@[inline; markused] +fn v_fixed_index_u64(i u64, len int) int { + $if !no_bounds_checking { + if i >= u64(len) { + panic('fixed array index out of range (index: ' + i.str() + ', len: ' + i64(len).str() + + ')') + } + } + return int(i) +} + @[inline; markused] fn v_fixed_index_ni(i int, len int) int { return v_fixed_index(v_ni_index(i, len), len) diff --git a/vlib/builtin/js/map_test.js.v b/vlib/builtin/js/map_test.js.v index 1a989a13b..24cf9aa37 100644 --- a/vlib/builtin/js/map_test.js.v +++ b/vlib/builtin/js/map_test.js.v @@ -923,7 +923,7 @@ fn test_i64_keys() { assert m.len == end keys := m.keys() for i in i64(0) .. end { - assert keys[i] == i + assert keys[int(i)] == i } for i in i64(0) .. end { m.delete(i) @@ -949,7 +949,7 @@ fn test_u64_keys() { assert u64(m.len) == end keys := m.keys() for i in u64(0) .. end { - assert keys[i] == i + assert keys[int(i)] == i } for i in u64(0) .. end { m.delete(i) diff --git a/vlib/builtin/string.v b/vlib/builtin/string.v index b0bf13ac4..12ccb396e 100644 --- a/vlib/builtin/string.v +++ b/vlib/builtin/string.v @@ -2156,6 +2156,27 @@ fn (s string) at(idx int) u8 { return unsafe { s.str[idx] } } +@[markused] +fn (s string) at_i64(idx i64) u8 { + $if !no_bounds_checking { + if idx < 0 || idx >= i64(s.len) { + panic_n2('string index out of range(idx,s.len):', idx, s.len) + } + } + return unsafe { s.str[int(idx)] } +} + +@[markused] +fn (s string) at_u64(idx u64) u8 { + $if !no_bounds_checking { + if idx >= u64(s.len) { + panic('string index out of range(idx,s.len): ' + idx.str() + ', ' + + impl_i64_to_string(s.len)) + } + } + return unsafe { s.str[int(idx)] } +} + @[markused] fn (s string) at_ni(idx int) u8 { return s.at(v_ni_index(idx, s.len)) @@ -2172,6 +2193,26 @@ fn (s string) at_with_check(idx int) ?u8 { } } +@[markused] +fn (s string) at_with_check_i64(idx i64) ?u8 { + if idx < 0 || idx >= i64(s.len) { + return none + } + unsafe { + return s.str[int(idx)] + } +} + +@[markused] +fn (s string) at_with_check_u64(idx u64) ?u8 { + if idx >= u64(s.len) { + return none + } + unsafe { + return s.str[int(idx)] + } +} + @[markused] fn (s string) at_with_check_ni(idx int) ?u8 { return s.at_with_check(v_ni_index(idx, s.len)) diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index d34b51458..4ccfe86a5 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -7707,7 +7707,7 @@ fn (c &Checker) internal_index_type(index_type ast.Type) ast.Type { return internal_index_type } -fn (mut c Checker) check_internal_index_type(index ast.Expr, index_type ast.Type, typ_sym &ast.TypeSymbol) bool { +fn (mut c Checker) check_internal_index_type(index ast.Expr, index_type ast.Type, typ_sym &ast.TypeSymbol, range_index bool, is_gated bool) bool { if c.pref.translated || c.file.is_translated { return true } @@ -7720,6 +7720,9 @@ fn (mut c Checker) check_internal_index_type(index ast.Expr, index_type ast.Type } return true } + if c.pref.backend == .c && !range_index && !is_gated { + return true + } int_size, _ := c.table.type_size(ast.int_type_idx) internal_index_size, _ := c.table.type_size(internal_index_type.idx_type()) if internal_index_size > int_size { @@ -7758,7 +7761,7 @@ fn (mut c Checker) check_index(typ_sym &ast.TypeSymbol, index ast.Expr, index_ty c.error('cannot use Option or Result as index ${type_str}', index.pos()) return } - if !c.check_internal_index_type(index, index_type, typ_sym) { + if !c.check_internal_index_type(index, index_type, typ_sym, range_index, is_gated) { return } if index is ast.IntegerLiteral && !is_gated { diff --git a/vlib/v/checker/tests/index_expr_implicit_int_downcast_err.out b/vlib/v/checker/tests/index_expr_implicit_int_downcast_err.out index e4983f488..498615417 100644 --- a/vlib/v/checker/tests/index_expr_implicit_int_downcast_err.out +++ b/vlib/v/checker/tests/index_expr_implicit_int_downcast_err.out @@ -1,20 +1,6 @@ -vlib/v/checker/tests/index_expr_implicit_int_downcast_err.vv:10:12: error: cannot use `u64` as string index type `int`, use an explicit cast like `int(expr)` - 8 | _ = s[int(idx_u64)] - 9 | _ = a[idx_u32] - 10 | println(s[idx_u64]) - | ~~~~~~~ - 11 | println(a[idx_i64]) - 12 | println(a[idx_usize..]) -vlib/v/checker/tests/index_expr_implicit_int_downcast_err.vv:11:12: error: cannot use `i64` as index type `int`, use an explicit cast like `int(expr)` - 9 | _ = a[idx_u32] - 10 | println(s[idx_u64]) - 11 | println(a[idx_i64]) - | ~~~~~~~ - 12 | println(a[idx_usize..]) - 13 | } -vlib/v/checker/tests/index_expr_implicit_int_downcast_err.vv:12:12: error: cannot use `usize` as index type `int`, use an explicit cast like `int(expr)` - 10 | println(s[idx_u64]) - 11 | println(a[idx_i64]) - 12 | println(a[idx_usize..]) +vlib/v/checker/tests/index_expr_implicit_int_downcast_err.vv:4:12: error: cannot use `usize` as index type `int`, use an explicit cast like `int(expr)` + 2 | a := [1, 2, 3] + 3 | idx_usize := usize(1) + 4 | println(a[idx_usize..]) | ~~~~~~~~~ - 13 | } + 5 | } diff --git a/vlib/v/checker/tests/index_expr_implicit_int_downcast_err.vv b/vlib/v/checker/tests/index_expr_implicit_int_downcast_err.vv index 2708d9b79..77d2c6fbb 100644 --- a/vlib/v/checker/tests/index_expr_implicit_int_downcast_err.vv +++ b/vlib/v/checker/tests/index_expr_implicit_int_downcast_err.vv @@ -1,13 +1,5 @@ fn main() { - s := 'abc' a := [1, 2, 3] - idx_u64 := u64(1) - idx_u32 := u32(1) - idx_i64 := i64(1) idx_usize := usize(1) - _ = s[int(idx_u64)] - _ = a[idx_u32] - println(s[idx_u64]) - println(a[idx_i64]) println(a[idx_usize..]) } diff --git a/vlib/v/gen/c/index.v b/vlib/v/gen/c/index.v index a8dbabe3d..4f657535e 100644 --- a/vlib/v/gen/c/index.v +++ b/vlib/v/gen/c/index.v @@ -12,6 +12,32 @@ struct IndexOperatorMethodInfo { receiver_type ast.Type } +enum CWideIndexKind { + plain + signed_64 + unsigned_64 +} + +fn (mut g Gen) c_wide_index_kind(index_type ast.Type) CWideIndexKind { + if g.pref.backend != .c || index_type == 0 { + return .plain + } + mut internal_index_type := g.table.unaliased_type(index_type.clear_flag(.variadic)) + internal_index_sym := g.table.final_sym(internal_index_type) + if internal_index_sym.kind == .enum { + internal_index_type = internal_index_sym.enum_info().typ + } + if internal_index_type == ast.int_literal_type || !internal_index_type.is_int() { + return .plain + } + int_size, _ := g.table.type_size(ast.int_type_idx) + internal_index_size, _ := g.table.type_size(internal_index_type.idx_type()) + if internal_index_type.is_signed() { + return if internal_index_size <= int_size { .plain } else { .signed_64 } + } + return if internal_index_size < int_size { .plain } else { .unsigned_64 } +} + fn (mut g Gen) resolved_index_operator_receiver_type(receiver ast.Expr, receiver_type ast.Type) ast.Type { mut resolved_type := g.recheck_concrete_type(g.resolved_expr_type(receiver, receiver_type)) if resolved_type == 0 { @@ -113,15 +139,24 @@ fn (mut g Gen) index_expr(node ast.IndexExpr) { g.index_of_map(node, sym) } else if sym.kind == .string && !node.left_type.is_ptr() { gen_or := node.or_expr.kind != .absent || node.is_option + wide_index_kind := g.c_wide_index_kind(node.index_type) string_at_fn := if node.is_gated { 'builtin__string_at_ni' } else { - 'builtin__string_at' + match wide_index_kind { + .signed_64 { 'builtin__string_at_i64' } + .unsigned_64 { 'builtin__string_at_u64' } + else { 'builtin__string_at' } + } } string_at_with_check_fn := if node.is_gated { 'builtin__string_at_with_check_ni' } else { - 'builtin__string_at_with_check' + match wide_index_kind { + .signed_64 { 'builtin__string_at_with_check_i64' } + .unsigned_64 { 'builtin__string_at_with_check_u64' } + else { 'builtin__string_at_with_check' } + } } if gen_or { tmp_opt := g.new_tmp_var() @@ -138,7 +173,7 @@ fn (mut g Gen) index_expr(node ast.IndexExpr) { } g.write('\n${cur_line}*(byte*)&${tmp_opt}.data') } else { - is_direct_array_access := !node.is_gated + is_direct_array_access := !node.is_gated && wide_index_kind == .plain && (g.is_direct_array_access || node.is_direct) if is_direct_array_access { g.expr(ast.Expr(node.left)) @@ -336,17 +371,39 @@ fn (mut g Gen) index_of_array(node ast.IndexExpr, sym ast.TypeSymbol) { elem_type_str := if elem_sym.kind == .function { 'voidptr' } else { g.styp(elem_type) } result_type_str := if result_sym.kind == .function { 'voidptr' } else { g.styp(result_type) } left_is_shared := array_left_type.has_flag(.shared_f) - array_get_fn := if node.is_gated { 'builtin__array_get_ni' } else { 'builtin__array_get' } + wide_index_kind := g.c_wide_index_kind(node.index_type) + array_get_fn := if node.is_gated { + 'builtin__array_get_ni' + } else { + match wide_index_kind { + .signed_64 { 'builtin__array_get_i64' } + .unsigned_64 { 'builtin__array_get_u64' } + else { 'builtin__array_get' } + } + } array_get_with_check_fn := if node.is_gated { 'builtin__array_get_with_check_ni' } else { - 'builtin__array_get_with_check' + match wide_index_kind { + .signed_64 { 'builtin__array_get_with_check_i64' } + .unsigned_64 { 'builtin__array_get_with_check_u64' } + else { 'builtin__array_get_with_check' } + } + } + array_set_fn := if node.is_gated { + 'builtin__array_set_ni' + } else { + match wide_index_kind { + .signed_64 { 'builtin__array_set_i64' } + .unsigned_64 { 'builtin__array_set_u64' } + else { 'builtin__array_set' } + } } - array_set_fn := if node.is_gated { 'builtin__array_set_ni' } else { 'builtin__array_set' } // `vals[i].field = x` is an exception and requires `array_get`: // `(*(Val*)array_get(vals, i)).field = x;` if g.is_assign_lhs && node.is_setter { - is_direct_array_access := !node.is_gated && (g.is_direct_array_access || node.is_direct) + is_direct_array_access := !node.is_gated && wide_index_kind == .plain + && (g.is_direct_array_access || node.is_direct) is_op_assign := g.assign_op != .assign && info.elem_type != ast.string_type if is_direct_array_access { g.write('((${elem_type_str}*)') @@ -415,7 +472,8 @@ fn (mut g Gen) index_of_array(node ast.IndexExpr, sym ast.TypeSymbol) { } } } else { - is_direct_array_access := !node.is_gated && (g.is_direct_array_access || node.is_direct) + is_direct_array_access := !node.is_gated && wide_index_kind == .plain + && (g.is_direct_array_access || node.is_direct) is_fn_index_call := g.is_fn_index_call && elem_sym.info is ast.FnType // do not clone inside `opt_ok(opt_ok(&(string[]) {..})` before returns needs_clone := info.elem_type == ast.string_type_idx && g.is_autofree && !(g.inside_return @@ -539,6 +597,7 @@ fn (mut g Gen) index_of_fixed_array(node ast.IndexExpr, sym ast.TypeSymbol) { elem_type := info.elem_type elem_sym := g.table.sym(elem_type) is_fn_index_call := g.is_fn_index_call && elem_sym.info is ast.FnType + wide_index_kind := g.c_wide_index_kind(node.index_type) if node.left is ast.ArrayInit { past := g.past_tmp_var_new() @@ -579,6 +638,14 @@ fn (mut g Gen) index_of_fixed_array(node ast.IndexExpr, sym ast.TypeSymbol) { g.write('builtin__v_fixed_index_ni(') g.expr(node.index) g.write(', ${info.size})') + } else if wide_index_kind == .signed_64 { + g.write('builtin__v_fixed_index_i64(') + g.expr(node.index) + g.write(', ${info.size})') + } else if wide_index_kind == .unsigned_64 { + g.write('builtin__v_fixed_index_u64(') + g.expr(node.index) + g.write(', ${info.size})') } else if g.is_direct_array_access || g.pref.translated || node.index is ast.IntegerLiteral { g.expr(node.index) } else { diff --git a/vlib/v/markused/walker.v b/vlib/v/markused/walker.v index 6f30ea57a..e8701b541 100644 --- a/vlib/v/markused/walker.v +++ b/vlib/v/markused/walker.v @@ -3281,6 +3281,15 @@ fn (mut w Walker) mark_resource_dependencies() { w.fn_by_name(array_idx_str + '.slice') w.fn_by_name(array_idx_str + '.get') } + if w.pref.backend == .c + && (w.uses_arr_getter || w.uses_arr_setter || w.uses_guard || w.uses_index_check) { + w.mark_builtin_array_method_as_used('get_i64') + w.mark_builtin_array_method_as_used('get_u64') + w.mark_builtin_array_method_as_used('get_with_check_i64') + w.mark_builtin_array_method_as_used('get_with_check_u64') + w.mark_builtin_array_method_as_used('set_i64') + w.mark_builtin_array_method_as_used('set_u64') + } if w.uses_str_index { w.fn_by_name(string_idx_str + '.at') if w.uses_str_index_check { @@ -3290,6 +3299,12 @@ fn (mut w Walker) mark_resource_dependencies() { w.fn_by_name(string_idx_str + '.substr') } } + if w.pref.backend == .c && w.uses_str_index { + w.fn_by_name(string_idx_str + '.at_i64') + w.fn_by_name(string_idx_str + '.at_u64') + w.fn_by_name(string_idx_str + '.at_with_check_i64') + w.fn_by_name(string_idx_str + '.at_with_check_u64') + } for typ, _ in w.table.used_features.print_types { w.mark_by_type(typ) } @@ -3409,6 +3424,10 @@ fn (mut w Walker) mark_resource_dependencies() { if w.uses_fixed_arr_int { w.fn_by_name('v_fixed_index') } + if w.pref.backend == .c && w.uses_fixed_arr_int { + w.fn_by_name('v_fixed_index_i64') + w.fn_by_name('v_fixed_index_u64') + } if w.uses_str_range_index { w.fn_by_name(string_idx_str + '.substr') } -- 2.39.5