Skip to content
Draft
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions base/Base.jl
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,8 @@ include("irrationals.jl")
include("mathconstants.jl")
using .MathConstants: ℯ, π, pi

include("staticint.jl")

# metaprogramming
include("meta.jl")

Expand Down
105 changes: 105 additions & 0 deletions base/staticint.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@

"""
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}()
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.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(@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_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)
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)
end
end
Base.promote_rule(@nospecialize(T1::Type{<:StaticInt}), @nospecialize(T2::Type{<:StaticInt})) = Int

Base.:(%)(@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()

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
end
end
@inline Base.:(-)(::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)}()
end
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))
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))
end
end

@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))
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))
Base.UnitRange(@nospecialize(start::StaticInt), @nospecialize(stop::StaticInt))
UnitRange(Int(start), Int(stop))
end

78 changes: 78 additions & 0 deletions test/staticint.jl
Original file line number Diff line number Diff line change
@@ -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