Skip to content

Commit 1a4e094

Browse files
jishnubtimholy
andauthored
Add AbstractOneTo and have OneTo be its subtype (JuliaLang#56902)
Currently, `Base` defines `similar` for `Base.OneTo`, with the understanding that offset axes will be handled elsewhere. However, `Base.OneTo` is just one possible one-based range, and there are others such as `StaticArrays.SOneTo` that exist in the ecosystem. `Base` doesn't provide a method to handle a combination of different one-based ranges in `similar`, which leaves the packages in an awkward position: they need to define methods like ```julia similar(A::AbstractArray, ::Type{T}, shape::HeterogeneousShapeTuple) where {T} = similar(A, T, homogenize_shape(shape)) ``` where `HeterogeneousShapeTuple` is defined as ```julia Tuple{Vararg{Union{Integer, Base.OneTo, SOneTo}}} ``` https://github.com/JuliaArrays/StaticArrays.jl/blob/07c12450d1b3481dda4b503564ae4a5cb4e27ce4/src/abstractarray.jl#L141-L146 Unfortunately, such methods are borderline type-piracy, as noted in JuliaArrays/StaticArrays.jl#1248. In particular, if the narrower `Base` method that handles `Union{Integer, OneTo}` is removed, then this method explicitly becomes pirating. A solution to this situation is to have `Base` handle all one-based ranges, such that arbitrary combinations of one-based ranges hit fallback methods in `Base`. This PR is a first step in this direction. We add the abstract type `AbstractOneTo`, and have `OneTo` be its subtype. We also add methods to `similar` and `reshape` that accept `AbstractOneTo` arguments. This makes it unnecessary for packages to dispatch on awkward combinations of `Union{Integer, OneTo}` and custom one-based axes, as the base implementation would handle such cases already. There may be other methods that accept an `AbstractOneTo` instead of a `OneTo`, but these may be addressed in separate PRs. Also, there may be one-based ranges that can't subtype `AbstractOneTo`, and a full solution that accepts such ranges as well needs to be implemented through a trait. This may also be handled in a separate PR. --------- Co-authored-by: Tim Holy <[email protected]>
1 parent 73ac39e commit 1a4e094

File tree

6 files changed

+49
-16
lines changed

6 files changed

+49
-16
lines changed

NEWS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ New library functions
2525
New library features
2626
--------------------
2727

28-
`sort(keys(::Dict))` and `sort(values(::Dict))` now automatically collect, they previously threw ([#56978]).
28+
* `sort(keys(::Dict))` and `sort(values(::Dict))` now automatically collect, they previously threw ([#56978]).
29+
* `Base.AbstractOneTo` is added as a supertype of one-based axes, with `Base.OneTo` as its subtype ([#56902]).
2930

3031
Standard library changes
3132
------------------------

base/abstractarray.jl

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -818,15 +818,18 @@ julia> similar(falses(10), Float64, 2, 4)
818818
See also: [`undef`](@ref), [`isassigned`](@ref).
819819
"""
820820
similar(a::AbstractArray{T}) where {T} = similar(a, T)
821-
similar(a::AbstractArray, ::Type{T}) where {T} = similar(a, T, to_shape(axes(a)))
822-
similar(a::AbstractArray{T}, dims::Tuple) where {T} = similar(a, T, to_shape(dims))
823-
similar(a::AbstractArray{T}, dims::DimOrInd...) where {T} = similar(a, T, to_shape(dims))
824-
similar(a::AbstractArray, ::Type{T}, dims::DimOrInd...) where {T} = similar(a, T, to_shape(dims))
821+
similar(a::AbstractArray, ::Type{T}) where {T} = similar(a, T, axes(a))
822+
similar(a::AbstractArray{T}, dims::Tuple) where {T} = similar(a, T, dims)
823+
similar(a::AbstractArray{T}, dims::DimOrInd...) where {T} = similar(a, T, dims)
824+
similar(a::AbstractArray, ::Type{T}, dims::DimOrInd...) where {T} = similar(a, T, dims)
825825
# Similar supports specifying dims as either Integers or AbstractUnitRanges or any mixed combination
826826
# thereof. Ideally, we'd just convert Integers to OneTos and then call a canonical method with the axes,
827827
# but we don't want to require all AbstractArray subtypes to dispatch on Base.OneTo. So instead we
828828
# define this method to convert supported axes to Ints, with the expectation that an offset array
829829
# package will define a method with dims::Tuple{Union{Integer, UnitRange}, Vararg{Union{Integer, UnitRange}}}
830+
similar(a::AbstractArray, ::Type{T}, dims::Tuple{Union{Integer, AbstractOneTo}, Vararg{Union{Integer, AbstractOneTo}}}) where {T} = similar(a, T, to_shape(dims))
831+
# legacy method for packages that specialize similar(A::AbstractArray, ::Type{T}, dims::Tuple{Union{Integer, OneTo, CustomAxis}, Vararg{Union{Integer, OneTo, CustomAxis}}}
832+
# leaving this method in ensures that Base owns the more specific method
830833
similar(a::AbstractArray, ::Type{T}, dims::Tuple{Union{Integer, OneTo}, Vararg{Union{Integer, OneTo}}}) where {T} = similar(a, T, to_shape(dims))
831834
# similar creates an Array by default
832835
similar(a::AbstractArray, ::Type{T}, dims::Dims{N}) where {T,N} = Array{T,N}(undef, dims)
@@ -837,7 +840,7 @@ to_shape(dims::DimsOrInds) = map(to_shape, dims)::DimsOrInds
837840
# each dimension
838841
to_shape(i::Int) = i
839842
to_shape(i::Integer) = Int(i)
840-
to_shape(r::OneTo) = Int(last(r))
843+
to_shape(r::AbstractOneTo) = Int(last(r))
841844
to_shape(r::AbstractUnitRange) = r
842845

843846
"""
@@ -863,6 +866,8 @@ would create a 1-dimensional logical array whose indices match those
863866
of the columns of `A`.
864867
"""
865868
similar(::Type{T}, dims::DimOrInd...) where {T<:AbstractArray} = similar(T, dims)
869+
similar(::Type{T}, shape::Tuple{Union{Integer, AbstractOneTo}, Vararg{Union{Integer, AbstractOneTo}}}) where {T<:AbstractArray} = similar(T, to_shape(shape))
870+
# legacy method for packages that specialize similar(::Type{T}, dims::Tuple{Union{Integer, OneTo, CustomAxis}, Vararg{Union{Integer, OneTo, CustomAxis}})
866871
similar(::Type{T}, shape::Tuple{Union{Integer, OneTo}, Vararg{Union{Integer, OneTo}}}) where {T<:AbstractArray} = similar(T, to_shape(shape))
867872
similar(::Type{T}, dims::Dims) where {T<:AbstractArray} = T(undef, dims)
868873

base/range.jl

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,14 +451,21 @@ if isdefined(Main, :Base)
451451
end
452452
end
453453

454+
"""
455+
Base.AbstractOneTo
456+
457+
Abstract type for ranges that start at 1 and have a step size of 1.
458+
"""
459+
abstract type AbstractOneTo{T} <: AbstractUnitRange{T} end
460+
454461
"""
455462
Base.OneTo(n)
456463
457464
Define an `AbstractUnitRange` that behaves like `1:n`, with the added
458465
distinction that the lower limit is guaranteed (by the type system) to
459466
be 1.
460467
"""
461-
struct OneTo{T<:Integer} <: AbstractUnitRange{T}
468+
struct OneTo{T<:Integer} <: AbstractOneTo{T}
462469
stop::T # invariant: stop >= zero(stop)
463470
function OneTo{T}(stop) where {T<:Integer}
464471
throwbool(r) = (@noinline; throw(ArgumentError("invalid index: $r of type Bool")))

base/reshapedarray.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ julia> reshape(1:6, 2, 3)
120120
reshape
121121

122122
reshape(parent::AbstractArray, dims::IntOrInd...) = reshape(parent, dims)
123+
reshape(parent::AbstractArray, shp::Tuple{Union{Integer,AbstractOneTo}, Vararg{Union{Integer,AbstractOneTo}}}) = reshape(parent, to_shape(shp))
124+
# legacy method for packages that specialize reshape(parent::AbstractArray, shp::Tuple{Union{Integer,OneTo,CustomAxis}, Vararg{Union{Integer,OneTo,CustomAxis}}})
125+
# leaving this method in ensures that Base owns the more specific method
123126
reshape(parent::AbstractArray, shp::Tuple{Union{Integer,OneTo}, Vararg{Union{Integer,OneTo}}}) = reshape(parent, to_shape(shp))
124127
reshape(parent::AbstractArray, dims::Tuple{Integer, Vararg{Integer}}) = reshape(parent, map(Int, dims))
125128
reshape(parent::AbstractArray, dims::Dims) = _reshape(parent, dims)

test/abstractarray.jl

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2227,3 +2227,24 @@ end
22272227
@test_throws MethodError isreal(G)
22282228
end
22292229
end
2230+
2231+
@testset "similar/reshape for AbstractOneTo" begin
2232+
A = [1,2]
2233+
@testset "reshape" begin
2234+
@test reshape(A, 2, SizedArrays.SOneTo(1)) == reshape(A, 2, 1)
2235+
@test reshape(A, Base.OneTo(2), SizedArrays.SOneTo(1)) == reshape(A, 2, 1)
2236+
@test reshape(A, SizedArrays.SOneTo(1), 2) == reshape(A, 1, 2)
2237+
@test reshape(A, SizedArrays.SOneTo(1), Base.OneTo(2)) == reshape(A, 1, 2)
2238+
end
2239+
@testset "similar" begin
2240+
b = similar(A, SizedArrays.SOneTo(1), big(2))
2241+
@test b isa Array{Int, 2}
2242+
@test size(b) == (1, 2)
2243+
b = similar(A, SizedArrays.SOneTo(1), Base.OneTo(2))
2244+
@test b isa Array{Int, 2}
2245+
@test size(b) == (1, 2)
2246+
b = similar(A, SizedArrays.SOneTo(1), 2, Base.OneTo(2))
2247+
@test b isa Array{Int, 3}
2248+
@test size(b) == (1, 2, 2)
2249+
end
2250+
end

test/testhelpers/SizedArrays.jl

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import LinearAlgebra: mul!
1414

1515
export SizedArray
1616

17-
struct SOneTo{N} <: AbstractUnitRange{Int} end
17+
struct SOneTo{N} <: Base.AbstractOneTo{Int} end
1818
SOneTo(N) = SOneTo{N}()
1919
Base.length(::SOneTo{N}) where {N} = N
2020
Base.size(r::SOneTo) = (length(r),)
@@ -58,14 +58,6 @@ Base.parent(S::SizedArray) = S.data
5858
+(S1::SizedArray{SZ}, S2::SizedArray{SZ}) where {SZ} = SizedArray{SZ}(S1.data + S2.data)
5959
==(S1::SizedArray{SZ}, S2::SizedArray{SZ}) where {SZ} = S1.data == S2.data
6060

61-
homogenize_shape(t::Tuple) = (_homogenize_shape(first(t)), homogenize_shape(Base.tail(t))...)
62-
homogenize_shape(::Tuple{}) = ()
63-
_homogenize_shape(x::Integer) = x
64-
_homogenize_shape(x::AbstractUnitRange) = length(x)
65-
const Dims = Union{Integer, Base.OneTo, SOneTo}
66-
function Base.similar(::Type{A}, shape::Tuple{Dims, Vararg{Dims}}) where {A<:AbstractArray}
67-
similar(A, homogenize_shape(shape))
68-
end
6961
function Base.similar(::Type{A}, shape::Tuple{SOneTo, Vararg{SOneTo}}) where {A<:AbstractArray}
7062
R = similar(A, length.(shape))
7163
SizedArray{length.(shape)}(R)
@@ -74,6 +66,10 @@ function Base.similar(x::SizedArray, ::Type{T}, shape::Tuple{SOneTo, Vararg{SOne
7466
sz = map(length, shape)
7567
SizedArray{sz}(similar(parent(x), T, sz))
7668
end
69+
function Base.reshape(x::AbstractArray, shape::Tuple{SOneTo, Vararg{SOneTo}})
70+
sz = map(length, shape)
71+
SizedArray{length.(sz)}(reshape(x, length.(sz)))
72+
end
7773

7874
const SizedMatrixLike = Union{SizedMatrix, Transpose{<:Any, <:SizedMatrix}, Adjoint{<:Any, <:SizedMatrix}}
7975

0 commit comments

Comments
 (0)