diff --git a/src/lazy.jl b/src/lazy.jl index e3d91bcd..7efc7fa4 100644 --- a/src/lazy.jl +++ b/src/lazy.jl @@ -100,3 +100,9 @@ function Base.showarg(io::IO, s::LazyRows{T}, toplevel) where T showfields(io, Tuple(components(s))) toplevel && print(io, " with eltype LazyRow{", T, "}") end + +# "materialize" / "evaluate" +function Base.convert(::Type{T}, c::LazyRow{T}) where {T} + columns, index = getfield(c, 1), getfield(c, 2) + columns[index...] +end diff --git a/src/structarray.jl b/src/structarray.jl index a2f5afb9..a19b9c49 100644 --- a/src/structarray.jl +++ b/src/structarray.jl @@ -348,24 +348,63 @@ function Base.view(s::StructArray{T, N, C}, I...) where {T, N, C} StructArray{T}(map(v -> view(v, I...), components(s))) end -Base.@propagate_inbounds function Base.setindex!(s::StructArray{<:Any, <:Any, <:Any, CartesianIndex{N}}, vals, I::Vararg{Int, N}) where {N} +""" +By default, this package behave just as `<:Base.AbstractArray` in terms of `convert`. +It does not do any "structural" conversion. +Exceptions include `NamedTuples`. +""" +struct Structural{T} + _::T +end + +maybe_structural(x) = x + +# this exception needs to be removed to comply with `AbstractArray` +maybe_structural(x::NamedTuple) = Structural(x) + +function Base.convert(T, vals::Structural) + # TODO more efficient + s = StructVector{T}(undef, 0) + foreachfield(push!, s, vals._) + only(s) +end + +Base.@propagate_inbounds function Base.setindex!(s::StructArray{T, <:Any, <:Any, CartesianIndex{N}}, vals, I::Vararg{Int, N}) where {T, N} + setindex!(s, convert(T, maybe_structural(vals)), I...) +end + +Base.@propagate_inbounds function Base.setindex!(s::StructArray{T, <:Any, <:Any, Int}, vals, I::Int) where {T} + setindex!(s, convert(T, maybe_structural(vals)), I) +end + +Base.@propagate_inbounds function Base.setindex!(s::StructArray{T, <:Any, <:Any, CartesianIndex{N}}, vals::T, I::Vararg{Int, N}) where {T, N} @boundscheck checkbounds(s, I...) foreachfield((col, val) -> (@inbounds col[I...] = val), s, vals) s end -Base.@propagate_inbounds function Base.setindex!(s::StructArray{<:Any, <:Any, <:Any, Int}, vals, I::Int) +Base.@propagate_inbounds function Base.setindex!(s::StructArray{T, <:Any, <:Any, Int}, vals::T, I::Int) where {T} @boundscheck checkbounds(s, I) foreachfield((col, val) -> (@inbounds col[I] = val), s, vals) s end -function Base.push!(s::StructVector, vals) +function Base.push!(s::StructVector{T}, vals) where {T} + push!(s, convert(T, maybe_structural(vals))) +end + +function Base.push!(s::StructVector{T}, vals::T) where T foreachfield(push!, s, vals) return s end function Base.append!(s::StructVector, vals::StructVector) + # TODO, more efficient implementation + foldl(push!, vals; init = s) + return s +end + +function Base.append!(s::StructVector{T}, vals::StructVector{T}) where T foreachfield(append!, s, vals) return s end diff --git a/test/runtests.jl b/test/runtests.jl index f9baf42e..56d60b43 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -809,3 +809,46 @@ Base.similar(bc::Broadcast.Broadcasted{Broadcast.ArrayStyle{MyArray}}, ::Type{El s = StructArray{ComplexF64}((MyArray(rand(2,2)), MyArray(rand(2,2)))) @test_throws MethodError s .+ s end + +# define two types that are "structurally" equivalent: +struct Foo1 + x::Int + y::Int +end + +struct Foo2 + x::Int + y::Int +end + +# but not otherwise interchangeable: +Base.convert(::Type{Foo1}, v::Foo2) = Foo1(-v.x, v.x + v.y) +# Base.convert(::Type{Foo2}, v::Foo1) = Foo2(-v.x, v.y + v.x) + +# should_be_identity(v::Foo1) = convert(Foo1, convert(Foo2, v)) +# should_be_identity(v::Foo2) = convert(Foo2, convert(Foo1, v)) + +function check_setindex!_convert(TArray) + a = TArray{Foo1}(undef, 1) + a[1] = Foo2(1, 2) + a[1] == Foo1(-1, 3) +end + +function check_push!_convert(TArray) + a = TArray{Foo1}(undef, 0) + push!(a, Foo2(1, 2)) + a[1] == Foo1(-1, 3) +end + +@testset "convert on setindex!/push!" begin + # check behavior of `Base.Array` + @test check_setindex!_convert(Array) + @test check_push!_convert(Vector) + + @test check_setindex!_convert(StructArray) + @test check_push!_convert(StructVector) + + soa = StructVector{Foo1}(undef, 0) + append!(soa, StructVector([Foo2(1, 2)])) + @test soa[1] == Foo1(-1, 3) +end