diff --git a/core/variant/array.cpp b/core/variant/array.cpp index 8e13c89da6..81caa5d147 100644 --- a/core/variant/array.cpp +++ b/core/variant/array.cpp @@ -312,6 +312,14 @@ Error Array::insert(int p_pos, const Variant &p_value) { ERR_FAIL_COND_V_MSG(_p->read_only, ERR_LOCKED, "Array is in read-only state."); Variant value = p_value; ERR_FAIL_COND_V(!_p->typed.validate(value, "insert"), ERR_INVALID_PARAMETER); + + if (p_pos < 0) { + // Relative offset from the end. + p_pos = _p->array.size() + p_pos; + } + + ERR_FAIL_INDEX_V_MSG(p_pos, _p->array.size(), ERR_INVALID_PARAMETER, vformat("The calculated index %d is out of bounds (the array has %d elements). Leaving the array untouched.", p_pos, _p->array.size())); + return _p->array.insert(p_pos, std::move(value)); } @@ -481,6 +489,14 @@ bool Array::has(const Variant &p_value) const { void Array::remove_at(int p_pos) { ERR_FAIL_COND_MSG(_p->read_only, "Array is in read-only state."); + + if (p_pos < 0) { + // Relative offset from the end. + p_pos = _p->array.size() + p_pos; + } + + ERR_FAIL_INDEX_MSG(p_pos, _p->array.size(), vformat("The calculated index %d is out of bounds (the array has %d elements). Leaving the array untouched.", p_pos, _p->array.size())); + _p->array.remove_at(p_pos); } diff --git a/doc/classes/Array.xml b/doc/classes/Array.xml index cb41f0055e..3dbf34f5a5 100644 --- a/doc/classes/Array.xml +++ b/doc/classes/Array.xml @@ -488,7 +488,7 @@ - Inserts a new element ([param value]) at a given index ([param position]) in the array. [param position] should be between [code]0[/code] and the array's [method size]. + Inserts a new element ([param value]) at a given index ([param position]) in the array. [param position] should be between [code]0[/code] and the array's [method size]. If negative, [param position] is considered relative to the end of the array. Returns [constant OK] on success, or one of the other [enum Error] constants if this method fails. [b]Note:[/b] Every element's index after [param position] needs to be shifted forward, which may have a noticeable performance cost, especially on larger arrays. @@ -663,7 +663,7 @@ - Removes the element from the array at the given index ([param position]). If the index is out of bounds, this method fails. + Removes the element from the array at the given index ([param position]). If the index is out of bounds, this method fails. If the index is negative, [param position] is considered relative to the end of the array. If you need to return the removed element, use [method pop_at]. To remove an element by value, use [method erase] instead. [b]Note:[/b] This method shifts every element's index after [param position] back, which may have a noticeable performance cost, especially on larger arrays. [b]Note:[/b] The [param position] cannot be negative. To remove an element relative to the end of the array, use [code]arr.remove_at(arr.size() - (i + 1))[/code]. To remove the last element from the array, use [code]arr.resize(arr.size() - 1)[/code]. diff --git a/tests/core/variant/test_array.h b/tests/core/variant/test_array.h index 48780115c6..a79dd46fee 100644 --- a/tests/core/variant/test_array.h +++ b/tests/core/variant/test_array.h @@ -126,6 +126,12 @@ TEST_CASE("[Array] resize(), insert(), and erase()") { CHECK(int(arr[0]) == 2); arr.erase(2); CHECK(int(arr[0]) == 1); + + // Negative index on insert. + CHECK(arr.size() == 3); + arr.insert(-1, 3); + CHECK(int(arr[2]) == 3); + CHECK(arr.size() == 4); } TEST_CASE("[Array] front() and back()") { @@ -154,6 +160,15 @@ TEST_CASE("[Array] remove_at()") { arr.remove_at(0); CHECK(arr.size() == 0); + // Negative index. + arr.push_back(3); + arr.push_back(4); + arr.remove_at(-1); + CHECK(arr.size() == 1); + CHECK(int(arr[0]) == 3); + arr.remove_at(-1); + CHECK(arr.size() == 0); + // The array is now empty; try to use `remove_at()` again. // Normally, this prints an error message so we silence it. ERR_PRINT_OFF;