From 7161cfa44bde2a4b238dfb971cb11d52ff2fa3f2 Mon Sep 17 00:00:00 2001 From: Zachary P Christensen Date: Wed, 9 Mar 2022 08:44:33 -0500 Subject: [PATCH 1/8] Create staticint.jl --- base/staticint.jl | 138 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 base/staticint.jl diff --git a/base/staticint.jl b/base/staticint.jl new file mode 100644 index 0000000000000..b57d0251b26de --- /dev/null +++ b/base/staticint.jl @@ -0,0 +1,138 @@ + +""" + StaticInt(N::Int) -> StaticInt{N}() + +A statically sized `Int`. Use `StaticInt(N)` instead of `Val(N)` when you want it to behave like a number. +""" +struct StaticInt{N} <: Integer + StaticInt{N}() where {N} = new{N::Int}() +end + +const Zero = StaticInt{0} +const One = StaticInt{1} + +Base.show(io::IO, ::StaticInt{N}) where {N} = print(io, "static($N)") + +StaticInt(N::Int) = StaticInt{N}() +StaticInt(N::Integer) = StaticInt(convert(Int, N)) +StaticInt(::StaticInt{N}) where {N} = StaticInt{N}() +StaticInt(::Val{N}) where {N} = StaticInt{N}() +# Base.Val(::StaticInt{N}) where {N} = Val{N}() +Base.convert(::Type{T}, ::StaticInt{N}) where {T<:Number,N} = convert(T, N) +Base.Bool(x::StaticInt{N}) where {N} = Bool(N) +Base.BigInt(x::StaticInt{N}) where {N} = BigInt(N) +Base.Integer(x::StaticInt{N}) where {N} = x +(::Type{T})(x::StaticInt{N}) where {T<:Integer,N} = T(N) +(::Type{T})(x::Int) where {T<:StaticInt} = StaticInt(x) +Base.convert(::Type{StaticInt{N}}, ::StaticInt{N}) where {N} = StaticInt{N}() + +Base.promote_rule(::Type{<:StaticInt}, ::Type{T}) where {T<:Number} = promote_type(Int, T) +function Base.promote_rule(::Type{<:StaticInt}, ::Type{T}) where {T<:AbstractIrrational} + return promote_type(Int, T) +end +# Base.promote_rule(::Type{T}, ::Type{<:StaticInt}) where {T <: AbstractIrrational} = promote_rule(T, Int) +for (S, T) in [(:Complex, :Real), (:Rational, :Integer), (:(Base.TwicePrecision), :Any)] + @eval function Base.promote_rule(::Type{$S{T}}, ::Type{<:StaticInt}) where {T<:$T} + return promote_type($S{T}, Int) + end +end +function Base.promote_rule(::Type{Union{Nothing,Missing}}, ::Type{<:StaticInt}) + return Union{Nothing,Missing,Int} +end +function Base.promote_rule(::Type{T}, ::Type{<:StaticInt}) where {T>:Union{Missing,Nothing}} + return promote_type(T, Int) +end +Base.promote_rule(::Type{T}, ::Type{<:StaticInt}) where {T>:Nothing} = promote_type(T, Int) +Base.promote_rule(::Type{T}, ::Type{<:StaticInt}) where {T>:Missing} = promote_type(T, Int) +for T in [:Bool, :Missing, :BigFloat, :BigInt, :Nothing, :Any] + # let S = :Any + @eval begin + function Base.promote_rule(::Type{S}, ::Type{$T}) where {S<:StaticInt} + return promote_type(Int, $T) + end + function Base.promote_rule(::Type{$T}, ::Type{S}) where {S<:StaticInt} + return promote_type($T, Int) + end + end +end +Base.promote_rule(::Type{<:StaticInt}, ::Type{<:StaticInt}) = Int +Base.:(%)(::StaticInt{N}, ::Type{Integer}) where {N} = N + +Base.eltype(::Type{T}) where {T<:StaticInt} = Int +Base.iszero(::Zero) = true +Base.iszero(::StaticInt) = false +Base.isone(::One) = true +Base.isone(::StaticInt) = false +Base.zero(::Type{T}) where {T<:StaticInt} = Zero() +Base.one(::Type{T}) where {T<:StaticInt} = One() + +for T in [:Real, :Rational, :Integer] + @eval begin + @inline Base.:(+)(i::$T, ::Zero) = i + @inline Base.:(+)(i::$T, ::StaticInt{M}) where {M} = i + M + @inline Base.:(+)(::Zero, i::$T) = i + @inline Base.:(+)(::StaticInt{M}, i::$T) where {M} = M + i + @inline Base.:(-)(i::$T, ::Zero) = i + @inline Base.:(-)(i::$T, ::StaticInt{M}) where {M} = i - M + @inline Base.:(*)(i::$T, ::Zero) = Zero() + @inline Base.:(*)(i::$T, ::One) = i + @inline Base.:(*)(i::$T, ::StaticInt{M}) where {M} = i * M + @inline Base.:(*)(::Zero, i::$T) = Zero() + @inline Base.:(*)(::One, i::$T) = i + @inline Base.:(*)(::StaticInt{M}, i::$T) where {M} = M * i + end +end +@inline Base.:(+)(::Zero, ::Zero) = Zero() +@inline Base.:(+)(::Zero, ::StaticInt{M}) where {M} = StaticInt{M}() +@inline Base.:(+)(::StaticInt{M}, ::Zero) where {M} = StaticInt{M}() + +@inline Base.:(-)(::StaticInt{M}) where {M} = StaticInt{-M}() +@inline Base.:(-)(::StaticInt{M}, ::Zero) where {M} = StaticInt{M}() + +@inline Base.:(*)(::Zero, ::Zero) = Zero() +@inline Base.:(*)(::One, ::Zero) = Zero() +@inline Base.:(*)(::Zero, ::One) = Zero() +@inline Base.:(*)(::One, ::One) = One() +@inline Base.:(*)(::StaticInt{M}, ::Zero) where {M} = Zero() +@inline Base.:(*)(::Zero, ::StaticInt{M}) where {M} = Zero() +@inline Base.:(*)(::StaticInt{M}, ::One) where {M} = StaticInt{M}() +@inline Base.:(*)(::One, ::StaticInt{M}) where {M} = StaticInt{M}() +for f in [:(+), :(-), :(*), :(/), :(÷), :(%), :(<<), :(>>), :(>>>), :(&), :(|), :(⊻)] + @eval @generated function Base.$f(::StaticInt{M}, ::StaticInt{N}) where {M,N} + return Expr(:call, Expr(:curly, :StaticInt, $f(M, N))) + end +end +for f in [:(<<), :(>>), :(>>>)] + @eval begin + @inline Base.$f(::StaticInt{M}, x::UInt) where {M} = $f(M, x) + @inline Base.$f(x::Integer, ::StaticInt{M}) where {M} = $f(x, M) + end +end +for f in [:(==), :(!=), :(<), :(≤), :(>), :(≥)] + @eval begin + @inline Base.$f(::StaticInt{M}, ::StaticInt{N}) where {M,N} = $f(M, N) + @inline Base.$f(::StaticInt{M}, x::Int) where {M} = $f(M, x) + @inline Base.$f(x::Int, ::StaticInt{M}) where {M} = $f(x, M) + end +end + +@inline function maybe_static(f::F, g::G, x) where {F,G} + L = f(x) + if L === nothing + return g(x) + else + return static(L) + end +end + +@inline Base.widen(::StaticInt{N}) where {N} = widen(N) + +Base.UnitRange{T}(start::StaticInt, stop) where {T<:Real} = UnitRange{T}(T(start), T(stop)) +Base.UnitRange{T}(start, stop::StaticInt) where {T<:Real} = UnitRange{T}(T(start), T(stop)) +function Base.UnitRange{T}(start::StaticInt, stop::StaticInt) where {T<:Real} + return UnitRange{T}(T(start), T(stop)) +end + +Base.UnitRange(start::StaticInt, stop) = UnitRange(Int(start), stop) +Base.UnitRange(start, stop::StaticInt) = UnitRange(start, Int(stop)) +Base.UnitRange(start::StaticInt, stop::StaticInt) = UnitRange(Int(start), Int(stop)) From 81be6ddce485c21f0b0285392a08c6ee5897ec59 Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Wed, 9 Mar 2022 08:51:31 -0500 Subject: [PATCH 2/8] Add test file --- base/Base.jl | 1 + test/staticint.jl | 78 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 test/staticint.jl diff --git a/base/Base.jl b/base/Base.jl index cbfa5ede6aef9..301fa4a35192c 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -117,6 +117,7 @@ include("error.jl") include("bool.jl") include("number.jl") include("int.jl") +include("staticint.jl") include("operators.jl") include("pointer.jl") include("refvalue.jl") diff --git a/test/staticint.jl b/test/staticint.jl new file mode 100644 index 0000000000000..754622ae78876 --- /dev/null +++ b/test/staticint.jl @@ -0,0 +1,78 @@ + +@testset "StaticInt" begin + @test static(UInt(8)) === StaticInt(UInt(8)) === StaticInt{8}() + @test iszero(StaticInt(0)) + @test !iszero(StaticInt(1)) + @test !isone(StaticInt(0)) + @test isone(StaticInt(1)) + @test @inferred(one(StaticInt(1))) === StaticInt(1) + @test @inferred(zero(StaticInt(1))) === StaticInt(0) + @test @inferred(one(StaticInt)) === StaticInt(1) + @test @inferred(zero(StaticInt)) === StaticInt(0) === StaticInt(StaticInt(Val(0))) + @test eltype(one(StaticInt)) <: Int + + x = StaticInt(1) + @test @inferred(Bool(x)) isa Bool + @test @inferred(BigInt(x)) isa BigInt + @test @inferred(Integer(x)) === x + @test @inferred(%(x, Integer)) === 1 + # test for ambiguities and correctness + for i ∈ Any[StaticInt(0), StaticInt(1), StaticInt(2), 3] + for j ∈ Any[StaticInt(0), StaticInt(1), StaticInt(2), 3] + i === j === 3 && continue + for f ∈ [+, -, *, ÷, %, <<, >>, >>>, &, |, ⊻, ==, ≤, ≥] + (iszero(j) && ((f === ÷) || (f === %))) && continue # integer division error + @test convert(Int, @inferred(f(i,j))) == f(convert(Int, i), convert(Int, j)) + end + end + i == 3 && break + for f ∈ [+, -, *, /, ÷, %, ==, ≤, ≥] + w = f(convert(Int, i), 1.4) + x = f(1.4, convert(Int, i)) + @test convert(typeof(w), @inferred(f(i, 1.4))) === w + @test convert(typeof(x), @inferred(f(1.4, i))) === x # if f is division and i === StaticInt(0), returns `NaN`; hence use of ==== in check. + (((f === ÷) || (f === %)) && (i === StaticInt(0))) && continue + y = f(convert(Int, i), 2 // 7) + z = f(2 // 7, convert(Int, i)) + @test convert(typeof(y), @inferred(f(i, 2 // 7))) === y + @test convert(typeof(z), @inferred(f(2 // 7, i))) === z + end + end + + @test UnitRange{Int16}(StaticInt(-9), 17) === Int16(-9):Int16(17) + @test UnitRange{Int16}(-7, StaticInt(19)) === Int16(-7):Int16(19) + @test UnitRange(-11, StaticInt(15)) === -11:15 + @test UnitRange(StaticInt(-11), 15) === -11:15 + @test UnitRange{Int}(StaticInt(-11), StaticInt(15)) === -11:15 + @test UnitRange(StaticInt(-11), StaticInt(15)) === -11:15 + @test float(StaticInt(8)) === static(8.0) + + # test specific promote rules to ensure we don't cause ambiguities + SI = StaticInt{1} + IR = typeof(1//1) + PI = typeof(pi) + @test @inferred(convert(SI, SI())) === SI() + @test @inferred(promote_rule(SI, PI)) <: promote_type(Int, PI) + @test @inferred(promote_rule(SI, IR)) <: promote_type(Int, IR) + @test @inferred(promote_rule(SI, SI)) <: Int + @test @inferred(promote_rule(Missing, SI)) <: promote_type(Missing, Int) + @test @inferred(promote_rule(Nothing, SI)) <: promote_type(Nothing, Int) + @test @inferred(promote_rule(SI, Missing)) <: promote_type(Int, Missing) + @test @inferred(promote_rule(SI, Nothing)) <: promote_type(Int, Nothing) + @test @inferred(promote_rule(Union{Missing,Int}, SI)) <: promote_type(Union{Missing,Int}, Int) + @test @inferred(promote_rule(Union{Nothing,Int}, SI)) <: promote_type(Union{Nothing,Int}, Int) + @test @inferred(promote_rule(Union{Nothing,Missing,Int}, SI)) <: Union{Nothing,Missing,Int} + @test @inferred(promote_rule(Union{Nothing,Missing}, SI)) <: promote_type(Union{Nothing,Missing}, Int) + @test @inferred(promote_rule(SI, Missing)) <: promote_type(Int, Missing) + @test @inferred(promote_rule(Base.TwicePrecision{Int}, StaticInt{1})) <: Base.TwicePrecision{Int} + + @test static(Int8(-18)) === static(-18) + @test static(0xef) === static(239) + @test static(Int16(-18)) === static(-18) + @test static(0xffef) === static(65519) + if sizeof(Int) == 8 + @test static(Int32(-18)) === static(-18) + @test static(0xffffffef) === static(4294967279) + end +end + From 98e26e193feabfd477ea0f716fa0da87d7036bbd Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Wed, 9 Mar 2022 09:44:25 -0500 Subject: [PATCH 3/8] Use `@nospecialize` Avoid generating new code when `StaticInt` will ultimately be lowered to `Int` anyway. --- base/Base.jl | 3 +- base/staticint.jl | 121 ++++++++++++++++++++++------------------------ 2 files changed, 60 insertions(+), 64 deletions(-) diff --git a/base/Base.jl b/base/Base.jl index 301fa4a35192c..54b4b8f1ae297 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -117,7 +117,6 @@ include("error.jl") include("bool.jl") include("number.jl") include("int.jl") -include("staticint.jl") include("operators.jl") include("pointer.jl") include("refvalue.jl") @@ -370,6 +369,8 @@ include("irrationals.jl") include("mathconstants.jl") using .MathConstants: ℯ, π, pi +include("staticint.jl") + # metaprogramming include("meta.jl") diff --git a/base/staticint.jl b/base/staticint.jl index b57d0251b26de..1f451796b2536 100644 --- a/base/staticint.jl +++ b/base/staticint.jl @@ -6,80 +6,72 @@ A statically sized `Int`. Use `StaticInt(N)` instead of `Val(N)` when you want i """ struct StaticInt{N} <: Integer StaticInt{N}() where {N} = new{N::Int}() + StaticInt(N::Int) = StaticInt{N}() + StaticInt(N::StaticInt) = N + StaticInt(::Val{N}) where {N} = StaticInt(N) + StaticInt(N) = StaticInt(Int(N)) end +_dynamic_int(@nospecialize(x::StaticInt))::Int = _dynamic_int(typeof(x)) +_dynamic_int(@nospecialize(x::Type{<:StaticInt}))::Int = x.parameters[1] + const Zero = StaticInt{0} const One = StaticInt{1} -Base.show(io::IO, ::StaticInt{N}) where {N} = print(io, "static($N)") - -StaticInt(N::Int) = StaticInt{N}() -StaticInt(N::Integer) = StaticInt(convert(Int, N)) -StaticInt(::StaticInt{N}) where {N} = StaticInt{N}() -StaticInt(::Val{N}) where {N} = StaticInt{N}() -# Base.Val(::StaticInt{N}) where {N} = Val{N}() -Base.convert(::Type{T}, ::StaticInt{N}) where {T<:Number,N} = convert(T, N) -Base.Bool(x::StaticInt{N}) where {N} = Bool(N) -Base.BigInt(x::StaticInt{N}) where {N} = BigInt(N) -Base.Integer(x::StaticInt{N}) where {N} = x -(::Type{T})(x::StaticInt{N}) where {T<:Integer,N} = T(N) +Base.convert(::Type{T}, @nospecialize(N::StaticInt)) where {T<:Number} = convert(T, Int(N)) +Base.Bool(x::StaticInt{0}) = false +Base.Bool(x::StaticInt{1}) = true + +Base.BigInt(@nospecialize(x::StaticInt)) = BigInt(Int(x)) +Base.Integer(x::StaticInt) = x +(::Type{T})(@nospecialize(x::StaticInt)) where {T<:Integer} = T(_dynamic_int(x)) (::Type{T})(x::Int) where {T<:StaticInt} = StaticInt(x) Base.convert(::Type{StaticInt{N}}, ::StaticInt{N}) where {N} = StaticInt{N}() -Base.promote_rule(::Type{<:StaticInt}, ::Type{T}) where {T<:Number} = promote_type(Int, T) -function Base.promote_rule(::Type{<:StaticInt}, ::Type{T}) where {T<:AbstractIrrational} - return promote_type(Int, T) -end -# Base.promote_rule(::Type{T}, ::Type{<:StaticInt}) where {T <: AbstractIrrational} = promote_rule(T, Int) +Base.promote_rule(@nospecialize(T1::Type{<:StaticInt}), ::Type{T2}) where {T2<:Number} = promote_type(Int, T2) +Base.promote_rule(@nospecialize(T1::Type{<:StaticInt}), ::Type{T2}) where {T2<:AbstractIrrational} = promote_type(Int, T2) for (S, T) in [(:Complex, :Real), (:Rational, :Integer), (:(Base.TwicePrecision), :Any)] - @eval function Base.promote_rule(::Type{$S{T}}, ::Type{<:StaticInt}) where {T<:$T} - return promote_type($S{T}, Int) + @eval function Base.promote_rule(::Type{$S{T}}, @nospecialize(SI::Type{<:StaticInt})) where {T<:$T} + promote_type($S{T}, Int) end end -function Base.promote_rule(::Type{Union{Nothing,Missing}}, ::Type{<:StaticInt}) - return Union{Nothing,Missing,Int} -end -function Base.promote_rule(::Type{T}, ::Type{<:StaticInt}) where {T>:Union{Missing,Nothing}} - return promote_type(T, Int) -end -Base.promote_rule(::Type{T}, ::Type{<:StaticInt}) where {T>:Nothing} = promote_type(T, Int) -Base.promote_rule(::Type{T}, ::Type{<:StaticInt}) where {T>:Missing} = promote_type(T, Int) +Base.promote_rule(::Type{Union{Nothing,Missing}}, @nospecialize(T::Type{<:StaticInt})) = Union{Nothing,Missing,Int} +Base.promote_rule(::Type{T1}, @nospecialize(T2::Type{<:StaticInt})) where {T1>:Union{Missing,Nothing}} = promote_type(T1, Int) +Base.promote_rule(::Type{T1}, @nospecialize(T2::Type{<:StaticInt})) where {T1>:Nothing} = promote_type(T1, Int) +Base.promote_rule(::Type{T1}, @nospecialize(T2::Type{<:StaticInt})) where {T1>:Missing} = promote_type(T1, Int) for T in [:Bool, :Missing, :BigFloat, :BigInt, :Nothing, :Any] # let S = :Any @eval begin - function Base.promote_rule(::Type{S}, ::Type{$T}) where {S<:StaticInt} - return promote_type(Int, $T) - end - function Base.promote_rule(::Type{$T}, ::Type{S}) where {S<:StaticInt} - return promote_type($T, Int) - end + Base.promote_rule(@nospecialize(S::Type{<:StaticInt}), ::Type{$T}) = promote_type(Int, $T) + Base.promote_rule(::Type{$T}, @nospecialize(S::Type{<:StaticInt})) = promote_type($T, Int) end end -Base.promote_rule(::Type{<:StaticInt}, ::Type{<:StaticInt}) = Int -Base.:(%)(::StaticInt{N}, ::Type{Integer}) where {N} = N +Base.promote_rule(@nospecialize(T1::Type{<:StaticInt}), @nospecialize(T2::Type{<:StaticInt})) = Int + +Base.:(%)(@nospecialize(n::StaticInt), ::Type{Integer}) = Int(n) -Base.eltype(::Type{T}) where {T<:StaticInt} = Int +Base.eltype(@nospecialize(T::Type{<:StaticInt})) = Int Base.iszero(::Zero) = true -Base.iszero(::StaticInt) = false +Base.iszero(@nospecialize(x::StaticInt)) = false Base.isone(::One) = true -Base.isone(::StaticInt) = false -Base.zero(::Type{T}) where {T<:StaticInt} = Zero() -Base.one(::Type{T}) where {T<:StaticInt} = One() +Base.isone(@nospecialize(x::StaticInt)) = false +Base.zero(@nospecialize(x::Type{<:StaticInt})) = Zero() +Base.one(@nospecialize(x::Type{<:StaticInt})) = One() for T in [:Real, :Rational, :Integer] @eval begin - @inline Base.:(+)(i::$T, ::Zero) = i - @inline Base.:(+)(i::$T, ::StaticInt{M}) where {M} = i + M - @inline Base.:(+)(::Zero, i::$T) = i - @inline Base.:(+)(::StaticInt{M}, i::$T) where {M} = M + i - @inline Base.:(-)(i::$T, ::Zero) = i - @inline Base.:(-)(i::$T, ::StaticInt{M}) where {M} = i - M - @inline Base.:(*)(i::$T, ::Zero) = Zero() - @inline Base.:(*)(i::$T, ::One) = i - @inline Base.:(*)(i::$T, ::StaticInt{M}) where {M} = i * M - @inline Base.:(*)(::Zero, i::$T) = Zero() - @inline Base.:(*)(::One, i::$T) = i - @inline Base.:(*)(::StaticInt{M}, i::$T) where {M} = M * i + @inline Base.:(+)(x::$T, y::Zero) = x + @inline Base.:(+)(x::$T, @nospecialize(y::StaticInt)) = x + Int(y) + @inline Base.:(+)(x::Zero, y::$T) = x + @inline Base.:(+)(@nospecialize(x::StaticInt), y::$T) = Int(x) + y + @inline Base.:(-)(x::$T, y::Zero) = x + @inline Base.:(-)(x::$T, @nospecialize(y::StaticInt)) = x - Int(y) + @inline Base.:(*)(x::$T, y::Zero) = Zero() + @inline Base.:(*)(x::$T, y::One) = x + @inline Base.:(*)(x::$T, @nospecialize(y::StaticInt)) = x * Int(y) + @inline Base.:(*)(x::Zero, y::$T) = Zero() + @inline Base.:(*)(x::One, y::$T) = y + @inline Base.:(*)(@nospecialize(x::StaticInt), y::$T) = Int(x) * y end end @inline Base.:(+)(::Zero, ::Zero) = Zero() @@ -104,15 +96,15 @@ for f in [:(+), :(-), :(*), :(/), :(÷), :(%), :(<<), :(>>), :(>>>), :(&), :(|), end for f in [:(<<), :(>>), :(>>>)] @eval begin - @inline Base.$f(::StaticInt{M}, x::UInt) where {M} = $f(M, x) - @inline Base.$f(x::Integer, ::StaticInt{M}) where {M} = $f(x, M) + @inline Base.$f(@nospecialize(x::StaticInt), y::UInt) where {M} = $f(Int(x), y) + @inline Base.$f(x::Integer, @nospecialize(y::StaticInt)) = $f(x, Int(y)) end end for f in [:(==), :(!=), :(<), :(≤), :(>), :(≥)] @eval begin @inline Base.$f(::StaticInt{M}, ::StaticInt{N}) where {M,N} = $f(M, N) - @inline Base.$f(::StaticInt{M}, x::Int) where {M} = $f(M, x) - @inline Base.$f(x::Int, ::StaticInt{M}) where {M} = $f(x, M) + @inline Base.$f(@nospecialize(x::StaticInt), y::Int) where {M} = $f(Int(x), y) + @inline Base.$f(x::Int, @nospecialize(y::StaticInt)) = $f(x, Int(y)) end end @@ -127,12 +119,15 @@ end @inline Base.widen(::StaticInt{N}) where {N} = widen(N) -Base.UnitRange{T}(start::StaticInt, stop) where {T<:Real} = UnitRange{T}(T(start), T(stop)) -Base.UnitRange{T}(start, stop::StaticInt) where {T<:Real} = UnitRange{T}(T(start), T(stop)) -function Base.UnitRange{T}(start::StaticInt, stop::StaticInt) where {T<:Real} - return UnitRange{T}(T(start), T(stop)) +Base.UnitRange{T}(@nospecialize(start::StaticInt), stop) where {T<:Real} = UnitRange{T}(T(start), T(stop)) +Base.UnitRange{T}(start, @nospecialize(stop::StaticInt)) where {T<:Real} = UnitRange{T}(T(start), T(stop)) +function Base.UnitRange{T}(@nospecialize(start::StaticInt), @nospecialize(stop::StaticInt)) where {T<:Real} + UnitRange{T}(T(start), T(stop)) +end + +Base.UnitRange(@nospecialize(start::StaticInt), stop) = UnitRange(Int(start), stop) +Base.UnitRange(start, @nospecialize(stop::StaticInt)) = UnitRange(start, Int(stop)) +function Base.UnitRange(@nospecialize(start::StaticInt), @nospecialize(stop::StaticInt)) + UnitRange(Int(start), Int(stop)) end -Base.UnitRange(start::StaticInt, stop) = UnitRange(Int(start), stop) -Base.UnitRange(start, stop::StaticInt) = UnitRange(start, Int(stop)) -Base.UnitRange(start::StaticInt, stop::StaticInt) = UnitRange(Int(start), Int(stop)) From 9322676871be8fecedd7ae5f24e1f19814eb70d4 Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Wed, 9 Mar 2022 11:16:54 -0500 Subject: [PATCH 4/8] Remove explicit definitions of static methods and `/` --- base/staticint.jl | 32 +++++--------------------------- 1 file changed, 5 insertions(+), 27 deletions(-) diff --git a/base/staticint.jl b/base/staticint.jl index 1f451796b2536..acdc145718008 100644 --- a/base/staticint.jl +++ b/base/staticint.jl @@ -74,24 +74,11 @@ for T in [:Real, :Rational, :Integer] @inline Base.:(*)(@nospecialize(x::StaticInt), y::$T) = Int(x) * y end end -@inline Base.:(+)(::Zero, ::Zero) = Zero() -@inline Base.:(+)(::Zero, ::StaticInt{M}) where {M} = StaticInt{M}() -@inline Base.:(+)(::StaticInt{M}, ::Zero) where {M} = StaticInt{M}() - @inline Base.:(-)(::StaticInt{M}) where {M} = StaticInt{-M}() -@inline Base.:(-)(::StaticInt{M}, ::Zero) where {M} = StaticInt{M}() -@inline Base.:(*)(::Zero, ::Zero) = Zero() -@inline Base.:(*)(::One, ::Zero) = Zero() -@inline Base.:(*)(::Zero, ::One) = Zero() -@inline Base.:(*)(::One, ::One) = One() -@inline Base.:(*)(::StaticInt{M}, ::Zero) where {M} = Zero() -@inline Base.:(*)(::Zero, ::StaticInt{M}) where {M} = Zero() -@inline Base.:(*)(::StaticInt{M}, ::One) where {M} = StaticInt{M}() -@inline Base.:(*)(::One, ::StaticInt{M}) where {M} = StaticInt{M}() -for f in [:(+), :(-), :(*), :(/), :(÷), :(%), :(<<), :(>>), :(>>>), :(&), :(|), :(⊻)] - @eval @generated function Base.$f(::StaticInt{M}, ::StaticInt{N}) where {M,N} - return Expr(:call, Expr(:curly, :StaticInt, $f(M, N))) +for f in [:(+), :(-), :(*), :(÷), :(%), :(<<), :(>>), :(>>>), :(&), :(|), :(⊻)] + @eval begin + @inline Base.$f(::StaticInt{M}, ::StaticInt{N}) where {M,N} = StaticInt{$f(M,N)}() end end for f in [:(<<), :(>>), :(>>>)] @@ -108,16 +95,7 @@ for f in [:(==), :(!=), :(<), :(≤), :(>), :(≥)] end end -@inline function maybe_static(f::F, g::G, x) where {F,G} - L = f(x) - if L === nothing - return g(x) - else - return static(L) - end -end - -@inline Base.widen(::StaticInt{N}) where {N} = widen(N) +@inline Base.widen(@nospecialize(x::StaticInt)) = Int(x) Base.UnitRange{T}(@nospecialize(start::StaticInt), stop) where {T<:Real} = UnitRange{T}(T(start), T(stop)) Base.UnitRange{T}(start, @nospecialize(stop::StaticInt)) where {T<:Real} = UnitRange{T}(T(start), T(stop)) @@ -127,7 +105,7 @@ end Base.UnitRange(@nospecialize(start::StaticInt), stop) = UnitRange(Int(start), stop) Base.UnitRange(start, @nospecialize(stop::StaticInt)) = UnitRange(start, Int(stop)) -function Base.UnitRange(@nospecialize(start::StaticInt), @nospecialize(stop::StaticInt)) +Base.UnitRange(@nospecialize(start::StaticInt), @nospecialize(stop::StaticInt)) UnitRange(Int(start), Int(stop)) end From 3b883ff925c63045151e8f350a2a231bcc58aead Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Wed, 9 Mar 2022 12:04:59 -0500 Subject: [PATCH 5/8] Remove `Zero` `One` specialization --- base/staticint.jl | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/base/staticint.jl b/base/staticint.jl index acdc145718008..0eef9fb97e3ba 100644 --- a/base/staticint.jl +++ b/base/staticint.jl @@ -60,17 +60,11 @@ Base.one(@nospecialize(x::Type{<:StaticInt})) = One() for T in [:Real, :Rational, :Integer] @eval begin - @inline Base.:(+)(x::$T, y::Zero) = x @inline Base.:(+)(x::$T, @nospecialize(y::StaticInt)) = x + Int(y) - @inline Base.:(+)(x::Zero, y::$T) = x @inline Base.:(+)(@nospecialize(x::StaticInt), y::$T) = Int(x) + y - @inline Base.:(-)(x::$T, y::Zero) = x @inline Base.:(-)(x::$T, @nospecialize(y::StaticInt)) = x - Int(y) - @inline Base.:(*)(x::$T, y::Zero) = Zero() - @inline Base.:(*)(x::$T, y::One) = x + @inline Base.:(-)(@nospecialize(x::StaticInt), - y::$T) = Int(x) - y @inline Base.:(*)(x::$T, @nospecialize(y::StaticInt)) = x * Int(y) - @inline Base.:(*)(x::Zero, y::$T) = Zero() - @inline Base.:(*)(x::One, y::$T) = y @inline Base.:(*)(@nospecialize(x::StaticInt), y::$T) = Int(x) * y end end From a11c8745064de11459f0600cbe14fbd88d7c2a04 Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Wed, 9 Mar 2022 19:42:22 -0500 Subject: [PATCH 6/8] remove unecessary `@inline`s --- base/staticint.jl | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/base/staticint.jl b/base/staticint.jl index 0eef9fb97e3ba..c4a06904ba76a 100644 --- a/base/staticint.jl +++ b/base/staticint.jl @@ -60,12 +60,12 @@ Base.one(@nospecialize(x::Type{<:StaticInt})) = One() for T in [:Real, :Rational, :Integer] @eval begin - @inline Base.:(+)(x::$T, @nospecialize(y::StaticInt)) = x + Int(y) - @inline Base.:(+)(@nospecialize(x::StaticInt), y::$T) = Int(x) + y - @inline Base.:(-)(x::$T, @nospecialize(y::StaticInt)) = x - Int(y) - @inline Base.:(-)(@nospecialize(x::StaticInt), - y::$T) = Int(x) - y - @inline Base.:(*)(x::$T, @nospecialize(y::StaticInt)) = x * Int(y) - @inline Base.:(*)(@nospecialize(x::StaticInt), y::$T) = Int(x) * y + Base.:(+)(x::$T, @nospecialize(y::StaticInt)) = x + Int(y) + Base.:(+)(@nospecialize(x::StaticInt), y::$T) = Int(x) + y + Base.:(-)(x::$T, @nospecialize(y::StaticInt)) = x - Int(y) + Base.:(-)(@nospecialize(x::StaticInt), - y::$T) = Int(x) - y + Base.:(*)(x::$T, @nospecialize(y::StaticInt)) = x * Int(y) + Base.:(*)(@nospecialize(x::StaticInt), y::$T) = Int(x) * y end end @inline Base.:(-)(::StaticInt{M}) where {M} = StaticInt{-M}() @@ -77,19 +77,19 @@ for f in [:(+), :(-), :(*), :(÷), :(%), :(<<), :(>>), :(>>>), :(&), :(|), :(⊻ end for f in [:(<<), :(>>), :(>>>)] @eval begin - @inline Base.$f(@nospecialize(x::StaticInt), y::UInt) where {M} = $f(Int(x), y) - @inline Base.$f(x::Integer, @nospecialize(y::StaticInt)) = $f(x, Int(y)) + Base.$f(@nospecialize(x::StaticInt), y::UInt) where {M} = $f(Int(x), y) + Base.$f(x::Integer, @nospecialize(y::StaticInt)) = $f(x, Int(y)) end end for f in [:(==), :(!=), :(<), :(≤), :(>), :(≥)] @eval begin - @inline Base.$f(::StaticInt{M}, ::StaticInt{N}) where {M,N} = $f(M, N) - @inline Base.$f(@nospecialize(x::StaticInt), y::Int) where {M} = $f(Int(x), y) - @inline Base.$f(x::Int, @nospecialize(y::StaticInt)) = $f(x, Int(y)) + Base.$f(::StaticInt{M}, ::StaticInt{N}) where {M,N} = $f(M, N) + Base.$f(@nospecialize(x::StaticInt), y::Int) where {M} = $f(Int(x), y) + Base.$f(x::Int, @nospecialize(y::StaticInt)) = $f(x, Int(y)) end end -@inline Base.widen(@nospecialize(x::StaticInt)) = Int(x) +Base.widen(@nospecialize(x::StaticInt)) = Int(x) Base.UnitRange{T}(@nospecialize(start::StaticInt), stop) where {T<:Real} = UnitRange{T}(T(start), T(stop)) Base.UnitRange{T}(start, @nospecialize(stop::StaticInt)) where {T<:Real} = UnitRange{T}(T(start), T(stop)) From c02016d2cebdc1136fff2096de627613d16cd195 Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Wed, 9 Mar 2022 20:24:13 -0500 Subject: [PATCH 7/8] Clean up `Base.` prefixes --- base/staticint.jl | 93 ++++++++++++++++++++++++----------------------- 1 file changed, 48 insertions(+), 45 deletions(-) diff --git a/base/staticint.jl b/base/staticint.jl index c4a06904ba76a..0f382f722daae 100644 --- a/base/staticint.jl +++ b/base/staticint.jl @@ -17,89 +17,92 @@ _dynamic_int(@nospecialize(x::Type{<:StaticInt}))::Int = x.parameters[1] const Zero = StaticInt{0} const One = StaticInt{1} +const IntType = Union{Int,StaticInt} +IntType(x::Integer) = Int(x) +IntType(x::IntType) = x -Base.convert(::Type{T}, @nospecialize(N::StaticInt)) where {T<:Number} = convert(T, Int(N)) -Base.Bool(x::StaticInt{0}) = false -Base.Bool(x::StaticInt{1}) = true +convert(::Type{T}, @nospecialize(N::StaticInt)) where {T<:Number} = convert(T, Int(N)) +Bool(x::StaticInt{0}) = false +Bool(x::StaticInt{1}) = true -Base.BigInt(@nospecialize(x::StaticInt)) = BigInt(Int(x)) -Base.Integer(x::StaticInt) = x +BigInt(@nospecialize(x::StaticInt)) = BigInt(Int(x)) +Integer(x::StaticInt) = x (::Type{T})(@nospecialize(x::StaticInt)) where {T<:Integer} = T(_dynamic_int(x)) (::Type{T})(x::Int) where {T<:StaticInt} = StaticInt(x) -Base.convert(::Type{StaticInt{N}}, ::StaticInt{N}) where {N} = StaticInt{N}() +convert(::Type{StaticInt{N}}, ::StaticInt{N}) where {N} = StaticInt{N}() -Base.promote_rule(@nospecialize(T1::Type{<:StaticInt}), ::Type{T2}) where {T2<:Number} = promote_type(Int, T2) -Base.promote_rule(@nospecialize(T1::Type{<:StaticInt}), ::Type{T2}) where {T2<:AbstractIrrational} = promote_type(Int, T2) -for (S, T) in [(:Complex, :Real), (:Rational, :Integer), (:(Base.TwicePrecision), :Any)] - @eval function Base.promote_rule(::Type{$S{T}}, @nospecialize(SI::Type{<:StaticInt})) where {T<:$T} +promote_rule(@nospecialize(T1::Type{<:StaticInt}), ::Type{T2}) where {T2<:Number} = promote_type(Int, T2) +promote_rule(@nospecialize(T1::Type{<:StaticInt}), ::Type{T2}) where {T2<:AbstractIrrational} = promote_type(Int, T2) +for (S, T) in [(:Complex, :Real), (:Rational, :Integer), (:(TwicePrecision), :Any)] + @eval function promote_rule(::Type{$S{T}}, @nospecialize(SI::Type{<:StaticInt})) where {T<:$T} promote_type($S{T}, Int) end end -Base.promote_rule(::Type{Union{Nothing,Missing}}, @nospecialize(T::Type{<:StaticInt})) = Union{Nothing,Missing,Int} -Base.promote_rule(::Type{T1}, @nospecialize(T2::Type{<:StaticInt})) where {T1>:Union{Missing,Nothing}} = promote_type(T1, Int) -Base.promote_rule(::Type{T1}, @nospecialize(T2::Type{<:StaticInt})) where {T1>:Nothing} = promote_type(T1, Int) -Base.promote_rule(::Type{T1}, @nospecialize(T2::Type{<:StaticInt})) where {T1>:Missing} = promote_type(T1, Int) +promote_rule(::Type{Union{Nothing,Missing}}, @nospecialize(T::Type{<:StaticInt})) = Union{Nothing,Missing,Int} +promote_rule(::Type{T1}, @nospecialize(T2::Type{<:StaticInt})) where {T1>:Union{Missing,Nothing}} = promote_type(T1, Int) +promote_rule(::Type{T1}, @nospecialize(T2::Type{<:StaticInt})) where {T1>:Nothing} = promote_type(T1, Int) +promote_rule(::Type{T1}, @nospecialize(T2::Type{<:StaticInt})) where {T1>:Missing} = promote_type(T1, Int) for T in [:Bool, :Missing, :BigFloat, :BigInt, :Nothing, :Any] # let S = :Any @eval begin - Base.promote_rule(@nospecialize(S::Type{<:StaticInt}), ::Type{$T}) = promote_type(Int, $T) - Base.promote_rule(::Type{$T}, @nospecialize(S::Type{<:StaticInt})) = promote_type($T, Int) + promote_rule(@nospecialize(S::Type{<:StaticInt}), ::Type{$T}) = promote_type(Int, $T) + promote_rule(::Type{$T}, @nospecialize(S::Type{<:StaticInt})) = promote_type($T, Int) end end -Base.promote_rule(@nospecialize(T1::Type{<:StaticInt}), @nospecialize(T2::Type{<:StaticInt})) = Int +promote_rule(@nospecialize(T1::Type{<:StaticInt}), @nospecialize(T2::Type{<:StaticInt})) = Int -Base.:(%)(@nospecialize(n::StaticInt), ::Type{Integer}) = Int(n) +:(%)(@nospecialize(n::StaticInt), ::Type{Integer}) = Int(n) -Base.eltype(@nospecialize(T::Type{<:StaticInt})) = Int -Base.iszero(::Zero) = true -Base.iszero(@nospecialize(x::StaticInt)) = false -Base.isone(::One) = true -Base.isone(@nospecialize(x::StaticInt)) = false -Base.zero(@nospecialize(x::Type{<:StaticInt})) = Zero() -Base.one(@nospecialize(x::Type{<:StaticInt})) = One() +eltype(@nospecialize(T::Type{<:StaticInt})) = Int +iszero(::Zero) = true +iszero(@nospecialize(x::StaticInt)) = false +isone(::One) = true +isone(@nospecialize(x::StaticInt)) = false +zero(@nospecialize(x::Type{<:StaticInt})) = Zero() +one(@nospecialize(x::Type{<:StaticInt})) = One() for T in [:Real, :Rational, :Integer] @eval begin - Base.:(+)(x::$T, @nospecialize(y::StaticInt)) = x + Int(y) - Base.:(+)(@nospecialize(x::StaticInt), y::$T) = Int(x) + y - Base.:(-)(x::$T, @nospecialize(y::StaticInt)) = x - Int(y) - Base.:(-)(@nospecialize(x::StaticInt), - y::$T) = Int(x) - y - Base.:(*)(x::$T, @nospecialize(y::StaticInt)) = x * Int(y) - Base.:(*)(@nospecialize(x::StaticInt), y::$T) = Int(x) * y + +(x::$T, @nospecialize(y::StaticInt)) = x + Int(y) + +(@nospecialize(x::StaticInt), y::$T) = Int(x) + y + -(x::$T, @nospecialize(y::StaticInt)) = x - Int(y) + -(@nospecialize(x::StaticInt), - y::$T) = Int(x) - y + *(x::$T, @nospecialize(y::StaticInt)) = x * Int(y) + *(@nospecialize(x::StaticInt), y::$T) = Int(x) * y end end -@inline Base.:(-)(::StaticInt{M}) where {M} = StaticInt{-M}() +@inline :(-)(::StaticInt{M}) where {M} = StaticInt{-M}() for f in [:(+), :(-), :(*), :(÷), :(%), :(<<), :(>>), :(>>>), :(&), :(|), :(⊻)] @eval begin - @inline Base.$f(::StaticInt{M}, ::StaticInt{N}) where {M,N} = StaticInt{$f(M,N)}() + @inline $f(::StaticInt{M}, ::StaticInt{N}) where {M,N} = StaticInt{$f(M,N)}() end end for f in [:(<<), :(>>), :(>>>)] @eval begin - Base.$f(@nospecialize(x::StaticInt), y::UInt) where {M} = $f(Int(x), y) - Base.$f(x::Integer, @nospecialize(y::StaticInt)) = $f(x, Int(y)) + $f(@nospecialize(x::StaticInt), y::UInt) where {M} = $f(Int(x), y) + $f(x::Integer, @nospecialize(y::StaticInt)) = $f(x, Int(y)) end end for f in [:(==), :(!=), :(<), :(≤), :(>), :(≥)] @eval begin - Base.$f(::StaticInt{M}, ::StaticInt{N}) where {M,N} = $f(M, N) - Base.$f(@nospecialize(x::StaticInt), y::Int) where {M} = $f(Int(x), y) - Base.$f(x::Int, @nospecialize(y::StaticInt)) = $f(x, Int(y)) + $f(::StaticInt{M}, ::StaticInt{N}) where {M,N} = $f(M, N) + $f(@nospecialize(x::StaticInt), y::Int) where {M} = $f(Int(x), y) + $f(x::Int, @nospecialize(y::StaticInt)) = $f(x, Int(y)) end end -Base.widen(@nospecialize(x::StaticInt)) = Int(x) +widen(@nospecialize(x::StaticInt)) = Int(x) -Base.UnitRange{T}(@nospecialize(start::StaticInt), stop) where {T<:Real} = UnitRange{T}(T(start), T(stop)) -Base.UnitRange{T}(start, @nospecialize(stop::StaticInt)) where {T<:Real} = UnitRange{T}(T(start), T(stop)) -function Base.UnitRange{T}(@nospecialize(start::StaticInt), @nospecialize(stop::StaticInt)) where {T<:Real} +UnitRange{T}(@nospecialize(start::StaticInt), stop) where {T<:Real} = UnitRange{T}(T(start), T(stop)) +UnitRange{T}(start, @nospecialize(stop::StaticInt)) where {T<:Real} = UnitRange{T}(T(start), T(stop)) +function UnitRange{T}(@nospecialize(start::StaticInt), @nospecialize(stop::StaticInt)) where {T<:Real} UnitRange{T}(T(start), T(stop)) end -Base.UnitRange(@nospecialize(start::StaticInt), stop) = UnitRange(Int(start), stop) -Base.UnitRange(start, @nospecialize(stop::StaticInt)) = UnitRange(start, Int(stop)) -Base.UnitRange(@nospecialize(start::StaticInt), @nospecialize(stop::StaticInt)) +UnitRange(@nospecialize(start::StaticInt), stop) = UnitRange(Int(start), stop) +UnitRange(start, @nospecialize(stop::StaticInt)) = UnitRange(start, Int(stop)) +UnitRange(@nospecialize(start::StaticInt), @nospecialize(stop::StaticInt)) UnitRange(Int(start), Int(stop)) end From a53d585a80cccc63e9e09514484a7ce87154852c Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Wed, 16 Mar 2022 03:56:43 -0400 Subject: [PATCH 8/8] Include potential traits/types --- base/staticint.jl | 271 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 260 insertions(+), 11 deletions(-) diff --git a/base/staticint.jl b/base/staticint.jl index 0f382f722daae..60a0102d9b5ad 100644 --- a/base/staticint.jl +++ b/base/staticint.jl @@ -51,7 +51,7 @@ for T in [:Bool, :Missing, :BigFloat, :BigInt, :Nothing, :Any] end promote_rule(@nospecialize(T1::Type{<:StaticInt}), @nospecialize(T2::Type{<:StaticInt})) = Int -:(%)(@nospecialize(n::StaticInt), ::Type{Integer}) = Int(n) +%(@nospecialize(n::StaticInt), ::Type{Integer}) = Int(n) eltype(@nospecialize(T::Type{<:StaticInt})) = Int iszero(::Zero) = true @@ -62,16 +62,14 @@ zero(@nospecialize(x::Type{<:StaticInt})) = Zero() one(@nospecialize(x::Type{<:StaticInt})) = One() for T in [:Real, :Rational, :Integer] - @eval begin - +(x::$T, @nospecialize(y::StaticInt)) = x + Int(y) - +(@nospecialize(x::StaticInt), y::$T) = Int(x) + y - -(x::$T, @nospecialize(y::StaticInt)) = x - Int(y) - -(@nospecialize(x::StaticInt), - y::$T) = Int(x) - y - *(x::$T, @nospecialize(y::StaticInt)) = x * Int(y) - *(@nospecialize(x::StaticInt), y::$T) = Int(x) * y + for f in [:(-), :(+), :(*)] + @eval begin + $(f)(x::$T, @nospecialize(y::StaticInt)) = $(f)(x, Int(y)) + $(f)(@nospecialize(x::StaticInt), y::$T) = $(f)(Int(x), y) + end end end -@inline :(-)(::StaticInt{M}) where {M} = StaticInt{-M}() +@inline -(::StaticInt{M}) where {M} = StaticInt{-M}() for f in [:(+), :(-), :(*), :(÷), :(%), :(<<), :(>>), :(>>>), :(&), :(|), :(⊻)] @eval begin @@ -99,10 +97,261 @@ UnitRange{T}(start, @nospecialize(stop::StaticInt)) where {T<:Real} = UnitRange{ function UnitRange{T}(@nospecialize(start::StaticInt), @nospecialize(stop::StaticInt)) where {T<:Real} UnitRange{T}(T(start), T(stop)) end - UnitRange(@nospecialize(start::StaticInt), stop) = UnitRange(Int(start), stop) UnitRange(start, @nospecialize(stop::StaticInt)) = UnitRange(start, Int(stop)) -UnitRange(@nospecialize(start::StaticInt), @nospecialize(stop::StaticInt)) +function UnitRange(@nospecialize(start::StaticInt), @nospecialize(stop::StaticInt)) UnitRange(Int(start), Int(stop)) end +## ranges + +""" + 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::Integer, stop::Integer) + OptionallyStaticUnitRange(IntType(start), IntType(stop)) + end + function OptionallyStaticUnitRange(x::AbstractRange) + step(x) == 1 && return OptionallyStaticUnitRange(maybe_static_first(x), maybe_static_last(x)) + + errmsg(x) = throw(ArgumentError("step must be 1, got $(step(x))")) # avoid GC frame + errmsg(x) + end + OptionallyStaticUnitRange{F,L}(x::AbstractRange) where {F,L} = OptionallyStaticUnitRange(x) + 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., `:`). +""" +struct OptionallyStaticStepRange{F<:IntType,S<:IntType,L<:IntType} <: OrdinalRange{Int,Int} + start::F + step::S + stop::L + + function OptionallyStaticStepRange(start::IntType, step::IntType, stop::IntType) + lst = _steprange_last(start, step, stop) + new{typeof(start),typeof(step),typeof(lst)}(start, step, lst) + end + function OptionallyStaticStepRange(start::Integer, step::Integer, stop::Integer) + OptionallyStaticStepRange(IntType(start), IntType(step), IntType(stop)) + end + function OptionallyStaticStepRange(x::AbstractRange) + OptionallyStaticStepRange(maybe_static_first(x), maybe_static_step(x), maybe_static_last(x)) + end +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::Integer, step::StaticInt, stop::StaticInt) + if step === one(step) + # we don't need to check the `stop` if we know it acts like a unit range + return stop + else + return _steprange_last(start, Int(step), Int(stop)) + end +end +@inline function _steprange_last(start::Integer, step::Integer, stop::Integer) + z = zero(step) + if step === z + throw(ArgumentError("step cannot be zero")) + else + if stop == start + return Int(stop) + else + if step > z + if stop > start + return stop - Int(unsigned(stop - start) % step) + else + return Int(start - one(start)) + end + else + if stop > start + return Int(start + one(start)) + else + return stop + Int(unsigned(start - stop) % -step) + end + end + end + end +end + +const OptionallyStaticRange = Union{<:OptionallyStaticUnitRange,<:OptionallyStaticStepRange} + +first(r::OptionallyStaticRange) = Int(r.start) + +step(r::OptionallyStaticStepRange)= Int(r.step) + +last(r::OptionallyStaticRange) = Int(r.stop) + +lastindex(x::OptionallyStaticRange) = length(x) +function length(r::OptionallyStaticUnitRange) + if isempty(r) + return 0 + else + return Int(r.stop - (r.start + One())) + end +end +length(r::OptionallyStaticStepRange) = _range_length(r.start, r.step, r.stop) +_range_length(start, s, stop) = nothing +@inline function _range_length(start::IntType, s::IntType, stop::IntType) + if s > 0 + if stop < start # isempty + return 0 + else + return Int(div(stop - start, s)) + 1 + end + else + if stop > start # isempty + return 0 + else + return Int(div(start - stop, -s)) + 1 + end + end +end + +AbstractUnitRange{Int}(r::OptionallyStaticUnitRange) = r +function AbstractUnitRange{T}(r::OptionallyStaticUnitRange) where {T} + if known_first(r) === 1 && T <: Integer + return OneTo{T}(last(r)) + else + return UnitRange{T}(first(r), last(r)) + end +end + +(:)(start::Integer, stop::StaticInt) = OptionallyStaticUnitRange(start, stop) +(:)(start::StaticInt, stop::Integer) = OptionallyStaticUnitRange(start, stop) +(:)(start::StaticInt, stop::StaticInt) = OptionallyStaticUnitRange(start, stop) +function (:)(start::StaticInt, step::StaticInt, stop::StaticInt) + OptionallyStaticStepRange(start, step, stop) +end +function (:)(start::Integer, step::StaticInt, stop::StaticInt) + OptionallyStaticStepRange(start, step, stop) +end +function (:)(start::StaticInt, step::StaticInt, stop::Integer) + OptionallyStaticStepRange(start, step, stop) +end +function (:)(start::StaticInt, step::Integer, stop::StaticInt) + OptionallyStaticStepRange(start, step, stop) +end +function (:)(start::Integer, step::Integer, stop::StaticInt) + OptionallyStaticStepRange(start, step, stop) +end +function (:)(start::Integer, step::StaticInt, stop::Integer) + OptionallyStaticStepRange(start, step, stop) +end +function (:)(start::StaticInt, step::Integer, stop::Integer) + OptionallyStaticStepRange(start, step, stop) +end +(:)(start::StaticInt, ::StaticInt{1}, stop::StaticInt) = start:stop +(:)(start::Integer, ::StaticInt{1}, stop::StaticInt) = start:stop +(:)(start::StaticInt, ::StaticInt{1}, stop::Integer) = start:stop +function (:)(start::Integer, step::StaticInt{1}, stop::Integer) + OptionallyStaticUnitRange(start, stop) +end +isempty(r::OptionallyStaticUnitRange) = r.start > r.stop +function isempty(r::OptionallyStaticStepRange) + (r.start != r.stop) & ((r.step > 0) != (r.stop > r.start)) +end + +function getindex(r::OptionallyStaticUnitRange, s::AbstractUnitRange{<:Integer}) + @boundscheck checkbounds(r, s) + f = r.start + fnew = f - one(f) + return (fnew+maybe_static_first(s)):(fnew+maybe_static_last(s)) +end + +function getindex(x::OptionallyStaticUnitRange, i::Int)::Int + val = (x.start - One()) + i + @boundscheck ((i < 1) || val > last(x)) && throw(BoundsError(x, i)) + val +end + +## traits +""" + known_first(::Type{T}) -> Union{Int,Nothing} + +If `first` of an instance of type `T` is known at compile time, return it. +Otherwise, return `nothing`. +""" +known_first(x) = known_first(typeof(x)) +known_first(::Type) = nothing +known_first(::Type{OneTo{T}}) where {T} = T(1) +known_first(::Type{<:OptionallyStaticUnitRange{StaticInt{F}}}) where {F} = F::Int +known_first(::Type{<:OptionallyStaticStepRange{StaticInt{F}}}) where {F} = F::Int +known_first(::Type{Slice{T}}) where {T} = known_first(T) +known_first(::Type{IdentityUnitRange{T}}) where {T} = known_first(T) + +""" + known_step(::Type{T}) -> Union{Int,Nothing} + +If `step` of an instance of type `T` is known at compile time, return it. +Otherwise, return `nothing`. +""" +known_step(x) = known_step(typeof(x)) +known_step(::Type) = nothing +known_step(T::Type{<:AbstractUnitRange}) = eltype(T)(1) +known_step(::Type{<:OptionallyStaticStepRange{<:Any,StaticInt{S}}}) where {S} = S::Int + +""" + known_last(::Type{T}) -> Union{Int,Nothing} + +If `last` of an instance of type `T` is known at compile time, return it. +Otherwise, return `nothing`. +""" +known_last(x) = known_last(typeof(x)) +known_last(::Type) = nothing +known_last(::Type{<:OptionallyStaticUnitRange{<:Any,StaticInt{L}}}) where {L} = L::Int +known_last(::Type{<:OptionallyStaticStepRange{<:Any,<:Any,StaticInt{L}}}) where {L} = L::Int +known_last(::Type{Slice{T}}) where {T} = known_last(T) +known_last(::Type{IdentityUnitRange{T}}) where {T} = known_last(T) + +""" + known_length(::Type{T}) -> Union{Int,Nothing} + +If `length` of an instance of type `T` is known at compile time, return it. +Otherwise, return `nothing`. +""" +known_length(x) = known_length(typeof(x)) +known_length(@nospecialize T::Type{<:Tuple}) = nfields(T) +known_length(@nospecialize T::Type{<:NamedTuple}) = fieldcount(T) +known_length(::Type{<:AbstractCartesianIndex{N}}) where {N} = N +function known_length(T::Type{<:AbstractRange}) + _range_length(known_first(T), known_step(T), known_last(T)) +end + +@inline maybe_static_length(x) = maybe_static(known_length, length, x) +@inline maybe_static_first(x) = maybe_static(known_first, first, x) +@inline maybe_static_last(x) = maybe_static(known_last, last, x) +@inline maybe_static_step(x) = maybe_static(known_step, step, x) +@inline function maybe_static(f::F, g::G, x) where {F,G} + L = f(x) + if L === nothing + return g(x) + else + return StaticInt(L) + end +end +