Skip to content

Commit

Permalink
Non-throwing boundscheck functions are written as f(Bool, args...)
Browse files Browse the repository at this point in the history
  • Loading branch information
timholy committed Jul 14, 2016
1 parent 81f8d4f commit f16a3fe
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 48 deletions.
79 changes: 49 additions & 30 deletions base/abstractarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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.
#
Expand All @@ -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.
Expand All @@ -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")
Expand Down
26 changes: 13 additions & 13 deletions base/multidimensional.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions doc/devdocs/boundscheck.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down
1 change: 0 additions & 1 deletion doc/stdlib/arrays.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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`

3 changes: 3 additions & 0 deletions test/replutil.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit f16a3fe

Please sign in to comment.