diff --git a/src/utils.jl b/src/utils.jl index 0209140a..7b0ef882 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -86,7 +86,7 @@ function recursivecopy!( T2 <: StaticArraysCore.StaticArray, N, } - return @inbounds for i in eachindex(a) + return @inbounds for i in eachindex(b, a) # TODO: Check for `setindex!`` and use `copy!(b[i],a[i])` or `b[i] = a[i]`, see #19 b[i] = copy(a[i]) end @@ -367,6 +367,7 @@ values == [[1, 2], [3, 4]] ``` """ function copyat_or_push!(a::AbstractVector{T}, i::Int, x, perform_copy = true) where {T} + i >= firstindex(a) || throw(BoundsError(a, i)) @inbounds if length(a) >= i if !ArrayInterface.ismutable(T) || !perform_copy # TODO: Check for `setindex!`` if T <: StaticArraysCore.StaticArray and use `copy!(b[i],a[i])` diff --git a/test/Core/utils_test.jl b/test/Core/utils_test.jl index 3ef3bb0c..da93cfba 100644 --- a/test/Core/utils_test.jl +++ b/test/Core/utils_test.jl @@ -2,6 +2,17 @@ using RecursiveArrayTools, StaticArrays using RecursiveArrayToolsShorthandConstructors using Test +# A vector whose own indices do not start at 1, e.g. `ZeroVec(v)` has axes +# `0:length(v)-1`, like `OffsetArrays.OffsetArray(v, 0:length(v)-1)`. +struct ZeroVec{T} <: AbstractVector{T} + data::Vector{T} +end +Base.axes(z::ZeroVec) = (0:(length(z.data) - 1),) +Base.size(z::ZeroVec) = size(z.data) +Base.getindex(z::ZeroVec, i::Int) = z.data[i + 1] +Base.setindex!(z::ZeroVec, v, i::Int) = (z.data[i + 1] = v) +Base.IndexStyle(::Type{<:ZeroVec}) = IndexLinear() + t = collect(range(0, stop = 10, length = 200)) randomized = VectorOfArray([0.01randn(2) for i in 1:10]) data = convert(Array, randomized) @@ -152,6 +163,40 @@ end @test a.u[1][1] == 1.0 end +@testset "recursivecopy! destination shorter than source throws" begin + b = [MVector{2, Float64}(0, 0)] + a = [MVector{2, Float64}(1, 1), MVector{2, Float64}(2, 2)] + @test_throws DimensionMismatch recursivecopy!(b, a) + + # matching lengths still work + b2 = [MVector{2, Float64}(0, 0), MVector{2, Float64}(0, 0)] + recursivecopy!(b2, a) + @test b2[1] == [1.0, 1.0] + @test b2[2] == [2.0, 2.0] +end + +@testset "copyat_or_push! rejects indices below firstindex" begin + vals = [[1, 2], [3, 4]] + @test_throws BoundsError copyat_or_push!(vals, 0, [9, 9]) + @test_throws BoundsError copyat_or_push!(vals, -1, [9, 9]) + @test vals == [[1, 2], [3, 4]] # unchanged by the rejected calls + + # normal update and append still work + copyat_or_push!(vals, 1, [9, 9]) + @test vals[1] == [9, 9] + copyat_or_push!(vals, 3, [5, 6]) + @test vals == [[9, 9], [3, 4], [5, 6]] + + # a 0-based AbstractVector's own index 0 is in-bounds and must still + # update, matching master: the bounds check is against `firstindex(a)`, + # not the literal `1`. + z = ZeroVec([[1, 2], [3, 4]]) + copyat_or_push!(z, 0, [9, 9]) + @test z[0] == [9, 9] + @test z[1] == [3, 4] + @test_throws BoundsError copyat_or_push!(z, -1, [9, 9]) +end + # OrdinaryDiffEq.jl#1365: recursivecopy must not broadcast-assign into an # immutable outer container such as SVector of arrays. @testset "recursivecopy VectorOfArray with SVector outer (ODE#1365)" begin