diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..700707ce --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 22b10837..d534fd99 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -23,12 +23,12 @@ jobs: arch: - x64 steps: - - uses: actions/checkout@v2 - - uses: julia-actions/setup-julia@v1 + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v2 with: version: ${{ matrix.version }} arch: ${{ matrix.arch }} - - uses: actions/cache@v1 + - uses: actions/cache@v4 env: cache-name: cache-artifacts with: @@ -41,6 +41,7 @@ jobs: - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-runtest@v1 - uses: julia-actions/julia-processcoverage@v1 - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v4 with: + token: ${{ secrets.CODECOV_TOKEN }} file: lcov.info diff --git a/.github/workflows/Documenter.yml b/.github/workflows/Documenter.yml index 7710aea8..a789f056 100644 --- a/.github/workflows/Documenter.yml +++ b/.github/workflows/Documenter.yml @@ -9,21 +9,20 @@ on: tags: '*' jobs: docs: + permissions: + contents: write + statuses: write name: Documentation runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: julia-actions/setup-julia@v1 + - uses: actions/checkout@v4 + - uses: julia-actions/setup-julia@v2 with: version: '1' - - run: | - julia --project -e ' - using Pkg - Pkg.instantiate()' - julia --project=docs -e ' - using Pkg - Pkg.instantiate()' - - run: julia --project=docs docs/make.jl + - name: Install dependencies + run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' + - name: Build and deploy env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} \ No newline at end of file + DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} + run: julia --project=docs/ docs/make.jl diff --git a/.github/workflows/Invalidations.yml b/.github/workflows/Invalidations.yml index 4d0004e8..66c86a36 100644 --- a/.github/workflows/Invalidations.yml +++ b/.github/workflows/Invalidations.yml @@ -16,15 +16,15 @@ jobs: if: github.base_ref == github.event.repository.default_branch runs-on: ubuntu-latest steps: - - uses: julia-actions/setup-julia@v1 + - uses: julia-actions/setup-julia@v2 with: version: '1' - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: julia-actions/julia-buildpkg@v1 - uses: julia-actions/julia-invalidations@v1 id: invs_pr - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: ref: ${{ github.event.repository.default_branch }} - uses: julia-actions/julia-buildpkg@v1 diff --git a/.github/workflows/TagBot.yml b/.github/workflows/TagBot.yml index 623860f7..2bacdb87 100644 --- a/.github/workflows/TagBot.yml +++ b/.github/workflows/TagBot.yml @@ -4,6 +4,22 @@ on: types: - created workflow_dispatch: + inputs: + lookback: + default: 3 +permissions: + actions: read + checks: read + contents: write + deployments: read + issues: read + discussions: read + packages: read + pages: read + pull-requests: read + repository-projects: read + security-events: read + statuses: read jobs: TagBot: if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' @@ -12,4 +28,4 @@ jobs: - uses: JuliaRegistries/TagBot@v1 with: token: ${{ secrets.GITHUB_TOKEN }} - ssh: ${{ secrets.DOCUMENTER_KEY }} \ No newline at end of file + ssh: ${{ secrets.DOCUMENTER_KEY }} diff --git a/Project.toml b/Project.toml index 91a87102..176f2bff 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "StructArrays" uuid = "09ab397b-f2b6-538f-b94a-2f83cf4a842a" -version = "0.6.16" +version = "0.6.18" [deps] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" @@ -27,18 +27,28 @@ StructArraysStaticArraysExt = "StaticArrays" StructArraysLinearAlgebraExt = "LinearAlgebra" [compat] -Adapt = "3.4" +Adapt = "3.4, 4" +Aqua = "0.8" ConstructionBase = "1" DataAPI = "1" +Documenter = "1" GPUArraysCore = "0.1.2" InfiniteArrays = "0.13" +JLArrays = "0.1" LinearAlgebra = "1" +OffsetArrays = "1" +PooledArrays = "1" +SparseArrays = "1" StaticArrays = "1.5.6" Tables = "1" +Test = "1" +TypedTables = "1" +WeakRefStrings = "1" julia = "1.6" [extras] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" GPUArraysCore = "46192b85-c4d5-4398-a991-12ede77f4527" InfiniteArrays = "4858937d-0d70-526a-a4dd-2d5cb5dd786c" @@ -53,4 +63,4 @@ TypedTables = "9d95f2ec-7b3d-5a63-8d20-e2491e220bb9" WeakRefStrings = "ea10d353-3f73-51f8-a26c-33c1cb351aa5" [targets] -test = ["Adapt", "Documenter", "GPUArraysCore", "InfiniteArrays", "JLArrays", "LinearAlgebra", "OffsetArrays", "PooledArrays", "SparseArrays", "StaticArrays", "Test", "TypedTables", "WeakRefStrings"] +test = ["Adapt", "Aqua", "Documenter", "GPUArraysCore", "InfiniteArrays", "JLArrays", "LinearAlgebra", "OffsetArrays", "PooledArrays", "SparseArrays", "StaticArrays", "Test", "TypedTables", "WeakRefStrings"] diff --git a/ext/StructArraysStaticArraysExt.jl b/ext/StructArraysStaticArraysExt.jl index 0c84f5b7..6e87707d 100644 --- a/ext/StructArraysStaticArraysExt.jl +++ b/ext/StructArraysStaticArraysExt.jl @@ -1,7 +1,7 @@ module StructArraysStaticArraysExt using StructArrays -using StaticArrays: StaticArray, FieldArray, tuple_prod +using StaticArrays: StaticArray, FieldArray, tuple_prod, SVector, MVector, SOneTo """ StructArrays.staticschema(::Type{<:StaticArray{S, T}}) where {S, T} @@ -22,7 +22,16 @@ which subtypes `FieldArray`. end end StructArrays.createinstance(::Type{T}, args...) where {T<:StaticArray} = T(args) -StructArrays.component(s::StaticArray, i) = getindex(s, i) +StructArrays.component(s::StaticArray, i::Integer) = getindex(s, i) + +function StructArrays.component(s::StructArray{<:Union{SVector,MVector}}, key::Symbol) + i = key == :x ? 1 : + key == :y ? 2 : + key == :z ? 3 : + key == :w ? 4 : + throw(ArgumentError("invalid key $key")) + StructArrays.component(s, i) +end # invoke general fallbacks for a `FieldArray` type. @inline function StructArrays.staticschema(T::Type{<:FieldArray}) @@ -31,6 +40,10 @@ end StructArrays.component(s::FieldArray, i) = invoke(StructArrays.component, Tuple{Any, Any}, s, i) StructArrays.createinstance(T::Type{<:FieldArray}, args...) = invoke(StructArrays.createinstance, Tuple{Type{<:Any}, Vararg}, T, args...) +# disambiguation +Base.similar(s::StructArray, S::Type, sz::Tuple{Union{Integer, Base.OneTo, SOneTo}, Vararg{Union{Union{Integer, Base.OneTo, SOneTo}}}}) = StructArrays._similar(s, S, sz) +Base.reshape(s::StructArray{T}, d::Tuple{SOneTo, Vararg{SOneTo}}) where {T} = StructArray{T}(map(x -> reshape(x, d), StructArrays.components(s))) + # Broadcast overload using StaticArrays: StaticArrayStyle, similar_type, Size, SOneTo using StaticArrays: broadcast_flatten, broadcast_sizes, first_statictype diff --git a/src/structarray.jl b/src/structarray.jl index 239c5d7d..0f13ad48 100644 --- a/src/structarray.jl +++ b/src/structarray.jl @@ -339,12 +339,7 @@ to map(c -> c[I...], Tuple(cols)) ``` """ -@inline get_ith(cols::NamedTuple, I...) = get_ith(Tuple(cols), I...) -@inline function get_ith(cols::Tuple, I...) - @inbounds r = first(cols)[I...] - return (r, get_ith(Base.tail(cols), I...)...) -end -@inline get_ith(::Tuple{}, I...) = () +@inline @generated get_ith(cols::Tup, I...) = :(Base.Cartesian.@ntuple $(fieldcount(cols)) i -> @inbounds cols[i][I...]) Base.@propagate_inbounds Base.getindex(x::StructArray, I...) = _getindex(x, to_indices(x, I)...) @@ -419,6 +414,13 @@ function Base.deleteat!(s::StructVector{T}, idxs) where T return StructVector{T}(t) end +@static if VERSION >= v"1.7.0" + function Base.keepat!(s::StructVector{T}, idxs) where T + t = map(Base.Fix2(keepat!, idxs), components(s)) + return StructVector{T}(t) + end +end + Base.copyto!(I::StructArray, J::StructArray) = (foreachfield(copyto!, I, J); I) function Base.copyto!(I::StructArray, doffs::Integer, J::StructArray, soffs::Integer, n::Integer) diff --git a/src/tables.jl b/src/tables.jl index d6ac2248..432a75b2 100644 --- a/src/tables.jl +++ b/src/tables.jl @@ -12,6 +12,14 @@ function _schema(::Type{T}) where {T<:NTuple{N, Any}} where N return Tables.Schema{ntuple(identity, N), T} end +StructArray(cols::Tables.AbstractColumns) = StructArray(Tables.columntable(cols)) +StructArray{T}(cols::Tables.AbstractColumns) where {T} = StructArray{T}(Tables.columntable(cols)) + +# convert from any Tables-compliant object +fromtable(cols) = StructArray(Tables.columntable(cols)) +Tables.materializer(::Type{<:StructArray}) = fromtable +Tables.materializer(::StructArray) = fromtable # Tables documentation says it's not needed, but actually it is + function try_compatible_columns(rows::R, s::StructArray) where {R} Tables.isrowtable(rows) && Tables.columnaccess(rows) || return nothing T = eltype(rows) diff --git a/test/runtests.jl b/test/runtests.jl index 42ca4502..b2c35a5a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -12,9 +12,10 @@ using LinearAlgebra using Test using SparseArrays using InfiniteArrays +import Aqua using Documenter: doctest -if Base.VERSION >= v"1.6" && Int === Int64 +if Base.VERSION == v"1.6" && Int === Int64 doctest(StructArrays) end @@ -259,6 +260,10 @@ end @test d == c == StructArray(a=[1,10,2,3], b=[2,20,3,4], c=["a","A","b","c"]) d = deleteat!(c, 2) @test d == c == StructArray(a=[1,2,3], b=[2,3,4], c=["a","b","c"]) + if Base.VERSION >= v"1.7.0" + d = keepat!(c, 2) + @test d == c == StructArray(a=[2], b=[3], c=["b"]) + end c = StructArray(a=[1], b=[2], c=["a"]) d = [(a=10, b=20, c="A")] @@ -295,6 +300,10 @@ end @test d == c == StructArray{C}(a=[1,10,2,3], b=[2,20,3,4], c=["a","A","b","c"]) d = deleteat!(c, 2) @test d == c == StructArray{C}(a=[1,2,3], b=[2,3,4], c=["a","b","c"]) + if Base.VERSION >= v"1.7.0" + d = keepat!(c, 2) + @test d == c == StructArray{C}(a=[2], b=[3], c=["b"]) + end c = StructArray{C}(a=[1], b=[2], c=["a"]) d = [C(10, 20, "A")] @@ -717,6 +726,22 @@ end # Testing integer column "names": @test invoke(append!, Tuple{StructVector,Any}, StructArray(([0],)), StructArray(([1],))) == StructArray(([0, 1],)) + + dtab = (a=[1,2],) |> Tables.dictcolumntable + @test StructArray(dtab) == [(a=1,), (a=2,)] + @test StructArray{NamedTuple{(:a,), Tuple{Float64}}}(dtab) == [(a=1.,), (a=2.,)] + @test StructVector{NamedTuple{(:a,), Tuple{Float64}}}(dtab) == [(a=1,), (a=2,)] + + tblbase = (a=[1,2], b=["3", "4"]) + @testset for tblfunc in [Tables.columntable, Tables.rowtable, Tables.dictcolumntable, Tables.dictrowtable] + tbl = tblfunc(tblbase) + sa = StructArrays.fromtable(tbl) + @test sa::StructArray == [(a=1, b="3"), (a=2, b="4")] + sa = Tables.materializer(StructArray)(tbl) + @test sa::StructArray == [(a=1, b="3"), (a=2, b="4")] + sa = Tables.materializer(sa)(tbl) + @test sa::StructArray == [(a=1, b="3"), (a=2, b="4")] + end end struct S @@ -1405,6 +1430,12 @@ end @test StructArrays.components(x) == ([1.0,2.0], [2.0,3.0]) @test x .+ y == StructArray([StaticVectorType{2}(Float64[2*i+1;2*i+3]) for i = 1:2]) end + for StaticVectorType = [SVector, MVector] + x = @inferred StructArray([StaticVectorType{2}(Float64[i;i+1]) for i = 1:2]) + # numbered and named property access: + @test x.:1 == [1.0,2.0] + @test x.y == [2.0,3.0] + end # test broadcast + components for general arrays for StaticArrayType = [SArray, MArray, SizedArray] x = @inferred StructArray([StaticArrayType{Tuple{1,2}}(ones(1,2) .+ i) for i = 0:1]) @@ -1413,6 +1444,14 @@ end @test x .+ y == StructArray([StaticArrayType{Tuple{1,2}}(3*ones(1,2) .+ 2*i) for i = 0:1]) end + let + # potential ambiguities + x = StructArray((SA[1,2], SA[1,2])) + @test eltype(similar(x)::StructArray) == eltype(x) + @test map(x->x, x)::StructArray == x + @test size(reshape(x, (SOneTo(2), SOneTo(1)))::StructArray) == (2, 1) + end + # test FieldVector constructor (see https://github.com/JuliaArrays/StructArrays.jl/issues/205) struct FlippedVec2D <: FieldVector{2,Float64} x::Float64 @@ -1527,3 +1566,9 @@ end @test mul!(ones(ComplexF64,size(v)), A, v, 2.0, 3.0) ≈ 2 * A * v .+ 3 end end + +@testset "project quality" begin + Aqua.test_all(StructArrays, ambiguities=(; broken=true)) +end + +end