diff --git a/Project.toml b/Project.toml index af0dbd8..acdfd74 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "Static" uuid = "aedffcd0-7271-4cad-89d0-dc628f76c6d3" authors = ["chriselrod", "ChrisRackauckas", "Tokazama"] -version = "0.7.8" +version = "0.8" [deps] IfElse = "615f187c-cbe4-4ef1-ba3b-2fcf58d6d173" diff --git a/src/Static.jl b/src/Static.jl index 971c74b..d5489ce 100644 --- a/src/Static.jl +++ b/src/Static.jl @@ -61,6 +61,16 @@ struct StaticInt{N} <: StaticInteger{N} StaticInt(::Val{N}) where {N} = StaticInt(N) end +""" + IntType(x::Integer) -> Union{Int,StaticInt} + +`IntType` is a union of `Int` and `StaticInt`. As a function, it ensures that `x` one of the +two. +""" +const IntType = Union{StaticInt, Int} +IntType(x::Integer) = Int(x) +IntType(@nospecialize x::Union{Int, StaticInt}) = x + include("float.jl") const StaticNumber{N} = Union{StaticInt{N}, StaticBool{N}, StaticFloat64{N}} @@ -260,6 +270,36 @@ _static_promote(::Nothing, ::Nothing) = nothing _static_promote(x, ::Nothing) = x _static_promote(::Nothing, y) = y +""" + static_promote(x::AbstractRange{<:Integer}, y::AbstractRange{<:Integer}) + +A type stable method for combining two equal ranges into a new range that preserves static +parameters. Throws an error if `x != y`. + +# Examples + +```julia +julia> static_promote(static(1):10, 1:static(10)) +static(1):static(10) + +julia> static_promote(1:2:9, static(1):static(2):static(9)) +static(1):static(2):static(9) +``` +""" +Base.@propagate_inbounds @inline function static_promote(x::AbstractUnitRange{<:Integer}, + y::AbstractUnitRange{<:Integer}) + fst = static_promote(static_first(x), static_first(y)) + lst = static_promote(static_last(x), static_last(y)) + return OptionallyStaticUnitRange(fst, lst) +end +Base.@propagate_inbounds @inline function static_promote(x::AbstractRange{<:Integer}, + y::AbstractRange{<:Integer}) + fst = static_promote(static_first(x), static_first(y)) + stp = static_promote(static_step(x), static_step(y)) + lst = static_promote(static_last(x), static_last(y)) + return _OptionallyStaticStepRange(fst, stp, lst) +end + Base.@propagate_inbounds function _promote_shape(a::Tuple{A, Vararg{Any}}, b::Tuple{B, Vararg{Any}}) where {A, B} (static_promote(getfield(a, 1), getfield(b, 1)), @@ -879,4 +919,6 @@ function Base.show(io::IO, m::MIME"text/plain", @nospecialize(x::NDIndex)) nothing end +include("ranges.jl") + end diff --git a/src/ranges.jl b/src/ranges.jl new file mode 100644 index 0000000..d5756c5 --- /dev/null +++ b/src/ranges.jl @@ -0,0 +1,368 @@ + +""" + OptionallyStaticUnitRange(start, stop) <: AbstractUnitRange{Int} + +Similar to `UnitRange` except each field may be an `Int` or `StaticInt`. An +`OptionallyStaticUnitRange` is intended to be constructed internally from other valid +indices. Therefore, users should not expect the same checks are used to ensure construction +of a valid `OptionallyStaticUnitRange` as a `UnitRange`. +""" +struct OptionallyStaticUnitRange{F <: IntType, L <: IntType} <: + AbstractUnitRange{Int} + start::F + stop::L + + function OptionallyStaticUnitRange(start::IntType, + stop::IntType) + new{typeof(start), typeof(stop)}(start, stop) + end + function OptionallyStaticUnitRange(start, stop) + OptionallyStaticUnitRange(IntType(start), IntType(stop)) + end + OptionallyStaticUnitRange(@nospecialize x::OptionallyStaticUnitRange) = x + function OptionallyStaticUnitRange(x::AbstractRange) + step(x) == 1 && return OptionallyStaticUnitRange(static_first(x), static_last(x)) + + errmsg(x) = throw(ArgumentError("step must be 1, got $(step(x))")) # avoid GC frame + errmsg(x) + end + function OptionallyStaticUnitRange{F, L}(x::AbstractRange) where {F, L} + OptionallyStaticUnitRange(x) + end + function OptionallyStaticUnitRange{StaticInt{F}, StaticInt{L}}() where {F, L} + new{StaticInt{F}, StaticInt{L}}() + end +end + +""" + OptionallyStaticStepRange(start, step, stop) <: OrdinalRange{Int,Int} + +Similarly to [`OptionallyStaticUnitRange`](@ref), `OptionallyStaticStepRange` permits +a combination of static and standard primitive `Int`s to construct a range. It +specifically enables the use of ranges without a step size of 1. It may be constructed +through the use of `OptionallyStaticStepRange` directly or using static integers with +the range operator (i.e., `:`). + +```julia +julia> using Static + +julia> x = static(2); + +julia> x:x:10 +static(2):static(2):10 + +julia> Static.OptionallyStaticStepRange(x, x, 10) +static(2):static(2):10 + +``` +""" +struct OptionallyStaticStepRange{F <: IntType, S <: IntType, + L <: IntType} <: OrdinalRange{Int, Int} + start::F + step::S + stop::L + + global function _OptionallyStaticStepRange(@nospecialize(start::IntType), + @nospecialize(step::IntType), + @nospecialize(stop::IntType)) + new{typeof(start), typeof(step), typeof(stop)}(start, step, stop) + end +end +@noinline function OptionallyStaticStepRange(@nospecialize(start::IntType), + ::StaticInt{0}, + @nospecialize(stop::IntType)) + throw(ArgumentError("step cannot be zero")) +end +# we don't need to check the `stop` if we know it acts like a unit range +function OptionallyStaticStepRange(@nospecialize(start::IntType), + step::StaticInt{1}, + @nospecialize(stop::IntType)) + _OptionallyStaticStepRange(start, step, stop) +end +function OptionallyStaticStepRange(@nospecialize(start::IntType), + @nospecialize(step::StaticInt), + @nospecialize(stop::IntType)) + _OptionallyStaticStepRange(start, step, _steprange_last(start, step, stop)) +end +function OptionallyStaticStepRange(start, step, stop) + OptionallyStaticStepRange(IntType(start), IntType(step), IntType(stop)) +end +function OptionallyStaticStepRange(@nospecialize(start::IntType), + step::Int, + @nospecialize(stop::IntType)) + if step === 0 + throw(ArgumentError("step cannot be zero")) + else + _OptionallyStaticStepRange(start, step, _steprange_last(start, step, stop)) + end +end +OptionallyStaticStepRange(@nospecialize x::OptionallyStaticStepRange) = x +function OptionallyStaticStepRange(x::AbstractRange) + _OptionallyStaticStepRange(IntType(first(x)), IntType(step(x)), IntType(last(x))) +end + +# to make StepRange constructor inlineable, so optimizer can see `step` value +@inline function _steprange_last(start::StaticInt, step::StaticInt, stop::StaticInt) + StaticInt(_steprange_last(Int(start), Int(step), Int(stop))) +end +@inline function _steprange_last(start::Union{StaticInt, Int}, + step::Union{StaticInt, Int}, + stop::Union{StaticInt, Int}) + _steprange_last(Int(start), Int(step), Int(stop)) +end +@inline function _steprange_last(start::Int, step::Int, stop::Int) + if stop === start + return stop + elseif step > 0 + if stop > start + return stop - rem(stop - start, step) + else + return start - 1 + end + else + if stop > start + return start + 1 + else + return stop + rem(start - stop, -step) + end + end +end + +""" + SUnitRange(start::Int, stop::Int) + +An alias for `OptionallyStaticUnitRange` where both the start and stop are known statically. +""" +const SUnitRange{F, L} = OptionallyStaticUnitRange{StaticInt{F}, StaticInt{L}} +SUnitRange(start::Int, stop::Int) = SUnitRange{start, stop}() + +""" + SOneTo(n::Int) + +An alias for `OptionallyStaticUnitRange` usfeul for statically sized axes. +""" +const SOneTo{L} = SUnitRange{1, L} +SOneTo(n::Int) = SOneTo{n}() + +const OptionallyStaticRange{F, L} = Union{OptionallyStaticUnitRange{F, L}, + OptionallyStaticStepRange{F, <:Any, L}} + +# these probide a generic method for extracting potentially static values. +static_first(x::Base.OneTo) = StaticInt(1) +static_first(x::Union{Base.Slice, Base.IdentityUnitRange}) = static_first(x.indices) +static_first(x::OptionallyStaticRange) = getfield(x, :start) +static_first(x) = first(x) + +static_step(@nospecialize x::AbstractUnitRange) = StaticInt(1) +static_step(x::OptionallyStaticStepRange) = getfield(x, :step) +static_step(x) = step(x) + +static_last(x::OptionallyStaticRange) = getfield(x, :stop) +static_last(x) = last(x) +static_last(x::Union{Base.Slice, Base.IdentityUnitRange}) = static_last(x.indices) + +Base.first(x::OptionallyStaticRange{Int}) = getfield(x, :start) +Base.first(::OptionallyStaticRange{StaticInt{F}}) where {F} = F +Base.step(x::OptionallyStaticStepRange{<:Any, Int}) = getfield(x, :step) +Base.step(::OptionallyStaticStepRange{<:Any, StaticInt{S}}) where {S} = S +Base.last(x::OptionallyStaticRange{<:Any, Int}) = getfield(x, :stop) +Base.last(::OptionallyStaticRange{<:Any, StaticInt{L}}) where {L} = L + +# FIXME this line causes invalidations +Base.:(:)(L::Integer, ::StaticInt{U}) where {U} = OptionallyStaticUnitRange(L, StaticInt(U)) +Base.:(:)(::StaticInt{L}, U::Integer) where {L} = OptionallyStaticUnitRange(StaticInt(L), U) +function Base.:(:)(::StaticInt{L}, ::StaticInt{U}) where {L, U} + OptionallyStaticUnitRange(StaticInt(L), StaticInt(U)) +end +function Base.:(:)(::StaticInt{F}, ::StaticInt{S}, ::StaticInt{L}) where {F, S, L} + OptionallyStaticStepRange(StaticInt(F), StaticInt(S), StaticInt(L)) +end +function Base.:(:)(start::Integer, ::StaticInt{S}, ::StaticInt{L}) where {S, L} + OptionallyStaticStepRange(start, StaticInt(S), StaticInt(L)) +end +function Base.:(:)(::StaticInt{F}, ::StaticInt{S}, stop::Integer) where {F, S} + OptionallyStaticStepRange(StaticInt(F), StaticInt(S), stop) +end +function Base.:(:)(::StaticInt{F}, step::Integer, ::StaticInt{L}) where {F, L} + OptionallyStaticStepRange(StaticInt(F), step, StaticInt(L)) +end +function Base.:(:)(start::Integer, step::Integer, ::StaticInt{L}) where {L} + OptionallyStaticStepRange(start, step, StaticInt(L)) +end +function Base.:(:)(start::Integer, ::StaticInt{S}, stop::Integer) where {S} + OptionallyStaticStepRange(start, StaticInt(S), stop) +end +function Base.:(:)(::StaticInt{F}, step::Integer, stop::Integer) where {F} + OptionallyStaticStepRange(StaticInt(F), step, stop) +end +Base.:(:)(start::StaticInt{F}, ::StaticInt{1}, stop::StaticInt{L}) where {F, L} = start:stop +Base.:(:)(start::Integer, ::StaticInt{1}, stop::StaticInt{L}) where {L} = start:stop +Base.:(:)(start::StaticInt{F}, ::StaticInt{1}, stop::Integer) where {F} = start:stop +function Base.:(:)(start::Integer, ::StaticInt{1}, stop::Integer) + OptionallyStaticUnitRange(start, stop) +end + +Base.isempty(r::OptionallyStaticUnitRange) = first(r) > last(r) +@inline function Base.isempty(x::OptionallyStaticStepRange) + start = first(x) + stop = last(x) + if start === stop + return false + else + s = step(x) + s > 0 ? start > stop : start < stop + end +end + +function Base.checkindex(::Type{Bool}, + ::SUnitRange{F1, L1}, + ::SUnitRange{F2, L2}) where {F1, L1, F2, L2} + (F1::Int <= F2::Int) && (L1::Int >= L2::Int) +end + +function Base.getindex(r::OptionallyStaticUnitRange, + s::AbstractUnitRange{<:Integer}) + @boundscheck checkbounds(r, s) + f = static_first(r) + fnew = f - one(f) + return (fnew + static_first(s)):(fnew + static_last(s)) +end + +function Base.getindex(x::OptionallyStaticUnitRange{StaticInt{1}}, i::Int) + @boundscheck checkbounds(x, i) + i +end +function Base.getindex(x::OptionallyStaticUnitRange, i::Int) + val = first(x) + (i - 1) + @boundscheck ((i < 1) || val > last(x)) && throw(BoundsError(x, i)) + val::Int +end + +## length +@inline function Base.length(x::OptionallyStaticUnitRange) + start = first(x) + stop = last(x) + start > stop ? 0 : stop - start + 1 +end +Base.length(r::OptionallyStaticStepRange) = _range_length(first(r), step(r), last(r)) +@inline function _range_length(start::Int, s::Int, stop::Int) + if s > 0 + stop < start ? 0 : div(stop - start, s) + 1 + else + stop > start ? 0 : div(start - stop, -s) + 1 + end +end + +Base.AbstractUnitRange{Int}(r::OptionallyStaticUnitRange) = r +function Base.AbstractUnitRange{T}(r::OptionallyStaticUnitRange) where {T} + start = static_first(r) + if isa(start, StaticInt{1}) && T <: Integer + return Base.OneTo{T}(T(static_last(r))) + else + return UnitRange{T}(T(static_first(r)), T(static_last(r))) + end +end + +Base.isdone(x::OptionallyStaticRange, state::Int) = state === last(x) +function _next(x::OptionallyStaticRange) + new_state = first(x) + (new_state, new_state) +end +@inline function _next(@nospecialize(x::OptionallyStaticUnitRange), state::Int) + new_state = state + 1 + (new_state, new_state) +end +@inline function _next(x::OptionallyStaticStepRange, state::Int) + new_state = state + step(x) + (new_state, new_state) +end +@inline Base.iterate(x::OptionallyStaticRange) = isempty(x) ? nothing : _next(x) +@inline function Base.iterate(x::OptionallyStaticRange, s::Int) + Base.isdone(x, s) ? nothing : _next(x, s) +end + +Base.to_shape(x::OptionallyStaticRange) = length(x) +Base.to_shape(x::Base.Slice{T}) where {T <: OptionallyStaticRange} = Base.length(x) +Base.axes(S::Base.Slice{<:OptionallyStaticUnitRange{StaticInt{1}}}) = (S.indices,) +Base.axes(S::Base.Slice{<:OptionallyStaticRange}) = (Base.IdentityUnitRange(S.indices),) + +Base.axes(x::OptionallyStaticRange) = (Base.axes1(x),) +function Base.axes1(x::OptionallyStaticUnitRange) + OptionallyStaticUnitRange(StaticInt(1), length(x)) +end +Base.axes1(x::OptionallyStaticUnitRange{StaticInt{1}}) = x +function Base.axes1(x::OptionallyStaticUnitRange{StaticInt{F}, StaticInt{L}}) where {F, L} + OptionallyStaticUnitRange(StaticInt(1), StaticInt(L - F + 1)) +end +function Base.axes1(x::OptionallyStaticStepRange) + OptionallyStaticUnitRange(StaticInt(1), length(x)) +end +function Base.axes1(x::OptionallyStaticStepRange{StaticInt{F}, StaticInt{S}, StaticInt{L}}) where { + F, + S, + L + } + OptionallyStaticUnitRange(StaticInt(1), StaticInt(_range_length(F, S, L))) +end +Base.axes1(x::Base.Slice{<:OptionallyStaticUnitRange{One}}) = x.indices +Base.axes1(x::Base.Slice{<:OptionallyStaticRange}) = Base.IdentityUnitRange(x.indices) + +Base.:(-)(r::OptionallyStaticRange) = (-static_first(r)):(-static_step(r)):(-static_last(r)) + +function Base.reverse(x::OptionallyStaticUnitRange) + _OptionallyStaticStepRange(getfield(x, :stop), StaticInt(-1), getfield(x, :start)) +end +function Base.reverse(x::OptionallyStaticStepRange) + _OptionallyStaticStepRange(getfield(x, :stop), -getfield(x, :step), getfield(x, :start)) +end + +Base.show(io::IO, @nospecialize(x::OptionallyStaticRange)) = show(io, MIME"text/plain"(), x) +function Base.show(io::IO, ::MIME"text/plain", @nospecialize(r::OptionallyStaticUnitRange)) + print(io, "$(getfield(r, :start)):$(getfield(r, :stop))") +end +function Base.show(io::IO, ::MIME"text/plain", @nospecialize(r::OptionallyStaticStepRange)) + print(io, "$(getfield(r, :start)):$(getfield(r, :step)):$(getfield(r, :stop))") +end + +# we overload properties because occasionally Base assumes that abstract range types have +# the same exact same set up as native types where `x.start === first(x)` +@inline function Base.getproperty(x::OptionallyStaticRange, s::Symbol) + if s === :start + return first(x) + elseif s === :step + return step(x) + elseif s === :stop + return last(x) + else + error("$x has no property $s") + end +end + +function Base.Broadcast.axistype(r::OptionallyStaticUnitRange{StaticInt{1}}, _) + Base.OneTo(last(r)) +end +function Base.Broadcast.axistype(_, r::OptionallyStaticUnitRange{StaticInt{1}}) + Base.OneTo(last(r)) +end +function Base.Broadcast.axistype(r::OptionallyStaticUnitRange{StaticInt{1}}, + ::OptionallyStaticUnitRange{StaticInt{1}}) + Base.OneTo(last(r)) +end +function Base.similar(::Type{<:Array{T}}, + axes::Tuple{OptionallyStaticUnitRange{StaticInt{1}}, + Vararg{ + Union{Base.OneTo, + OptionallyStaticUnitRange{StaticInt{1}}}}}) where { + T + } + Array{T}(undef, map(last, axes)) +end +function Base.similar(::Type{<:Array{T}}, + axes::Tuple{Base.OneTo, OptionallyStaticUnitRange{StaticInt{1}}, + Vararg{ + Union{Base.OneTo, + OptionallyStaticUnitRange{StaticInt{1}}}}}) where { + T + } + Array{T}(undef, map(last, axes)) +end diff --git a/test/ranges.jl b/test/ranges.jl new file mode 100644 index 0000000..05670db --- /dev/null +++ b/test/ranges.jl @@ -0,0 +1,147 @@ + +@testset "Range Constructors" begin + @test @inferred(static(1):static(10)) == 1:10 + @test @inferred(Static.SUnitRange{1, 10}()) == 1:10 + @test @inferred(static(1):static(2):static(10)) == 1:2:10 + @test @inferred(1:static(2):static(10)) == 1:2:10 + @test @inferred(static(1):static(2):10) == 1:2:10 + @test @inferred(static(1):2:static(10)) == 1:2:10 + @test @inferred(1:2:static(10)) == 1:2:10 + @test @inferred(1:static(2):10) == 1:2:10 + @test @inferred(static(1):2:10) == 1:2:10 + @test @inferred(static(1):UInt(10)) === static(1):10 + @test @inferred(UInt(1):static(1):static(10)) === 1:static(10) + @test Static.SUnitRange(1, 10) == 1:10 + @test @inferred(Static.OptionallyStaticUnitRange{Int, Int}(1:10)) == 1:10 + @test @inferred(Static.OptionallyStaticUnitRange(1:10)) == 1:10 == + @inferred(Static.OptionallyStaticUnitRange(Static.OptionallyStaticUnitRange(1:10))) + + sr = Static.OptionallyStaticStepRange(static(1), static(1), static(1)) + @test @inferred(Static.OptionallyStaticStepRange(sr)) == sr == 1:1:1 + @test @inferred(Static.OptionallyStaticStepRange(static(1), 1, UInt(10))) == + static(1):1:10 == Static.SOneTo(10) + @test @inferred(Static.OptionallyStaticStepRange(UInt(1), 1, static(10))) == + static(1):1:10 + @test @inferred(Static.OptionallyStaticStepRange(1:10)) == 1:1:10 + + @test_throws ArgumentError Static.OptionallyStaticUnitRange(1:2:10) + @test_throws ArgumentError Static.OptionallyStaticUnitRange{Int, Int}(1:2:10) + @test_throws ArgumentError Static.OptionallyStaticStepRange(1, 0, 10) + @test_throws ArgumentError Static.OptionallyStaticStepRange(1, StaticInt(0), 10) + + @test @inferred(static(1):static(1):static(10)) === + Static.OptionallyStaticUnitRange(static(1), static(10)) + @test @inferred(static(1):static(1):10) === + Static.OptionallyStaticUnitRange(static(1), 10) + @test @inferred(1:static(1):10) === Static.OptionallyStaticUnitRange(1, 10) + @test length(static(-1):static(-1):static(-10)) == 10 == + lastindex(static(-1):static(-1):static(-10)) + + @test UnitRange(Static.OptionallyStaticUnitRange(static(1), static(10))) === + UnitRange(1, 10) + @test UnitRange{Int}(Static.OptionallyStaticUnitRange(static(1), static(10))) === + UnitRange(1, 10) + + @test AbstractUnitRange{Int}(Static.OptionallyStaticUnitRange(static(1), static(10))) isa + Static.OptionallyStaticUnitRange + @test AbstractUnitRange{UInt}(Static.OptionallyStaticUnitRange(static(1), static(10))) isa + Base.OneTo + @test AbstractUnitRange{UInt}(Static.OptionallyStaticUnitRange(static(2), static(10))) isa + UnitRange + + @test @inferred((static(1):static(10))[static(2):static(3)]) === static(2):static(3) + @test @inferred((static(1):static(10))[static(2):3]) === static(2):3 + @test @inferred((static(1):static(10))[2:3]) === 2:3 + @test @inferred((1:static(10))[static(2):static(3)]) === 2:3 + + @test Base.checkindex(Bool, static(1):static(10), static(1):static(5)) + @test -(static(1):static(10)) === static(-1):static(-1):static(-10) + + @test reverse(static(1):static(10)) === static(10):static(-1):static(1) + @test reverse(static(1):static(2):static(9)) === static(9):static(-2):static(1) +end + +@testset "range properties" begin + x = static(1):static(2):static(9) + @test getproperty(x, :start) === first(x) + @test getproperty(x, :step) === step(x) + @test getproperty(x, :stop) === last(x) + @test_throws ErrorException getproperty(x, :foo) +end + +@testset "iterate" begin + @test iterate(static(1):static(5)) === (1, 1) + @test iterate(static(1):static(5), 1) === (2, 2) + @test iterate(static(1):static(5), 5) === nothing + @test iterate(static(2):static(5), 5) === nothing + @test iterate(static(1):static(2):static(9), 1) === (3, 3) + @test iterate(static(1):static(2):static(9), 9) === nothing + # make sure single length ranges work correctly + @test iterate(static(2):static(3):static(2))[1] === 2 === + (static(2):static(3):static(2))[1] + @test iterate(static(2):static(3):static(2), 2) === nothing +end + +# CartesianIndices +CI = CartesianIndices((static(1):static(2), static(1):static(2))) + +@testset "length" begin + @test @inferred(length(Static.OptionallyStaticUnitRange(1, 0))) == 0 + @test @inferred(length(Static.OptionallyStaticUnitRange(1, 10))) == 10 + @test @inferred(length(Static.OptionallyStaticUnitRange(static(1), 10))) == 10 + @test @inferred(length(Static.OptionallyStaticUnitRange(static(0), 10))) == 11 + @test @inferred(length(Static.OptionallyStaticUnitRange(static(1), static(10)))) == 10 + @test @inferred(length(Static.OptionallyStaticUnitRange(static(0), static(10)))) == 11 + + @test @inferred(length(static(1):static(2):static(0))) == 0 + @test @inferred(length(static(0):static(-2):static(1))) == 0 + + @test @inferred(length(Static.OptionallyStaticStepRange(static(1), 2, 10))) == 5 + @test @inferred(length(Static.OptionallyStaticStepRange(static(1), static(1), + static(10)))) == 10 + @test @inferred(length(Static.OptionallyStaticStepRange(static(2), static(1), + static(10)))) == 9 + @test @inferred(length(Static.OptionallyStaticStepRange(static(2), static(2), + static(10)))) == 5 +end + +@test @inferred(getindex(static(1):10, Base.Slice(static(1):10))) === static(1):10 +@test @inferred(getindex(Static.OptionallyStaticUnitRange(static(1), 10), 1)) == 1 +@test @inferred(getindex(Static.OptionallyStaticUnitRange(static(0), 10), 1)) == 0 +@test_throws BoundsError getindex(Static.OptionallyStaticUnitRange(static(1), 10), 0) +@test_throws BoundsError getindex(Static.OptionallyStaticStepRange(static(1), 2, 10), 0) +@test_throws BoundsError getindex(Static.OptionallyStaticUnitRange(static(1), 10), 11) +@test_throws BoundsError getindex(Static.OptionallyStaticStepRange(static(1), 2, 10), 11) + +@test Static.static_first(Base.OneTo(one(UInt))) === static(1) +@test Static.static_step(Base.OneTo(one(UInt))) === static(1) + +@test @inferred(eachindex(static(-7):static(7))) === static(1):static(15) +@test @inferred((static(-7):static(7))[first(eachindex(static(-7):static(7)))]) == -7 + +@test @inferred(firstindex(128:static(-1):1)) == 1 + +@test identity.(static(1):5) isa Vector{Int} +@test (static(1):5) .+ (1:3)' isa Matrix{Int} +@test similar(Array{Int}, (static(1):(4),)) isa Vector{Int} +@test similar(Array{Int}, (static(1):(4), Base.OneTo(4))) isa Matrix{Int} +@test similar(Array{Int}, (Base.OneTo(4), static(1):(4))) isa Matrix{Int} + +@test Base.to_shape(static(1):10) == 10 +@test Base.to_shape(Base.Slice(static(1):10)) == 10 +@test Base.axes1(Base.Slice(static(1):10)) === static(1):10 +@test axes(Base.Slice(static(1):10)) === (static(1):10,) +@test isa(axes(Base.Slice(static(0):static(1):10))[1], Base.IdentityUnitRange) +@test isa(Base.axes1(Base.Slice(static(0):static(1):10)), Base.IdentityUnitRange) + +@test Base.Broadcast.axistype(static(1):10, static(1):10) === Base.OneTo(10) +@test Base.Broadcast.axistype(Base.OneTo(10), static(1):10) === Base.OneTo(10) + +@testset "static_promote(::AbstractRange, ::AbstractRange)" begin + ur1 = static(1):10 + ur2 = 1:static(10) + @test @inferred(static_promote(ur1, ur2)) === static(1):static(10) + sr1 = static(1):2:10 + sr2 = static(1):static(2):static(10) + @test @inferred(static_promote(sr1, sr2)) === sr2 +end diff --git a/test/runtests.jl b/test/runtests.jl index 32f9d71..bf6ee60 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -518,4 +518,8 @@ end @test repr(static(true)) == "static(true)" @test repr(static(CartesianIndex(1, 1))) == "NDIndex(static(1), static(1))" @test string(static(true)) == "static(true)" == "$(static(true))" + @test repr(static(1):static(10)) == "static(1):static(10)" + @test repr(static(1):static(2):static(9)) == "static(1):static(2):static(9)" end + +include("ranges.jl")