diff --git a/NEWS.md b/NEWS.md new file mode 100644 index 0000000..3b1f913 --- /dev/null +++ b/NEWS.md @@ -0,0 +1,24 @@ +# StaticThings.jl news + +## v0.3.0 + +Breaking: + +* `maybestatic_reshape(A, sz)` only returns a static array if `A` is one. + It used to wrap any array in a `StaticArrays.SArray` for a static `sz`, + which copies device arrays and traced arrays to the host. The input + decides now: a static-size variate taken from a plain `Vector` is a + plain array or view, one taken from an `SVector` stays static. + +New: + +* `size_dims(sz)`, the inverse of `canonical_size`, and a type-level + `axes2size(::Type{<:Tuple{Vararg{StaticOneToLike}}})`. +* `static_mapreduce(f, op, T)`, `static_reduce(op, T)`, `static_all(f, T)` + and `static_any(f, T)`, pairwise folds over the element types of a tuple + type that infer as compile-time constants. +* `sum_leading_dims(A, n)`, `drop_leading_dims(A, n)`, + `merge_leading_dims(A, n)` and `all_leading_dims(A, n)` over a static + number of leading dimensions, keeping static arrays static. +* `maybestatic_view(A, r)` and `split_at(A, n)`, views and splits of + vectors and tuples that stay static for static inputs and indices. diff --git a/Project.toml b/Project.toml index ad1a3d9..d92fa99 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "StaticThings" uuid = "7e4b4f32-fbf9-4b74-9510-4d15222ac973" -version = "0.2.0" +version = "0.3.0" [deps] FillArrays = "1a297f60-69ca-5386-bcde-b61e274b549b" diff --git a/docs/src/index.md b/docs/src/index.md index d612252..a987d6d 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -15,7 +15,13 @@ Built around these alias types, StaticThings provides: * Length/size/axes query functions: [`maybestatic_length`](@ref), [`maybestatic_size`](@ref), [`maybestatic_axes`](@ref), [`maybestatic_eachindex`](@ref), [`maybestatic_first`](@ref), [`maybestatic_last`](@ref) and [`size_from_type`](@ref). -* Size/axes conversion functions: [`axes2size`](@ref), [`size2axes`](@ref), [`size2length`](@ref), [`asaxes`](@ref), [`canonical_indices`](@ref), [`canonical_size`](@ref) and [`canonical_axes`](@ref). +* Size/axes conversion functions: [`axes2size`](@ref), [`size2axes`](@ref), [`size2length`](@ref), [`size_dims`](@ref), [`asaxes`](@ref), [`canonical_indices`](@ref), [`canonical_size`](@ref) and [`canonical_axes`](@ref). + +* Functions over a static number of leading array dimensions: [`sum_leading_dims`](@ref), [`drop_leading_dims`](@ref), [`merge_leading_dims`](@ref) and [`all_leading_dims`](@ref). + +* Static-preserving views and splits: [`maybestatic_view`](@ref) and [`split_at`](@ref). + +* Constant-folding reductions over the element types of tuple types: [`static_mapreduce`](@ref), [`static_reduce`](@ref), [`static_all`](@ref) and [`static_any`](@ref). When creating arrays, StaticThings prefers allocation-free types like `StaticArrays.SArray` and `FillArrays.Fill` where feasible. diff --git a/src/StaticThings.jl b/src/StaticThings.jl index 683b0f5..4e6faf9 100644 --- a/src/StaticThings.jl +++ b/src/StaticThings.jl @@ -8,13 +8,15 @@ static and non-static values and arrays. """ module StaticThings +using Base: Broadcast + using FillArrays: FillArrays, Fill using Static: Static, StaticInteger, static, dynamic using StaticArrayInterface: StaticArrayInterface -using StaticArrays: StaticArrays, StaticArray, SArray, SVector +using StaticArrays: StaticArrays, StaticArray, StaticVector, SArray, SVector include("aliases.jl") include("tools.jl") diff --git a/src/tools.jl b/src/tools.jl index 3b066e5..1910076 100644 --- a/src/tools.jl +++ b/src/tools.jl @@ -93,22 +93,20 @@ end """ - maybestatic_reshape(A, sz) + maybestatic_reshape(A, sz::SizeLike) -Reshapes array `A` to sizes `sz`. +Reshapes array `A` to size `sz`. If `A` is a static array and `sz` is static, the result is a static array. +Other arrays are reshaped to the non-static size, so that device arrays and +traced arrays keep their type. """ function maybestatic_reshape end export maybestatic_reshape -maybestatic_reshape(A, sz) = reshape(A, canonical_size(sz)) -function maybestatic_reshape(A, sz::StaticSizeLike) - SArray(reshape(A, canonical_size(sz))) -end -function maybestatic_reshape(A::StaticArray, sz::Tuple{Vararg{StaticInteger}}) - staticarray_type(eltype(A), canonical_size(sz))(Tuple(A)) -end +@inline maybestatic_reshape(A, sz::SizeLike) = reshape(A, asnonstatic(sz)) +@inline maybestatic_reshape(A::StaticArray, sz::StaticSizeLike) = + reshape(A, canonical_size(sz)) """ @@ -167,10 +165,13 @@ export maybestatic_axes """ - StaticThings.axes2size(x::Tuple) - StaticThings.axes2size(x::StaticArrays.Size) + StaticThings.axes2size(axs::AxesLike) + StaticThings.axes2size(::Type{<:Tuple{Vararg{StaticOneToLike}}}) Get the size of a collection-like object from its axes. + +The type-level form gives the size of collections with statically sized +axes without an instance at hand. """ function axes2size end export axes2size @@ -178,6 +179,18 @@ export axes2size @inline axes2size(::Tuple{}) = () @inline axes2size(axs::Tuple) = canonical_size(map(maybestatic_length, axs)) +@inline axes2size(::Type{A}) where {A<:Tuple{Vararg{StaticOneToLike}}} = + canonical_size(_static_axes_lengths(A)) + +@inline _static_axes_lengths(::Type{Tuple{}}) = () +@inline _static_axes_lengths(::Type{A}) where {A<:Tuple} = ( + _static_oneto_length(Base.tuple_type_head(A)), + _static_axes_lengths(Base.tuple_type_tail(A))..., +) + +@inline _static_oneto_length(::Type{<:StaticArrays.SOneTo{N}}) where {N} = static(N) +@inline _static_oneto_length(::Type{<:Static.SOneTo{N}}) where {N} = static(N) + """ size2axes(sz::Tuple) @@ -324,6 +337,21 @@ export canonical_size @inline canonical_size(sz::Tuple{Vararg{Static.StaticInteger}}) = StaticArrays.Size{map(dynamic, sz)}() + +""" + size_dims(sz::SizeLike)::Tuple{Vararg{IntegerLike}} + +Return the dimensions of the size `sz` as a tuple of dynamic or static +integers. + +Inverse of [`canonical_size`](@ref). +""" +function size_dims end +export size_dims + +@inline size_dims(sz::Tuple{Vararg{IntegerLike}}) = sz +@inline size_dims(::StaticArrays.Size{S}) where {S} = map(static, S) + """ canonical_axes(axs::AxesLike) @@ -373,3 +401,252 @@ end # Convert a `StaticArrayInterface.known_size` result to a canonical size: @inline _knownsize2size(::Type, ksz::Tuple{Vararg{Int}}) = canonical_size(static(ksz)) @inline _knownsize2size(::Type{AT}, ::Tuple) where {AT} = NoTypeSize{AT}() + + +""" + maybestatic_view(A, r::AbstractUnitRange) + maybestatic_view(A, from::IntegerLike, until::IntegerLike) + +The elements of the vector or tuple `A` over the index range `r`, resp. +from index `from` to index `until`. + +Static vectors and tuples give static results for static indices, other +vectors give a `view`. +""" +function maybestatic_view end +export maybestatic_view + +Base.@propagate_inbounds maybestatic_view(A, r::AbstractUnitRange) = + maybestatic_view(A, maybestatic_first(r), maybestatic_last(r)) + +Base.@propagate_inbounds function maybestatic_view( + A::AbstractVector, + from::IntegerLike, + until::IntegerLike, +) + view(A, dynamic(from):dynamic(until)) +end + +Base.@propagate_inbounds function maybestatic_view( + A::StaticVector, + from::StaticInteger{F}, + until::StaticInteger{U}, +) where {F,U} + SVector{U - F + 1,eltype(A)}(maybestatic_view(Tuple(A), from, until)) +end + +Base.@propagate_inbounds function maybestatic_view( + tpl::Tuple, + from::IntegerLike, + until::IntegerLike, +) + ntuple(i -> tpl[from+i-1], Val(dynamic(until - from + one(from)))) +end + + +""" + split_at(A::AbstractVector, n::IntegerLike) + +Split `A` into its first `n` elements and the rest. + +Static vectors give static results for a static `n`. +""" +function split_at end +export split_at + +@inline function split_at(A::AbstractVector, n::IntegerLike) + idxs = maybestatic_eachindex(A) + i_first = maybestatic_first(idxs) + i_last = maybestatic_last(idxs) + maybestatic_view(A, i_first, i_first + n - one(n)), + maybestatic_view(A, i_first + n, i_last) +end + + +""" + static_mapreduce(f, op, ::Type{<:Tuple}) + +Reduce `f` of the element types of a tuple type with `op`. + +Folded pairwise from the right, so that the result is a compile-time +constant where `mapreduce` over a tuple of values isn't (Julia 1.10). +Empty tuple types have no result, pass an `init` to `op` yourself. +""" +function static_mapreduce end +export static_mapreduce + +@inline static_mapreduce(f::F, ::OP, ::Type{Tuple{T}}) where {F,OP,T} = f(T) +@inline function static_mapreduce(f::F, op::OP, ::Type{T}) where {F,OP,T<:Tuple} + op(f(Base.tuple_type_head(T)), static_mapreduce(f, op, Base.tuple_type_tail(T))) +end +@noinline static_mapreduce(::F, ::OP, ::Type{Tuple{}}) where {F,OP} = + throw(ArgumentError("Can't reduce over the element types of an empty tuple type")) + + +""" + static_reduce(op, ::Type{<:Tuple}) + +Reduce the element types of a tuple type with `op`. + +The `f = identity` case of [`static_mapreduce`](@ref). +""" +function static_reduce end +export static_reduce + +@inline static_reduce(op::OP, ::Type{T}) where {OP,T<:Tuple} = static_mapreduce(identity, op, T) + + +""" + static_all(f, ::Type{<:Tuple}) + +Whether `f` holds for every element type of a tuple type. + +Returns `Static.True` or `Static.False`, folded pairwise so that the result +is a compile-time constant where `all` isn't (Julia 1.10). +""" +function static_all end +export static_all + +@inline static_all(::F, ::Type{Tuple{}}) where {F} = static(true) +@inline function static_all(f::F, ::Type{T}) where {F,T<:Tuple} + static(f(Base.tuple_type_head(T))) & static_all(f, Base.tuple_type_tail(T)) +end + + +""" + static_any(f, ::Type{<:Tuple}) + +Whether `f` holds for any element type of a tuple type. + +Returns `Static.True` or `Static.False`, folded pairwise so that the result +is a compile-time constant where `any` isn't (Julia 1.10). +""" +function static_any end +export static_any + +@inline static_any(::F, ::Type{Tuple{}}) where {F} = static(false) +@inline function static_any(f::F, ::Type{T}) where {F,T<:Tuple} + static(f(Base.tuple_type_head(T))) | static_any(f, Base.tuple_type_tail(T)) +end + + +# Dimensions of an array as a tuple of dynamic or static integers: +@inline _dims_of(A) = size_dims(maybestatic_size(A)) + +@noinline _throw_too_few_dims(n, N) = + throw(DimensionMismatch("Can't operate on the $N leading dimensions of a $n-dimensional object")) + + +""" + drop_leading_dims(A::AbstractArray, ::StaticInteger{N}) + +Drop the `N` leading (singleton) dimensions of `A`. + +Reshaping instead of `dropdims` keeps static arrays static and infers. +""" +function drop_leading_dims end +export drop_leading_dims + +@inline function drop_leading_dims(A::AbstractArray, ::StaticInteger{N}) where {N} + dims = _dims_of(A) + length(dims) >= N || _throw_too_few_dims(length(dims), N) + maybestatic_reshape(A, ntuple(i -> dims[N+i], Val(length(dims) - N))) +end + + +""" + merge_leading_dims(A::AbstractArray, ::StaticInteger{N}) + +Merge the `N` leading dimensions of `A` into one. + +`N == 0` adds a leading dimension of size one. Static arrays stay static. +""" +function merge_leading_dims end +export merge_leading_dims + +@inline merge_leading_dims(A::AbstractArray, ::StaticInteger{0}) = + maybestatic_reshape(A, (static(1), _dims_of(A)...)) + +@inline function merge_leading_dims(A::AbstractArray, ::StaticInteger{N}) where {N} + dims = _dims_of(A) + length(dims) >= N || _throw_too_few_dims(length(dims), N) + lead = ntuple(i -> dims[i], Val(N)) + maybestatic_reshape(A, (prod(lead), ntuple(i -> dims[N+i], Val(length(dims) - N))...)) +end + + +""" + all_leading_dims(A::AbstractArray{Bool}, ::StaticInteger{N}) + +Reduce `A` with `all` over its `N` leading dimensions. + +Returns an array over the remaining dimensions, `true` or `false` if there +are none. Static arrays stay static. +""" +function all_leading_dims end +export all_leading_dims + +@inline all_leading_dims(A::AbstractArray{Bool,N}, ::StaticInteger{N}) where {N} = all(A) +@inline function all_leading_dims(A::AbstractArray{Bool}, ::StaticInteger{N}) where {N} + drop_leading_dims(all(A; dims = ntuple(identity, Val(N))), static(N)) +end + +# StaticArrays only reduces over a single dimension at a time: +@inline all_leading_dims(A::StaticArray{<:Any,Bool,N}, ::StaticInteger{N}) where {N} = all(A) +@inline function all_leading_dims(A::StaticArray{<:Any,Bool}, ::StaticInteger{N}) where {N} + drop_leading_dims(_all_dims_seq(A, static(N)), static(N)) +end + +@inline _all_dims_seq(A::AbstractArray, ::StaticInteger{0}) = A +@inline _all_dims_seq(A::AbstractArray, ::StaticInteger{N}) where {N} = + _all_dims_seq(all(A; dims = N), static(N - 1)) + + +""" + sum_leading_dims(A, ::StaticInteger{N}) + +Sum `A` over its `N` leading dimensions. + +Returns an array over the remaining dimensions, a number if there are none. +Static arrays stay static. Lazy broadcasts are reduced without +materialization where their style supports it. +""" +function sum_leading_dims end +export sum_leading_dims + +@inline sum_leading_dims(x::Number, ::StaticInteger{0}) = x +@noinline sum_leading_dims(::Number, ::StaticInteger{N}) where {N} = _throw_too_few_dims(0, N) + +@inline sum_leading_dims(A::AbstractArray, n::StaticInteger) = + _sum_leading_dims_impl(A, n, static(ndims(A))) + +@inline _sum_leading_dims_impl(A::AbstractArray, ::StaticInteger{0}, ::StaticInteger) = A +@inline _sum_leading_dims_impl(A::AbstractArray, ::StaticInteger{0}, ::StaticInteger{0}) = A +@inline _sum_leading_dims_impl(A::AbstractArray, ::StaticInteger{N}, ::StaticInteger{N}) where {N} = sum(A) +@inline function _sum_leading_dims_impl(A::AbstractArray, ::StaticInteger{N}, ::StaticInteger) where {N} + drop_leading_dims(_sum_dims_seq(A, static(N)), static(N)) +end + +@inline _sum_dims_seq(A::AbstractArray, ::StaticInteger{0}) = A +@inline _sum_dims_seq(A::AbstractArray, ::StaticInteger{N}) where {N} = + _sum_dims_seq(sum(A; dims = N), static(N - 1)) + +# Broadcast styles whose lazy reductions work without materialization: +const _EagerReducibleBroadcast = Broadcast.Broadcasted{ + <:Union{Broadcast.DefaultArrayStyle,StaticArrays.StaticArrayStyle}, +} + +@inline sum_leading_dims(bc::Broadcast.Broadcasted, n::StaticInteger) = + _sum_leading_dims_lazy(bc, n, static(ndims(bc))) + +@inline _sum_leading_dims_lazy(bc::Broadcast.Broadcasted, ::StaticInteger{0}, ::StaticInteger) = bc +@inline _sum_leading_dims_lazy(bc::Broadcast.Broadcasted, ::StaticInteger{0}, ::StaticInteger{0}) = bc +@inline _sum_leading_dims_lazy(bc::_EagerReducibleBroadcast, ::StaticInteger{0}, ::StaticInteger{0}) = bc +@inline function _sum_leading_dims_lazy(bc::_EagerReducibleBroadcast, ::StaticInteger{N}, ::StaticInteger{N}) where {N} + # An empty broadcast has no neutral element to start from, the empty + # array it materializes to has one: + length(bc) == 0 ? sum(copy(bc)) : sum(bc) +end +@inline _sum_leading_dims_lazy(bc::Broadcast.Broadcasted, ::StaticInteger{N}, ::StaticInteger{N}) where {N} = sum(copy(bc)) +@inline _sum_leading_dims_lazy(bc::Broadcast.Broadcasted, n::StaticInteger, ::StaticInteger) = + sum_leading_dims(copy(bc), n) diff --git a/test/test_tools.jl b/test/test_tools.jl index a8b13db..feb4ca8 100644 --- a/test/test_tools.jl +++ b/test/test_tools.jl @@ -159,19 +159,20 @@ using StaticArrays: SArray, SVector rshpFA = Fill(v, sz) rshpSA = SArray{Tuple{sz...},T}(A) + # Only static arrays become static arrays, other arrays keep their type: @test @inferred(maybestatic_reshape(A, sz)) == rshpA @test typeof(maybestatic_reshape(A, sz)) == typeof(rshpA) @test @inferred(maybestatic_reshape(A, sasz)) == rshpA - @test maybestatic_reshape(A, sasz) isa SArray + @test typeof(maybestatic_reshape(A, sasz)) == typeof(rshpA) @test @inferred(maybestatic_reshape(A, sisz)) == rshpA - @test maybestatic_reshape(A, sisz) isa SArray + @test typeof(maybestatic_reshape(A, sisz)) == typeof(rshpA) @test @inferred(maybestatic_reshape(FA, sz)) == rshpFA @test typeof(maybestatic_reshape(FA, sz)) == typeof(rshpFA) @test @inferred(maybestatic_reshape(FA, sasz)) == rshpFA - @test maybestatic_reshape(FA, sasz) isa SArray + @test typeof(maybestatic_reshape(FA, sasz)) == typeof(rshpFA) @test @inferred(maybestatic_reshape(FA, sisz)) == rshpFA - @test maybestatic_reshape(FA, sisz) isa SArray + @test typeof(maybestatic_reshape(FA, sisz)) == typeof(rshpFA) @test @inferred(maybestatic_reshape(SA, sz)) == rshpA @test maybestatic_reshape(SA, sz) isa Base.ReshapedArray{T,3,<:SVector} @@ -179,7 +180,14 @@ using StaticArrays: SArray, SVector @test @inferred(maybestatic_reshape(SA, sisz)) === rshpSA @test @inferred(maybestatic_reshape(SVector(v), ())) === SArray{Tuple{},T,0,1}(v) - @test @inferred(maybestatic_reshape([v], ())) === SArray{Tuple{},T,0,1}(v) + @test @inferred(maybestatic_reshape([v], ())) == fill(v) + @test typeof(maybestatic_reshape([v], ())) == typeof(fill(v)) + + @test @inferred(size_dims(sz)) === sz + @test @inferred(size_dims(sasz)) === sisz + @test @inferred(size_dims(sisz)) === sisz + @test @inferred(size_dims(())) === () + @test @inferred(canonical_size(size_dims(sasz))) === sasz @test @inferred(maybestatic_length(5)) === static(1) @test @inferred(maybestatic_length(())) === static(0) @@ -352,3 +360,111 @@ using StaticArrays: SArray, SVector @test @inferred(size_from_type(typeof(A))) === NoTypeSize{typeof(A)}() @test @inferred(size_from_type(String)) === NoTypeSize{String}() end + + +@testset "static type reductions" begin + @test @inferred(static_all(T -> T <: Integer, Tuple{})) === static(true) + @test @inferred(static_all(T -> T <: Integer, Tuple{Int,Bool})) === static(true) + @test @inferred(static_all(T -> T <: Integer, Tuple{Int,Float64})) === static(false) + @test @inferred(static_all(T -> static(T <: Integer), Tuple{Int,Bool})) === static(true) + + @test @inferred(static_any(T -> T <: Integer, Tuple{})) === static(false) + @test @inferred(static_any(T -> T <: Integer, Tuple{Float64,Bool})) === static(true) + @test @inferred(static_any(T -> T <: Integer, Tuple{Float64,String})) === static(false) + + @test @inferred(static_mapreduce(sizeof, +, Tuple{Int32,Int64,Int16})) === 14 + @test @inferred(static_reduce(promote_type, Tuple{Int,Float32})) === Float32 + @test @inferred(static_reduce(promote_type, Tuple{Int})) === Int + @test @inferred(static_mapreduce(T -> static(T <: Integer), &, Tuple{Int,Bool})) === + static(true) + @test_throws ArgumentError static_reduce(+, Tuple{}) + @test_throws ArgumentError static_mapreduce(sizeof, +, Tuple{}) + + # The results must be constants, not just inferred: + f_all() = static_all(T -> T <: Integer, Tuple{Int,Bool,Float64}) + f_any() = static_any(T -> T <: Integer, Tuple{Float64,Bool}) + f_red() = static_mapreduce(T -> static(sizeof(T)), +, Tuple{Int32,Int64}) + @test @inferred(Static.False, f_all()) === static(false) + @test @inferred(Static.True, f_any()) === static(true) + @test @inferred(Static.StaticInt{12}, f_red()) === static(12) +end + + +@testset "leading dimensions" begin + A = rand(2, 3, 4) + SA = SArray{Tuple{2,3,4}}(A) + + @test @inferred(sum_leading_dims(4.2, static(0))) === 4.2 + @test_throws DimensionMismatch sum_leading_dims(4.2, static(1)) + + @test @inferred(sum_leading_dims(A, static(0))) === A + @test @inferred(sum_leading_dims(A, static(1))) ≈ dropdims(sum(A, dims = 1), dims = 1) + @test @inferred(sum_leading_dims(A, static(2))) ≈ + dropdims(sum(A, dims = (1, 2)), dims = (1, 2)) + @test @inferred(sum_leading_dims(A, static(3))) ≈ sum(A) + + @test @inferred(sum_leading_dims(SA, static(1))) isa SArray{Tuple{3,4}} + @test @inferred(sum_leading_dims(SA, static(1))) ≈ sum_leading_dims(A, static(1)) + @test @inferred(sum_leading_dims(SA, static(3))) ≈ sum(A) + + bc = Broadcast.instantiate(Broadcast.broadcasted(+, A, 1)) + @test @inferred(sum_leading_dims(bc, static(3))) ≈ sum(A .+ 1) + @test @inferred(sum_leading_dims(bc, static(1))) ≈ sum_leading_dims(A .+ 1, static(1)) + sbc = Broadcast.instantiate(Broadcast.broadcasted(+, SA, 1)) + @test @inferred(sum_leading_dims(sbc, static(3))) ≈ sum(A .+ 1) + + @test @inferred(drop_leading_dims(reshape(A, 1, 1, 2, 3, 4), static(2))) == A + @test @inferred(drop_leading_dims(SArray{Tuple{1,3,4}}(A[1:1, :, :]), static(1))) isa + SArray{Tuple{3,4}} + @test_throws DimensionMismatch drop_leading_dims(A, static(4)) + + @test @inferred(merge_leading_dims(A, static(0))) == reshape(A, 1, 2, 3, 4) + @test @inferred(merge_leading_dims(A, static(2))) == reshape(A, 6, 4) + @test @inferred(merge_leading_dims(SA, static(2))) isa SArray{Tuple{6,4}} + @test @inferred(merge_leading_dims(SA, static(0))) isa SArray{Tuple{1,2,3,4}} + @test @inferred(merge_leading_dims(A, static(3))) == reshape(A, 24) + + B = A .> 0.5 + SB = SA .> 0.5 + @test @inferred(all_leading_dims(B, static(3))) === all(B) + @test @inferred(all_leading_dims(B, static(1))) == + dropdims(all(B, dims = 1), dims = 1) + @test @inferred(all_leading_dims(SB, static(1))) isa SArray{Tuple{3,4},Bool} + @test all_leading_dims(SB, static(1)) == all_leading_dims(B, static(1)) + @test @inferred(all_leading_dims(SB, static(3))) === all(B) + @test @inferred(all_leading_dims(B, static(2))) == + dropdims(all(B, dims = (1, 2)), dims = (1, 2)) +end + + +@testset "vector splitting" begin + A = rand(6) + SA = SVector{6}(A) + tpl = Tuple(A) + + @test @inferred(maybestatic_view(A, 2, 4)) == A[2:4] + @test @inferred(maybestatic_view(A, 2, 4)) isa SubArray + @test @inferred(maybestatic_view(SA, static(2), static(4))) === SVector{3}(A[2:4]) + @test @inferred(maybestatic_view(tpl, static(2), static(4))) === Tuple(A[2:4]) + + @test @inferred(maybestatic_view(A, 2:4)) == A[2:4] + @test @inferred(maybestatic_view(SA, StaticOneTo(4))) === SVector{4}(A[1:4]) + @test @inferred(maybestatic_view(tpl, static(2):static(4))) === Tuple(A[2:4]) + + a, b = @inferred split_at(A, 2) + @test a == A[1:2] && b == A[3:6] + sa, sb = @inferred split_at(SA, static(2)) + @test sa === SVector{2}(A[1:2]) && sb === SVector{4}(A[3:6]) + sa0, sb0 = @inferred split_at(SA, static(0)) + @test sa0 === SVector{0,Float64}() && sb0 === SA +end + + +@testset "type-level axes2size" begin + @test @inferred(axes2size(Tuple{})) === StaticArrays.Size() + @test @inferred(axes2size(typeof((StaticOneTo(2), StaticOneTo(3))))) === + StaticArrays.Size(2, 3) + @test @inferred(axes2size(typeof((Static.SOneTo(2), StaticOneTo(3))))) === + StaticArrays.Size(2, 3) + @test @inferred(axes2size(typeof((StaticOneTo(2),)))) === StaticArrays.Size(2) +end