From ef05fb1210fcc8cd4c3814627e35b12dcc5b335d Mon Sep 17 00:00:00 2001 From: kbkpbot Date: Sun, 4 Jan 2026 04:59:27 +0800 Subject: [PATCH] builtin: add array.last_index() (#26252) --- vlib/builtin/array.v | 3 ++ vlib/builtin/array_test.v | 23 +++++++++++++ vlib/builtin/js/array.js.v | 11 +++++++ vlib/builtin/js/array_test.js.v | 23 +++++++++++++ vlib/v/ast/ast.v | 1 + vlib/v/checker/checker.v | 10 +++--- vlib/v/checker/fn.v | 19 ++++++----- vlib/v/gen/c/array.v | 40 ++++++++++++++++------- vlib/v/gen/c/cgen.v | 5 ++- vlib/v/gen/c/fn.v | 10 ++++-- vlib/v/gen/c/if.v | 2 +- vlib/v/gen/js/array.v | 25 ++++++++++---- vlib/v/markused/walker.v | 2 +- vlib/v/parser/fn.v | 10 ++++++ vlib/v/token/keywords_matcher_trie_test.v | 2 +- 15 files changed, 149 insertions(+), 37 deletions(-) diff --git a/vlib/builtin/array.v b/vlib/builtin/array.v index 5955e0819..9a95248ff 100644 --- a/vlib/builtin/array.v +++ b/vlib/builtin/array.v @@ -996,6 +996,9 @@ pub fn (a array) contains(value voidptr) bool // index returns the first index at which a given element can be found in the array or `-1` if the value is not found. pub fn (a array) index(value voidptr) int +// last_index returns the last index at which a given element can be found in the array or `-1` if the value is not found. +pub fn (a array) last_index(value voidptr) int + @[direct_array_access; unsafe] pub fn (mut a []string) free() { $if prealloc { diff --git a/vlib/builtin/array_test.v b/vlib/builtin/array_test.v index 960fad151..52f2d72ee 100644 --- a/vlib/builtin/array_test.v +++ b/vlib/builtin/array_test.v @@ -556,6 +556,29 @@ fn test_find_index() { assert d.index(`u`) == -1 } +fn test_find_last_index() { + // string + a := ['v', 'is', 'great', 'is', 'k', 'm'] + assert a.last_index('v') == 0 + assert a.last_index('is') == 3 + assert a.last_index('gre') == -1 + // int + b := [1, 2, 3, 4, 0, 1, 2, 3, 0, 1, 2, 3] + assert b.last_index(1) == 9 + assert b.last_index(4) == 3 + assert b.last_index(5) == -1 + // byte + c := [0x22, 0x33, 0x55, 0x22, 0x44, 0x55] + assert c.last_index(0x22) == 3 + assert c.last_index(0x55) == 5 + assert c.last_index(0x99) == -1 + // char + d := [`a`, `b`, `c`, `e`, `a`, `b`, `c`, `k`] + assert d.last_index(`b`) == 5 + assert d.last_index(`c`) == 6 + assert d.last_index(`u`) == -1 +} + fn test_multi() { a := [[1, 2, 3], [4, 5, 6]] assert a.len == 2 diff --git a/vlib/builtin/js/array.js.v b/vlib/builtin/js/array.js.v index fd8a5ad42..6751e698d 100644 --- a/vlib/builtin/js/array.js.v +++ b/vlib/builtin/js/array.js.v @@ -182,6 +182,17 @@ pub fn (a array) index(v string) int { return -1 } +pub fn (a array) last_index(v string) int { + for i := a.len - 1; i >= 0; i-- { + #if (a.arr.get(i).toString() == v.toString()) + + { + return i + } + } + return -1 +} + pub fn (a array) slice(start int, end int) array { mut result := a #let slice = a.arr.arr.slice(start,end) diff --git a/vlib/builtin/js/array_test.js.v b/vlib/builtin/js/array_test.js.v index a82cde8b7..ec1008d8c 100644 --- a/vlib/builtin/js/array_test.js.v +++ b/vlib/builtin/js/array_test.js.v @@ -542,6 +542,29 @@ fn test_find_index() { assert d.index(`u`) == -1 } +fn test_find_last_index() { + // string + a := ['v', 'is', 'great', 'is', 'k', 'm'] + assert a.last_index('v') == 0 + assert a.last_index('is') == 3 + assert a.last_index('gre') == -1 + // int + b := [1, 2, 3, 4, 0, 1, 2, 3, 0, 1, 2, 3] + assert b.last_index(1) == 9 + assert b.last_index(4) == 3 + assert b.last_index(5) == -1 + // byte + c := [0x22, 0x33, 0x55, 0x22, 0x44, 0x55] + assert c.last_index(0x22) == 3 + assert c.last_index(0x55) == 5 + assert c.last_index(0x99) == -1 + // char + d := [`a`, `b`, `c`, `e`, `a`, `b`, `c`, `k`] + assert d.last_index(`b`) == 5 + assert d.last_index(`c`) == 6 + assert d.last_index(`u`) == -1 +} + fn test_multi() { a := [[1, 2, 3], [4, 5, 6]] assert a.len == 2 diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 9da8fc4ff..da0491074 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -840,6 +840,7 @@ pub enum CallKind { trim contains index + last_index first last pop_left diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 8c79c5c67..ffec432a1 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -29,12 +29,12 @@ const generic_fn_postprocess_iterations_cutoff_limit = 1_000_000 // are properly checked. // Note that methods that do not return anything, or that return known types, are not listed here, since they are just ordinary non generic methods. pub const array_builtin_methods = ['filter', 'clone', 'repeat', 'reverse', 'map', 'slice', 'sort', - 'sort_with_compare', 'sorted', 'sorted_with_compare', 'contains', 'index', 'wait', 'any', 'all', - 'first', 'last', 'pop_left', 'pop', 'delete', 'insert', 'prepend', 'count'] + 'sort_with_compare', 'sorted', 'sorted_with_compare', 'contains', 'index', 'last_index', 'wait', + 'any', 'all', 'first', 'last', 'pop_left', 'pop', 'delete', 'insert', 'prepend', 'count'] pub const array_builtin_methods_chk = token.new_keywords_matcher_from_array_trie(array_builtin_methods) -pub const fixed_array_builtin_methods = ['contains', 'index', 'any', 'all', 'wait', 'map', 'sort', - 'sorted', 'sort_with_compare', 'sorted_with_compare', 'reverse', 'reverse_in_place', 'count', - 'filter'] +pub const fixed_array_builtin_methods = ['contains', 'index', 'last_index', 'any', 'all', 'wait', + 'map', 'sort', 'sorted', 'sort_with_compare', 'sorted_with_compare', 'reverse', + 'reverse_in_place', 'count', 'filter'] pub const fixed_array_builtin_methods_chk = token.new_keywords_matcher_from_array_trie(fixed_array_builtin_methods) // TODO: remove `byte` from this list when it is no longer supported pub const reserved_type_names = ['bool', 'char', 'i8', 'i16', 'i32', 'int', 'i64', 'u8', 'u16', diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index d8f836807..4ef57d2b6 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -3638,13 +3638,14 @@ fn (mut c Checker) array_builtin_method_call(mut node ast.CallExpr, left_type as node.args[i].typ = c.expr(mut arg.expr) } node.return_type = ast.bool_type - } else if node.kind == .index { + } else if node.kind in [.index, .last_index] { if node_args_len != 1 { - c.error('`.index()` expected 1 argument, but got ${node_args_len}', node.pos) - } else if !left_sym.has_method('index') { + c.error('`.${method_name}()` expected 1 argument, but got ${node_args_len}', + node.pos) + } else if !left_sym.has_method(method_name) { arg_typ := c.unwrap_generic(c.expr(mut arg0.expr)) c.check_expected_call_arg(arg_typ, c.unwrap_generic(elem_typ), node.language, - arg0) or { c.error('${err.msg()} in argument 1 to `.index()`', arg0.pos) } + arg0) or { c.error('${err.msg()} in argument 1 to `.${method_name}()`', arg0.pos) } } for i, mut arg in node.args { node.args[i].typ = c.expr(mut arg.expr) @@ -3710,14 +3711,16 @@ fn (mut c Checker) fixed_array_builtin_method_call(mut node ast.CallExpr, left_t node_args_len := node.args.len mut arg0 := if node_args_len > 0 { node.args[0] } else { ast.CallArg{} } elem_typ := array_info.elem_type - if node.kind == .index { + if node.kind in [.index, .last_index] { if node_args_len != 1 { - c.error('`.index()` expected 1 argument, but got ${node_args_len}', node.pos) + c.error('`.${method_name}()` expected 1 argument, but got ${node_args_len}', + node.pos) return ast.int_type - } else if !left_sym.has_method('index') { + } else if (node.kind == .index && !left_sym.has_method('index')) + || (node.kind == .last_index && !left_sym.has_method('last_index')) { arg_typ := c.expr(mut arg0.expr) c.check_expected_call_arg(arg_typ, elem_typ, node.language, arg0) or { - c.error('${err.msg()} in argument 1 to `.index()`', arg0.pos) + c.error('${err.msg()} in argument 1 to `.${method_name}()`', arg0.pos) return ast.int_type } } diff --git a/vlib/v/gen/c/array.v b/vlib/v/gen/c/array.v index 461fc05e1..705e4286b 100644 --- a/vlib/v/gen/c/array.v +++ b/vlib/v/gen/c/array.v @@ -1308,22 +1308,33 @@ fn (mut g Gen) gen_array_contains(left_type ast.Type, left ast.Expr, right_type g.write(')') } -fn (mut g Gen) get_array_index_method(typ ast.Type) string { +fn (mut g Gen) get_array_index_method(typ ast.Type, is_last_index bool) string { t := g.unwrap_generic(typ).set_nr_muls(0) - g.array_index_types << t - return g.styp(t) + '_index' + return if is_last_index { + g.array_last_index_types << t + g.styp(t) + '_last_index' + } else { + g.array_index_types << t + g.styp(t) + '_index' + } } -fn (mut g Gen) gen_array_index_methods() { +fn (mut g Gen) gen_array_index_methods(is_last_index bool) { mut done := []ast.Type{} - for t in g.array_index_types { - if t in done || g.table.sym(t).has_method('index') { + indxe_types := if is_last_index { g.array_last_index_types } else { g.array_index_types } + for t in indxe_types { + if t in done || (is_last_index && g.table.sym(t).has_method('last_index')) + || (!is_last_index && g.table.sym(t).has_method('index')) { continue } done << t final_left_sym := g.table.final_sym(t) mut left_type_str := g.styp(t) - fn_name := '${left_type_str}_index' + fn_name := if is_last_index { + '${left_type_str}_last_index' + } else { + '${left_type_str}_index' + } mut fn_builder := strings.new_builder(512) if final_left_sym.kind == .array { @@ -1336,8 +1347,14 @@ fn (mut g Gen) gen_array_index_methods() { } g.type_definitions.writeln('${g.static_non_parallel}${ast.int_type_name} ${fn_name}(${left_type_str} a, ${elem_type_str} v);') fn_builder.writeln('${g.static_non_parallel}${ast.int_type_name} ${fn_name}(${left_type_str} a, ${elem_type_str} v) {') - fn_builder.writeln('\t${elem_type_str}* pelem = a.data;') - fn_builder.writeln('\tfor (${ast.int_type_name} i = 0; i < a.len; ++i, ++pelem) {') + if is_last_index { + fn_builder.writeln('\tif (a.len == 0) return -1;') + fn_builder.writeln('\t${elem_type_str}* pelem = (${elem_type_str}*)((byte*)a.data + (a.len-1)*a.element_size);') + fn_builder.writeln('\tfor (${ast.int_type_name} i = a.len-1; i >= 0; --i, --pelem) {') + } else { + fn_builder.writeln('\t${elem_type_str}* pelem = a.data;') + fn_builder.writeln('\tfor (${ast.int_type_name} i = 0; i < a.len; ++i, ++pelem) {') + } if elem_sym.kind == .string { fn_builder.writeln('\t\tif (builtin__fast_string_eq(*pelem, v)) {') } else if elem_sym.kind in [.array, .array_fixed] && !info.elem_type.is_ptr() { @@ -1425,8 +1442,9 @@ fn (mut g Gen) gen_array_index_methods() { } // `nums.index(2)` -fn (mut g Gen) gen_array_index(node ast.CallExpr) { - fn_name := g.get_array_index_method(node.left_type) +// `nums.last_index(2)` +fn (mut g Gen) gen_array_index(node ast.CallExpr, is_last_index bool) { + fn_name := g.get_array_index_method(node.left_type, is_last_index) left_sym := g.table.final_sym(node.left_type) g.write('${fn_name}(') if node.left_type.is_ptr() { diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index a86393790..b61a173e6 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -196,6 +196,7 @@ mut: array_sort_fn shared []string array_contains_types []ast.Type array_index_types []ast.Type + array_last_index_types []ast.Type auto_fn_definitions []string // auto generated functions definition list sumtype_casting_fns []SumtypeCastingFn anon_fn_definitions []string // anon generated functions definition list @@ -480,6 +481,7 @@ pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) GenO global_g.needed_equality_fns << g.needed_equality_fns // duplicates are resolved later in gen_equality_fns global_g.array_contains_types << g.array_contains_types global_g.array_index_types << g.array_index_types + global_g.array_last_index_types << g.array_last_index_types global_g.pcs << g.pcs global_g.json_types << g.json_types global_g.hotcode_fn_names << g.hotcode_fn_names @@ -522,7 +524,8 @@ pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) GenO global_g.write_chan_pop_option_fns() global_g.write_chan_push_option_fns() global_g.gen_array_contains_methods() - global_g.gen_array_index_methods() + global_g.gen_array_index_methods(false) // .index() + global_g.gen_array_index_methods(true) // .last_index() global_g.gen_equality_fns() global_g.gen_free_methods() global_g.register_iface_return_types() diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 2de17fea4..9e1eb3304 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -1203,7 +1203,10 @@ fn (mut g Gen) gen_array_method_call(node ast.CallExpr, left_type ast.Type, left g.gen_array_contains(left_type, node.left, node.args[0].typ, node.args[0].expr) } .index { - g.gen_array_index(node) + g.gen_array_index(node, false) + } + .last_index { + g.gen_array_index(node, true) } .wait { g.gen_array_wait(node) @@ -1308,7 +1311,10 @@ fn (mut g Gen) gen_fixed_array_method_call(node ast.CallExpr, left_type ast.Type g.gen_array_filter(node) } .index { - g.gen_array_index(node) + g.gen_array_index(node, false) + } + .last_index { + g.gen_array_index(node, true) } .contains { g.gen_array_contains(left_type, node.left, node.args[0].typ, node.args[0].expr) diff --git a/vlib/v/gen/c/if.v b/vlib/v/gen/c/if.v index 90f94c9ea..143745f62 100644 --- a/vlib/v/gen/c/if.v +++ b/vlib/v/gen/c/if.v @@ -63,7 +63,7 @@ fn (mut g Gen) need_tmp_var_in_expr(expr ast.Expr) bool { left_sym := g.table.sym(expr.receiver_type) if left_sym.kind in [.array, .array_fixed, .map] { return expr.name !in ['contains', 'exists', 'len', 'cap', 'first', 'last', - 'index', 'get'] + 'index', 'last_index', 'get'] } } if expr.or_block.kind != .absent { diff --git a/vlib/v/gen/js/array.v b/vlib/v/gen/js/array.v index 3faa1d6a9..5bddcb338 100644 --- a/vlib/v/gen/js/array.v +++ b/vlib/v/gen/js/array.v @@ -8,16 +8,18 @@ const special_array_methods = [ 'insert', 'prepend', 'index', + 'last_index', 'contains', ] -fn (mut g JsGen) gen_array_index_method(left_type ast.Type) string { +fn (mut g JsGen) gen_array_index_method(left_type ast.Type, is_last_index bool) string { unwrap_left_type := g.unwrap_generic(left_type) mut left_sym := g.table.sym(unwrap_left_type) mut left_type_str := g.styp(unwrap_left_type).trim('*') - fn_name := '${left_type_str}_index' + fn_name := if is_last_index { '${left_type_str}_last_index' } else { '${left_type_str}_index' } - if !left_sym.has_method('index') { + if (!is_last_index && !left_sym.has_method('index')) + || (is_last_index && !left_sym.has_method('last_index')) { info := left_sym.info as ast.Array elem_sym := g.table.sym(info.elem_type) if elem_sym.kind == .function { @@ -27,7 +29,12 @@ fn (mut g JsGen) gen_array_index_method(left_type ast.Type) string { mut fn_builder := strings.new_builder(512) fn_builder.writeln('function ${fn_name}(a, v) {') fn_builder.writeln('\tlet pelem = a.arr;') - fn_builder.writeln('\tfor (let i = 0; i < pelem.arr.length; ++i) {') + if is_last_index { + fn_builder.writeln('\tif (pelem.arr.length == 0) return new int(-1);') + fn_builder.writeln('\tfor (let i = pelem.arr.length - 1; i >=0; --i) {') + } else { + fn_builder.writeln('\tfor (let i = 0; i < pelem.arr.length; ++i) {') + } if elem_sym.kind == .string { fn_builder.writeln('\t\tif (pelem.get(new int(i)).str == v.str) {') } else if elem_sym.kind == .array && !info.elem_type.is_ptr() { @@ -68,7 +75,11 @@ fn (mut g JsGen) gen_array_method_call(it ast.CallExpr) { match node.name { 'index' { - g.gen_array_index(node) + g.gen_array_index(node, false) + return + } + 'last_index' { + g.gen_array_index(node, true) return } 'contains' { @@ -138,8 +149,8 @@ fn (mut g JsGen) gen_array_method_call(it ast.CallExpr) { } } -fn (mut g JsGen) gen_array_index(node ast.CallExpr) { - fn_name := g.gen_array_index_method(node.left_type) +fn (mut g JsGen) gen_array_index(node ast.CallExpr, is_last_index bool) { + fn_name := g.gen_array_index_method(node.left_type, is_last_index) g.write('${fn_name}(') g.expr(node.left) g.gen_deref_ptr(node.left_type) diff --git a/vlib/v/markused/walker.v b/vlib/v/markused/walker.v index 0615a4dcb..b7d3bbd5a 100644 --- a/vlib/v/markused/walker.v +++ b/vlib/v/markused/walker.v @@ -1046,7 +1046,7 @@ pub fn (mut w Walker) call_expr(mut node ast.CallExpr) { } else { match left_sym.info { ast.Array, ast.ArrayFixed { - if !w.uses_arr_void && node.name in ['contains', 'index'] { + if !w.uses_arr_void && node.name in ['contains', 'index', 'last_index'] { if w.table.final_sym(left_sym.info.elem_type).kind == .function { w.uses_arr_void = true } diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index e6608f468..4bbb4225e 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -335,6 +335,16 @@ fn (mut p Parser) call_kind(fn_name string) ast.CallKind { } } } + 10 { + return match fn_name { + 'last_index' { + .last_index + } + else { + .unknown + } + } + } 11 { return match fn_name { 'delete_many' { diff --git a/vlib/v/token/keywords_matcher_trie_test.v b/vlib/v/token/keywords_matcher_trie_test.v index bb64c5cab..c23543ec5 100644 --- a/vlib/v/token/keywords_matcher_trie_test.v +++ b/vlib/v/token/keywords_matcher_trie_test.v @@ -2,7 +2,7 @@ import v.token fn test_new_keywords_matcher_from_array_trie_works() { method_names := ['filter', 'clone', 'repeat', 'reverse', 'map', 'slice', 'sort', 'contains', - 'index', 'wait', 'any', 'all', 'first', 'last', 'pop_left', 'pop'] + 'index', 'last_index', 'wait', 'any', 'all', 'first', 'last', 'pop_left', 'pop'] matcher := token.new_keywords_matcher_from_array_trie(method_names) for word in [method_names.first(), method_names.last(), 'something', 'another', 'x', 'y', '', '---', '123', 'abc.def', 'xyz !@#'] { -- 2.39.5