From e4fbcc40a322fd1557f0370a3b19fe6c48398525 Mon Sep 17 00:00:00 2001 From: Chris Elrod <elrodc@gmail.com> Date: Tue, 8 Sep 2020 23:30:24 -0400 Subject: [PATCH 01/12] Added tests for Static. --- README.md | 8 +++++ src/ArrayInterface.jl | 1 + src/ranges.jl | 59 +++++++++++++++++---------------- src/static.jl | 76 +++++++++++++++++++++++++++++++++++++++++++ test/runtests.jl | 42 +++++++++++++++++++----- 5 files changed, 150 insertions(+), 36 deletions(-) create mode 100644 src/static.jl diff --git a/README.md b/README.md index 5fcc56f64..ca1e862f9 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,14 @@ Otherwise, returns `nothing`. For example, `known_step(UnitRange{Int})` returns If `length` of an instance of type `T` is known at compile time, return it. Otherwise, return `nothing`. +## Static(N::Int) + +Creates a static integer with value known at compile time. It is a number, +supporting basic arithmetic. Many operations with two `Static` integers +will produce another `Static` integer. If one of the arguments to a +function call isn't static (e.g., `Static(4) + 3`) then the `Static` +number will promote to a dynamic value. + # List of things to add - https://github.com/JuliaLang/julia/issues/22216 diff --git a/src/ArrayInterface.jl b/src/ArrayInterface.jl index d666ac594..cb6aab1fa 100644 --- a/src/ArrayInterface.jl +++ b/src/ArrayInterface.jl @@ -699,6 +699,7 @@ function __init__() end end +include("static.jl") include("ranges.jl") end diff --git a/src/ranges.jl b/src/ranges.jl index e47be321f..a4f626e5a 100644 --- a/src/ranges.jl +++ b/src/ranges.jl @@ -44,6 +44,8 @@ known_step(::Type{<:AbstractUnitRange{T}}) where {T} = one(T) _get(x) = x _get(::Val{V}) where {V} = V +_get(::Static{V}) where {V} = V +_get(::Type{Static{V}}) where {V} = V _convert(::Type{T}, x) where {T} = convert(T, x) _convert(::Type{T}, ::Val{V}) where {T,V} = Val(convert(T, V)) @@ -56,7 +58,7 @@ at compile time. An `OptionallyStaticUnitRange` is intended to be constructed in 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{T,F,L} <: AbstractUnitRange{T} +struct OptionallyStaticUnitRange{T <: Integer, F <: Integer, L <: Integer} <: AbstractUnitRange{T} start::F stop::L @@ -79,10 +81,8 @@ struct OptionallyStaticUnitRange{T,F,L} <: AbstractUnitRange{T} function OptionallyStaticUnitRange(x::AbstractRange) if step(x) == 1 - fst = known_first(x) - fst = fst === nothing ? first(x) : Val(fst) - lst = known_last(x) - lst = lst === nothing ? last(x) : Val(lst) + fst = static_first(x) + lst = static_last(x) return OptionallyStaticUnitRange(fst, lst) else throw(ArgumentError("step must be 1, got $(step(r))")) @@ -90,17 +90,17 @@ struct OptionallyStaticUnitRange{T,F,L} <: AbstractUnitRange{T} end end -Base.first(r::OptionallyStaticUnitRange{<:Any,Val{F}}) where {F} = F -Base.first(r::OptionallyStaticUnitRange{<:Any,<:Any}) = r.start +Base.:(:)(L, ::Static{U}) where {U} = OptionallyStaticUnitRange(L, Static(U)) +Base.:(:)(::Static{L}, U) where {L} = OptionallyStaticUnitRange(Static(L), U) +Base.:(:)(::Static{L}, ::Static{U}) where {L,U} = OptionallyStaticUnitRange(Static(L), Static(U)) +Base.first(r::OptionallyStaticUnitRange) = r.start Base.step(r::OptionallyStaticUnitRange{T}) where {T} = oneunit(T) +Base.last(r::OptionallyStaticUnitRange) = r.stop -Base.last(r::OptionallyStaticUnitRange{<:Any,<:Any,Val{L}}) where {L} = L -Base.last(r::OptionallyStaticUnitRange{<:Any,<:Any,<:Any}) = r.stop - -known_first(::Type{<:OptionallyStaticUnitRange{<:Any,Val{F}}}) where {F} = F +known_first(::Type{<:OptionallyStaticUnitRange{<:Any,Static{F}}}) where {F} = F known_step(::Type{<:OptionallyStaticUnitRange{T}}) where {T} = one(T) -known_last(::Type{<:OptionallyStaticUnitRange{<:Any,<:Any,Val{L}}}) where {L} = L +known_last(::Type{<:OptionallyStaticUnitRange{<:Any,<:Any,Static{L}}}) where {L} = L function Base.isempty(r::OptionallyStaticUnitRange) if known_first(r) === oneunit(eltype(r)) @@ -141,10 +141,19 @@ end return convert(eltype(r), val) end -_try_static(x, y) = Val(x) -_try_static(::Nothing, y) = Val(y) -_try_static(x, ::Nothing) = Val(x) -_try_static(::Nothing, ::Nothing) = nothing +@inline _try_static(::Static{N}, ::Static{N}) where {N} = Static{N}() +function _try_static(::Static{N}, x) where {N} + @assert N == x "Unequal Indices: Static{$N}() != x == $x" + Static{N}() +end +function _try_static(x, ::Static{N}) where {N} + @assert N == x "Unequal Indices: x == $x != Static{$N}()" + Static{N}() +end +function _try_static(x, y) + @assert x == y "Unequal Indicess: x == $x != $y == y" + x +end ### ### length @@ -193,7 +202,7 @@ specified then indices for visiting each index of `x` is returned. """ @inline function indices(x) inds = eachindex(x) - if inds isa AbstractUnitRange{<:Integer} + if inds isa AbstractUnitRange#{<:Integer} # prevents inference return Base.Slice(OptionallyStaticUnitRange(inds)) else return inds @@ -202,30 +211,24 @@ end function indices(x::Tuple) inds = map(eachindex, x) - @assert all(isequal(first(inds)), Base.tail(inds)) "Not all specified axes are equal: $inds" return reduce(_pick_range, inds) end -indices(x, d) = indices(axes(x, d)) +@inline indices(x, d) = indices(axes(x, d)) -@inline function indices(x::NTuple{N,<:Any}, dim) where {N} +@inline function indices(x::Tuple{Vararg{Any,N}}, dim) where {N} inds = map(x_i -> indices(x_i, dim), x) - @assert all(isequal(first(inds)), Base.tail(inds)) "Not all specified axes are equal: $inds" return reduce(_pick_range, inds) end -@inline function indices(x::NTuple{N,<:Any}, dim::NTuple{N,<:Any}) where {N} +@inline function indices(x::Tuple{Vararg{Any,N}}, dim::Tuple{Vararg{Any,N}}) where {N} inds = map(indices, x, dim) - @assert all(isequal(first(inds)), Base.tail(inds)) "Not all specified axes are equal: $inds" return reduce(_pick_range, inds) end @inline function _pick_range(x, y) - fst = _try_static(known_first(x), known_first(y)) - fst = fst === nothing ? first(x) : fst - - lst = _try_static(known_last(x), known_last(y)) - lst = lst === nothing ? last(x) : lst + fst = _try_static(static_first(x), static_first(y)) + lst = _try_static(static_last(x), static_last(y)) return Base.Slice(OptionallyStaticUnitRange(fst, lst)) end diff --git a/src/static.jl b/src/static.jl new file mode 100644 index 000000000..28d7a1e3a --- /dev/null +++ b/src/static.jl @@ -0,0 +1,76 @@ + +""" +A statically sized `Int`. +Use `Static(N)` instead of `Val(N)` when you want it to behave like a number. +""" +struct Static{N} <: Integer + Static{N}() where {N} = new{N::Int}() +end +Base.@pure Static(N::Int) = Static{N}() +Static(N) = Static(convert(Int,N)) +Static(::Val{N}) where {N} = Static{N}() +@inline Base.Val(::Static{N}) where {N} = Val{N}() +Base.convert(::Type{T}, ::Static{N}) where {T<:Number,N} = convert(T, N) +Base.convert(::Type{Static{N}}, ::Static{N}) where {N} = Static{N}() +Base.promote_rule(::Type{<:Static}, ::Type{T}) where {T} = promote_rule(Int, T) +Base.promote_rule(::Type{T}, ::Type{<:Static}) where {T} = promote_rule(T, Int) +Base.promote_rule(::Type{<:Static}, ::Type{<:Static}) where {T} = Int +Base.:(%)(::Static{N}, ::Type{Integer}) where {N} = N + +@inline Base.iszero(::Static{0}) = true +@inline Base.iszero(::Static) = false + +Base.:(+)(i::Number, ::Static{0}) = i +Base.:(+)(::Static{0}, i::Number) = i +Base.:(+)(::Static{0}, i::Integer) = i +Base.:(+)(::Static{0}, ::Static{0}) = Static{0}() +Base.:(+)(::Static{N}, ::Static{0}) where {N} = Static{N}() +Base.:(+)(::Static{0}, ::Static{N}) where {N} = Static{N}() +Base.:(+)(::Static{M}, ::Static{N}) where {M,N} = Static{M + N}() + +Base.:(-)(::Static{0}, i::Number) = -i +Base.:(-)(i::Number, ::Static{0}) = i +Base.:(-)(::Static{0}, ::Static{0}) = Static{0}() +Base.:(-)(::Static{0}, ::Static{N}) where {N} = Static{-N}() +Base.:(-)(::Static{N}, ::Static{0}) where {N} = Static{N}() +Base.:(-)(::Static{M}, ::Static{N}) where {M,N} = Static{M - N}() + +Base.:(*)(::Static{0}, i::Number) = Static{0}() +Base.:(*)(i::Number, ::Static{0}) = Static{0}() +Base.:(*)(::Static{0}, ::Static{M}) where {M} = Static{0}() +Base.:(*)(::Static{M}, ::Static{0}) where {M} = Static{0}() +Base.:(*)(::Static{0}, ::Static{0}) = Static{0}() +Base.:(*)(::Static{1}, i::Number) = i +Base.:(*)(i::Number, ::Static{1}) = i +Base.:(*)(::Static{0}, ::Static{1}) where {M} = Static{0}() +Base.:(*)(::Static{1}, ::Static{0}) where {M} = Static{0}() +Base.:(*)(::Static{M}, ::Static{1}) where {M} = Static{M}() +Base.:(*)(::Static{1}, ::Static{M}) where {M} = Static{M}() +Base.:(*)(::Static{1}, ::Static{1}) = Static{1}() +Base.:(*)(::Static{M}, ::Static{N}) where {M,N} = Static{M * N}() + +Base.:(÷)(::Static{M}, ::Static{N}) where {M,N} = Static{M ÷ N}() +Base.:(%)(::Static{M}, ::Static{N}) where {M,N} = Static{M % N}() +Base.:(<<)(::Static{M}, ::Static{N}) where {M,N} = Static{M << N}() +Base.:(>>)(::Static{M}, ::Static{N}) where {M,N} = Static{M >> N}() +Base.:(>>>)(::Static{M}, ::Static{N}) where {M,N} = Static{M >>> N}() +Base.:(&)(::Static{M}, ::Static{N}) where {M,N} = Static{M & N}() +Base.:(|)(::Static{M}, ::Static{N}) where {M,N} = Static{M | N}() +Base.:(⊻)(::Static{M}, ::Static{N}) where {M,N} = Static{M ⊻ N}() + +Base.:(==)(::Static{M}, ::Static{N}) where {M,N} = false +Base.:(==)(::Static{M}, ::Static{M}) where {M} = true +Base.:(≤)(::Static{M}, N::Int) where {M} = M ≤ N +Base.:(≤)(N::Int, ::Static{M}) where {M} = N ≤ M +Base.:(≥)(::Static{M}, N::Int) where {M} = M ≤ N +Base.:(≥)(N::Int, ::Static{M}) where {M} = N ≥ M + +@inline function maybe_static(f::F, g::G, x) where {F, G} + L = f(x) + isnothing(L) ? g(x) : Static(L) +end +@inline static_length(x) = maybe_static(known_length, length, x) +@inline static_first(x) = maybe_static(known_first, first, x) +@inline static_last(x) = maybe_static(known_last, last, x) +@inline static_step(x) = maybe_static(known_step, step, x) + diff --git a/test/runtests.jl b/test/runtests.jl index 4a6902341..29192e694 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,6 +1,6 @@ using ArrayInterface, Test using Base: setindex -import ArrayInterface: has_sparsestruct, findstructralnz, fast_scalar_indexing, lu_instance +import ArrayInterface: has_sparsestruct, findstructralnz, fast_scalar_indexing, lu_instance, Static @test ArrayInterface.ismutable(rand(3)) using StaticArrays @@ -220,12 +220,38 @@ end end @testset "indices" begin - @test @inferred(ArrayInterface.indices((ones(2, 3), ones(3, 2)))) == 1:6 - @test @inferred(ArrayInterface.indices(ones(2, 3))) == 1:6 - @test @inferred(ArrayInterface.indices(ones(2, 3), 1)) == 1:2 - @test @inferred(ArrayInterface.indices((ones(2, 3), ones(3, 2)), (1, 2))) == 1:2 - @test @inferred(ArrayInterface.indices((ones(2, 3), ones(2, 3)), 1)) == 1:2 - @test_throws AssertionError ArrayInterface.indices((ones(2, 3), ones(3, 3)), 1) - @test_throws AssertionError ArrayInterface.indices((ones(2, 3), ones(3, 3)), (1, 2)) + A23 = ones(2,3); SA23 = @SMatrix ones(2,3); + A32 = ones(3,2); SA32 = @SMatrix ones(3,2); + @test @inferred(ArrayInterface.indices((A23, A32))) == 1:6 + @test @inferred(ArrayInterface.indices((SA23, A32))) == 1:6 + @test @inferred(ArrayInterface.indices((A23, SA32))) == 1:6 + @test @inferred(ArrayInterface.indices((SA23, SA32))) == 1:6 + @test @inferred(ArrayInterface.indices(A23)) == 1:6 + @test @inferred(ArrayInterface.indices(SA23)) == 1:6 + @test @inferred(ArrayInterface.indices(A23, 1)) == 1:2 + @test @inferred(ArrayInterface.indices(SA23, Static(1))) === Base.Slice(Static(1):Static(2)) + @test @inferred(ArrayInterface.indices((A23, A32), (1, 2))) == 1:2 + @test @inferred(ArrayInterface.indices((SA23, A32), (Static(1), 2))) === Base.Slice(Static(1):Static(2)) + @test @inferred(ArrayInterface.indices((A23, SA32), (1, Static(2)))) === Base.Slice(Static(1):Static(2)) + @test @inferred(ArrayInterface.indices((SA23, SA32), (Static(1), Static(2)))) === Base.Slice(Static(1):Static(2)) + @test @inferred(ArrayInterface.indices((A23, A23), 1)) == 1:2 + @test @inferred(ArrayInterface.indices((SA23, SA23), Static(1))) === Base.Slice(Static(1):Static(2)) + @test @inferred(ArrayInterface.indices((SA23, A23), Static(1))) === Base.Slice(Static(1):Static(2)) + @test @inferred(ArrayInterface.indices((A23, SA23), Static(1))) === Base.Slice(Static(1):Static(2)) + @test @inferred(ArrayInterface.indices((SA23, SA23), Static(1))) === Base.Slice(Static(1):Static(2)) + @test_throws AssertionError ArrayInterface.indices((A23, ones(3, 3)), 1) + @test_throws AssertionError ArrayInterface.indices((A23, ones(3, 3)), (1, 2)) + @test_throws AssertionError ArrayInterface.indices((SA23, ones(3, 3)), Static(1)) + @test_throws AssertionError ArrayInterface.indices((SA23, ones(3, 3)), (Static(1), 2)) +end + +@testset "Static" begin + @test iszero(Static(0)) + @test !iszero(Static(1)) + # test for ambiguities and correctness + for i ∈ [Static(0), Static(1), Static(2), 3], j ∈ [Static(0), Static(1), Static(2), 3], 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 From 1260be6751e020b7afaaf9b42b80751c5d228de1 Mon Sep 17 00:00:00 2001 From: Chris Elrod <elrodc@gmail.com> Date: Wed, 9 Sep 2020 03:36:47 -0400 Subject: [PATCH 02/12] Allow all bitsinteger types. --- src/static.jl | 112 ++++++++++++++++++++++++++++---------------------- 1 file changed, 62 insertions(+), 50 deletions(-) diff --git a/src/static.jl b/src/static.jl index 28d7a1e3a..3bf4ce488 100644 --- a/src/static.jl +++ b/src/static.jl @@ -4,12 +4,15 @@ A statically sized `Int`. Use `Static(N)` instead of `Val(N)` when you want it to behave like a number. """ struct Static{N} <: Integer - Static{N}() where {N} = new{N::Int}() + function Static{N}() where {N} + @assert isa(typeof(N), Base.BitIntegerType) "$N is not a primitive integer type" + return new{N}() + end end -Base.@pure Static(N::Int) = Static{N}() -Static(N) = Static(convert(Int,N)) +Base.@pure Static(N::Union{Int8, Int16, Int32, Int64, Int128, UInt8, UInt16, UInt32, UInt64, UInt128}) = Static{N}() +Static(N) = Static(convert(Int, N)) Static(::Val{N}) where {N} = Static{N}() -@inline Base.Val(::Static{N}) where {N} = Val{N}() +Base.Val(::Static{N}) where {N} = Val{N}() Base.convert(::Type{T}, ::Static{N}) where {T<:Number,N} = convert(T, N) Base.convert(::Type{Static{N}}, ::Static{N}) where {N} = Static{N}() Base.promote_rule(::Type{<:Static}, ::Type{T}) where {T} = promote_rule(Int, T) @@ -17,53 +20,62 @@ Base.promote_rule(::Type{T}, ::Type{<:Static}) where {T} = promote_rule(T, Int) Base.promote_rule(::Type{<:Static}, ::Type{<:Static}) where {T} = Int Base.:(%)(::Static{N}, ::Type{Integer}) where {N} = N -@inline Base.iszero(::Static{0}) = true -@inline Base.iszero(::Static) = false +@inline Base.iszero(::Static{M}) where {M} = iszero(M) +@inline Base.isone(::Static{M}) where {M} = isone(M) -Base.:(+)(i::Number, ::Static{0}) = i -Base.:(+)(::Static{0}, i::Number) = i -Base.:(+)(::Static{0}, i::Integer) = i -Base.:(+)(::Static{0}, ::Static{0}) = Static{0}() -Base.:(+)(::Static{N}, ::Static{0}) where {N} = Static{N}() -Base.:(+)(::Static{0}, ::Static{N}) where {N} = Static{N}() -Base.:(+)(::Static{M}, ::Static{N}) where {M,N} = Static{M + N}() - -Base.:(-)(::Static{0}, i::Number) = -i -Base.:(-)(i::Number, ::Static{0}) = i -Base.:(-)(::Static{0}, ::Static{0}) = Static{0}() -Base.:(-)(::Static{0}, ::Static{N}) where {N} = Static{-N}() -Base.:(-)(::Static{N}, ::Static{0}) where {N} = Static{N}() -Base.:(-)(::Static{M}, ::Static{N}) where {M,N} = Static{M - N}() - -Base.:(*)(::Static{0}, i::Number) = Static{0}() -Base.:(*)(i::Number, ::Static{0}) = Static{0}() -Base.:(*)(::Static{0}, ::Static{M}) where {M} = Static{0}() -Base.:(*)(::Static{M}, ::Static{0}) where {M} = Static{0}() -Base.:(*)(::Static{0}, ::Static{0}) = Static{0}() -Base.:(*)(::Static{1}, i::Number) = i -Base.:(*)(i::Number, ::Static{1}) = i -Base.:(*)(::Static{0}, ::Static{1}) where {M} = Static{0}() -Base.:(*)(::Static{1}, ::Static{0}) where {M} = Static{0}() -Base.:(*)(::Static{M}, ::Static{1}) where {M} = Static{M}() -Base.:(*)(::Static{1}, ::Static{M}) where {M} = Static{M}() -Base.:(*)(::Static{1}, ::Static{1}) = Static{1}() -Base.:(*)(::Static{M}, ::Static{N}) where {M,N} = Static{M * N}() - -Base.:(÷)(::Static{M}, ::Static{N}) where {M,N} = Static{M ÷ N}() -Base.:(%)(::Static{M}, ::Static{N}) where {M,N} = Static{M % N}() -Base.:(<<)(::Static{M}, ::Static{N}) where {M,N} = Static{M << N}() -Base.:(>>)(::Static{M}, ::Static{N}) where {M,N} = Static{M >> N}() -Base.:(>>>)(::Static{M}, ::Static{N}) where {M,N} = Static{M >>> N}() -Base.:(&)(::Static{M}, ::Static{N}) where {M,N} = Static{M & N}() -Base.:(|)(::Static{M}, ::Static{N}) where {M,N} = Static{M | N}() -Base.:(⊻)(::Static{M}, ::Static{N}) where {M,N} = Static{M ⊻ N}() - -Base.:(==)(::Static{M}, ::Static{N}) where {M,N} = false -Base.:(==)(::Static{M}, ::Static{M}) where {M} = true -Base.:(≤)(::Static{M}, N::Int) where {M} = M ≤ N -Base.:(≤)(N::Int, ::Static{M}) where {M} = N ≤ M -Base.:(≥)(::Static{M}, N::Int) where {M} = M ≤ N -Base.:(≥)(N::Int, ::Static{M}) where {M} = N ≥ M +for T ∈ [:Any, :Number] + @eval begin + @inline function Base.:(+)(i::$T, ::Static{M}) where {M} + if iszero(M) + i + else + i + M + end + end + @inline function Base.:(+)(::Static{M}, i::$T) where {M} + if iszero(M) + i + else + M + i + end + end + @inline function Base.:(-)(i::$T, ::Static{M}) where {M} + if iszero(M) + i + else + i - M + end + end + @inline function Base.:(*)(i::$T, j::Static{M}) where {M} + if iszero(M) + j + elseif isone(M) + i + else + i * M + end + end + @inline function Base.:(*)(j::Static{M}, i::$T) where {M} + if iszero(M) + j + elseif isone(M) + i + else + M * i + end + end + end +end +for f ∈ [:(+), :(-), :(*), :(/), :(÷), :(%), :(<<), :(>>), :(>>>), :(&), :(|), :(⊻)] + @eval @generated Base.$f(::Static{M}, ::Static{N}) where {M,N} = Expr(:call, Expr(:curly, :Static, $f(M, N))) +end +for f ∈ [:(==), :(!=), :(<), :(≤), :(>), :(≥)] + @eval begin + @inline Base.$f(::Static{M}, ::Static{N}) where {M,N} = $f(M, N) + @inline Base.$f(::Static{M}, x::Integer) where {M} = $f(M, x) + @inline Base.$f(x::Integer, ::Static{M}) where {M} = $f(x, M) + end +end @inline function maybe_static(f::F, g::G, x) where {F, G} L = f(x) From 0ce3a686b6ac9a438cdfa9f5f6e6cc2ad983c417 Mon Sep 17 00:00:00 2001 From: Chris Elrod <elrodc@gmail.com> Date: Wed, 9 Sep 2020 03:50:06 -0400 Subject: [PATCH 03/12] Only allow `Int` once again. --- src/static.jl | 66 +++++++++++++++++---------------------------------- 1 file changed, 22 insertions(+), 44 deletions(-) diff --git a/src/static.jl b/src/static.jl index 3bf4ce488..ef42c6714 100644 --- a/src/static.jl +++ b/src/static.jl @@ -5,11 +5,10 @@ Use `Static(N)` instead of `Val(N)` when you want it to behave like a number. """ struct Static{N} <: Integer function Static{N}() where {N} - @assert isa(typeof(N), Base.BitIntegerType) "$N is not a primitive integer type" - return new{N}() + return new{N::Int}() end end -Base.@pure Static(N::Union{Int8, Int16, Int32, Int64, Int128, UInt8, UInt16, UInt32, UInt64, UInt128}) = Static{N}() +Base.@pure Static(N::Int) = Static{N}() Static(N) = Static(convert(Int, N)) Static(::Val{N}) where {N} = Static{N}() Base.Val(::Static{N}) where {N} = Val{N}() @@ -20,52 +19,31 @@ Base.promote_rule(::Type{T}, ::Type{<:Static}) where {T} = promote_rule(T, Int) Base.promote_rule(::Type{<:Static}, ::Type{<:Static}) where {T} = Int Base.:(%)(::Static{N}, ::Type{Integer}) where {N} = N -@inline Base.iszero(::Static{M}) where {M} = iszero(M) -@inline Base.isone(::Static{M}) where {M} = isone(M) +Base.iszero(::Static{0}) = true +Base.iszero(::Static) = false +Base.isone(::Static{1}) = true +Base.isone(::Static) = false for T ∈ [:Any, :Number] @eval begin - @inline function Base.:(+)(i::$T, ::Static{M}) where {M} - if iszero(M) - i - else - i + M - end - end - @inline function Base.:(+)(::Static{M}, i::$T) where {M} - if iszero(M) - i - else - M + i - end - end - @inline function Base.:(-)(i::$T, ::Static{M}) where {M} - if iszero(M) - i - else - i - M - end - end - @inline function Base.:(*)(i::$T, j::Static{M}) where {M} - if iszero(M) - j - elseif isone(M) - i - else - i * M - end - end - @inline function Base.:(*)(j::Static{M}, i::$T) where {M} - if iszero(M) - j - elseif isone(M) - i - else - M * i - end - end + @inline Base.:(+)(i::$T, ::Static{0}) = i + @inline Base.:(+)(i::$T, ::Static{M}) where {M} = i + M + @inline Base.:(+)(::Static{0}, i::$T) = i + @inline Base.:(+)(::Static{M}, i::$T) where {M} = M + i + @inline Base.:(-)(i::$T, ::Static{0}) = i + @inline Base.:(-)(i::$T, ::Static{M}) where {M} = i - M + @inline Base.:(*)(i::$T, ::Static{0}) = Static{0}() + @inline Base.:(*)(i::$T, ::Static{1}) = i + @inline Base.:(*)(i::$T, ::Static{M}) where {M} = i * M + @inline Base.:(*)(::Static{0}, i::$T) = Static{0}() + @inline Base.:(*)(::Static{1}, i::$T) = i + @inline Base.:(*)(::Static{M}, i::$T) where {M} = M * i end end +@inline Base.:(*)(::Static{0}, ::Static{0}) = Static{0}() +@inline Base.:(*)(::Static{1}, ::Static{0}) = Static{0}() +@inline Base.:(*)(::Static{0}, ::Static{1}) = Static{0}() +@inline Base.:(*)(::Static{1}, ::Static{1}) = Static{1}() for f ∈ [:(+), :(-), :(*), :(/), :(÷), :(%), :(<<), :(>>), :(>>>), :(&), :(|), :(⊻)] @eval @generated Base.$f(::Static{M}, ::Static{N}) where {M,N} = Expr(:call, Expr(:curly, :Static, $f(M, N))) end From c6c9c9b95538b6309a65d987322e0ddca42070e7 Mon Sep 17 00:00:00 2001 From: Chris Elrod <elrodc@gmail.com> Date: Wed, 9 Sep 2020 04:09:34 -0400 Subject: [PATCH 04/12] More tests. --- src/static.jl | 2 +- test/runtests.jl | 22 +++++++++++++++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/static.jl b/src/static.jl index ef42c6714..34a77356f 100644 --- a/src/static.jl +++ b/src/static.jl @@ -24,7 +24,7 @@ Base.iszero(::Static) = false Base.isone(::Static{1}) = true Base.isone(::Static) = false -for T ∈ [:Any, :Number] +for T ∈ [:Any, :Number, :Integer] @eval begin @inline Base.:(+)(i::$T, ::Static{0}) = i @inline Base.:(+)(i::$T, ::Static{M}) where {M} = i + M diff --git a/test/runtests.jl b/test/runtests.jl index 29192e694..f7e80a09f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -249,9 +249,25 @@ end @test iszero(Static(0)) @test !iszero(Static(1)) # test for ambiguities and correctness - for i ∈ [Static(0), Static(1), Static(2), 3], j ∈ [Static(0), Static(1), Static(2), 3], f ∈ [+, -, *, ÷, %, <<, >>, >>>, &, |, ⊻, ==, ≤, ≥] - (iszero(j) && ((f === ÷) || (f === %))) && continue # integer division error - @test convert(Int, @inferred(f(i,j))) == f(convert(Int, i), convert(Int, j)) + for i ∈ [Static(0), Static(1), Static(2), 3] + for j ∈ [Static(0), Static(1), Static(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 ∈ [+, -, *, /, ÷, %, ==, ≤, ≥] + x = f(convert(Int, i), 1.4) + y = f(1.4, convert(Int, i)) + @test convert(typeof(x), @inferred(f(i, 1.4))) === x + @test convert(typeof(y), @inferred(f(1.4, i))) === y # if f is division and i === Static(0), returns `NaN`; hence use of ==== in check. + end end + @test @inferred("Hello world!" * Static(0)) === Static(0) + @test @inferred("Hello world!" * Static(1)) === "Hello world!" + @test @inferred(Static(0) * "Hello world!") === Static(0) + @test @inferred(Static(1) * "Hello world!") === "Hello world!" end From e679776be9cdddaee72fd3f471dc433d65c7c692 Mon Sep 17 00:00:00 2001 From: Chris Elrod <elrodc@gmail.com> Date: Wed, 9 Sep 2020 04:20:55 -0400 Subject: [PATCH 05/12] Detect ambiguities and unbound_args. --- src/ranges.jl | 5 +++-- test/runtests.jl | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/ranges.jl b/src/ranges.jl index a4f626e5a..be0460c86 100644 --- a/src/ranges.jl +++ b/src/ranges.jl @@ -90,8 +90,8 @@ struct OptionallyStaticUnitRange{T <: Integer, F <: Integer, L <: Integer} <: Ab end end -Base.:(:)(L, ::Static{U}) where {U} = OptionallyStaticUnitRange(L, Static(U)) -Base.:(:)(::Static{L}, U) where {L} = OptionallyStaticUnitRange(Static(L), U) +Base.:(:)(L::Integer, ::Static{U}) where {U} = OptionallyStaticUnitRange(L, Static(U)) +Base.:(:)(::Static{L}, U::Integer) where {L} = OptionallyStaticUnitRange(Static(L), U) Base.:(:)(::Static{L}, ::Static{U}) where {L,U} = OptionallyStaticUnitRange(Static(L), Static(U)) Base.first(r::OptionallyStaticUnitRange) = r.start @@ -142,6 +142,7 @@ end end @inline _try_static(::Static{N}, ::Static{N}) where {N} = Static{N}() +@inline _try_static(::Static{M}, ::Static{N}) where {M, N} = @assert false "Unequal Indices: Static{$M}() != Static{$N}()" function _try_static(::Static{N}, x) where {N} @assert N == x "Unequal Indices: Static{$N}() != x == $x" Static{N}() diff --git a/test/runtests.jl b/test/runtests.jl index f7e80a09f..70b7821f7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,6 +3,9 @@ using Base: setindex import ArrayInterface: has_sparsestruct, findstructralnz, fast_scalar_indexing, lu_instance, Static @test ArrayInterface.ismutable(rand(3)) +@test isempty(detect_unbound_args(ArrayInterface)) +@test isempty(detect_ambiguities(ArrayInterface)) + using StaticArrays x = @SVector [1,2,3] @test ArrayInterface.ismutable(x) == false @@ -243,6 +246,7 @@ end @test_throws AssertionError ArrayInterface.indices((A23, ones(3, 3)), (1, 2)) @test_throws AssertionError ArrayInterface.indices((SA23, ones(3, 3)), Static(1)) @test_throws AssertionError ArrayInterface.indices((SA23, ones(3, 3)), (Static(1), 2)) + @test_throws AssertionError ArrayInterface.indices((SA23, SA23), (Static(1), Static(2))) end @testset "Static" begin From b68a8a6cfa11daa6480716dd5b681b70fc6624b6 Mon Sep 17 00:00:00 2001 From: Chris Elrod <elrodc@gmail.com> Date: Wed, 9 Sep 2020 04:30:22 -0400 Subject: [PATCH 06/12] One line inner constructor. --- src/static.jl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/static.jl b/src/static.jl index 34a77356f..cfc1b0ddb 100644 --- a/src/static.jl +++ b/src/static.jl @@ -4,9 +4,7 @@ A statically sized `Int`. Use `Static(N)` instead of `Val(N)` when you want it to behave like a number. """ struct Static{N} <: Integer - function Static{N}() where {N} - return new{N::Int}() - end + Static{N}() where {N} = new{N::Int}() end Base.@pure Static(N::Int) = Static{N}() Static(N) = Static(convert(Int, N)) From 9bae9f64d0560805b6a8e02e4308bf9a155cb84a Mon Sep 17 00:00:00 2001 From: Chris Elrod <elrodc@gmail.com> Date: Wed, 9 Sep 2020 06:30:48 -0400 Subject: [PATCH 07/12] Fix type ambiguities on master. --- src/static.jl | 44 +++++++++++++++++++++++++++++++++++++------- test/runtests.jl | 4 ---- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/static.jl b/src/static.jl index cfc1b0ddb..e81ca828f 100644 --- a/src/static.jl +++ b/src/static.jl @@ -7,14 +7,34 @@ struct Static{N} <: Integer Static{N}() where {N} = new{N::Int}() end Base.@pure Static(N::Int) = Static{N}() -Static(N) = Static(convert(Int, N)) +Static(N::Integer) = Static(convert(Int, N)) +Static(::Static{N}) where {N} = Static{N}() Static(::Val{N}) where {N} = Static{N}() Base.Val(::Static{N}) where {N} = Val{N}() Base.convert(::Type{T}, ::Static{N}) where {T<:Number,N} = convert(T, N) Base.convert(::Type{Static{N}}, ::Static{N}) where {N} = Static{N}() -Base.promote_rule(::Type{<:Static}, ::Type{T}) where {T} = promote_rule(Int, T) -Base.promote_rule(::Type{T}, ::Type{<:Static}) where {T} = promote_rule(T, Int) -Base.promote_rule(::Type{<:Static}, ::Type{<:Static}) where {T} = Int +for S ∈ [:Any, :AbstractIrrational]#, :(Complex{<:Real})] +# let S = :Any + @eval begin + Base.promote_rule(::Type{<:Static}, ::Type{T}) where {T <: $S} = promote_rule(Int, T) + Base.promote_rule(::Type{T}, ::Type{<:Static}) where {T <: $S} = promote_rule(T, Int) + end +end +for (S,T) ∈ [(:Complex,:Real), (:Rational, :Integer), (:(Base.TwicePrecision),:Any)] + @eval Base.promote_rule(::Type{$S{T}}, ::Type{<:Static}) where {T <: $T} = promote_rule($S{T}, Int) +end +Base.promote_rule(::Type{Union{Nothing,Missing}}, ::Type{<:Static}) = Union{Nothing, Missing, Int} +Base.promote_rule(::Type{T}, ::Type{<:Static}) where {T >: Union{Missing,Nothing}} = promote_rule(T, Int) +Base.promote_rule(::Type{T}, ::Type{<:Static}) where {T >: Nothing} = promote_rule(T, Int) +Base.promote_rule(::Type{T}, ::Type{<:Static}) where {T >: Missing} = promote_rule(T, Int) +for T ∈ [:Bool, :Missing, :BigFloat, :BigInt, :Nothing] +# let S = :Any + @eval begin + Base.promote_rule(::Type{<:Static}, ::Type{$T}) = promote_rule(Int, $T) + Base.promote_rule(::Type{$T}, ::Type{<:Static}) = promote_rule($T, Int) + end +end +Base.promote_rule(::Type{<:Static}, ::Type{<:Static}) = Int Base.:(%)(::Static{N}, ::Type{Integer}) where {N} = N Base.iszero(::Static{0}) = true @@ -22,7 +42,7 @@ Base.iszero(::Static) = false Base.isone(::Static{1}) = true Base.isone(::Static) = false -for T ∈ [:Any, :Number, :Integer] +for T = [:Real, :Rational, :Integer] @eval begin @inline Base.:(+)(i::$T, ::Static{0}) = i @inline Base.:(+)(i::$T, ::Static{M}) where {M} = i + M @@ -38,18 +58,28 @@ for T ∈ [:Any, :Number, :Integer] @inline Base.:(*)(::Static{M}, i::$T) where {M} = M * i end end +@inline Base.:(+)(::Static{0}, ::Static{0}) = Static{0}() +@inline Base.:(+)(::Static{0}, ::Static{M}) where {M} = Static{M}() +@inline Base.:(+)(::Static{M}, ::Static{0}) where {M} = Static{M}() + +@inline Base.:(-)(::Static{M}, ::Static{0}) where {M} = Static{M}() + @inline Base.:(*)(::Static{0}, ::Static{0}) = Static{0}() @inline Base.:(*)(::Static{1}, ::Static{0}) = Static{0}() @inline Base.:(*)(::Static{0}, ::Static{1}) = Static{0}() @inline Base.:(*)(::Static{1}, ::Static{1}) = Static{1}() +@inline Base.:(*)(::Static{M}, ::Static{0}) where {M} = Static{0}() +@inline Base.:(*)(::Static{0}, ::Static{M}) where {M} = Static{0}() +@inline Base.:(*)(::Static{M}, ::Static{1}) where {M} = Static{M}() +@inline Base.:(*)(::Static{1}, ::Static{M}) where {M} = Static{M}() for f ∈ [:(+), :(-), :(*), :(/), :(÷), :(%), :(<<), :(>>), :(>>>), :(&), :(|), :(⊻)] @eval @generated Base.$f(::Static{M}, ::Static{N}) where {M,N} = Expr(:call, Expr(:curly, :Static, $f(M, N))) end for f ∈ [:(==), :(!=), :(<), :(≤), :(>), :(≥)] @eval begin @inline Base.$f(::Static{M}, ::Static{N}) where {M,N} = $f(M, N) - @inline Base.$f(::Static{M}, x::Integer) where {M} = $f(M, x) - @inline Base.$f(x::Integer, ::Static{M}) where {M} = $f(x, M) + @inline Base.$f(::Static{M}, x::Int) where {M} = $f(M, x) + @inline Base.$f(x::Int, ::Static{M}) where {M} = $f(x, M) end end diff --git a/test/runtests.jl b/test/runtests.jl index 70b7821f7..523081c5c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -269,9 +269,5 @@ end @test convert(typeof(y), @inferred(f(1.4, i))) === y # if f is division and i === Static(0), returns `NaN`; hence use of ==== in check. end end - @test @inferred("Hello world!" * Static(0)) === Static(0) - @test @inferred("Hello world!" * Static(1)) === "Hello world!" - @test @inferred(Static(0) * "Hello world!") === Static(0) - @test @inferred(Static(1) * "Hello world!") === "Hello world!" end From dc1727c87baef98517b4caa8a8fe00e1272df0a7 Mon Sep 17 00:00:00 2001 From: Chris Elrod <elrodc@gmail.com> Date: Wed, 9 Sep 2020 11:25:10 -0400 Subject: [PATCH 08/12] Use Aqua to test ambiguities and unbound_args (as well as undef exports. --- Project.toml | 3 ++- test/runtests.jl | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Project.toml b/Project.toml index 4473467d6..d70b9896f 100644 --- a/Project.toml +++ b/Project.toml @@ -12,6 +12,7 @@ Requires = "0.5, 1.0" julia = "1.2" [extras] +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" BandedMatrices = "aae01518-5342-5314-be14-df237901396f" BlockBandedMatrices = "ffab5731-97b5-5995-9138-79e8c1846df0" LabelledArrays = "2ee39098-c373-598a-b85f-a56591580800" @@ -21,4 +22,4 @@ SuiteSparse = "4607b0f0-06f3-5cda-b6b1-a6196a1729e9" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Test", "LabelledArrays", "StaticArrays", "BandedMatrices", "BlockBandedMatrices", "SuiteSparse", "Random"] +test = ["Test", "LabelledArrays", "StaticArrays", "BandedMatrices", "BlockBandedMatrices", "SuiteSparse", "Random", "Aqua"] diff --git a/test/runtests.jl b/test/runtests.jl index 523081c5c..7e3e839f0 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,8 +3,8 @@ using Base: setindex import ArrayInterface: has_sparsestruct, findstructralnz, fast_scalar_indexing, lu_instance, Static @test ArrayInterface.ismutable(rand(3)) -@test isempty(detect_unbound_args(ArrayInterface)) -@test isempty(detect_ambiguities(ArrayInterface)) +using Aqua +Aqua.test_all(ArrayInterface) using StaticArrays x = @SVector [1,2,3] From dbb41cc4eea91cbdeaa906ea191b0b9ca032c0f2 Mon Sep 17 00:00:00 2001 From: Chris Elrod <elrodc@gmail.com> Date: Wed, 9 Sep 2020 12:03:52 -0400 Subject: [PATCH 09/12] Minor change. --- src/static.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/static.jl b/src/static.jl index e81ca828f..3312c5e91 100644 --- a/src/static.jl +++ b/src/static.jl @@ -30,8 +30,8 @@ Base.promote_rule(::Type{T}, ::Type{<:Static}) where {T >: Missing} = promote_ru for T ∈ [:Bool, :Missing, :BigFloat, :BigInt, :Nothing] # let S = :Any @eval begin - Base.promote_rule(::Type{<:Static}, ::Type{$T}) = promote_rule(Int, $T) - Base.promote_rule(::Type{$T}, ::Type{<:Static}) = promote_rule($T, Int) + Base.promote_rule(::Type{S}, ::Type{$T}) where {S <: Static} = promote_rule(Int, $T) + Base.promote_rule(::Type{$T}, ::Type{S}) where {S <: Static} = promote_rule($T, Int) end end Base.promote_rule(::Type{<:Static}, ::Type{<:Static}) = Int From 32afe1c9bf7bf4ca8dd1a86e3e77480bfadd158d Mon Sep 17 00:00:00 2001 From: Chris Elrod <elrodc@gmail.com> Date: Wed, 9 Sep 2020 12:20:06 -0400 Subject: [PATCH 10/12] Trying fix for Julia 1.2. --- src/static.jl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/static.jl b/src/static.jl index 3312c5e91..29d532588 100644 --- a/src/static.jl +++ b/src/static.jl @@ -13,8 +13,9 @@ Static(::Val{N}) where {N} = Static{N}() Base.Val(::Static{N}) where {N} = Val{N}() Base.convert(::Type{T}, ::Static{N}) where {T<:Number,N} = convert(T, N) Base.convert(::Type{Static{N}}, ::Static{N}) where {N} = Static{N}() -for S ∈ [:Any, :AbstractIrrational]#, :(Complex{<:Real})] -# let S = :Any +# for S ∈ [:Any, :AbstractIrrational]#, :(Complex{<:Real})] + # let S = :Any +let S = :AbstractIrrational @eval begin Base.promote_rule(::Type{<:Static}, ::Type{T}) where {T <: $S} = promote_rule(Int, T) Base.promote_rule(::Type{T}, ::Type{<:Static}) where {T <: $S} = promote_rule(T, Int) @@ -27,7 +28,7 @@ Base.promote_rule(::Type{Union{Nothing,Missing}}, ::Type{<:Static}) = Union{Noth Base.promote_rule(::Type{T}, ::Type{<:Static}) where {T >: Union{Missing,Nothing}} = promote_rule(T, Int) Base.promote_rule(::Type{T}, ::Type{<:Static}) where {T >: Nothing} = promote_rule(T, Int) Base.promote_rule(::Type{T}, ::Type{<:Static}) where {T >: Missing} = promote_rule(T, Int) -for T ∈ [:Bool, :Missing, :BigFloat, :BigInt, :Nothing] +for T ∈ [:Bool, :Missing, :BigFloat, :BigInt, :Nothing, :Any] # let S = :Any @eval begin Base.promote_rule(::Type{S}, ::Type{$T}) where {S <: Static} = promote_rule(Int, $T) From 9ab918e5a7d85c27052ffd1c4d9300d1b48a4ad0 Mon Sep 17 00:00:00 2001 From: Chris Elrod <elrodc@gmail.com> Date: Wed, 9 Sep 2020 13:41:10 -0400 Subject: [PATCH 11/12] Add integer check to `indices` via `eltype(inds) <: Integer --- src/ranges.jl | 2 +- src/static.jl | 11 +++-------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/ranges.jl b/src/ranges.jl index be0460c86..fddef2bf6 100644 --- a/src/ranges.jl +++ b/src/ranges.jl @@ -203,7 +203,7 @@ specified then indices for visiting each index of `x` is returned. """ @inline function indices(x) inds = eachindex(x) - if inds isa AbstractUnitRange#{<:Integer} # prevents inference + if inds isa AbstractUnitRange && eltype(inds) <: Integer return Base.Slice(OptionallyStaticUnitRange(inds)) else return inds diff --git a/src/static.jl b/src/static.jl index 29d532588..6a90cecd5 100644 --- a/src/static.jl +++ b/src/static.jl @@ -13,14 +13,9 @@ Static(::Val{N}) where {N} = Static{N}() Base.Val(::Static{N}) where {N} = Val{N}() Base.convert(::Type{T}, ::Static{N}) where {T<:Number,N} = convert(T, N) Base.convert(::Type{Static{N}}, ::Static{N}) where {N} = Static{N}() -# for S ∈ [:Any, :AbstractIrrational]#, :(Complex{<:Real})] - # let S = :Any -let S = :AbstractIrrational - @eval begin - Base.promote_rule(::Type{<:Static}, ::Type{T}) where {T <: $S} = promote_rule(Int, T) - Base.promote_rule(::Type{T}, ::Type{<:Static}) where {T <: $S} = promote_rule(T, Int) - end -end + +Base.promote_rule(::Type{<:Static}, ::Type{T}) where {T <: AbstractIrrational} = promote_rule(Int, T) +Base.promote_rule(::Type{T}, ::Type{<:Static}) where {T <: AbstractIrrational} = promote_rule(T, Int) for (S,T) ∈ [(:Complex,:Real), (:Rational, :Integer), (:(Base.TwicePrecision),:Any)] @eval Base.promote_rule(::Type{$S{T}}, ::Type{<:Static}) where {T <: $T} = promote_rule($S{T}, Int) end From 581b18a4ac80649fde465a78ea918e1b8e7eae83 Mon Sep 17 00:00:00 2001 From: chriselrod <elrodc@gmail.com> Date: Wed, 9 Sep 2020 14:46:11 -0400 Subject: [PATCH 12/12] Remove `_get` method. --- src/ranges.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ranges.jl b/src/ranges.jl index fddef2bf6..601c8455e 100644 --- a/src/ranges.jl +++ b/src/ranges.jl @@ -43,7 +43,6 @@ known_step(::Type{<:AbstractUnitRange{T}}) where {T} = one(T) # add methods to support ArrayInterface _get(x) = x -_get(::Val{V}) where {V} = V _get(::Static{V}) where {V} = V _get(::Type{Static{V}}) where {V} = V _convert(::Type{T}, x) where {T} = convert(T, x)