diff --git a/vlib/builtin/array.v b/vlib/builtin/array.v index e4a7880d5f..2f2fa6084e 100644 --- a/vlib/builtin/array.v +++ b/vlib/builtin/array.v @@ -511,6 +511,50 @@ pub fn (a array) last() voidptr { } } +// pop_left returns the first element of the array and removes it by advancing the data pointer. +// If the `array` is empty, this will panic. +// NOTE: This function: +// - Reduces both length and capacity by 1 +// - Advances the underlying data pointer by one element +// - Leaves subsequent elements in-place (no memory copying) +// Sliced views will retain access to the original first element position, +// which is now detached from the array's active memory range. +// +// Example: +// ```v +// mut a := [1, 2, 3, 4, 5] +// b := unsafe { a[..5] } // full slice view +// first := a.pop_left() +// +// // Array now starts from second element +// dump(a) // a: [2, 3, 4, 5] +// assert a.len == 4 +// assert a.cap == 4 +// +// // Slice retains original memory layout +// dump(b) // b: [1, 2, 3, 4, 5] +// assert b.len == 5 +// +// assert first == 1 +// +// // Modifications affect both array and slice views +// a[0] = 99 +// assert b[1] == 99 // changed in both +// ``` +pub fn (mut a array) pop_left() voidptr { + if a.len == 0 { + panic('array.pop_left: array is empty') + } + first_elem := a.data + unsafe { + a.data = &u8(a.data) + u64(a.element_size) + } + a.offset += a.element_size + a.len-- + a.cap-- + return first_elem +} + // pop returns the last element of the array, and removes it. // If the `array` is empty, this will panic. // NOTE: this function reduces the length of the given array, diff --git a/vlib/builtin/array_d_gcboehm_opt.v b/vlib/builtin/array_d_gcboehm_opt.v index e2bdb04414..250de7d36b 100644 --- a/vlib/builtin/array_d_gcboehm_opt.v +++ b/vlib/builtin/array_d_gcboehm_opt.v @@ -216,6 +216,21 @@ fn (mut a array) prepend_many_noscan(val voidptr, size int) { unsafe { a.insert_many_noscan(0, val, size) } } +// pop_left returns the first element of the array and removes it by advancing the data pointer. +fn (mut a array) pop_left_noscan() voidptr { + if a.len == 0 { + panic('array.pop_left: array is empty') + } + first_elem := a.data + unsafe { + a.data = &u8(a.data) + u64(a.element_size) + } + a.offset += a.element_size + a.len-- + a.cap-- + return unsafe { memdup_noscan(first_elem, a.element_size) } +} + // pop returns the last element of the array, and removes it. fn (mut a array) pop_noscan() voidptr { // in a sense, this is the opposite of `a << x` diff --git a/vlib/builtin/array_test.v b/vlib/builtin/array_test.v index 941cdb5250..8cab14a763 100644 --- a/vlib/builtin/array_test.v +++ b/vlib/builtin/array_test.v @@ -1197,6 +1197,40 @@ fn test_reverse_in_place() { assert c == [[5, 6], [3, 4], [1, 2]] } +fn test_array_int_pop_left() { + mut a := [1, 2, 3, 4, 5] + b := unsafe { a[..5] } // full slice view + assert a.len == 5 + first := a[0] + x := a.pop_left() + assert first == x + assert a.len == 4 + assert a.cap == 4 + y := a.pop_left() + assert y == 2 + a[0] = 100 + // NOTE: update a[0] also update b[2] + assert b == [1, 2, 100, 4, 5] + + mut one_elem := [1] + one := one_elem.pop_left() + assert one_elem.len == 0 + assert one_elem.cap == 0 + assert one == 1 +} + +fn test_array_string_pop_left() { + mut a := ['abc', 'def', 'xyz'] + assert a.len == 3 + x := a.first() + y := a.pop_left() + assert x == y + assert a.pop_left() == 'def' + assert a.pop_left() == 'xyz' + assert a.len == 0 + assert a.cap == 0 +} + fn test_array_int_pop() { mut a := [1, 2, 3, 4, 5] assert a.len == 5 diff --git a/vlib/v/ast/table.v b/vlib/v/ast/table.v index 05bf379a68..aeca41c120 100644 --- a/vlib/v/ast/table.v +++ b/vlib/v/ast/table.v @@ -19,6 +19,7 @@ pub mut: arr_insert bool // arr.insert() arr_first bool // arr.first() arr_last bool // arr.last() + arr_pop_left bool // arr.pop_left() arr_pop bool // arr.pop() arr_delete bool // arr.delete() arr_reverse bool // arr.reverse() diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index f32d4163e3..b0b08fc09b 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -30,7 +30,7 @@ const generic_fn_postprocess_iterations_cutoff_limit = 1_000_000 // 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', 'delete', 'insert', 'prepend', 'count'] + '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'] diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index f5ed8705de..ef32676dbf 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -3506,13 +3506,13 @@ 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 in ['first', 'last', 'pop'] { + } else if method_name in ['first', 'last', 'pop_left', 'pop'] { c.markused_array_method(!c.is_builtin_mod, method_name) if node_args_len != 0 { c.error('`.${method_name}()` does not have any arguments', arg0.pos) } node.return_type = array_info.elem_type - if method_name == 'pop' { + if method_name in ['pop_left', 'pop'] { c.check_for_mut_receiver(mut node.left) node.receiver_type = node.left_type.ref() } else { diff --git a/vlib/v/checker/used_features.v b/vlib/v/checker/used_features.v index 43b7e608cd..1b28a712ff 100644 --- a/vlib/v/checker/used_features.v +++ b/vlib/v/checker/used_features.v @@ -201,6 +201,9 @@ fn (mut c Checker) markused_array_method(check bool, method_name string) { 'last' { c.table.used_features.arr_last = true } + 'pop_left' { + c.table.used_features.arr_pop_left = true + } 'pop' { c.table.used_features.arr_pop = true } diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index 5ecf2ae122..781341e150 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -1207,15 +1207,15 @@ fn (mut g Gen) gen_array_method_call(node ast.CallExpr, left_type ast.Type, left g.expr(node.args[0].expr) g.write(')') } - 'first', 'last', 'pop' { + 'first', 'last', 'pop_left', 'pop' { mut noscan := '' array_info := left_sym.info as ast.Array - if node.name == 'pop' { + if node.name in ['pop_left', 'pop'] { noscan = g.check_noscan(array_info.elem_type) } return_type_str := g.styp(node.return_type) g.write('(*(${return_type_str}*)array_${node.name}${noscan}(') - if node.name == 'pop' { + if node.name in ['pop_left', 'pop'] { g.gen_arg_from_type(left_type, node.left) } else { if node.left_type.is_ptr() { @@ -1475,7 +1475,7 @@ fn (mut g Gen) resolve_receiver_name(node ast.CallExpr, unwrapped_rec_type ast.T receiver_type_name = 'map' } if final_left_sym.kind == .array && !(left_sym.kind == .alias && left_sym.has_method(node.name)) - && node.name in ['clear', 'repeat', 'sort_with_compare', 'sorted_with_compare', 'push_many', 'trim', 'first', 'last', 'pop', 'clone', 'reverse', 'slice', 'pointers'] { + && node.name in ['clear', 'repeat', 'sort_with_compare', 'sorted_with_compare', 'push_many', 'trim', 'first', 'last', 'pop_left', 'pop', 'clone', 'reverse', 'slice', 'pointers'] { if !(left_sym.info is ast.Alias && typ_sym.has_method(node.name)) { // `array_Xyz_clone` => `array_clone` receiver_type_name = 'array' diff --git a/vlib/v/gen/js/fn.v b/vlib/v/gen/js/fn.v index ae369a9725..e857ca341a 100644 --- a/vlib/v/gen/js/fn.v +++ b/vlib/v/gen/js/fn.v @@ -319,7 +319,7 @@ fn (mut g JsGen) method_call(node ast.CallExpr) { } if node.name in ['repeat', 'sort_with_compare', 'free', 'push_many', 'trim', 'first', 'last', - 'pop', 'clone', 'reverse', 'slice', 'pointers'] { + 'pop_left', 'pop', 'clone', 'reverse', 'slice', 'pointers'] { if !(left_sym.info is ast.Alias && typ_sym.has_method(node.name)) { // `array_Xyz_clone` => `array_clone` receiver_type_name = 'array' diff --git a/vlib/v/markused/markused.v b/vlib/v/markused/markused.v index 50782bfb56..93010cbfba 100644 --- a/vlib/v/markused/markused.v +++ b/vlib/v/markused/markused.v @@ -88,6 +88,10 @@ pub fn mark_used(mut table ast.Table, mut pref_ pref.Preferences, ast_files []&a if table.used_features.arr_reverse { core_fns << array_idx_str + '.reverse' } + if table.used_features.arr_pop_left { + core_fns << ref_array_idx_str + '.pop_left' + core_fns << ref_array_idx_str + '.pop_left_noscan' + } if table.used_features.arr_pop { core_fns << ref_array_idx_str + '.pop' core_fns << ref_array_idx_str + '.pop_noscan' diff --git a/vlib/v/token/keywords_matcher_trie_test.v b/vlib/v/token/keywords_matcher_trie_test.v index 988b50d02c..bb64c5cab9 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'] + '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 !@#'] {