From 922454be5324e8abb2d0f80458eed93c939baf15 Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Thu, 18 Sep 2025 10:34:29 -0400 Subject: [PATCH 1/6] Support public interface for non-exported --- src/Static.jl | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Static.jl b/src/Static.jl index 1f2ccef..2abae97 100644 --- a/src/Static.jl +++ b/src/Static.jl @@ -5,6 +5,10 @@ import IfElse: ifelse export StaticInt, StaticFloat64, StaticSymbol, True, False, StaticBool, NDIndex export dynamic, is_static, known, static, static_promote +if VERSION >= v"1.11.0-DEV.469" + eval(Meta.parse("public OptionallyStaticRange, OptionallyStaticUnitRange, OptionallyStaticStepRange, SUnitRange, SOneTo, static_promote, eachop, eachop_tuple, reduce_tup, eq, ne, gt, ge, le, lt, mul, add")) +end + import PrecompileTools: @recompile_invalidations @recompile_invalidations begin @@ -966,10 +970,10 @@ end return (Base.to_index(A, I[1]), to_indices(A, indstail, Base.tail(I))...) end -function Base.show(io::IO, @nospecialize(x::Union{StaticNumber, StaticSymbol, NDIndex})) +function Base.show(@nospecialize(io::IO), @nospecialize(x::Union{StaticNumber, StaticSymbol, NDIndex})) show(io, MIME"text/plain"(), x) end -function Base.show(io::IO, ::MIME"text/plain", +function Base.show(@nospecialize(io::IO), ::MIME"text/plain", @nospecialize(x::Union{StaticNumber, StaticSymbol})) print(io, "static(" * repr(known(typeof(x))) * ")") nothing From f8d870646fc7689df80492f711045957c199f02b Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Fri, 19 Sep 2025 00:41:30 -0400 Subject: [PATCH 2/6] Remove unecessary `public static_promote` --- src/Static.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Static.jl b/src/Static.jl index 2abae97..e11f7d8 100644 --- a/src/Static.jl +++ b/src/Static.jl @@ -6,7 +6,7 @@ export StaticInt, StaticFloat64, StaticSymbol, True, False, StaticBool, NDIndex export dynamic, is_static, known, static, static_promote if VERSION >= v"1.11.0-DEV.469" - eval(Meta.parse("public OptionallyStaticRange, OptionallyStaticUnitRange, OptionallyStaticStepRange, SUnitRange, SOneTo, static_promote, eachop, eachop_tuple, reduce_tup, eq, ne, gt, ge, le, lt, mul, add")) + eval(Meta.parse("public OptionallyStaticRange, OptionallyStaticUnitRange, OptionallyStaticStepRange, SUnitRange, SOneTo, eachop, eachop_tuple, reduce_tup, eq, ne, gt, ge, le, lt, mul, add")) end import PrecompileTools: @recompile_invalidations From 14e531eeb7249551434fb0950d2a62eb0c4d7b57 Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Fri, 19 Sep 2025 05:45:18 -0400 Subject: [PATCH 3/6] Add + doc static_first, static_step, static_last to exports These methods are used in downstream packages and are useful for reconstructing static indices. They now have docs and are exported to be officially made part of the public API. --- src/Static.jl | 9 +++---- src/ranges.jl | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/src/Static.jl b/src/Static.jl index e11f7d8..bd45405 100644 --- a/src/Static.jl +++ b/src/Static.jl @@ -3,7 +3,8 @@ module Static import IfElse: ifelse export StaticInt, StaticFloat64, StaticSymbol, True, False, StaticBool, NDIndex -export dynamic, is_static, known, static, static_promote +export dynamic, is_static, known, static, static_promote, static_first, static_step, + static_last if VERSION >= v"1.11.0-DEV.469" eval(Meta.parse("public OptionallyStaticRange, OptionallyStaticUnitRange, OptionallyStaticStepRange, SUnitRange, SOneTo, eachop, eachop_tuple, reduce_tup, eq, ne, gt, ge, le, lt, mul, add")) @@ -970,11 +971,11 @@ end return (Base.to_index(A, I[1]), to_indices(A, indstail, Base.tail(I))...) end -function Base.show(@nospecialize(io::IO), @nospecialize(x::Union{StaticNumber, StaticSymbol, NDIndex})) +function Base.show(io::IO, @nospecialize(x::Union{StaticNumber, StaticSymbol, NDIndex})) show(io, MIME"text/plain"(), x) end -function Base.show(@nospecialize(io::IO), ::MIME"text/plain", - @nospecialize(x::Union{StaticNumber, StaticSymbol})) +function Base.show( + io::IO, ::MIME"text/plain", @nospecialize(x::Union{StaticNumber, StaticSymbol})) print(io, "static(" * repr(known(typeof(x))) * ")") nothing end diff --git a/src/ranges.jl b/src/ranges.jl index b93bb5a..3ee76a8 100644 --- a/src/ranges.jl +++ b/src/ranges.jl @@ -150,16 +150,79 @@ const OptionallyStaticRange{ F, L} = Union{OptionallyStaticUnitRange{F, L}, OptionallyStaticStepRange{F, <:Any, L}} -# these probide a generic method for extracting potentially static values. +""" + static_first(x::AbstractRange) + +Attempt to return `static(first(x))`, if known at compile time. Otherwise, return +`first(x)`. + +See also: [`static_step`](@ref), [`static_last`](@ref) + +# Examples + +```julia +julia> static_first(static(2):10) +static(2) + +julia> static_first(1:10) +1 + +julia> static_first(Base.OneTo(10)) +static(1) + +``` +""" static_first(x::Base.OneTo) = StaticInt(1) static_first(x::Union{Base.Slice, Base.IdentityUnitRange}) = static_first(x.indices) static_first(x::OptionallyStaticRange) = getfield(x, :start) static_first(x) = first(x) +""" + static_step(x::AbstractRange) + +Attempt to return `static(step(x))`, if known at compile time. Otherwise, return +`step(x)`. + +See also: [`static_first`](@ref), [`static_last`](@ref) + +# Examples + +```julia +julia> static_step(static(1):static(3):9) +static(3) + +julia> static_step(1:3:9) +3 + +julia> static_step(1:9) +static(1) + +``` +""" static_step(@nospecialize x::AbstractUnitRange) = StaticInt(1) static_step(x::OptionallyStaticStepRange) = getfield(x, :step) static_step(x) = step(x) + +""" + static_last(x::AbstractRange) + +Attempt to return `static(last(x))`, if known at compile time. Otherwise, return +`last(x)`. + +See also: [`static_first`](@ref), [`static_step`](@ref) + +# Examples + +```julia +julia> static_last(static(1):static(10)) +static(10) + +julia> static_last(static(1):10) +10 + +``` +""" static_last(x::OptionallyStaticRange) = getfield(x, :stop) static_last(x) = last(x) static_last(x::Union{Base.Slice, Base.IdentityUnitRange}) = static_last(x.indices) From 274833f87189ebe8aef3017e6392376adc763c44 Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Fri, 19 Sep 2025 06:12:37 -0400 Subject: [PATCH 4/6] Use SciMLPublic.jl --- Project.toml | 2 ++ src/Static.jl | 7 ++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Project.toml b/Project.toml index facebf5..8eb2bb2 100644 --- a/Project.toml +++ b/Project.toml @@ -7,12 +7,14 @@ version = "1.2.0" CommonWorldInvalidations = "f70d9fcc-98c5-4d4a-abd7-e4cdeebd8ca8" IfElse = "615f187c-cbe4-4ef1-ba3b-2fcf58d6d173" PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a" +SciMLPublic = "431bcebd-1456-4ced-9d72-93c2757fff0b" [compat] Aqua = "0.8.4" CommonWorldInvalidations = "1" IfElse = "0.1" PrecompileTools = "1.1" +SciMLPublic = "1.0.0" Test = "1" julia = "1.10" diff --git a/src/Static.jl b/src/Static.jl index bd45405..5c06720 100644 --- a/src/Static.jl +++ b/src/Static.jl @@ -1,14 +1,15 @@ module Static import IfElse: ifelse +using SciMLPublic: @public export StaticInt, StaticFloat64, StaticSymbol, True, False, StaticBool, NDIndex export dynamic, is_static, known, static, static_promote, static_first, static_step, static_last -if VERSION >= v"1.11.0-DEV.469" - eval(Meta.parse("public OptionallyStaticRange, OptionallyStaticUnitRange, OptionallyStaticStepRange, SUnitRange, SOneTo, eachop, eachop_tuple, reduce_tup, eq, ne, gt, ge, le, lt, mul, add")) -end +@public OptionallyStaticRange, +OptionallyStaticUnitRange, OptionallyStaticStepRange, SUnitRange, SOneTo +@public eachop, eachop_tuple, reduce_tup, eq, ne, gt, ge, le, lt, mul, add import PrecompileTools: @recompile_invalidations From ccd7b31127484f4981aca0e2d870c3d4b6f84b27 Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Fri, 19 Sep 2025 06:32:57 -0400 Subject: [PATCH 5/6] Fixed formatting and minor version bump --- Project.toml | 2 +- src/ranges.jl | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Project.toml b/Project.toml index 8eb2bb2..b193577 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "Static" uuid = "aedffcd0-7271-4cad-89d0-dc628f76c6d3" authors = ["chriselrod", "ChrisRackauckas", "Tokazama"] -version = "1.2.0" +version = "1.3.0" [deps] CommonWorldInvalidations = "f70d9fcc-98c5-4d4a-abd7-e4cdeebd8ca8" diff --git a/src/ranges.jl b/src/ranges.jl index 3ee76a8..17bb500 100644 --- a/src/ranges.jl +++ b/src/ranges.jl @@ -147,7 +147,7 @@ SOneTo(n::Int) = SOneTo{n}() Base.oneto(::StaticInt{N}) where {N} = SOneTo{N}() const OptionallyStaticRange{ - F, L} = Union{OptionallyStaticUnitRange{F, L}, +F, L} = Union{OptionallyStaticUnitRange{F, L}, OptionallyStaticStepRange{F, <:Any, L}} """ @@ -203,7 +203,6 @@ static_step(@nospecialize x::AbstractUnitRange) = StaticInt(1) static_step(x::OptionallyStaticStepRange) = getfield(x, :step) static_step(x) = step(x) - """ static_last(x::AbstractRange) From e65f29def56e6b918820117702071c021953f2aa Mon Sep 17 00:00:00 2001 From: "Zachary P. Christensen" Date: Sat, 20 Sep 2025 22:42:16 -0400 Subject: [PATCH 6/6] Fix docs, particularly to reflect their public scoping For unexported public methods, this adds `Static.` to the docs. Some of these docs weren't fully implemented, requiring some new doc strings. This also fixed some of the consistency between docs. --- src/Static.jl | 104 ++++++++++++++++++++++++++++++++++++-------------- src/float.jl | 2 +- src/ranges.jl | 2 +- 3 files changed, 77 insertions(+), 31 deletions(-) diff --git a/src/Static.jl b/src/Static.jl index 5c06720..207cf94 100644 --- a/src/Static.jl +++ b/src/Static.jl @@ -18,7 +18,7 @@ import PrecompileTools: @recompile_invalidations end """ - StaticSymbol + StaticSymbol(S::Symbol)::StaticSymbol{S} A statically typed `Symbol`. """ @@ -39,7 +39,7 @@ Base.Symbol(@nospecialize(s::StaticSymbol)) = known(s) abstract type StaticInteger{N} <: Number end """ - StaticBool(x::Bool) -> True/False + StaticBool(x::Bool)::Union{True, False} A statically typed `Bool`. """ @@ -61,7 +61,7 @@ function StaticBool(x::Bool) end """ - StaticInt(N::Int) -> StaticInt{N}() + 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. @@ -74,7 +74,7 @@ struct StaticInt{N} <: StaticInteger{N} end """ - IntType(x::Integer) -> Union{Int,StaticInt} + IntType(x::Integer)::Union{Int, StaticInt} `IntType` is a union of `Int` and `StaticInt`. As a function, it ensures that `x` one of the two. @@ -262,9 +262,10 @@ function static(x::X) where {X} end """ - is_static(::Type{T}) -> StaticBool + is_static(::Type{T})::Union{True, False} -Returns `True` if `T` is a static type. +If `T` is a static type return `static(true)::True` and otherwise returns +`static(false)::False` See also: [`static`](@ref), [`known`](@ref) """ @@ -588,7 +589,7 @@ permute(@nospecialize(x::Tuple), @nospecialize(perm::Val)) = permute(x, static(p end """ - eachop(op, args...; iterator::Tuple{Vararg{StaticInt}}) -> Tuple + Static.eachop(op, args...; iterator::Tuple{Vararg{StaticInt}})::Tuple Produces a tuple of `(op(args..., iterator[1]), op(args..., iterator[2]),...)`. """ @@ -598,7 +599,7 @@ end eachop(::F, ::Tuple{}, args::Vararg{Any}) where {F} = () """ - eachop_tuple(op, arg, args...; iterator::Tuple{Vararg{StaticInt}}) -> Type{Tuple} + Static.eachop_tuple(op, arg, args...; iterator::Tuple{Vararg{StaticInt}})::Type{Tuple} Produces a tuple type of `Tuple{op(arg, args..., iterator[1]), op(arg, args..., iterator[2]),...}`. Note that if one of the arguments passed to `op` is a `Tuple` type then it should be the first argument @@ -787,66 +788,109 @@ end end """ - eq(x, y) + Static.eq(x, y)::Union{Bool, True, False} -Equivalent to `!=` but if `x` and `y` are both static returns a `StaticBool. +Equivalent to `==` but if `x` and `y` are static the return value is a `StaticBool. """ eq(x::X, y::Y) where {X, Y} = ifelse(is_static(X) & is_static(Y), static, identity)(x == y) + +""" + Static.eq(x)::Base.Fix2{typeof(Static.eq}} + +Create a function that compares `x` to other values using `Static.eq` (i.e. a +function equivalent to `y -> Static.eq(y, x)`). +""" eq(x) = Base.Fix2(eq, x) """ - ne(x, y) + Static.ne(x, y)::Union{Bool, True, False} -Equivalent to `!=` but if `x` and `y` are both static returns a `StaticBool. +Equivalent to `!=` but if `x` and `y` are static the return value is a `StaticBool. """ ne(x::X, y::Y) where {X, Y} = !eq(x, y) + +""" + Static.ne(x)::Base.Fix2{typeof(Static.ne}} + +Create a function that compares `x` to other values using `Static.ne` (i.e. a +function equivalent to `y -> Static.ne(y, x))`. +""" ne(x) = Base.Fix2(ne, x) """ - gt(x, y) + Static.ne(x, y)::Union{Bool, True, False} -Equivalent to `>` but if `x` and `y` are both static returns a `StaticBool. +Equivalent to `>` but if `x` and `y` are static the return value is a `StaticBool. """ gt(x::X, y::Y) where {X, Y} = ifelse(is_static(X) & is_static(Y), static, identity)(x > y) + +""" + Static.gt(x)::Base.Fix2{typeof(Static.gt}} + +Create a function that compares `x` to other values using `Static.gt` (i.e. a +function equivalent to `y -> Static.gt(y, x))`. +""" gt(x) = Base.Fix2(gt, x) """ - ge(x, y) + Static.ge(x, y)::Union{Bool, True, False} -Equivalent to `>=` but if `x` and `y` are both static returns a `StaticBool. +Equivalent to `>=` but if `x` and `y` are static the return value is a `StaticBool. """ ge(x::X, y::Y) where {X, Y} = ifelse(is_static(X) & is_static(Y), static, identity)(x >= y) + +""" + Static.ge(x)::Base.Fix2{typeof(Static.ge}} + +Create a function that compares `x` to other values using `Static.ge` (i.e. a +function equivalent to `y -> Static.ge(y, x)`). +""" ge(x) = Base.Fix2(ge, x) """ - le(x, y) + Static.le(x, y)::Union{Bool, True, False} -Equivalent to `<=` but if `x` and `y` are both static returns a `StaticBool. +Equivalent to `<=` but if `x` and `y` are static the return value is a `StaticBool. """ le(x::X, y::Y) where {X, Y} = ifelse(is_static(X) & is_static(Y), static, identity)(x <= y) + +""" + Static.le(x)::Base.Fix2{typeof(Static.le}} + +Create a function that compares `x` to other values using `Static.le` (i.e. a +function equivalent to `y -> Static.le(y, x)`). +""" le(x) = Base.Fix2(le, x) """ - lt(x, y) + Static.lt(x, y)::Union{Bool, True, False} -Equivalent to `<` but if `x` and `y` are both static returns a `StaticBool. +Equivalent to `<` but if `x` and `y` are static the return value is a `StaticBool.` """ lt(x::X, y::Y) where {X, Y} = ifelse(is_static(X) & is_static(Y), static, identity)(x < y) + +""" + Static.lt(x)::Base.Fix2{typeof(Static.lt}} + +Create a function that compares `x` to other values using `Static.lt` (i.e. a +function equivalent to y -> Static.lt(y, x)). +""" lt(x) = Base.Fix2(lt, x) """ - mul(x) -> Base.Fix2(*, x) - mul(x, y) -> + Static.mul(x)::Base.Fix2{typeof(*)} -Equivalent to `*` but allows for lazy multiplication when passing functions. +Create a function that multiplies `x` with other values (i.e. a function +equivalent to `y -> y * x`). """ mul(x) = Base.Fix2(*, x) """ - add(x) -> Base.Fix2(+, x) - add(x, y) -> + Static.add(x) -> Base.Fix2(+, x) + Static.add(x, y) -Equivalent to `+` but allows for lazy addition when passing functions. +Create a function that adds `x` to other values (i.e. a function equivalent to +`y -> y + x`). """ add(x) = Base.Fix2(+, x) @@ -972,15 +1016,17 @@ end return (Base.to_index(A, I[1]), to_indices(A, indstail, Base.tail(I))...) end -function Base.show(io::IO, @nospecialize(x::Union{StaticNumber, StaticSymbol, NDIndex})) +function Base.show(@nospecialize(io::IO), @nospecialize(x::Union{ + StaticNumber, StaticSymbol, NDIndex})) show(io, MIME"text/plain"(), x) end function Base.show( - io::IO, ::MIME"text/plain", @nospecialize(x::Union{StaticNumber, StaticSymbol})) + @nospecialize(io::IO), ::MIME"text/plain", @nospecialize(x::Union{ + StaticNumber, StaticSymbol})) print(io, "static(" * repr(known(typeof(x))) * ")") nothing end -function Base.show(io::IO, m::MIME"text/plain", @nospecialize(x::NDIndex)) +function Base.show(@nospecialize(io::IO), m::MIME"text/plain", @nospecialize(x::NDIndex)) print(io, "NDIndex") show(io, m, Tuple(x)) nothing diff --git a/src/float.jl b/src/float.jl index 00a66d7..5d4b6d1 100644 --- a/src/float.jl +++ b/src/float.jl @@ -1,6 +1,6 @@ """ - StaticFloat64{N} + StaticFloat64(F::Float64)::StaticFloat64{F} A statically sized `Float64`. Use `StaticFloat64(N)` instead of `Val(N)` when you want it to behave like a number. diff --git a/src/ranges.jl b/src/ranges.jl index 17bb500..3de504e 100644 --- a/src/ranges.jl +++ b/src/ranges.jl @@ -147,7 +147,7 @@ SOneTo(n::Int) = SOneTo{n}() Base.oneto(::StaticInt{N}) where {N} = SOneTo{N}() const OptionallyStaticRange{ -F, L} = Union{OptionallyStaticUnitRange{F, L}, + F, L} = Union{OptionallyStaticUnitRange{F, L}, OptionallyStaticStepRange{F, <:Any, L}} """