From 36710adccc8cf04c82a8e52ed8acabea826622d7 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 11 Mar 2026 16:10:01 +0300 Subject: [PATCH] builtin: array.get (fixes #22837) --- vlib/builtin/js/array.js.v | 3 + vlib/v/checker/checker.v | 4 +- vlib/v/checker/fn.v | 22 +++++- vlib/v/gen/c/array.v | 78 +++++++++++++++++++++ vlib/v/gen/c/cgen.v | 3 + vlib/v/gen/c/fn.v | 7 +- vlib/v/tests/conditions/ifs/if_guard_test.v | 28 ++++++++ 7 files changed, 141 insertions(+), 4 deletions(-) diff --git a/vlib/builtin/js/array.js.v b/vlib/builtin/js/array.js.v index 6751e698d..a1b508baa 100644 --- a/vlib/builtin/js/array.js.v +++ b/vlib/builtin/js/array.js.v @@ -134,6 +134,9 @@ pub fn (a array) last() voidptr { } fn (a array) get(ix int) voidptr { + if ix < 0 || ix >= a.len { + return unsafe { nil } + } mut result := unsafe { nil } #result = a.arr.get(ix) diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 53ab05570..6931a1aee 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -29,8 +29,8 @@ 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', 'last_index', 'wait', - 'any', 'all', 'first', 'last', 'pop_left', 'pop', 'delete', 'insert', 'prepend', 'count'] + 'sort_with_compare', 'sorted', 'sorted_with_compare', 'contains', 'index', 'last_index', 'get', + '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', 'last_index', 'any', 'all', 'wait', 'map', 'sort', 'sorted', 'sort_with_compare', 'sorted_with_compare', 'reverse', diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index 100bb2c07..af6321ee7 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -2283,8 +2283,14 @@ fn (mut c Checker) method_call(mut node ast.CallExpr, mut continue_check &bool) unwrapped_left_type := c.unwrap_generic(left_type) left_sym := c.table.sym(unwrapped_left_type) final_left_sym := c.table.final_sym(unwrapped_left_type) + mut has_custom_array_get := false method_name := node.name + if method_name == 'get' { + if method := c.table.find_method(left_sym, method_name) { + has_custom_array_get = method.receiver_type != ast.array_type + } + } if left_type.has_flag(.option) { c.error('Option type `${left_sym.name}` cannot be called directly, you should unwrap it first', node.left.pos()) @@ -2309,7 +2315,7 @@ fn (mut c Checker) method_call(mut node ast.CallExpr, mut continue_check &bool) return ast.void_type } if final_left_sym.kind == .array && array_builtin_methods_chk.matches(method_name) - && !(left_sym.kind == .alias && left_sym.has_method(method_name)) { + && !(left_sym.kind == .alias && left_sym.has_method(method_name)) && !has_custom_array_get { return c.array_builtin_method_call(mut node, left_type) } else if final_left_sym.kind == .array_fixed && fixed_array_builtin_methods_chk.matches(method_name) && !(left_sym.kind == .alias @@ -3791,6 +3797,20 @@ 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.int_type + } else if method_name == 'get' { + if node_args_len != 1 { + c.error('`.get()` expected 1 argument, but got ${node_args_len}', node.pos) + } else { + arg_typ := c.unwrap_generic(c.expr(mut arg0.expr)) + c.check_expected_call_arg(arg_typ, ast.int_type, node.language, arg0) or { + c.error('${err.msg()} in argument 1 to `.get()`', arg0.pos) + } + } + for i, mut arg in node.args { + node.args[i].typ = c.expr(mut arg.expr) + } + node.receiver_type = ast.array_type + node.return_type = array_info.elem_type.set_flag(.option) } else if node.kind in [.first, .last, .pop_left, .pop] { c.markused_array_method(!c.is_builtin_mod, method_name) if node_args_len != 0 { diff --git a/vlib/v/gen/c/array.v b/vlib/v/gen/c/array.v index b2d0f74e6..49243c6ea 100644 --- a/vlib/v/gen/c/array.v +++ b/vlib/v/gen/c/array.v @@ -1327,6 +1327,12 @@ fn (mut g Gen) get_array_index_method(typ ast.Type, is_last_index bool) string { } } +fn (mut g Gen) get_array_get_method(typ ast.Type) string { + t := g.unwrap_generic(typ).set_nr_muls(0) + g.array_get_types << t + return g.styp(t) + '_get' +} + fn (mut g Gen) gen_array_index_methods(is_last_index bool) { mut done := []ast.Type{} indxe_types := if is_last_index { g.array_last_index_types } else { g.array_index_types } @@ -1449,6 +1455,78 @@ fn (mut g Gen) gen_array_index_methods(is_last_index bool) { } } +fn (mut g Gen) gen_array_get_methods() { + mut done := []ast.Type{} + for t in g.array_get_types { + if t in done { + continue + } + done << t + final_left_sym := g.table.final_sym(t) + if final_left_sym.kind != .array { + continue + } + info := final_left_sym.info as ast.Array + left_type_str := g.styp(t) + elem_type_str := g.styp(info.elem_type) + elem_sym := g.table.sym(info.elem_type) + option_type_str := g.styp(info.elem_type.set_flag(.option)) + base_elem_type_str := g.base_type(info.elem_type) + default_value := if info.elem_type.has_flag(.option) { + g.type_default(info.elem_type.clear_flag(.option)) + } else if elem_sym.kind == .function { + '0' + } else { + g.type_default(info.elem_type) + } + fn_name := '${left_type_str}_get' + g.type_definitions.writeln('${g.static_non_parallel}${option_type_str} ${fn_name}(${left_type_str} a, ${ast.int_type_name} i);') + mut fn_builder := strings.new_builder(512) + fn_builder.writeln('${g.static_non_parallel}${option_type_str} ${fn_name}(${left_type_str} a, ${ast.int_type_name} i) {') + fn_builder.writeln('\t${option_type_str} res = {0};') + fn_builder.writeln('\tif (i < 0 || i >= a.len) {') + fn_builder.writeln('\t\tbuiltin___option_none(&(${base_elem_type_str}[]){ ${default_value} }, (_option*)&res, sizeof(${base_elem_type_str}));') + fn_builder.writeln('\t\treturn res;') + fn_builder.writeln('\t}') + if info.elem_type.has_flag(.option) { + fn_builder.writeln('\t${elem_type_str}* opt_elem = (${elem_type_str}*)((byte*)a.data + i * a.element_size);') + fn_builder.writeln('\tif (opt_elem->state == 0) {') + fn_builder.writeln('\t\tbuiltin___option_ok(opt_elem->data, (_option*)&res, sizeof(${base_elem_type_str}));') + fn_builder.writeln('\t} else {') + fn_builder.writeln('\t\tres.state = opt_elem->state;') + fn_builder.writeln('\t\tres.err = opt_elem->err;') + fn_builder.writeln('\t}') + } else { + storage_type_str := if elem_sym.kind == .function { 'voidptr' } else { elem_type_str } + fn_builder.writeln('\tbuiltin___option_ok(((${storage_type_str}*)((byte*)a.data + i * a.element_size)), (_option*)&res, sizeof(${storage_type_str}));') + } + fn_builder.writeln('\treturn res;') + fn_builder.writeln('}') + g.auto_fn_definitions << fn_builder.str() + } +} + +fn (mut g Gen) gen_array_get(node ast.CallExpr) { + fn_name := g.get_array_get_method(node.left_type) + left_is_ptr := node.left_type.is_ptr() + left_is_shared := node.left_type.has_flag(.shared_f) + g.write('${fn_name}(') + if left_is_ptr && !left_is_shared { + g.write('*') + } + g.expr(node.left) + if left_is_shared { + if left_is_ptr { + g.write('->val') + } else { + g.write('.val') + } + } + g.write(', ') + g.expr(node.args[0].expr) + g.write(')') +} + // `nums.index(2)` // `nums.last_index(2)` fn (mut g Gen) gen_array_index(node ast.CallExpr, is_last_index bool) { diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index 42f882b00..5420aabd6 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -196,6 +196,7 @@ mut: array_contains_types []ast.Type array_index_types []ast.Type array_last_index_types []ast.Type + array_get_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 @@ -482,6 +483,7 @@ pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) GenO 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.array_get_types << g.array_get_types global_g.pcs << g.pcs global_g.json_types << g.json_types global_g.hotcode_fn_names << g.hotcode_fn_names @@ -526,6 +528,7 @@ pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) GenO global_g.gen_array_contains_methods() global_g.gen_array_index_methods(false) // .index() global_g.gen_array_index_methods(true) // .last_index() + global_g.gen_array_get_methods() // .get() 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 90577e600..565700123 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -1322,6 +1322,10 @@ fn (mut g Gen) gen_map_method_call(node ast.CallExpr, left_type ast.Type, left_s } fn (mut g Gen) gen_array_method_call(node ast.CallExpr, left_type ast.Type, left_sym ast.TypeSymbol) bool { + if node.name == 'get' { + g.gen_array_get(node) + return true + } match node.kind { .filter { g.gen_array_filter(node) @@ -1795,7 +1799,8 @@ fn (mut g Gen) method_call(node ast.CallExpr) { left_sym := g.table.sym(left_type) final_left_sym := g.table.final_sym(left_type) if final_left_sym.kind == .array && !(left_sym.kind == .alias && left_sym.has_method(node.name)) { - if g.gen_array_method_call(node, left_type, final_left_sym) { + if !(node.name == 'get' && node.receiver_type != 0 && node.receiver_type != ast.array_type) + && g.gen_array_method_call(node, left_type, final_left_sym) { return } } diff --git a/vlib/v/tests/conditions/ifs/if_guard_test.v b/vlib/v/tests/conditions/ifs/if_guard_test.v index b1c99905d..ba300c71d 100644 --- a/vlib/v/tests/conditions/ifs/if_guard_test.v +++ b/vlib/v/tests/conditions/ifs/if_guard_test.v @@ -75,6 +75,34 @@ fn test_array_get_empty() { } } +fn maybe_text(ok bool) ?string { + if ok { + return 'hello' + } + return none +} + +fn test_array_get_method() { + mut a := [12.5, 6.5, -17.25] + mut res := []f64{cap: 2} + for i in [1, 4] { + if x := a.get(i) { + res << x + } else { + res << -23.0 + } + } + assert res == [6.5, -23.0] +} + +fn test_array_get_method_with_option_elements() { + arr := [maybe_text(true)] + x := arr.get(0) or { 'fallback' } + y := arr.get(99) or { 'fallback' } + assert x == 'hello' + assert y == 'fallback' +} + fn test_chan_pop() { mut res := []f64{cap: 3} ch := chan f64{cap: 10} -- 2.39.5