From f6e72d9c06f46325b76a233a9a8a7cd21eff3316 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sat, 9 Jul 2016 17:36:16 -0500 Subject: [PATCH 1/6] Revise checkbounds API and extend to support Array{CartesianIndex} --- base/abstractarray.jl | 53 +++++++++++------- base/multidimensional.jl | 45 ++++++++++++---- test/abstractarray.jl | 112 ++++++++++++++++++++++++++++++++------- 3 files changed, 163 insertions(+), 47 deletions(-) diff --git a/base/abstractarray.jl b/base/abstractarray.jl index f95e22c9688f0..5b5df6c4f7f64 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -181,38 +181,53 @@ function checkindex(::Type{Bool}, inds::AbstractUnitRange, I::AbstractArray) end # check all indices/dimensions -# To make extension easier, avoid specializations of checkbounds on index types -# (That said, typically one does not need to specialize this function.) + +# To facilitate extension for custom array types without triggering +# ambiguities, limit the number of specializations of checkbounds on +# the types of the indices. """ checkbounds(Bool, array, indexes...) -Return `true` if the specified `indexes` are in bounds for the given `array`. Subtypes of -`AbstractArray` should specialize this method if they need to provide custom bounds checking -behaviors. +Return `true` if the specified `indexes` are in bounds for the given +`array`. Subtypes of `AbstractArray` should specialize this method if +they need to provide custom bounds checking behaviors. """ -function checkbounds(::Type{Bool}, A::AbstractArray, i::Integer) - @_inline_meta - checkindex(Bool, linearindices(A), i) -end -function checkbounds{T}(::Type{Bool}, A::Union{Array{T,1},Range{T}}, i::Integer) +function checkbounds(::Type{Bool}, A::AbstractArray, I...) @_inline_meta - (1 <= i) & (i <= length(A)) + checkbounds_indices(indices(A), I) end function checkbounds(::Type{Bool}, A::AbstractArray, I::AbstractArray{Bool}) @_inline_meta checkbounds_logical(A, I) end -function checkbounds(::Type{Bool}, A::AbstractArray, I...) - @_inline_meta - checkbounds_indices(indices(A), I) -end +# checkbounds_indices iteratively consumes elements of the +# indices-tuple of an arrray and the indices-tuple supplied by the +# caller. These two tuples are usually consumed in a 1-for-1 fashion, +# i.e., +# +# checkbounds_indices((R1, R...), (I1, I...)) = checkindex(Bool, R1, I1) & +# checkbounds_indices(R, I) +# +# However, there are two exceptions: linear indexing and CartesianIndex{N}. checkbounds_indices(::Tuple{}, ::Tuple{}) = true checkbounds_indices(::Tuple{}, I::Tuple{Any}) = (@_inline_meta; checkindex(Bool, 1:1, I[1])) -checkbounds_indices(::Tuple{}, I::Tuple) = (@_inline_meta; checkindex(Bool, 1:1, I[1]) & checkbounds_indices((), tail(I))) -checkbounds_indices(inds::Tuple{Any}, I::Tuple{Any}) = (@_inline_meta; checkindex(Bool, inds[1], I[1])) -checkbounds_indices(inds::Tuple, I::Tuple{Any}) = (@_inline_meta; checkindex(Bool, 1:prod(map(dimlength, inds)), I[1])) -checkbounds_indices(inds::Tuple, I::Tuple) = (@_inline_meta; checkindex(Bool, inds[1], I[1]) & checkbounds_indices(tail(inds), tail(I))) +function checkbounds_indices(::Tuple{}, I::Tuple) + @_inline_meta + checkindex(Bool, 1:1, I[1]) & checkbounds_indices((), tail(I)) +end +function checkbounds_indices(inds::Tuple{Any}, I::Tuple{Any}) + @_inline_meta + checkindex(Bool, inds[1], I[1]) +end +function checkbounds_indices(inds::Tuple, I::Tuple{Any}) + @_inline_meta + checkindex(Bool, 1:prod(map(dimlength, inds)), I[1]) # linear indexing +end +function checkbounds_indices(inds::Tuple, I::Tuple) + @_inline_meta + checkindex(Bool, inds[1], I[1]) & checkbounds_indices(tail(inds), tail(I)) +end # Single logical array indexing: checkbounds_logical(A::AbstractArray, I::AbstractArray{Bool}) = indices(A) == indices(I) diff --git a/base/multidimensional.jl b/base/multidimensional.jl index 3c4e3b3bef930..02daee9106e6c 100644 --- a/base/multidimensional.jl +++ b/base/multidimensional.jl @@ -146,16 +146,41 @@ end # IteratorsMD using .IteratorsMD ## Bounds-checking with CartesianIndex -# Ambiguity with linear indexing: -@inline _chkbnds(A::AbstractVector, checked::NTuple{1,Bool}, I::CartesianIndex) = _chkbnds(A, checked, I.I...) -@inline _chkbnds(A::AbstractArray, checked::NTuple{1,Bool}, I::CartesianIndex) = _chkbnds(A, checked, I.I...) -# Generic bounds checking -@inline _chkbnds{T,N}(A::AbstractArray{T,N}, checked::NTuple{N,Bool}, I1::CartesianIndex, I...) = _chkbnds(A, checked, I1.I..., I...) -@inline _chkbnds{T,N,M}(A::AbstractArray{T,N}, checked::NTuple{M,Bool}, I1::CartesianIndex, I...) = _chkbnds(A, checked, I1.I..., I...) - -@inline checkbounds_indices(::Tuple{}, I::Tuple{CartesianIndex,Vararg{Any}}) = checkbounds_indices((), (I[1].I..., tail(I)...)) -@inline checkbounds_indices(inds::Tuple{Any}, I::Tuple{CartesianIndex,Vararg{Any}}) = checkbounds_indices(inds, (I[1].I..., tail(I)...)) -@inline checkbounds_indices(inds::Tuple, I::Tuple{CartesianIndex,Vararg{Any}}) = checkbounds_indices(inds, (I[1].I..., tail(I)...)) +@inline checkbounds_indices(::Tuple{}, I::Tuple{CartesianIndex,Vararg{Any}}) = + checkbounds_indices((), (I[1].I..., tail(I)...)) +@inline checkbounds_indices(inds::Tuple{Any}, I::Tuple{CartesianIndex,Vararg{Any}}) = + checkbounds_indices(inds, (I[1].I..., tail(I)...)) +@inline checkbounds_indices(inds::Tuple, I::Tuple{CartesianIndex,Vararg{Any}}) = + checkbounds_indices(inds, (I[1].I..., tail(I)...)) + +# Support indexing with an array of CartesianIndex{N}s +# Here we try to consume N of the indices (if there are that many available) +# The first two simply handle ambiguities +@inline function checkbounds_indices{N}(::Tuple{}, I::Tuple{AbstractArray{CartesianIndex{N}},Vararg{Any}}) + checkindex(Bool, (), I[1]) & checkbounds_indices((), tail(I)) +end +@inline function checkbounds_indices{N}(inds::Tuple{Any}, I::Tuple{AbstractArray{CartesianIndex{N}},Vararg{Any}}) + checkindex(Bool, inds, I[1]) & checkbounds_indices((), tail(I)) +end +@inline function checkbounds_indices{N}(inds::Tuple, I::Tuple{AbstractArray{CartesianIndex{N}},Vararg{Any}}) + inds1, indsrest = split(inds, Val{N}) + checkindex(Bool, inds1, I[1]) & checkbounds_indices(indsrest, tail(I)) +end + +@inline split{N}(inds, V::Type{Val{N}}) = _split((), inds, V) +@inline _split(out, indsrest, V) = _split((out..., indsrest[1]), tail(indsrest), V) +# exit either when we've exhausted the input tuple or when it has length N +@inline _split{N}(out::NTuple{N}, ::Tuple{}, ::Type{Val{N}}) = out, () # ambig. +@inline _split{N}(out, ::Tuple{}, ::Type{Val{N}}) = out, () +@inline _split{N}(out::NTuple{N}, indsrest, ::Type{Val{N}}) = out, indsrest + +function checkindex{N}(::Type{Bool}, inds::Tuple, I::AbstractArray{CartesianIndex{N}}) + b = true + for i in I + b &= checkbounds_indices(inds, (i,)) + end + b +end # Recursively compute the lengths of a list of indices, without dropping scalars # These need to be inlined for more than 3 indexes diff --git a/test/abstractarray.jl b/test/abstractarray.jl index 6847048da7770..b3ff1b2f18c77 100644 --- a/test/abstractarray.jl +++ b/test/abstractarray.jl @@ -1,26 +1,102 @@ # This file is a part of Julia. License is MIT: http://julialang.org/license # Bounds checking -A = rand(3,3,3) +A = rand(5,4,3) @test checkbounds(Bool, A, 1, 1, 1) == true -@test checkbounds(Bool, A, 1, 3, 3) == true -@test checkbounds(Bool, A, 1, 4, 3) == false -@test checkbounds(Bool, A, 1, -1, 3) == false -@test checkbounds(Bool, A, 1, 9) == true # partial linear indexing -@test checkbounds(Bool, A, 1, 10) == false # partial linear indexing -@test checkbounds(Bool, A, 1) == true -@test checkbounds(Bool, A, 27) == true -@test checkbounds(Bool, A, 28) == false -@test checkbounds(Bool, A, 2, 2, 2, 1) == true +@test checkbounds(Bool, A, 5, 4, 3) == true +@test checkbounds(Bool, A, 0, 1, 1) == false +@test checkbounds(Bool, A, 1, 0, 1) == false +@test checkbounds(Bool, A, 1, 1, 0) == false +@test checkbounds(Bool, A, 6, 4, 3) == false +@test checkbounds(Bool, A, 5, 5, 3) == false +@test checkbounds(Bool, A, 5, 4, 4) == false +@test checkbounds(Bool, A, 1) == true # linear indexing +@test checkbounds(Bool, A, 60) == true +@test checkbounds(Bool, A, 61) == false +@test checkbounds(Bool, A, 2, 2, 2, 1) == true # extra indices @test checkbounds(Bool, A, 2, 2, 2, 2) == false - -@test Base.checkbounds_indices((1:5, 1:5), (2,2)) -@test !Base.checkbounds_indices((1:5, 1:5), (7,2)) -@test !Base.checkbounds_indices((1:5, 1:5), (2,0)) -@test Base.checkbounds_indices((1:5, 1:5), (13,)) -@test !Base.checkbounds_indices((1:5, 1:5), (26,)) -@test Base.checkbounds_indices((1:5, 1:5), (2,2,1)) -@test !Base.checkbounds_indices((1:5, 1:5), (2,2,2)) +@test checkbounds(Bool, A, 1, 1) == true # partial linear indexing (PLI) +@test checkbounds(Bool, A, 1, 12) == true # PLI +@test checkbounds(Bool, A, 5, 12) == true # PLI +@test checkbounds(Bool, A, 1, 13) == false # PLI +@test checkbounds(Bool, A, 6, 12) == false # PLI + +# single CartesianIndex +@test checkbounds(Bool, A, CartesianIndex((1, 1, 1))) == true +@test checkbounds(Bool, A, CartesianIndex((5, 4, 3))) == true +@test checkbounds(Bool, A, CartesianIndex((0, 1, 1))) == false +@test checkbounds(Bool, A, CartesianIndex((1, 0, 1))) == false +@test checkbounds(Bool, A, CartesianIndex((1, 1, 0))) == false +@test checkbounds(Bool, A, CartesianIndex((6, 4, 3))) == false +@test checkbounds(Bool, A, CartesianIndex((5, 5, 3))) == false +@test checkbounds(Bool, A, CartesianIndex((5, 4, 4))) == false +@test checkbounds(Bool, A, CartesianIndex((1,))) == true +@test checkbounds(Bool, A, CartesianIndex((60,))) == true +@test checkbounds(Bool, A, CartesianIndex((61,))) == false +@test checkbounds(Bool, A, CartesianIndex((2, 2, 2, 1,))) == true +@test checkbounds(Bool, A, CartesianIndex((2, 2, 2, 2,))) == false +@test checkbounds(Bool, A, CartesianIndex((1, 1,))) == true +@test checkbounds(Bool, A, CartesianIndex((1, 12,))) == true +@test checkbounds(Bool, A, CartesianIndex((5, 12,))) == true +@test checkbounds(Bool, A, CartesianIndex((1, 13,))) == false +@test checkbounds(Bool, A, CartesianIndex((6, 12,))) == false + +# mix of CartesianIndex and Int +@test checkbounds(Bool, A, CartesianIndex((1,)), 1, CartesianIndex((1,))) == true +@test checkbounds(Bool, A, CartesianIndex((5, 4)), 3) == true +@test checkbounds(Bool, A, CartesianIndex((0, 1)), 1) == false +@test checkbounds(Bool, A, 1, CartesianIndex((0, 1))) == false +@test checkbounds(Bool, A, 1, 1, CartesianIndex((0,))) == false +@test checkbounds(Bool, A, 6, CartesianIndex((4, 3))) == false +@test checkbounds(Bool, A, 5, CartesianIndex((5,)), 3) == false +@test checkbounds(Bool, A, CartesianIndex((5,)), CartesianIndex((4,)), CartesianIndex((4,))) == false + +# vector indices +@test checkbounds(Bool, A, 1:5, 1:4, 1:3) == true +@test checkbounds(Bool, A, 0:5, 1:4, 1:3) == false +@test checkbounds(Bool, A, 1:5, 0:4, 1:3) == false +@test checkbounds(Bool, A, 1:5, 1:4, 0:3) == false +@test checkbounds(Bool, A, 1:6, 1:4, 1:3) == false +@test checkbounds(Bool, A, 1:5, 1:5, 1:3) == false +@test checkbounds(Bool, A, 1:5, 1:4, 1:4) == false +@test checkbounds(Bool, A, 1:60) == true +@test checkbounds(Bool, A, 1:61) == false +@test checkbounds(Bool, A, 2, 2, 2, 1:1) == true # extra indices +@test checkbounds(Bool, A, 2, 2, 2, 1:2) == false +@test checkbounds(Bool, A, 1:5, 1:12) == true +@test checkbounds(Bool, A, 1:5, 1:13) == false +@test checkbounds(Bool, A, 1:6, 1:12) == false + +# logical +@test checkbounds(Bool, A, trues(5), trues(4), trues(3)) == true +@test checkbounds(Bool, A, trues(6), trues(4), trues(3)) == false +@test checkbounds(Bool, A, trues(5), trues(5), trues(3)) == false +@test checkbounds(Bool, A, trues(5), trues(4), trues(4)) == false +@test checkbounds(Bool, A, trues(60)) == true +@test checkbounds(Bool, A, trues(61)) == false +@test checkbounds(Bool, A, 2, 2, 2, trues(1)) == true # extra indices +@test checkbounds(Bool, A, 2, 2, 2, trues(2)) == false +@test checkbounds(Bool, A, trues(5), trues(12)) == true +@test checkbounds(Bool, A, trues(5), trues(13)) == false +@test checkbounds(Bool, A, trues(6), trues(12)) == false + +# array of CartesianIndex +@test checkbounds(Bool, A, [CartesianIndex((1, 1, 1))]) == true +@test checkbounds(Bool, A, [CartesianIndex((5, 4, 3))]) == true +@test checkbounds(Bool, A, [CartesianIndex((0, 1, 1))]) == false +@test checkbounds(Bool, A, [CartesianIndex((1, 0, 1))]) == false +@test checkbounds(Bool, A, [CartesianIndex((1, 1, 0))]) == false +@test checkbounds(Bool, A, [CartesianIndex((6, 4, 3))]) == false +@test checkbounds(Bool, A, [CartesianIndex((5, 5, 3))]) == false +@test checkbounds(Bool, A, [CartesianIndex((5, 4, 4))]) == false +@test checkbounds(Bool, A, [CartesianIndex((1, 1))], 1) == true +@test checkbounds(Bool, A, [CartesianIndex((5, 4))], 3) == true +@test checkbounds(Bool, A, [CartesianIndex((0, 1))], 1) == false +@test checkbounds(Bool, A, [CartesianIndex((1, 0))], 1) == false +@test checkbounds(Bool, A, [CartesianIndex((1, 1))], 0) == false +@test checkbounds(Bool, A, [CartesianIndex((6, 4))], 3) == false +@test checkbounds(Bool, A, [CartesianIndex((5, 5))], 3) == false +@test checkbounds(Bool, A, [CartesianIndex((5, 4))], 4) == false # sub2ind & ind2sub # 0-dimensional From 7fa7ed84e6162e94d9fd7c429301abdcaa24c857 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sat, 9 Jul 2016 05:19:21 -0500 Subject: [PATCH 2/6] Force inlining of `linearindices` and `indices1`. Ref #17340 --- base/abstractarray.jl | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/base/abstractarray.jl b/base/abstractarray.jl index 5b5df6c4f7f64..e52a3548326a2 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -45,8 +45,11 @@ function indices{T,N}(A::AbstractArray{T,N}) map(s->OneTo(s), size(A)) end +# Performance optimization: get rid of a branch on `d` in `indices(A, +# d)` for d=1. 1d arrays are heavily used, and the first dimension +# comes up in other applications. indices1{T}(A::AbstractArray{T,0}) = OneTo(1) -indices1{T}(A::AbstractArray{T}) = indices(A)[1] +indices1{T}(A::AbstractArray{T}) = (@_inline_meta; indices(A)[1]) """ linearindices(A) @@ -60,8 +63,8 @@ is `indices(A, 1)`. Calling this function is the "safe" way to write algorithms that exploit linear indexing. """ -linearindices(A) = 1:length(A) -linearindices(A::AbstractVector) = indices1(A) +linearindices(A) = (@_inline_meta; 1:length(A)) +linearindices(A::AbstractVector) = (@_inline_meta; indices1(A)) eltype{T}(::Type{AbstractArray{T}}) = T eltype{T,N}(::Type{AbstractArray{T,N}}) = T elsize{T}(::AbstractArray{T}) = sizeof(T) From 40b8d4be8af517435a19d83b128d13de549ba877 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Mon, 11 Jul 2016 11:22:32 -0500 Subject: [PATCH 3/6] Faster and simpler indices for SubArray --- base/abstractarray.jl | 6 ++++-- base/range.jl | 11 ++++++++--- base/subarray.jl | 16 +++++----------- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/base/abstractarray.jl b/base/abstractarray.jl index e52a3548326a2..266f65b1ea256 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -51,6 +51,9 @@ end indices1{T}(A::AbstractArray{T,0}) = OneTo(1) indices1{T}(A::AbstractArray{T}) = (@_inline_meta; indices(A)[1]) +unsafe_indices(A) = indices(A) +unsafe_indices(r::Range) = (OneTo(unsafe_length(r)),) # Ranges use checked_sub for size + """ linearindices(A) @@ -248,6 +251,7 @@ Throw an error if the specified `indexes` are not in bounds for the given `array function checkbounds(A::AbstractArray, I...) @_inline_meta checkbounds(Bool, A, I...) || throw_boundserror(A, I) + nothing end checkbounds(A::AbstractArray) = checkbounds(A, 1) # 0-d case @@ -1211,8 +1215,6 @@ nextL(L, l::Integer) = L*l nextL(L, r::AbstractUnitRange) = L*unsafe_length(r) offsetin(i, l::Integer) = i-1 offsetin(i, r::AbstractUnitRange) = i-first(r) -unsafe_length(r::UnitRange) = r.stop-r.start+1 -unsafe_length(r::OneTo) = length(r) ind2sub(::Tuple{}, ind::Integer) = (@_inline_meta; ind == 1 ? () : throw(BoundsError())) ind2sub(dims::DimsInteger, ind::Integer) = (@_inline_meta; _ind2sub(dims, ind-1)) diff --git a/base/range.jl b/base/range.jl index 335f2d89cb7d1..43e9b77778917 100644 --- a/base/range.jl +++ b/base/range.jl @@ -326,12 +326,17 @@ step(r::AbstractUnitRange) = 1 step(r::FloatRange) = r.step/r.divisor step{T}(r::LinSpace{T}) = ifelse(r.len <= 0, convert(T,NaN), (r.stop-r.start)/r.divisor) -function length(r::StepRange) +unsafe_length(r::Range) = length(r) # generic fallback + +function unsafe_length(r::StepRange) n = Integer(div(r.stop+r.step - r.start, r.step)) isempty(r) ? zero(n) : n end -length(r::AbstractUnitRange) = Integer(last(r) - first(r) + 1) -length(r::OneTo) = r.stop +length(r::StepRange) = unsafe_length(r) +unsafe_length(r::AbstractUnitRange) = Integer(last(r) - first(r) + 1) +unsafe_length(r::OneTo) = r.stop +length(r::AbstractUnitRange) = unsafe_length(r) +length(r::OneTo) = unsafe_length(r) length(r::FloatRange) = Integer(r.len) length(r::LinSpace) = Integer(r.len + signbit(r.len - 1)) diff --git a/base/subarray.jl b/base/subarray.jl index e9a4cf1c57765..48446fcc60cb4 100644 --- a/base/subarray.jl +++ b/base/subarray.jl @@ -278,17 +278,11 @@ end # they are taken from the range/vector # Since bounds-checking is performance-critical and uses # indices, it's worth optimizing these implementations thoroughly -indices(S::SubArray) = (@_inline_meta; _indices_sub(S, 1, S.indexes...)) -_indices_sub(S::SubArray, dim::Int) = () -_indices_sub(S::SubArray, dim::Int, ::Real, I...) = (@_inline_meta; _indices_sub(S, dim+1, I...)) -_indices_sub(S::SubArray, dim::Int, ::Colon, I...) = (@_inline_meta; (indices(parent(S), dim), _indices_sub(S, dim+1, I...)...)) -_indices_sub(S::SubArray, dim::Int, i1::AbstractArray, I...) = (@_inline_meta; (indices(i1)..., _indices_sub(S, dim+1, I...)...)) -indices1(S::SubArray) = (@_inline_meta; _indices1(S, 1, S.indexes...)) -_indices1(S::SubArray, dim) = OneTo(1) -_indices1(S::SubArray, dim, i1::Real, I...) = (@_inline_meta; _indices1(S, dim+1, I...)) -_indices1(S::SubArray, dim, i1::Colon, I...) = (@_inline_meta; indices(parent(S), dim)) -_indices1(S::SubArray, dim, i1::AbstractArray, I...) = (@_inline_meta; indices1(i1)) -_indices1{T}(S::SubArray, dim, i1::AbstractArray{T,0}, I...) = (@_inline_meta; _indices1(S, dim+1, I...)) +indices(S::SubArray) = (@_inline_pure_meta; _indices_sub(S, indices(S.parent), S.indexes...)) +_indices_sub(S::SubArray, pinds) = () +_indices_sub(S::SubArray, pinds, ::Real, I...) = _indices_sub(S, tail(pinds), I...) +_indices_sub(S::SubArray, pinds, ::Colon, I...) = (pinds[1], _indices_sub(S, tail(pinds), I...)...) +_indices_sub(S::SubArray, pinds, i1::AbstractArray, I...) = (unsafe_indices(i1)..., _indices_sub(S, tail(pinds), I...)...) ## Compatability # deprecate? From 33ee846de6fb2d62be6ed8b9dff4d3147f04ba7a Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Mon, 11 Jul 2016 11:26:59 -0500 Subject: [PATCH 4/6] Don't extend split to tuples (move it into IteratorsMD) --- base/multidimensional.jl | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/base/multidimensional.jl b/base/multidimensional.jl index 02daee9106e6c..f286c7c18de79 100644 --- a/base/multidimensional.jl +++ b/base/multidimensional.jl @@ -141,6 +141,14 @@ simd_index{I<:CartesianIndex{0}}(iter::CartesianRange{I}, ::CartesianIndex, I1:: CartesianIndex((I1+iter.start[1], Ilast.I...)) end +# Split out the first N elements of a tuple +@inline split{N}(t, V::Type{Val{N}}) = _split((), t, V) +@inline _split(tN, trest, V) = _split((tN..., trest[1]), tail(trest), V) +# exit either when we've exhausted the input tuple or when tN has length N +@inline _split{N}(tN::NTuple{N}, ::Tuple{}, ::Type{Val{N}}) = tN, () # ambig. +@inline _split{N}(tN, ::Tuple{}, ::Type{Val{N}}) = tN, () +@inline _split{N}(tN::NTuple{N}, trest, ::Type{Val{N}}) = tN, trest + end # IteratorsMD using .IteratorsMD @@ -163,17 +171,10 @@ end checkindex(Bool, inds, I[1]) & checkbounds_indices((), tail(I)) end @inline function checkbounds_indices{N}(inds::Tuple, I::Tuple{AbstractArray{CartesianIndex{N}},Vararg{Any}}) - inds1, indsrest = split(inds, Val{N}) + inds1, indsrest = IteratorsMD.split(inds, Val{N}) checkindex(Bool, inds1, I[1]) & checkbounds_indices(indsrest, tail(I)) end -@inline split{N}(inds, V::Type{Val{N}}) = _split((), inds, V) -@inline _split(out, indsrest, V) = _split((out..., indsrest[1]), tail(indsrest), V) -# exit either when we've exhausted the input tuple or when it has length N -@inline _split{N}(out::NTuple{N}, ::Tuple{}, ::Type{Val{N}}) = out, () # ambig. -@inline _split{N}(out, ::Tuple{}, ::Type{Val{N}}) = out, () -@inline _split{N}(out::NTuple{N}, indsrest, ::Type{Val{N}}) = out, indsrest - function checkindex{N}(::Type{Bool}, inds::Tuple, I::AbstractArray{CartesianIndex{N}}) b = true for i in I From 81f8d4f71ade8e37359b5fe6d348f35b466e135d Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Tue, 12 Jul 2016 21:01:12 -0500 Subject: [PATCH 5/6] Add a lot of documentation on the bounds checking hierarchy [ci skip] This also reorganizes code to make the order follow the hierarchy; makes more sense for the "big picture" documentation to be near the top node. --- base/abstractarray.jl | 150 +++++++++++++++++++++--------------- base/multidimensional.jl | 18 ++--- doc/devdocs/boundscheck.rst | 49 ++++++++++++ doc/stdlib/arrays.rst | 12 +-- 4 files changed, 153 insertions(+), 76 deletions(-) diff --git a/base/abstractarray.jl b/base/abstractarray.jl index 266f65b1ea256..962574a3bf5ee 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -150,53 +150,30 @@ linearindexing(::LinearFast, ::LinearFast) = LinearFast() linearindexing(::LinearIndexing, ::LinearIndexing) = LinearSlow() ## Bounds checking ## -@generated function trailingsize{T,N,n}(A::AbstractArray{T,N}, ::Type{Val{n}}) - (isa(n, Int) && isa(N, Int)) || error("Must have concrete type") - n > N && return 1 - ex = :(size(A, $n)) - for m = n+1:N - ex = :($ex * size(A, $m)) - end - Expr(:block, Expr(:meta, :inline), ex) -end -# check along a single dimension -""" - checkindex(Bool, inds::UnitRange, index) +# The overall hierarchy is +# `checkbounds(A, I...)` -> +# `checkbounds(Bool, A, I...)` -> either of: +# - `checkbounds_indices(IA, I)` which calls `checkindex(Bool, inds, i)` +# - `checkbounds_logical(A, I)` when `I` is a single logical array +# +# See the "boundscheck" devdocs for more information. +# +# Note this hierarchy has been designed to reduce the likelihood of +# method ambiguities. We try to make `checkbounds` the place to +# specialize on array type, and try to avoid specializations on index +# types; conversely, `checkindex` is intended to be specialized only +# on index type (especially, its last argument). -Return `true` if the given `index` is within the bounds of -`inds`. Custom types that would like to behave as indices for all -arrays can extend this method in order to provide a specialized bounds -checking implementation. """ -checkindex(::Type{Bool}, inds::AbstractUnitRange, i) = throw(ArgumentError("unable to check bounds for indices of type $(typeof(i))")) -checkindex(::Type{Bool}, inds::AbstractUnitRange, i::Real) = (first(inds) <= i) & (i <= last(inds)) -checkindex(::Type{Bool}, inds::AbstractUnitRange, ::Colon) = true -function checkindex(::Type{Bool}, inds::AbstractUnitRange, r::Range) - @_propagate_inbounds_meta - isempty(r) | (checkindex(Bool, inds, first(r)) & checkindex(Bool, inds, last(r))) -end -checkindex{N}(::Type{Bool}, indx::AbstractUnitRange, I::AbstractArray{Bool,N}) = N == 1 && indx == indices1(I) -function checkindex(::Type{Bool}, inds::AbstractUnitRange, I::AbstractArray) - @_inline_meta - b = true - for i in I - b &= checkindex(Bool, inds, i) - end - b -end + checkbounds(Bool, A, I...) -# check all indices/dimensions +Return `true` if the specified indices `I` are in bounds for the given +array `A`. Subtypes of `AbstractArray` should specialize this method +if they need to provide custom bounds checking behaviors; however, in +many cases one can rely on `A`'s indices and `checkindex`. -# To facilitate extension for custom array types without triggering -# ambiguities, limit the number of specializations of checkbounds on -# the types of the indices. -""" - checkbounds(Bool, array, indexes...) - -Return `true` if the specified `indexes` are in bounds for the given -`array`. Subtypes of `AbstractArray` should specialize this method if -they need to provide custom bounds checking behaviors. +See also `checkindex`. """ function checkbounds(::Type{Bool}, A::AbstractArray, I...) @_inline_meta @@ -207,35 +184,48 @@ function checkbounds(::Type{Bool}, A::AbstractArray, I::AbstractArray{Bool}) checkbounds_logical(A, I) end -# checkbounds_indices iteratively consumes elements of the -# indices-tuple of an arrray and the indices-tuple supplied by the -# caller. These two tuples are usually consumed in a 1-for-1 fashion, -# i.e., -# -# checkbounds_indices((R1, R...), (I1, I...)) = checkindex(Bool, R1, I1) & -# checkbounds_indices(R, I) -# -# However, there are two exceptions: linear indexing and CartesianIndex{N}. +""" + checkbounds_indices(IA, I) + +checks whether the "requested" indices in the tuple `I` fall within +the bounds of the "permitted" indices specified by the tuple +`IA`. This function recursively consumes elements of these tuples, +usually in a 1-for-1 fashion, + + checkbounds_indices((IA1, IA...), (I1, I...)) = checkindex(Bool, IA1, I1) & + checkbounds_indices(IA, I) + +Note that `checkindex` is being used to perform the actual +bounds-check for a single dimension of the array. + +There are two important exceptions to the 1-1 rule: linear indexing and +CartesianIndex{N}, both of which may "consume" more than one element +of `IA`. +""" +function checkbounds_indices(IA::Tuple, I::Tuple) + @_inline_meta + checkindex(Bool, IA[1], I[1]) & checkbounds_indices(tail(IA), tail(I)) +end checkbounds_indices(::Tuple{}, ::Tuple{}) = true checkbounds_indices(::Tuple{}, I::Tuple{Any}) = (@_inline_meta; checkindex(Bool, 1:1, I[1])) function checkbounds_indices(::Tuple{}, I::Tuple) @_inline_meta checkindex(Bool, 1:1, I[1]) & checkbounds_indices((), tail(I)) end -function checkbounds_indices(inds::Tuple{Any}, I::Tuple{Any}) - @_inline_meta - checkindex(Bool, inds[1], I[1]) -end -function checkbounds_indices(inds::Tuple, I::Tuple{Any}) +function checkbounds_indices(IA::Tuple{Any}, I::Tuple{Any}) @_inline_meta - checkindex(Bool, 1:prod(map(dimlength, inds)), I[1]) # linear indexing + checkindex(Bool, IA[1], I[1]) end -function checkbounds_indices(inds::Tuple, I::Tuple) +function checkbounds_indices(IA::Tuple, I::Tuple{Any}) @_inline_meta - checkindex(Bool, inds[1], I[1]) & checkbounds_indices(tail(inds), tail(I)) + checkindex(Bool, 1:prod(map(dimlength, IA)), I[1]) # linear indexing end -# Single logical array indexing: +""" + checkbounds_logical(A, I::AbstractArray{Bool}) + +tests whether the logical array `I` is consistent with the indices of `A`. +""" checkbounds_logical(A::AbstractArray, I::AbstractArray{Bool}) = indices(A) == indices(I) checkbounds_logical(A::AbstractArray, I::AbstractVector{Bool}) = length(A) == length(I) checkbounds_logical(A::AbstractVector, I::AbstractArray{Bool}) = length(A) == length(I) @@ -244,9 +234,9 @@ checkbounds_logical(A::AbstractVector, I::AbstractVector{Bool}) = indices(A) == throw_boundserror(A, I) = (@_noinline_meta; throw(BoundsError(A, I))) """ - checkbounds(array, indexes...) + checkbounds(A, I...) -Throw an error if the specified `indexes` are not in bounds for the given `array`. +Throw an error if the specified indices `I` are not in bounds for the given array `A`. """ function checkbounds(A::AbstractArray, I...) @_inline_meta @@ -255,6 +245,42 @@ function checkbounds(A::AbstractArray, I...) end checkbounds(A::AbstractArray) = checkbounds(A, 1) # 0-d case +@generated function trailingsize{T,N,n}(A::AbstractArray{T,N}, ::Type{Val{n}}) + (isa(n, Int) && isa(N, Int)) || error("Must have concrete type") + n > N && return 1 + ex = :(size(A, $n)) + for m = n+1:N + ex = :($ex * size(A, $m)) + end + Expr(:block, Expr(:meta, :inline), ex) +end + +# check along a single dimension +""" + checkindex(Bool, inds::AbstractUnitRange, index) + +Return `true` if the given `index` is within the bounds of +`inds`. Custom types that would like to behave as indices for all +arrays can extend this method in order to provide a specialized bounds +checking implementation. +""" +checkindex(::Type{Bool}, inds::AbstractUnitRange, i) = throw(ArgumentError("unable to check bounds for indices of type $(typeof(i))")) +checkindex(::Type{Bool}, inds::AbstractUnitRange, i::Real) = (first(inds) <= i) & (i <= last(inds)) +checkindex(::Type{Bool}, inds::AbstractUnitRange, ::Colon) = true +function checkindex(::Type{Bool}, inds::AbstractUnitRange, r::Range) + @_propagate_inbounds_meta + isempty(r) | (checkindex(Bool, inds, first(r)) & checkindex(Bool, inds, last(r))) +end +checkindex{N}(::Type{Bool}, indx::AbstractUnitRange, I::AbstractArray{Bool,N}) = N == 1 && indx == indices1(I) +function checkindex(::Type{Bool}, inds::AbstractUnitRange, I::AbstractArray) + @_inline_meta + b = true + for i in I + b &= checkindex(Bool, inds, i) + end + b +end + # See also specializations in multidimensional ## Constructors ## diff --git a/base/multidimensional.jl b/base/multidimensional.jl index f286c7c18de79..6fe1ca22f52d5 100644 --- a/base/multidimensional.jl +++ b/base/multidimensional.jl @@ -156,10 +156,10 @@ using .IteratorsMD ## Bounds-checking with CartesianIndex @inline checkbounds_indices(::Tuple{}, I::Tuple{CartesianIndex,Vararg{Any}}) = checkbounds_indices((), (I[1].I..., tail(I)...)) -@inline checkbounds_indices(inds::Tuple{Any}, I::Tuple{CartesianIndex,Vararg{Any}}) = - checkbounds_indices(inds, (I[1].I..., tail(I)...)) -@inline checkbounds_indices(inds::Tuple, I::Tuple{CartesianIndex,Vararg{Any}}) = - checkbounds_indices(inds, (I[1].I..., tail(I)...)) +@inline checkbounds_indices(IA::Tuple{Any}, I::Tuple{CartesianIndex,Vararg{Any}}) = + checkbounds_indices(IA, (I[1].I..., tail(I)...)) +@inline checkbounds_indices(IA::Tuple, I::Tuple{CartesianIndex,Vararg{Any}}) = + checkbounds_indices(IA, (I[1].I..., tail(I)...)) # Support indexing with an array of CartesianIndex{N}s # Here we try to consume N of the indices (if there are that many available) @@ -167,12 +167,12 @@ using .IteratorsMD @inline function checkbounds_indices{N}(::Tuple{}, I::Tuple{AbstractArray{CartesianIndex{N}},Vararg{Any}}) checkindex(Bool, (), I[1]) & checkbounds_indices((), tail(I)) end -@inline function checkbounds_indices{N}(inds::Tuple{Any}, I::Tuple{AbstractArray{CartesianIndex{N}},Vararg{Any}}) - checkindex(Bool, inds, I[1]) & checkbounds_indices((), tail(I)) +@inline function checkbounds_indices{N}(IA::Tuple{Any}, I::Tuple{AbstractArray{CartesianIndex{N}},Vararg{Any}}) + checkindex(Bool, IA, I[1]) & checkbounds_indices((), tail(I)) end -@inline function checkbounds_indices{N}(inds::Tuple, I::Tuple{AbstractArray{CartesianIndex{N}},Vararg{Any}}) - inds1, indsrest = IteratorsMD.split(inds, Val{N}) - checkindex(Bool, inds1, I[1]) & checkbounds_indices(indsrest, tail(I)) +@inline function checkbounds_indices{N}(IA::Tuple, I::Tuple{AbstractArray{CartesianIndex{N}},Vararg{Any}}) + IA1, IArest = IteratorsMD.split(IA, Val{N}) + checkindex(Bool, IA1, I[1]) & checkbounds_indices(IArest, tail(I)) end function checkindex{N}(::Type{Bool}, inds::Tuple, I::AbstractArray{CartesianIndex{N}}) diff --git a/doc/devdocs/boundscheck.rst b/doc/devdocs/boundscheck.rst index c6ab3e758da29..1d324e9aa546b 100644 --- a/doc/devdocs/boundscheck.rst +++ b/doc/devdocs/boundscheck.rst @@ -59,3 +59,52 @@ instance, the default ``getindex`` methods have the chain To override the "one layer of inlining" rule, a function may be marked with ``@propagate_inbounds`` to propagate an inbounds context (or out of bounds context) through one additional layer of inlining. + +The bounds checking call hierarchy +---------------------------------- + +The overall hierarchy is: + +| ``checkbounds(A, I...)`` which calls +| ``checkbounds(Bool, A, I...)`` which calls either of: +| ``checkbounds_logical(A, I)`` when ``I`` is a single logical array +| ``checkbounds_indices(indices(A), I)`` otherwise +| + +Here ``A`` is the array, and ``I`` contains the "requested" indices. +``indices(A)`` returns a tuple of "permitted" indices of ``A``. + +``checkbounds(A, I...)`` throws an error if the indices are invalid, +whereas ``checkbounds(Bool, A, I...)`` returns ``false`` in that +circumstance. ``checkbounds_indices`` discards any information about +the array other than its ``indices`` tuple, and performs a pure +indices-vs-indices comparison: this allows relatively few compiled +methods to serve a huge variety of array types. Indices are specified +as tuples, and are usually compared in a 1-1 fashion with individual +dimensions handled by calling another important function, +``checkindex``: typically, +:: + + checkbounds_indices((IA1, IA...), (I1, I...)) = checkindex(Bool, IA1, I1) & + checkbounds_indices(IA, I) + +so ``checkindex`` checks a single dimension. All of these functions, +including the unexported ``checkbounds_indices`` and +``checkbounds_logical``, have docstrings accessible with ``?`` . + +If you have to customize bounds checking for a specific array type, +you should specialize ``checkbounds(Bool, A, I...)``. However, in most +cases you should be able to rely on ``checkbounds_indices`` as long as +you supply useful ``indices`` for your array type. + +If you have novel index types, first consider specializing +``checkindex``, which handles a single index for a particular +dimension of an array. If you have a custom multidimensional index +type (similar to ``CartesianIndex``), then you may have to consider +specializing ``checkbounds_indices``. + +Note this hierarchy has been designed to reduce the likelihood of +method ambiguities. We try to make ``checkbounds`` the place to +specialize on array type, and try to avoid specializations on index +types; conversely, ``checkindex`` is intended to be specialized only +on index type (especially, the last argument). diff --git a/doc/stdlib/arrays.rst b/doc/stdlib/arrays.rst index e4272781be1a5..b62866876cba1 100644 --- a/doc/stdlib/arrays.rst +++ b/doc/stdlib/arrays.rst @@ -616,19 +616,21 @@ Indexing, Assignment, and Concatenation Check two array shapes for compatibility, allowing trailing singleton dimensions, and return whichever shape has more dimensions. -.. function:: checkbounds(array, indexes...) +.. function:: checkbounds(A, I...) .. Docstring generated from Julia source - Throw an error if the specified ``indexes`` are not in bounds for the given ``array``\ . + Throw an error if the specified indices ``I`` are not in bounds for the given array ``A``\ . -.. function:: checkbounds(Bool, array, indexes...) +.. function:: checkbounds(Bool, A, I...) .. Docstring generated from Julia source - Return ``true`` if the specified ``indexes`` are in bounds for the given ``array``\ . Subtypes of ``AbstractArray`` should specialize this method if they need to provide custom bounds checking behaviors. + Return ``true`` if the specified indices ``I`` are in bounds for the given array ``A``\ . Subtypes of ``AbstractArray`` should specialize this method if they need to provide custom bounds checking behaviors; however, in many cases one can rely on ``A``\ 's indices and ``checkindex``\ . -.. function:: checkindex(Bool, inds::UnitRange, index) + See also ``checkindex``\ . + +.. function:: checkindex(Bool, inds::AbstractUnitRange, index) .. Docstring generated from Julia source From f16a3fe2e7f07707cb7e9304dfde4458a93992d1 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Wed, 13 Jul 2016 20:48:06 -0500 Subject: [PATCH 6/6] Non-throwing boundscheck functions are written as f(Bool, args...) --- base/abstractarray.jl | 79 +++++++++++++++++++++++-------------- base/multidimensional.jl | 26 ++++++------ doc/devdocs/boundscheck.rst | 8 ++-- doc/stdlib/arrays.rst | 1 - test/replutil.jl | 3 ++ 5 files changed, 69 insertions(+), 48 deletions(-) diff --git a/base/abstractarray.jl b/base/abstractarray.jl index 962574a3bf5ee..3e82aeb2245fa 100644 --- a/base/abstractarray.jl +++ b/base/abstractarray.jl @@ -154,8 +154,8 @@ linearindexing(::LinearIndexing, ::LinearIndexing) = LinearSlow() # The overall hierarchy is # `checkbounds(A, I...)` -> # `checkbounds(Bool, A, I...)` -> either of: -# - `checkbounds_indices(IA, I)` which calls `checkindex(Bool, inds, i)` -# - `checkbounds_logical(A, I)` when `I` is a single logical array +# - `checkbounds_logical(Bool, A, I)` when `I` is a single logical array +# - `checkbounds_indices(Bool, IA, I)` otherwise (uses `checkindex`) # # See the "boundscheck" devdocs for more information. # @@ -177,23 +177,35 @@ See also `checkindex`. """ function checkbounds(::Type{Bool}, A::AbstractArray, I...) @_inline_meta - checkbounds_indices(indices(A), I) + checkbounds_indices(Bool, indices(A), I) end function checkbounds(::Type{Bool}, A::AbstractArray, I::AbstractArray{Bool}) @_inline_meta - checkbounds_logical(A, I) + checkbounds_logical(Bool, A, I) end """ - checkbounds_indices(IA, I) + checkbounds(A, I...) + +Throw an error if the specified indices `I` are not in bounds for the given array `A`. +""" +function checkbounds(A::AbstractArray, I...) + @_inline_meta + checkbounds(Bool, A, I...) || throw_boundserror(A, I) + nothing +end +checkbounds(A::AbstractArray) = checkbounds(A, 1) # 0-d case -checks whether the "requested" indices in the tuple `I` fall within +""" + checkbounds_indices(Bool, IA, I) + +Return `true` if the "requested" indices in the tuple `I` fall within the bounds of the "permitted" indices specified by the tuple `IA`. This function recursively consumes elements of these tuples, usually in a 1-for-1 fashion, - checkbounds_indices((IA1, IA...), (I1, I...)) = checkindex(Bool, IA1, I1) & - checkbounds_indices(IA, I) + checkbounds_indices(Bool, (IA1, IA...), (I1, I...)) = checkindex(Bool, IA1, I1) & + checkbounds_indices(Bool, IA, I) Note that `checkindex` is being used to perform the actual bounds-check for a single dimension of the array. @@ -202,48 +214,55 @@ There are two important exceptions to the 1-1 rule: linear indexing and CartesianIndex{N}, both of which may "consume" more than one element of `IA`. """ -function checkbounds_indices(IA::Tuple, I::Tuple) +function checkbounds_indices(::Type{Bool}, IA::Tuple, I::Tuple) @_inline_meta - checkindex(Bool, IA[1], I[1]) & checkbounds_indices(tail(IA), tail(I)) + checkindex(Bool, IA[1], I[1]) & checkbounds_indices(Bool, tail(IA), tail(I)) end -checkbounds_indices(::Tuple{}, ::Tuple{}) = true -checkbounds_indices(::Tuple{}, I::Tuple{Any}) = (@_inline_meta; checkindex(Bool, 1:1, I[1])) -function checkbounds_indices(::Tuple{}, I::Tuple) +checkbounds_indices(::Type{Bool}, ::Tuple{}, ::Tuple{}) = true +checkbounds_indices(::Type{Bool}, ::Tuple{}, I::Tuple{Any}) = (@_inline_meta; checkindex(Bool, 1:1, I[1])) +function checkbounds_indices(::Type{Bool}, ::Tuple{}, I::Tuple) @_inline_meta - checkindex(Bool, 1:1, I[1]) & checkbounds_indices((), tail(I)) + checkindex(Bool, 1:1, I[1]) & checkbounds_indices(Bool, (), tail(I)) end -function checkbounds_indices(IA::Tuple{Any}, I::Tuple{Any}) +function checkbounds_indices(::Type{Bool}, IA::Tuple{Any}, I::Tuple{Any}) @_inline_meta checkindex(Bool, IA[1], I[1]) end -function checkbounds_indices(IA::Tuple, I::Tuple{Any}) +function checkbounds_indices(::Type{Bool}, IA::Tuple, I::Tuple{Any}) @_inline_meta checkindex(Bool, 1:prod(map(dimlength, IA)), I[1]) # linear indexing end """ - checkbounds_logical(A, I::AbstractArray{Bool}) + checkbounds_logical(Bool, A, I::AbstractArray{Bool}) -tests whether the logical array `I` is consistent with the indices of `A`. +Return `true` if the logical array `I` is consistent with the indices +of `A`. `I` and `A` should have the same size and compatible indices. """ -checkbounds_logical(A::AbstractArray, I::AbstractArray{Bool}) = indices(A) == indices(I) -checkbounds_logical(A::AbstractArray, I::AbstractVector{Bool}) = length(A) == length(I) -checkbounds_logical(A::AbstractVector, I::AbstractArray{Bool}) = length(A) == length(I) -checkbounds_logical(A::AbstractVector, I::AbstractVector{Bool}) = indices(A) == indices(I) - -throw_boundserror(A, I) = (@_noinline_meta; throw(BoundsError(A, I))) +function checkbounds_logical(::Type{Bool}, A::AbstractArray, I::AbstractArray{Bool}) + indices(A) == indices(I) +end +function checkbounds_logical(::Type{Bool}, A::AbstractArray, I::AbstractVector{Bool}) + length(A) == length(I) +end +function checkbounds_logical(::Type{Bool}, A::AbstractVector, I::AbstractArray{Bool}) + length(A) == length(I) +end +function checkbounds_logical(::Type{Bool}, A::AbstractVector, I::AbstractVector{Bool}) + indices(A) == indices(I) +end """ - checkbounds(A, I...) + checkbounds_logical(A, I::AbstractArray{Bool}) -Throw an error if the specified indices `I` are not in bounds for the given array `A`. +Throw an error if the logical array `I` is inconsistent with the indices of `A`. """ -function checkbounds(A::AbstractArray, I...) - @_inline_meta - checkbounds(Bool, A, I...) || throw_boundserror(A, I) +function checkbounds_logical(A, I::AbstractVector{Bool}) + checkbounds_logical(Bool, A, I) || throw_boundserror(A, I) nothing end -checkbounds(A::AbstractArray) = checkbounds(A, 1) # 0-d case + +throw_boundserror(A, I) = (@_noinline_meta; throw(BoundsError(A, I))) @generated function trailingsize{T,N,n}(A::AbstractArray{T,N}, ::Type{Val{n}}) (isa(n, Int) && isa(N, Int)) || error("Must have concrete type") diff --git a/base/multidimensional.jl b/base/multidimensional.jl index 6fe1ca22f52d5..199ef18cf5afc 100644 --- a/base/multidimensional.jl +++ b/base/multidimensional.jl @@ -154,31 +154,31 @@ end # IteratorsMD using .IteratorsMD ## Bounds-checking with CartesianIndex -@inline checkbounds_indices(::Tuple{}, I::Tuple{CartesianIndex,Vararg{Any}}) = - checkbounds_indices((), (I[1].I..., tail(I)...)) -@inline checkbounds_indices(IA::Tuple{Any}, I::Tuple{CartesianIndex,Vararg{Any}}) = - checkbounds_indices(IA, (I[1].I..., tail(I)...)) -@inline checkbounds_indices(IA::Tuple, I::Tuple{CartesianIndex,Vararg{Any}}) = - checkbounds_indices(IA, (I[1].I..., tail(I)...)) +@inline checkbounds_indices(::Type{Bool}, ::Tuple{}, I::Tuple{CartesianIndex,Vararg{Any}}) = + checkbounds_indices(Bool, (), (I[1].I..., tail(I)...)) +@inline checkbounds_indices(::Type{Bool}, IA::Tuple{Any}, I::Tuple{CartesianIndex,Vararg{Any}}) = + checkbounds_indices(Bool, IA, (I[1].I..., tail(I)...)) +@inline checkbounds_indices(::Type{Bool}, IA::Tuple, I::Tuple{CartesianIndex,Vararg{Any}}) = + checkbounds_indices(Bool, IA, (I[1].I..., tail(I)...)) # Support indexing with an array of CartesianIndex{N}s # Here we try to consume N of the indices (if there are that many available) # The first two simply handle ambiguities -@inline function checkbounds_indices{N}(::Tuple{}, I::Tuple{AbstractArray{CartesianIndex{N}},Vararg{Any}}) - checkindex(Bool, (), I[1]) & checkbounds_indices((), tail(I)) +@inline function checkbounds_indices{N}(::Type{Bool}, ::Tuple{}, I::Tuple{AbstractArray{CartesianIndex{N}},Vararg{Any}}) + checkindex(Bool, (), I[1]) & checkbounds_indices(Bool, (), tail(I)) end -@inline function checkbounds_indices{N}(IA::Tuple{Any}, I::Tuple{AbstractArray{CartesianIndex{N}},Vararg{Any}}) - checkindex(Bool, IA, I[1]) & checkbounds_indices((), tail(I)) +@inline function checkbounds_indices{N}(::Type{Bool}, IA::Tuple{Any}, I::Tuple{AbstractArray{CartesianIndex{N}},Vararg{Any}}) + checkindex(Bool, IA, I[1]) & checkbounds_indices(Bool, (), tail(I)) end -@inline function checkbounds_indices{N}(IA::Tuple, I::Tuple{AbstractArray{CartesianIndex{N}},Vararg{Any}}) +@inline function checkbounds_indices{N}(::Type{Bool}, IA::Tuple, I::Tuple{AbstractArray{CartesianIndex{N}},Vararg{Any}}) IA1, IArest = IteratorsMD.split(IA, Val{N}) - checkindex(Bool, IA1, I[1]) & checkbounds_indices(IArest, tail(I)) + checkindex(Bool, IA1, I[1]) & checkbounds_indices(Bool, IArest, tail(I)) end function checkindex{N}(::Type{Bool}, inds::Tuple, I::AbstractArray{CartesianIndex{N}}) b = true for i in I - b &= checkbounds_indices(inds, (i,)) + b &= checkbounds_indices(Bool, inds, (i,)) end b end diff --git a/doc/devdocs/boundscheck.rst b/doc/devdocs/boundscheck.rst index 1d324e9aa546b..6a85da5c0225e 100644 --- a/doc/devdocs/boundscheck.rst +++ b/doc/devdocs/boundscheck.rst @@ -67,8 +67,8 @@ The overall hierarchy is: | ``checkbounds(A, I...)`` which calls | ``checkbounds(Bool, A, I...)`` which calls either of: -| ``checkbounds_logical(A, I)`` when ``I`` is a single logical array -| ``checkbounds_indices(indices(A), I)`` otherwise +| ``checkbounds_logical(Bool, A, I)`` when ``I`` is a single logical array +| ``checkbounds_indices(Bool, indices(A), I)`` otherwise | Here ``A`` is the array, and ``I`` contains the "requested" indices. @@ -85,8 +85,8 @@ dimensions handled by calling another important function, ``checkindex``: typically, :: - checkbounds_indices((IA1, IA...), (I1, I...)) = checkindex(Bool, IA1, I1) & - checkbounds_indices(IA, I) + checkbounds_indices(Bool, (IA1, IA...), (I1, I...)) = checkindex(Bool, IA1, I1) & + checkbounds_indices(Bool, IA, I) so ``checkindex`` checks a single dimension. All of these functions, including the unexported ``checkbounds_indices`` and diff --git a/doc/stdlib/arrays.rst b/doc/stdlib/arrays.rst index b62866876cba1..7b222dea1416c 100644 --- a/doc/stdlib/arrays.rst +++ b/doc/stdlib/arrays.rst @@ -1080,4 +1080,3 @@ dense counterparts. The following functions are specific to sparse arrays. For additional (algorithmic) information, and for versions of these methods that forgo argument checking, see (unexported) parent methods :func:`Base.SparseArrays.unchecked_noalias_permute!` and :func:`Base.SparseArrays.unchecked_aliasing_permute!`\ . See also: :func:`Base.SparseArrays.permute` - diff --git a/test/replutil.jl b/test/replutil.jl index 3bd83c8f7f29a..c0a34b29df5e9 100644 --- a/test/replutil.jl +++ b/test/replutil.jl @@ -222,6 +222,9 @@ let undefvar err_str = @except_strbt (-1)^0.25 DomainError @test contains(err_str, "Exponentiation yielding a complex result requires a complex argument") + err_str = @except_str (1,2,3)[4] BoundsError + @test err_str == "BoundsError: attempt to access (1,2,3)\n at index [4]" + err_str = @except_str [5,4,3][-2,1] BoundsError @test err_str == "BoundsError: attempt to access 3-element Array{$Int,1} at index [-2,1]" err_str = @except_str [5,4,3][1:5] BoundsError