Skip to content

Commit e65fd01

Browse files
authored
Fix nested IdOffsetRange constructor (#185)
* Fix nested IdOffsetRange constructor * fix for 2D views * fix compute_offset1 * add type assertions to convert * Fix comment
1 parent eab4759 commit e65fd01

File tree

5 files changed

+70
-20
lines changed

5 files changed

+70
-20
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "OffsetArrays"
22
uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
3-
version = "1.5.0"
3+
version = "1.5.1"
44

55
[deps]
66
Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e"

docs/make.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using Documenter, JSON
22
using OffsetArrays
33

4+
DocMeta.setdocmeta!(OffsetArrays, :DocTestSetup, :(using OffsetArrays); recursive=true)
5+
46
makedocs(
57
sitename = "OffsetArrays",
68
format = Documenter.HTML(prettyurls = get(ENV, "CI", nothing) == "true"),

src/axes.jl

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -78,31 +78,38 @@ struct IdOffsetRange{T<:Integer,I<:AbstractUnitRange{T}} <: AbstractUnitRange{T}
7878
offset::T
7979

8080
IdOffsetRange{T,I}(r::I, offset::T) where {T<:Integer,I<:AbstractUnitRange{T}} = new{T,I}(r, offset)
81+
82+
#= This method is necessary to avoid a StackOverflowError in IdOffsetRange{T,I}(r::IdOffsetRange, offset::Integer).
83+
The type signature in that method is more specific than IdOffsetRange{T,I}(r::I, offset::T),
84+
so it ends up calling itself if I <: IdOffsetRange.
85+
=#
86+
function IdOffsetRange{T,IdOffsetRange{T,I}}(r::IdOffsetRange{T,I}, offset::T) where {T<:Integer,I<:AbstractUnitRange{T}}
87+
new{T,IdOffsetRange{T,I}}(r, offset)
88+
end
8189
end
8290

8391
# Construction/coercion from arbitrary AbstractUnitRanges
8492
function IdOffsetRange{T,I}(r::AbstractUnitRange, offset::Integer = 0) where {T<:Integer,I<:AbstractUnitRange{T}}
8593
rc, o = offset_coerce(I, r)
86-
return IdOffsetRange{T,I}(rc, convert(T, o+offset))
94+
return IdOffsetRange{T,I}(rc, convert(T, o+offset)::T)
8795
end
8896
function IdOffsetRange{T}(r::AbstractUnitRange, offset::Integer = 0) where T<:Integer
89-
rc = convert(AbstractUnitRange{T}, r)
90-
return IdOffsetRange{T,typeof(rc)}(rc, convert(T, offset))
97+
rc = convert(AbstractUnitRange{T}, r)::AbstractUnitRange{T}
98+
return IdOffsetRange{T,typeof(rc)}(rc, convert(T, offset)::T)
9199
end
92100
IdOffsetRange(r::AbstractUnitRange{T}, offset::Integer = 0) where T<:Integer =
93-
IdOffsetRange{T,typeof(r)}(r, convert(T, offset))
101+
IdOffsetRange{T,typeof(r)}(r, convert(T, offset)::T)
94102

95103
# Coercion from other IdOffsetRanges
96104
IdOffsetRange{T,I}(r::IdOffsetRange{T,I}) where {T<:Integer,I<:AbstractUnitRange{T}} = r
97105
function IdOffsetRange{T,I}(r::IdOffsetRange, offset::Integer = 0) where {T<:Integer,I<:AbstractUnitRange{T}}
98106
rc, offset_rc = offset_coerce(I, r.parent)
99-
return IdOffsetRange{T,I}(rc, r.offset + offset + offset_rc)
107+
return IdOffsetRange{T,I}(rc, convert(T, r.offset + offset + offset_rc)::T)
100108
end
101109
function IdOffsetRange{T}(r::IdOffsetRange, offset::Integer = 0) where T<:Integer
102110
return IdOffsetRange{T}(r.parent, r.offset + offset)
103111
end
104112
IdOffsetRange(r::IdOffsetRange) = r
105-
IdOffsetRange(r::IdOffsetRange, offset::Integer) = typeof(r)(r.parent, offset + r.offset)
106113

107114
# TODO: uncomment these when Julia is ready
108115
# # Conversion preserves both the values and the indexes, throwing an InexactError if this
@@ -123,12 +130,12 @@ end
123130

124131
# Fallback, specialze this method if `convert(I, r)` doesn't do what you need
125132
offset_coerce(::Type{I}, r::AbstractUnitRange) where I<:AbstractUnitRange =
126-
convert(I, r), 0
133+
convert(I, r)::I, 0
127134

128135
@inline Base.parent(r::IdOffsetRange) = r.parent
129136
@inline Base.axes(r::IdOffsetRange) = (Base.axes1(r),)
130137
@inline Base.axes1(r::IdOffsetRange) = IdOffsetRange(Base.axes1(r.parent), r.offset)
131-
@inline Base.unsafe_indices(r::IdOffsetRange) = (r,)
138+
@inline Base.unsafe_indices(r::IdOffsetRange) = (Base.axes1(r),)
132139
@inline Base.length(r::IdOffsetRange) = length(r.parent)
133140
Base.reduced_index(i::IdOffsetRange) = typeof(i)(first(i):first(i))
134141
# Workaround for #92 on Julia < 1.4
@@ -176,7 +183,7 @@ if VERSION < v"1.5.2"
176183
# issue 100, 133: IdOffsetRange as another index-preserving case shouldn't comtribute offsets
177184
# fixed by https://github.com/JuliaLang/julia/pull/37204
178185
@inline Base.compute_offset1(parent, stride1::Integer, dims::Tuple{Int}, inds::Tuple{IdOffsetRange}, I::Tuple) =
179-
Base.compute_linindex(parent, I) - stride1*first(inds[1])
186+
Base.compute_linindex(parent, I) - stride1*first(Base.axes1(inds[1]))
180187
end
181188

182189
# This was deemed "too private" to extend: see issue #184

src/utils.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,4 @@ function _checkindices(N::Integer, indices, label)
7373
end
7474

7575
_unwrap(r::IdOffsetRange) = r.parent .+ r.offset
76-
_unwrap(r::IdentityUnitRange) = r.indices
76+
_unwrap(r::IdentityUnitRange) = r.indices

test/runtests.jl

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ using EllipsisNotation
99
using Adapt
1010
using StaticArrays
1111

12+
DocMeta.setdocmeta!(OffsetArrays, :DocTestSetup, :(using OffsetArrays); recursive=true)
13+
1214
# https://github.com/JuliaLang/julia/pull/29440
1315
if VERSION < v"1.1.0-DEV.389"
1416
Base.:(:)(I::CartesianIndex{N}, J::CartesianIndex{N}) where N =
@@ -22,6 +24,14 @@ struct TupleOfRanges{N}
2224
x ::NTuple{N, UnitRange{Int}}
2325
end
2426

27+
function same_value(r1, r2)
28+
length(r1) == length(r2) || return false
29+
for (v1, v2) in zip(r1, r2)
30+
v1 == v2 || return false
31+
end
32+
return true
33+
end
34+
2535
@testset "Project meta quality checks" begin
2636
# Not checking compat section for test-only dependencies
2737
Aqua.test_all(OffsetArrays; project_extras=true, deps_compat=true, stale_deps=true, project_toml_formatting=true)
@@ -31,13 +41,7 @@ end
3141
end
3242

3343
@testset "IdOffsetRange" begin
34-
function same_value(r1, r2)
35-
length(r1) == length(r2) || return false
36-
for (v1, v2) in zip(r1, r2)
37-
v1 == v2 || return false
38-
end
39-
return true
40-
end
44+
4145
function check_indexed_by(r, rindx)
4246
for i in rindx
4347
r[i]
@@ -98,8 +102,16 @@ end
98102
@test same_value(r, 3:5)
99103
check_indexed_by(r, 3:5)
100104

101-
r = IdOffsetRange(IdOffsetRange(3:5, 2), 1)
102-
@test parent(r) isa UnitRange
105+
rp = Base.OneTo(3)
106+
r = IdOffsetRange(rp)
107+
r2 = IdOffsetRange{Int,typeof(r)}(r, 1)
108+
@test same_value(r2, 2:4)
109+
check_indexed_by(r2, 2:4)
110+
111+
r2 = IdOffsetRange{Int32,IdOffsetRange{Int32,Base.OneTo{Int32}}}(r, 1)
112+
@test typeof(r2) == IdOffsetRange{Int32,IdOffsetRange{Int32,Base.OneTo{Int32}}}
113+
@test same_value(r2, 2:4)
114+
check_indexed_by(r2, 2:4)
103115

104116
# conversion preserves both the values and the axes, throwing an error if this is not possible
105117
@test @inferred(oftype(ro, ro)) === ro
@@ -876,6 +888,35 @@ end
876888
@test S[0, 2, 2] == A[0, 4, 2]
877889
@test S[1, 1, 2] == A[1, 3, 2]
878890
@test axes(S) == (OffsetArrays.IdOffsetRange(0:1), Base.OneTo(2), OffsetArrays.IdOffsetRange(2:5))
891+
892+
# issue #186
893+
a = reshape(1:12, 3, 4)
894+
r = OffsetArrays.IdOffsetRange(3:4)
895+
av = view(a, :, r)
896+
@test av == a[:, 3:4]
897+
@test axes(av) == (axes(a,1), axes(r,1))
898+
r = OffsetArrays.IdOffsetRange(1:2,2)
899+
av = view(a, :, r)
900+
@test no_offset_view(av) == a[:, 3:4]
901+
@test axes(av) == (axes(a,1), axes(r,1))
902+
r = OffsetArrays.IdOffsetRange(2:3)
903+
av1d = view(a, r, 3)
904+
@test av1d == a[2:3, 3]
905+
@test axes(av1d) == (axes(r,1),)
906+
r = OffsetArrays.IdOffsetRange(Base.OneTo(2), 1)
907+
av1d = view(a, r, 3)
908+
@test no_offset_view(av1d) == a[2:3, 3]
909+
@test axes(av1d) == (axes(r,1),)
910+
911+
# fix IdOffsetRange(::IdOffsetRange, offset) nesting from #178
912+
b = 1:20
913+
bov = OffsetArray(view(b, 3:4), 3:4)
914+
c = @view b[bov]
915+
@test same_value(c, 3:4)
916+
@test axes(c,1) == 3:4
917+
d = OffsetArray(c, 1:2)
918+
@test same_value(d, c)
919+
@test axes(d,1) == 1:2
879920
end
880921

881922
@testset "iteration" begin

0 commit comments

Comments
 (0)