Skip to content

Commit

Permalink
Make size throw an error for arrays with non-1 indexing
Browse files Browse the repository at this point in the history
  • Loading branch information
timholy committed Jul 18, 2016
1 parent 828f7ae commit 7711aef
Show file tree
Hide file tree
Showing 22 changed files with 640 additions and 305 deletions.
30 changes: 12 additions & 18 deletions base/abstractarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ typealias AbstractVector{T} AbstractArray{T,1}
typealias AbstractMatrix{T} AbstractArray{T,2}
typealias AbstractVecOrMat{T} Union{AbstractVector{T}, AbstractMatrix{T}}
typealias RangeIndex Union{Int, Range{Int}, AbstractUnitRange{Int}, Colon}
typealias Indices{N} NTuple{N,AbstractUnitRange}
typealias IndicesOne{N} NTuple{N,OneTo}
typealias DimOrInd Union{Integer, AbstractUnitRange}
typealias DimsOrInds{N} NTuple{N,DimOrInd}

Expand All @@ -29,6 +27,7 @@ end

size{T,N}(t::AbstractArray{T,N}, d) = d <= N ? size(t)[d] : 1
size{N}(x, d1::Integer, d2::Integer, dx::Vararg{Integer, N}) = (size(x, d1), size(x, d2), ntuple(k->size(x, dx[k]), Val{N})...)

"""
indices(A, d)
Expand Down Expand Up @@ -66,15 +65,16 @@ is `indices(A, 1)`.
Calling this function is the "safe" way to write algorithms that
exploit linear indexing.
"""
linearindices(A) = (@_inline_meta; 1:length(A))
linearindices(A) = (@_inline_meta; OneTo(unsafe_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)
ndims{T,N}(::AbstractArray{T,N}) = N
ndims{T,N}(::Type{AbstractArray{T,N}}) = N
ndims{T<:AbstractArray}(::Type{T}) = ndims(supertype(T))
length(t::AbstractArray) = prod(size(t))::Int
length(t::AbstractArray) = prod(size(t))
unsafe_length(A::AbstractArray) = prod(map(unsafe_length, indices(A))) # internal method
endof(a::AbstractArray) = length(a)
first(a::AbstractArray) = a[first(eachindex(a))]

Expand Down Expand Up @@ -132,6 +132,10 @@ function trailingsize(A, n)
end
return s
end
function trailingsize(inds::Indices)
@_inline_meta
prod(map(unsafe_length, inds))
end

## Traits for array types ##

Expand Down Expand Up @@ -230,7 +234,7 @@ function checkbounds_indices(::Type{Bool}, IA::Tuple{Any}, I::Tuple{Any})
end
function checkbounds_indices(::Type{Bool}, IA::Tuple, I::Tuple{Any})
@_inline_meta
checkindex(Bool, 1:prod(map(dimlength, IA)), I[1]) # linear indexing
checkindex(Bool, OneTo(trailingsize(IA)), I[1]) # linear indexing
end

"""
Expand Down Expand Up @@ -264,16 +268,6 @@ end

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")
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)
Expand Down Expand Up @@ -357,7 +351,7 @@ to_shape(dims::DimsOrInds) = map(to_shape, dims)
to_shape(i::Int) = i
to_shape(i::Integer) = Int(i)
to_shape(r::OneTo) = Int(last(r))
to_shape(r::UnitRange) = convert(UnitRange{Int}, r)
to_shape(r::AbstractUnitRange) = r

"""
similar(storagetype, indices)
Expand Down Expand Up @@ -492,7 +486,7 @@ function copy!(::LinearIndexing, dest::AbstractArray, ::LinearSlow, src::Abstrac
end

function copy!(dest::AbstractArray, dstart::Integer, src::AbstractArray)
copy!(dest, dstart, src, first(linearindices(src)), length(src))
copy!(dest, dstart, src, first(linearindices(src)), unsafe_length(src))
end

function copy!(dest::AbstractArray, dstart::Integer, src::AbstractArray, sstart::Integer)
Expand Down Expand Up @@ -618,7 +612,7 @@ function _maxlength(A, B, C...)
max(length(A), _maxlength(B, C...))
end

isempty(a::AbstractArray) = (length(a) == 0)
isempty(a::AbstractArray) = (unsafe_length(a) == 0)

## Conversions ##

Expand Down
13 changes: 7 additions & 6 deletions base/abstractarraymath.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ transpose(a::AbstractArray) = error("transpose not implemented for $(typeof(a)).

## Constructors ##

vec(a::AbstractArray) = reshape(a,length(a))
vec(a::AbstractArray) = reshape(a,unsafe_length(a))
vec(a::AbstractVector) = a

_sub(::Tuple{}, ::Tuple{}) = ()
Expand Down Expand Up @@ -74,22 +74,23 @@ function flipdim(A::AbstractArray, d::Integer)
if d > nd || isempty(A)
return copy(A)
end
inds = indices(A)
B = similar(A)
nnd = 0
for i = 1:nd
nnd += Int(size(A,i)==1 || i==d)
nnd += Int(length(inds[i])==1 || i==d)
end
inds = indices(A, d)
sd = first(inds)+last(inds)
indsd = inds[d]
sd = first(indsd)+last(indsd)
if nnd==nd
# flip along the only non-singleton dimension
for i in inds
for i in indsd
B[i] = A[sd-i]
end
return B
end
alli = [ indices(B,n) for n in 1:nd ]
for i in inds
for i in indsd
B[[ n==d ? sd-i : alli[n] for n in 1:nd ]...] = slicedim(A, d, i)
end
return B
Expand Down
11 changes: 6 additions & 5 deletions base/arraymath.jl
Original file line number Diff line number Diff line change
Expand Up @@ -252,19 +252,20 @@ end

const transposebaselength=64
function transpose_f!(f,B::AbstractMatrix,A::AbstractMatrix)
indices(B,1) == indices(A,2) && indices(B,2) == indices(A,1) || throw(DimensionMismatch(string(f)))
inds = indices(A)
indices(B,1) == inds[2] && indices(B,2) == inds[1] || throw(DimensionMismatch(string(f)))

m, n = size(A)
m, n = length(inds[1]), length(inds[2])
if m*n<=4*transposebaselength
@inbounds begin
for j = indices(A,2)
for i = indices(A,1)
for j = inds[2]
for i = inds[1]
B[j,i] = f(A[i,j])
end
end
end
else
transposeblock!(f,B,A,m,n,first(indices(A,1))-1,first(indices(A,2))-1)
transposeblock!(f,B,A,m,n,first(inds[1])-1,first(inds[2])-1)
end
return B
end
Expand Down
3 changes: 1 addition & 2 deletions base/bitarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ type BitArray{N} <: DenseArray{Bool, N}
chunks::Vector{UInt64}
len::Int
dims::NTuple{N,Int}
function BitArray(dims::Int...)
length(dims) == N || throw(ArgumentError("number of dimensions must be $N, got $(length(dims))"))
function BitArray(dims::Vararg{Int,N})
n = 1
i = 1
for d in dims
Expand Down
88 changes: 48 additions & 40 deletions base/broadcast.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
module Broadcast

using Base.Cartesian
using Base: promote_op, promote_eltype, promote_eltype_op, @get!, _msk_end, unsafe_bitgetindex, linearindices, to_shape, tail, dimlength, OneTo
using Base: promote_op, promote_eltype, promote_eltype_op, @get!, _msk_end, unsafe_bitgetindex, linearindices, tail, OneTo, to_shape
import Base: .+, .-, .*, ./, .\, .//, .==, .<, .!=, .<=, , .%, .<<, .>>, .^
export broadcast, broadcast!, bitbroadcast
export broadcast_getindex, broadcast_setindex!
Expand All @@ -20,7 +20,7 @@ broadcast(f, x::Number...) = f(x...)
broadcast_shape() = ()
broadcast_shape(A) = indices(A)
@inline broadcast_shape(A, B...) = broadcast_shape((), indices(A), map(indices, B)...)
# shape inputs
# shape (i.e., tuple-of-indices) inputs
broadcast_shape(shape::Tuple) = shape
@inline broadcast_shape(shape::Tuple, shape1::Tuple, shapes::Tuple...) = broadcast_shape(_bcs((), shape, shape1), shapes...)
# _bcs consolidates two shapes into a single output shape
Expand All @@ -37,7 +37,7 @@ _bcs1(a::Integer, b) = a == 1 ? b : (first(b) == 1 && last(b) == a ? b : throw(D
_bcs1(a, b::Integer) = _bcs1(b, a)
_bcs1(a, b) = _bcsm(b, a) ? b : (_bcsm(a, b) ? a : throw(DimensionMismatch("arrays could not be broadcast to a common size")))
# _bcsm tests whether the second index is consistent with the first
_bcsm(a, b) = a == b || (dimlength(b) == 1 && first(b) == first(a))
_bcsm(a, b) = a == b || (length(b) == 1 && first(b) == 1)
_bcsm(a, b::Number) = b == 1
_bcsm(a::Number, b::Number) = a == b || b == 1

Expand All @@ -62,22 +62,30 @@ end
end

## Indexing manipulations
# newindex(I, rule) replaces a CartesianIndex with something that is
# appropriate for a particular array/scalar. `rule` is a tuple that
# describes the manipulations that should be made.
@inline newindex(I::CartesianIndex, ::Tuple{}) = 1 # for scalars
@inline newindex(I::CartesianIndex, indexmap) = CartesianIndex(_newindex((), I.I, indexmap...))
@inline _newindex(out, I) = out # can truncate if indexmap is shorter than I
@inline _newindex(out, I, keep::Bool, indexmap...) = _newindex((out..., ifelse(keep, I[1], 1)), tail(I), indexmap...)

newindexer(sz, x::Number) = ()
@inline newindexer(sz, A) = _newindexer(sz, size(A))
@inline _newindexer(sz, szA::Tuple{}) = ()
@inline _newindexer(sz, szA) = (sz[1] == szA[1], _newindexer(tail(sz), tail(szA))...)

# map(x->newindexer(sz, x), As), but see #15276
map_newindexer(sz, ::Tuple{}) = ()
@inline map_newindexer(sz, As) = (newindexer(sz, As[1]), map_newindexer(sz, tail(As))...)
# newindex(I, keep) replaces a CartesianIndex `I` with something that
# is appropriate for a particular broadcast array/scalar. `keep` is a
# NTuple{N,Bool}, where keep[d] == true means that one should preserve
# I[d]; if false, replace it with 1. In other words, this is
# equivalent to map((k,i)->k ? i : 1, keep, I.I) (but see #17126).
@inline newindex(I::CartesianIndex, ::Tuple{}) = 1 # for scalars
@inline newindex(I::CartesianIndex, indexmap) = CartesianIndex(_newindex(I.I, indexmap))
@inline _newindex(I, indexmap) =
(ifelse(indexmap[1], I[1], 1), _newindex(tail(I), tail(indexmap))...)
@inline _newindex(I, indexmap::Tuple{}) = () # truncate if indexmap is shorter than I

# newindexer(shape, A) generates `keep` (for use by `newindex` above)
# for a particular array `A`, given the broadcast_shape `shape`
# Equivalent to map(==, indices(A), shape) (but see #17126)
newindexer(shape, x::Number) = ()
@inline newindexer(shape, A) = newindexer(shape, indices(A))
@inline newindexer(shape, indsA::Tuple{}) = ()
@inline newindexer(shape, indsA::Tuple) =
(shape[1] == indsA[1], newindexer(tail(shape), tail(indsA))...)

# Equivalent to map(x->newindexer(shape, x), As) (but see #17126)
map_newindexer(shape, ::Tuple{}) = ()
@inline map_newindexer(shape, As) = (newindexer(shape, As[1]), map_newindexer(shape, tail(As))...)

# For output BitArrays
const bitcache_chunks = 64 # this can be changed
Expand Down Expand Up @@ -140,9 +148,9 @@ end
end

@inline function broadcast!{nargs}(f, B::AbstractArray, As::Vararg{Any,nargs})
check_broadcast_shape(indices(B), As...)
sz = size(B)
mapindex = map(x->newindexer(sz, x), As)
shape = indices(B)
check_broadcast_shape(shape, As...)
mapindex = map_newindexer(shape, As)
_broadcast!(f, B, mapindex, As, Val{nargs})
B
end
Expand Down Expand Up @@ -183,18 +191,17 @@ end
end

function broadcast_t(f, ::Type{Any}, As...)
shp = broadcast_shape(As...)
iter = CartesianRange(shp)
shape = broadcast_shape(As...)
iter = CartesianRange(shape)
if isempty(iter)
return similar(Array{Union{}}, shp)
return similar(Array{Union{}}, shape)
end
nargs = length(As)
sz = size(iter)
indexmaps = map(x->newindexer(sz, x), As)
indexmaps = map_newindexer(shape, As)
st = start(iter)
I, st = next(iter, st)
val = f([ As[i][newindex(I, indexmaps[i])] for i=1:nargs ]...)
B = similar(Array{typeof(val)}, shp)
B = similar(Array{typeof(val)}, shape)
B[I] = val
return _broadcast!(f, B, indexmaps, As, Val{nargs}, iter, st, 1)
end
Expand All @@ -213,26 +220,26 @@ end
end
function broadcast(f, As...)
shp = broadcast_shape(As...)
iter = CartesianRange(shp)
sz = size(iter)
indexmaps = map(x->newindexer(sz, x), As)
shape = broadcast_shape(As...)
iter = CartesianRange(shape)
indexmaps = map_newindexer(shape, As)
naT = Val{nfields(As)}
_broadcast(f, indexmaps, As, naT, iter)
end
=#

@inline bitbroadcast(f, As...) = broadcast!(f, similar(BitArray, broadcast_shape(As...)), As...)

broadcast_getindex(src::AbstractArray, I::AbstractArray...) = broadcast_getindex!(Array{eltype(src)}(to_shape(broadcast_shape(I...))), src, I...)
broadcast_getindex(src::AbstractArray, I::AbstractArray...) = broadcast_getindex!(similar(Array{eltype(src)}, broadcast_shape(I...)), src, I...)
@generated function broadcast_getindex!(dest::AbstractArray, src::AbstractArray, I::AbstractArray...)
N = length(I)
Isplat = Expr[:(I[$d]) for d = 1:N]
quote
@nexprs $N d->(I_d = I[d])
check_broadcast_shape(indices(dest), $(Isplat...)) # unnecessary if this function is never called directly
checkbounds(src, $(Isplat...))
@nloops $N i dest d->(@nexprs $N k->(j_d_k = size(I_k, d) == 1 ? 1 : i_d)) begin
@nexprs $N d->(@nexprs $N k->(Ibcast_d_k = indices(I_k, d) == OneTo(1)))
@nloops $N i dest d->(@nexprs $N k->(j_d_k = Ibcast_d_k ? 1 : i_d)) begin
@nexprs $N k->(@inbounds J_k = @nref $N I_k d->j_d_k)
@inbounds (@nref $N dest i) = (@nref $N src J)
end
Expand All @@ -248,18 +255,19 @@ end
checkbounds(A, $(Isplat...))
shape = broadcast_shape($(Isplat...))
@nextract $N shape d->(length(shape) < d ? OneTo(1) : shape[d])
@nexprs $N d->(@nexprs $N k->(Ibcast_d_k = indices(I_k, d) == 1:1))
if !isa(x, AbstractArray)
xA = convert(eltype(A), x)
@nloops $N i d->shape_d d->(@nexprs $N k->(j_d_k = size(I_k, d) == 1 ? 1 : i_d)) begin
@nloops $N i d->shape_d d->(@nexprs $N k->(j_d_k = Ibcast_d_k ? 1 : i_d)) begin
@nexprs $N k->(@inbounds J_k = @nref $N I_k d->j_d_k)
@inbounds (@nref $N A J) = xA
end
else
X = x
@nexprs $N d->(shapelen_d = dimlength(shape_d))
@nexprs $N d->(shapelen_d = length(shape_d))
@ncall $N Base.setindex_shape_check X shapelen
Xstate = start(X)
@inbounds @nloops $N i d->shape_d d->(@nexprs $N k->(j_d_k = size(I_k, d) == 1 ? 1 : i_d)) begin
@inbounds @nloops $N i d->shape_d d->(@nexprs $N k->(j_d_k = Ibcast_d_k ? 1 : i_d)) begin
@nexprs $N k->(J_k = @nref $N I_k d->j_d_k)
x_el, Xstate = next(X, Xstate)
(@nref $N A J) = x_el
Expand Down Expand Up @@ -290,11 +298,11 @@ end
function broadcast_bitarrays(scalarf, bitf, A::AbstractArray{Bool}, B::AbstractArray{Bool})
local shape
try
shape = promote_shape(size(A), size(B))
shape = promote_shape(indices(A), indices(B))
catch
return bitbroadcast(scalarf, A, B)
end
F = BitArray(shape)
F = BitArray(to_shape(shape))
Fc = F.chunks
Ac = BitArray(A).chunks
Bc = BitArray(B).chunks
Expand Down Expand Up @@ -381,11 +389,11 @@ end
function (.^){T<:Integer}(A::BitArray, B::Array{T})
local shape
try
shape = promote_shape(size(A), size(B))
shape = promote_shape(indices(A), indices(B))
catch
return bitbroadcast(^, A, B)
end
F = BitArray(shape)
F = BitArray(to_shape(shape))
l = length(F)
l == 0 && return F
Ac = A.chunks
Expand Down
14 changes: 12 additions & 2 deletions base/deprecated.jl
Original file line number Diff line number Diff line change
Expand Up @@ -752,16 +752,26 @@ function checkbounds{N,T}(::Type{Bool}, sz::NTuple{N,Integer}, I1::T, I...)
end

function first(::Colon)
depwarn("first(:) is no longer unambiguous, call Base._first(:, A, dim)", :first)
depwarn("first(:) is deprecated, see http://docs.julialang.org/en/latest/devdocs/offset-arrays/", :first)
1
end
function _first(i, A, d)
depwarn("_first is deprecated, see http://docs.julialang.org/en/latest/devdocs/offset-arrays/", :_first)
__first(i, A, d)
end
__first(::Colon, P, ::Colon) = first(linearindices(P))
__first(i, P, ::Colon) = first(i)
__first(::Colon, P, d) = first(indices(P, d))
__first(i, P, d) = first(i)

# Not exported, but may be useful just in case
# Not exported, but deprecation may be useful just in case
function Broadcast.check_broadcast_shape(sz::Dims, As::Union{AbstractArray,Number}...)
depwarn("check_broadcast_shape(size(A), B...) should be replaced with check_broadcast_shape(indices(A), B...)", :check_broadcast_shape)
Broadcast.check_broadcast_shape(map(OneTo, sz), As...)
end

@deprecate trailingsize{n}(A::AbstractArray, ::Type{Val{n}}) trailingsize(A, n)

@deprecate slice view
@deprecate sub view

Expand Down
Loading

0 comments on commit 7711aef

Please sign in to comment.