From c381843dccc43a5a1844bc8dbdf637c8a7b85000 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sun, 21 Feb 2021 10:45:02 -0600 Subject: [PATCH] Make IdOffsetRange printing round-trippable --- src/axes.jl | 29 ++++++++++++++++++----------- test/runtests.jl | 19 +++++++++++++++++-- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/axes.jl b/src/axes.jl index f54d68e0..8b3c2905 100644 --- a/src/axes.jl +++ b/src/axes.jl @@ -11,10 +11,10 @@ i.e., it's the "identity," which is the origin of the "Id" in `IdOffsetRange`. The most common case is shifting a range that starts at 1 (either `1:n` or `Base.OneTo(n)`): ```jldoctest; setup=:(import OffsetArrays) julia> ro = OffsetArrays.IdOffsetRange(1:3, -2) -OffsetArrays.IdOffsetRange(-1:1) +OffsetArrays.IdOffsetRange(values=-1:1, indices=-1:1) julia> axes(ro, 1) -OffsetArrays.IdOffsetRange(-1:1) +OffsetArrays.IdOffsetRange(values=-1:1, indices=-1:1) julia> ro[-1] -1 @@ -26,10 +26,10 @@ ERROR: BoundsError: attempt to access 3-element UnitRange{$Int} at index [5] If the range doesn't start at 1, the values may be different from the indices: ```jldoctest; setup=:(import OffsetArrays) julia> ro = OffsetArrays.IdOffsetRange(11:13, -2) -OffsetArrays.IdOffsetRange(9:11) +OffsetArrays.IdOffsetRange(values=9:11, indices=-1:1) julia> axes(ro, 1) # 11:13 is indexed by 1:3, and the offset is also applied to the axes -OffsetArrays.IdOffsetRange(-1:1) +OffsetArrays.IdOffsetRange(values=-1:1, indices=-1:1) julia> ro[-1] 9 @@ -41,7 +41,7 @@ ERROR: BoundsError: attempt to access 3-element UnitRange{$Int} at index [5] # Extended help Construction/coercion preserves the (shifted) values of the input range, but may modify -the indexes if required by the specified types. For example, +the indices if required by the specified types. For example, r = OffsetArrays.IdOffsetRange{Int,UnitRange{Int}}(3:4) @@ -78,10 +78,10 @@ struct IdOffsetRange{T<:Integer,I<:AbstractUnitRange{T}} <: AbstractUnitRange{T} offset::T IdOffsetRange{T,I}(r::I, offset::T) where {T<:Integer,I<:AbstractUnitRange{T}} = new{T,I}(r, offset) - - #= This method is necessary to avoid a StackOverflowError in IdOffsetRange{T,I}(r::IdOffsetRange, offset::Integer). - The type signature in that method is more specific than IdOffsetRange{T,I}(r::I, offset::T), - so it ends up calling itself if I <: IdOffsetRange. + + #= This method is necessary to avoid a StackOverflowError in IdOffsetRange{T,I}(r::IdOffsetRange, offset::Integer). + The type signature in that method is more specific than IdOffsetRange{T,I}(r::I, offset::T), + so it ends up calling itself if I <: IdOffsetRange. =# function IdOffsetRange{T,IdOffsetRange{T,I}}(r::IdOffsetRange{T,I}, offset::T) where {T<:Integer,I<:AbstractUnitRange{T}} new{T,IdOffsetRange{T,I}}(r, offset) @@ -111,8 +111,15 @@ function IdOffsetRange{T}(r::IdOffsetRange, offset::Integer = 0) where T<:Intege end IdOffsetRange(r::IdOffsetRange) = r +# Constructor to make `show` round-trippable +function IdOffsetRange(; values::AbstractUnitRange{<:Integer}, indices::AbstractUnitRange{<:Integer}) + length(values) == length(indices) || throw(ArgumentError("values and indices must have the same length")) + offset = first(indices) - 1 + return IdOffsetRange(values .- offset, offset) +end + # TODO: uncomment these when Julia is ready -# # Conversion preserves both the values and the indexes, throwing an InexactError if this +# # Conversion preserves both the values and the indices, throwing an InexactError if this # # is not possible. # Base.convert(::Type{IdOffsetRange{T,I}}, r::IdOffsetRange{T,I}) where {T<:Integer,I<:AbstractUnitRange{T}} = r # Base.convert(::Type{IdOffsetRange{T,I}}, r::IdOffsetRange) where {T<:Integer,I<:AbstractUnitRange{T}} = @@ -175,7 +182,7 @@ Broadcast.broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(+), r::IdO Broadcast.broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(+), x::Integer, r::IdOffsetRange{T}) where T = IdOffsetRange{T}(x .+ r.parent, r.offset) -Base.show(io::IO, r::IdOffsetRange) = print(io, "OffsetArrays.IdOffsetRange(",first(r), ':', last(r),")") +Base.show(io::IO, r::IdOffsetRange) = print(io, IdOffsetRange, "(values=",first(r), ':', last(r),", indices=",first(eachindex(r)),':',last(eachindex(r)), ")") # Optimizations @inline Base.checkindex(::Type{Bool}, inds::IdOffsetRange, i::Real) = Base.checkindex(Bool, inds.parent, i - inds.offset) diff --git a/test/runtests.jl b/test/runtests.jl index ac227c6e..3e18faba 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -163,6 +163,18 @@ end @test same_value(r2, 2:4) check_indexed_by(r2, 2:4) + # Constructor that's round-trippable with `show` + rrt = IdOffsetRange(values=7:9, indices=-1:1) + @test same_value(rrt, 7:9) + check_indexed_by(rrt, -1:1) + @test_throws ArgumentError IdOffsetRange(values=7:9, indices=-1:2) + @test_throws ArgumentError IdOffsetRange(values=7:9, indices=-1:0) + @test_throws TypeError IdOffsetRange(values=7:9, indices=-1) + @test_throws UndefKeywordError IdOffsetRange(values=7:9) + @test_throws UndefKeywordError IdOffsetRange(indices=-1:1) + @test_throws MethodError IdOffsetRange(7:9, indices=-1:1) + @test_throws MethodError IdOffsetRange(-1:1, values=7:9) + # conversion preserves both the values and the axes, throwing an error if this is not possible @test @inferred(oftype(ro, ro)) === ro @test @inferred(convert(OffsetArrays.IdOffsetRange{Int}, ro)) === ro @@ -1215,9 +1227,12 @@ end a = OffsetArray([1 2; 3 4], -1:0, 5:6) io = IOBuffer() show(io, axes(a, 1)) - @test String(take!(io)) == "OffsetArrays.IdOffsetRange(-1:0)" + @test String(take!(io)) == "IdOffsetRange(values=-1:0, indices=-1:0)" # not qualified because of the using OffsetArrays: IdOffsetRange at top show(io, axes(a, 2)) - @test String(take!(io)) == "OffsetArrays.IdOffsetRange(5:6)" + @test String(take!(io)) == "IdOffsetRange(values=5:6, indices=5:6)" + rrtable = IdOffsetRange(values=7:9, indices=-1:1) + rrted = eval(Meta.parse(string(rrtable))) + @test pairs(rrtable) == pairs(rrted) @test Base.inds2string(axes(a)) == Base.inds2string(map(UnitRange, axes(a)))