Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,10 @@ Language changes
* `try` blocks without `catch` or `finally` are no longer allowed. An explicit empty
`catch` block should be written instead ([#27554]).

* `AbstractArray` types that use unconventional (not 1-based) indexing can now support
`size`, `length`, and `@inbounds`. To optionally enforce conventional indices,
you can `@assert !has_offset_axes(A)`.

Breaking changes
----------------

Expand Down
39 changes: 24 additions & 15 deletions base/abstractarray.jl
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,23 @@ function axes(A)
map(OneTo, size(A))
end

"""
has_offset_axes(A)
has_offset_axes(A, B, ...)

Return `true` if the indices of `A` start with something other than 1 along any axis.
If multiple arguments are passed, equivalent to `has_offset_axes(A) | has_offset_axes(B) | ...`.
"""
has_offset_axes(A) = _tuple_any(x->first(x)!=1, axes(A))
has_offset_axes(A...) = _tuple_any(has_offset_axes, A)
has_offset_axes(::Colon) = false

# Performance optimization: get rid of a branch on `d` in `axes(A, d)`
# for d=1. 1d arrays are heavily used, and the first dimension comes up
# in other applications.
indices1(A::AbstractArray{<:Any,0}) = OneTo(1)
indices1(A::AbstractArray) = (@_inline_meta; axes(A)[1])
indices1(iter) = OneTo(length(iter))
axes1(A::AbstractArray{<:Any,0}) = OneTo(1)
axes1(A::AbstractArray) = (@_inline_meta; axes(A)[1])
axes1(iter) = OneTo(length(iter))

unsafe_indices(A) = axes(A)
unsafe_indices(r::AbstractRange) = (OneTo(unsafe_length(r)),) # Ranges use checked_sub for size
Expand Down Expand Up @@ -154,14 +165,12 @@ julia> length([1 2; 3 4])
```
"""
length(t::AbstractArray) = (@_inline_meta; prod(size(t)))
_length(A::AbstractArray) = (@_inline_meta; prod(map(unsafe_length, axes(A)))) # circumvent missing size
_length(A) = (@_inline_meta; length(A))

# `eachindex` is mostly an optimization of `keys`
eachindex(itrs...) = keys(itrs...)

# eachindex iterates over all indices. IndexCartesian definitions are later.
eachindex(A::AbstractVector) = (@_inline_meta(); indices1(A))
eachindex(A::AbstractVector) = (@_inline_meta(); axes1(A))

"""
eachindex(A...)
Expand Down Expand Up @@ -209,8 +218,8 @@ function eachindex(A::AbstractArray, B::AbstractArray...)
@_inline_meta
eachindex(IndexStyle(A,B...), A, B...)
end
eachindex(::IndexLinear, A::AbstractArray) = (@_inline_meta; OneTo(_length(A)))
eachindex(::IndexLinear, A::AbstractVector) = (@_inline_meta; indices1(A))
eachindex(::IndexLinear, A::AbstractArray) = (@_inline_meta; OneTo(length(A)))
eachindex(::IndexLinear, A::AbstractVector) = (@_inline_meta; axes1(A))
function eachindex(::IndexLinear, A::AbstractArray, B::AbstractArray...)
@_inline_meta
indsA = eachindex(IndexLinear(), A)
Expand Down Expand Up @@ -509,7 +518,7 @@ function checkindex(::Type{Bool}, inds::AbstractUnitRange, r::AbstractRange)
@_propagate_inbounds_meta
isempty(r) | (checkindex(Bool, inds, first(r)) & checkindex(Bool, inds, last(r)))
end
checkindex(::Type{Bool}, indx::AbstractUnitRange, I::AbstractVector{Bool}) = indx == indices1(I)
checkindex(::Type{Bool}, indx::AbstractUnitRange, I::AbstractVector{Bool}) = indx == axes1(I)
checkindex(::Type{Bool}, indx::AbstractUnitRange, I::AbstractArray{Bool}) = false
function checkindex(::Type{Bool}, inds::AbstractUnitRange, I::AbstractArray)
@_inline_meta
Expand Down Expand Up @@ -744,7 +753,7 @@ function copyto!(::IndexStyle, dest::AbstractArray, ::IndexCartesian, src::Abstr
end

function copyto!(dest::AbstractArray, dstart::Integer, src::AbstractArray)
copyto!(dest, dstart, src, first(LinearIndices(src)), _length(src))
copyto!(dest, dstart, src, first(LinearIndices(src)), length(src))
end

function copyto!(dest::AbstractArray, dstart::Integer, src::AbstractArray, sstart::Integer)
Expand Down Expand Up @@ -837,7 +846,7 @@ function iterate(A::AbstractArray, state=(eachindex(A),))
A[y[1]], (state[1], tail(y)...)
end

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


## range conversions ##
Expand Down Expand Up @@ -1148,9 +1157,9 @@ get(A::AbstractArray, I::Dims, default) = checkbounds(Bool, A, I...) ? A[I...] :

function get!(X::AbstractVector{T}, A::AbstractVector, I::Union{AbstractRange,AbstractVector{Int}}, default::T) where T
# 1d is not linear indexing
ind = findall(in(indices1(A)), I)
ind = findall(in(axes1(A)), I)
X[ind] = A[I[ind]]
Xind = indices1(X)
Xind = axes1(X)
X[first(Xind):first(ind)-1] = default
X[last(ind)+1:last(Xind)] = default
X
Expand Down Expand Up @@ -1776,9 +1785,9 @@ _sub2ind(inds::Union{DimsInteger,Indices}, I1::AbstractVector{T}, I::AbstractVec
_sub2ind_vecs(inds, I1, I...)
function _sub2ind_vecs(inds, I::AbstractVector...)
I1 = I[1]
Iinds = indices1(I1)
Iinds = axes1(I1)
for j = 2:length(I)
indices1(I[j]) == Iinds || throw(DimensionMismatch("indices of I[1] ($(Iinds)) does not match indices of I[$j] ($(indices1(I[j])))"))
axes1(I[j]) == Iinds || throw(DimensionMismatch("indices of I[1] ($(Iinds)) does not match indices of I[$j] ($(axes1(I[j])))"))
end
Iout = similar(I1)
_sub2ind!(Iout, inds, Iinds, I)
Expand Down
6 changes: 3 additions & 3 deletions base/abstractarraymath.jl
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ julia> vec(1:3)

See also [`reshape`](@ref).
"""
vec(a::AbstractArray) = reshape(a,_length(a))
vec(a::AbstractArray) = reshape(a,length(a))
vec(a::AbstractVector) = a

_sub(::Tuple{}, ::Tuple{}) = ()
Expand Down Expand Up @@ -72,7 +72,7 @@ squeeze(A; dims) = _squeeze(A, dims)
function _squeeze(A::AbstractArray, dims::Dims)
for i in 1:length(dims)
1 <= dims[i] <= ndims(A) || throw(ArgumentError("squeezed dims must be in range 1:ndims(A)"))
_length(axes(A, dims[i])) == 1 || throw(ArgumentError("squeezed dims must all be size 1"))
length(axes(A, dims[i])) == 1 || throw(ArgumentError("squeezed dims must all be size 1"))
for j = 1:i-1
dims[j] == dims[i] && throw(ArgumentError("squeezed dims must be unique"))
end
Expand Down Expand Up @@ -158,7 +158,7 @@ function reverse(A::AbstractArray; dims::Integer)
B = similar(A)
nnd = 0
for i = 1:nd
nnd += Int(_length(inds[i])==1 || i==d)
nnd += Int(length(inds[i])==1 || i==d)
end
indsd = inds[d]
sd = first(indsd)+last(indsd)
Expand Down
12 changes: 8 additions & 4 deletions base/array.jl
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,7 @@ for (fname, felt) in ((:zeros, :zero), (:ones, :one))
end

function _one(unit::T, x::AbstractMatrix) where T
@assert !has_offset_axes(x)
m,n = size(x)
m==n || throw(DimensionMismatch("multiplicative identity defined only for square matrices"))
# Matrix{T}(I, m, m)
Expand Down Expand Up @@ -547,9 +548,9 @@ end

_collect_indices(::Tuple{}, A) = copyto!(Array{eltype(A),0}(undef), A)
_collect_indices(indsA::Tuple{Vararg{OneTo}}, A) =
copyto!(Array{eltype(A)}(undef, _length.(indsA)), A)
copyto!(Array{eltype(A)}(undef, length.(indsA)), A)
function _collect_indices(indsA, A)
B = Array{eltype(A)}(undef, _length.(indsA))
B = Array{eltype(A)}(undef, length.(indsA))
copyto!(B, CartesianIndices(axes(B)), A, CartesianIndices(indsA))
end

Expand Down Expand Up @@ -748,6 +749,7 @@ function setindex! end
function setindex!(A::Array, X::AbstractArray, I::AbstractVector{Int})
@_propagate_inbounds_meta
@boundscheck setindex_shape_check(X, length(I))
@assert !has_offset_axes(X)
X′ = unalias(A, X)
I′ = unalias(A, I)
count = 1
Expand Down Expand Up @@ -866,7 +868,7 @@ themselves in another collection. The result is of the preceding example is equi
"""
function append!(a::Array{<:Any,1}, items::AbstractVector)
itemindices = eachindex(items)
n = _length(itemindices)
n = length(itemindices)
_growend!(a, n)
copyto!(a, length(a)-n+1, items, first(itemindices), n)
return a
Expand All @@ -876,6 +878,7 @@ append!(a::Vector, iter) = _append!(a, IteratorSize(iter), iter)
push!(a::Vector, iter...) = append!(a, iter)

function _append!(a, ::Union{HasLength,HasShape}, iter)
@assert !has_offset_axes(a)
n = length(a)
resize!(a, n+length(iter))
@inbounds for (i,item) in zip(n+1:length(a), iter)
Expand Down Expand Up @@ -909,7 +912,7 @@ function prepend! end

function prepend!(a::Array{<:Any,1}, items::AbstractVector)
itemindices = eachindex(items)
n = _length(itemindices)
n = length(itemindices)
_growbeg!(a, n)
if a === items
copyto!(a, 1, items, n+1, n)
Expand All @@ -923,6 +926,7 @@ prepend!(a::Vector, iter) = _prepend!(a, IteratorSize(iter), iter)
pushfirst!(a::Vector, iter...) = prepend!(a, iter)

function _prepend!(a, ::Union{HasLength,HasShape}, iter)
@assert !has_offset_axes(a)
n = length(iter)
_growbeg!(a, n)
i = 0
Expand Down
16 changes: 8 additions & 8 deletions base/arrayshow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ function alignment(io::IO, X::AbstractVecOrMat,
break
end
end
if 1 < length(a) < _length(axes(X,2))
if 1 < length(a) < length(axes(X,2))
while sum(map(sum,a)) + sep*length(a) >= cols_otherwise
pop!(a)
end
Expand Down Expand Up @@ -168,7 +168,7 @@ function print_matrix(io::IO, X::AbstractVecOrMat,
@assert textwidth(hdots) == textwidth(ddots)
sepsize = length(sep)
rowsA, colsA = axes(X,1), axes(X,2)
m, n = _length(rowsA), _length(colsA)
m, n = length(rowsA), length(colsA)
# To figure out alignments, only need to look at as many rows as could
# fit down screen. If screen has at least as many rows as A, look at A.
# If not, then we only need to look at the first and last chunks of A,
Expand Down Expand Up @@ -266,10 +266,10 @@ function show_nd(io::IO, a::AbstractArray, print_matrix::Function, label_slices:
for i = 1:nd
ii = idxs[i]
ind = tailinds[i]
if _length(ind) > 10
if length(ind) > 10
if ii == ind[firstindex(ind)+3] && all(d->idxs[d]==first(tailinds[d]),1:i-1)
for j=i+1:nd
szj = _length(axes(a, j+2))
szj = length(axes(a, j+2))
indj = tailinds[j]
if szj>10 && first(indj)+2 < idxs[j] <= last(indj)-3
@goto skip
Expand Down Expand Up @@ -313,7 +313,7 @@ print_array(io::IO, X::AbstractArray) = show_nd(io, X, print_matrix, true)
# implements: show(io::IO, ::MIME"text/plain", X::AbstractArray)
function show(io::IO, ::MIME"text/plain", X::AbstractArray)
# 0) compute new IOContext
if !haskey(io, :compact) && _length(axes(X, 2)) > 1
if !haskey(io, :compact) && length(axes(X, 2)) > 1
io = IOContext(io, :compact => true)
end
if get(io, :limit, false) && eltype(X) === Method
Expand Down Expand Up @@ -359,7 +359,7 @@ function _show_nonempty(io::IO, X::AbstractMatrix, prefix::String)
@assert !isempty(X)
limit = get(io, :limit, false)::Bool
indr, indc = axes(X,1), axes(X,2)
nr, nc = _length(indr), _length(indc)
nr, nc = length(indr), length(indc)
rdots, cdots = false, false
rr1, rr2 = UnitRange{Int}(indr), 1:0
cr1, cr2 = UnitRange{Int}(indc), 1:0
Expand Down Expand Up @@ -432,8 +432,8 @@ function show_vector(io::IO, v, opn='[', cls=']')
# directly or indirectly, the context now knows about eltype(v)
io = IOContext(io, :typeinfo => eltype(v), :compact => get(io, :compact, true))
limited = get(io, :limit, false)
if limited && _length(v) > 20
inds = indices1(v)
if limited && length(v) > 20
inds = axes1(v)
show_delim_array(io, v, opn, ",", "", false, inds[1], inds[1]+9)
print(io, " … ")
show_delim_array(io, v, "", ",", cls, false, inds[end-9], inds[end])
Expand Down
2 changes: 1 addition & 1 deletion base/broadcast.jl
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ Base.@propagate_inbounds _newindex(ax::Tuple{}, I::Tuple{}) = ()
@inline function _newindexer(indsA::Tuple)
ind1 = indsA[1]
keep, Idefault = _newindexer(tail(indsA))
(Base._length(ind1)!=1, keep...), (first(ind1), Idefault...)
(Base.length(ind1)!=1, keep...), (first(ind1), Idefault...)
end

@inline function Base.getindex(bc::Broadcasted, I::Union{Integer,CartesianIndex})
Expand Down
2 changes: 2 additions & 0 deletions base/c.jl
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ transcode(T, src::String) = transcode(T, codeunits(src))
transcode(::Type{String}, src) = String(transcode(UInt8, src))

function transcode(::Type{UInt16}, src::AbstractVector{UInt8})
@assert !has_offset_axes(src)
dst = UInt16[]
i, n = 1, length(src)
n > 0 || return dst
Expand Down Expand Up @@ -317,6 +318,7 @@ function transcode(::Type{UInt16}, src::AbstractVector{UInt8})
end

function transcode(::Type{UInt8}, src::AbstractVector{UInt16})
@assert !has_offset_axes(src)
n = length(src)
n == 0 && return UInt8[]

Expand Down
3 changes: 3 additions & 0 deletions base/combinatorics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ isperm(p::Tuple{Int}) = p[1] == 1
isperm(p::Tuple{Int,Int}) = ((p[1] == 1) & (p[2] == 2)) | ((p[1] == 2) & (p[2] == 1))

function permute!!(a, p::AbstractVector{<:Integer})
@assert !has_offset_axes(a, p)
count = 0
start = 0
while count < length(a)
Expand Down Expand Up @@ -114,6 +115,7 @@ julia> A
permute!(a, p::AbstractVector) = permute!!(a, copymutable(p))

function invpermute!!(a, p::AbstractVector{<:Integer})
@assert !has_offset_axes(a, p)
count = 0
start = 0
while count < length(a)
Expand Down Expand Up @@ -194,6 +196,7 @@ julia> B[invperm(v)]
```
"""
function invperm(a::AbstractVector)
@assert !has_offset_axes(a)
b = zero(a) # similar vector of zeros
n = length(a)
@inbounds for (i, j) in enumerate(a)
Expand Down
4 changes: 4 additions & 0 deletions base/deprecated.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1747,6 +1747,7 @@ end
@deprecate mapfoldl(f, op, v0, itr) mapfoldl(f, op, itr; init=v0)
@deprecate mapfoldr(f, op, v0, itr) mapfoldr(f, op, itr; init=v0)


@deprecate startswith(a::Vector{UInt8}, b::Vector{UInt8}) length(a) >= length(b) && view(a, 1:length(b)) == b

# PR #27859
Expand All @@ -1759,6 +1760,9 @@ function print(io::IO, ::Nothing)
show(io, nothing)
end

@deprecate indices1 axes1
@deprecate _length length

# END 0.7 deprecations

# BEGIN 1.0 deprecations
Expand Down
20 changes: 9 additions & 11 deletions base/indices.jl
Original file line number Diff line number Diff line change
Expand Up @@ -179,16 +179,16 @@ function setindex_shape_check(X::AbstractArray, I::Integer...)
end

setindex_shape_check(X::AbstractArray) =
(_length(X)==1 || throw_setindex_mismatch(X,()))
(length(X)==1 || throw_setindex_mismatch(X,()))

setindex_shape_check(X::AbstractArray, i::Integer) =
(_length(X)==i || throw_setindex_mismatch(X, (i,)))
(length(X)==i || throw_setindex_mismatch(X, (i,)))

setindex_shape_check(X::AbstractArray{<:Any,1}, i::Integer) =
(_length(X)==i || throw_setindex_mismatch(X, (i,)))
(length(X)==i || throw_setindex_mismatch(X, (i,)))

setindex_shape_check(X::AbstractArray{<:Any,1}, i::Integer, j::Integer) =
(_length(X)==i*j || throw_setindex_mismatch(X, (i,j)))
(length(X)==i*j || throw_setindex_mismatch(X, (i,j)))

function setindex_shape_check(X::AbstractArray{<:Any,2}, i::Integer, j::Integer)
if length(X) != i*j
Expand Down Expand Up @@ -282,17 +282,15 @@ end
Slice(S::Slice) = S
axes(S::Slice) = (S,)
unsafe_indices(S::Slice) = (S,)
indices1(S::Slice) = S
axes1(S::Slice) = S
axes(S::Slice{<:OneTo}) = (S.indices,)
unsafe_indices(S::Slice{<:OneTo}) = (S.indices,)
indices1(S::Slice{<:OneTo}) = S.indices
axes1(S::Slice{<:OneTo}) = S.indices

first(S::Slice) = first(S.indices)
last(S::Slice) = last(S.indices)
errmsg(A) = error("size not supported for arrays with indices $(axes(A)); see https://docs.julialang.org/en/latest/devdocs/offset-arrays/")
size(S::Slice) = first(S.indices) == 1 ? (length(S.indices),) : errmsg(S)
length(S::Slice) = first(S.indices) == 1 ? length(S.indices) : errmsg(S)
_length(S::Slice) = length(S.indices)
size(S::Slice) = (length(S.indices),)
length(S::Slice) = length(S.indices)
unsafe_length(S::Slice) = unsafe_length(S.indices)
getindex(S::Slice, i::Int) = (@_inline_meta; @boundscheck checkbounds(S, i); i)
getindex(S::Slice, i::AbstractUnitRange{<:Integer}) = (@_inline_meta; @boundscheck checkbounds(S, i); i)
Expand Down Expand Up @@ -361,7 +359,7 @@ LinearIndices(A::Union{AbstractArray,SimpleVector}) = LinearIndices(axes(A))

# AbstractArray implementation
IndexStyle(::Type{<:LinearIndices}) = IndexLinear()
axes(iter::LinearIndices) = map(indices1, iter.indices)
axes(iter::LinearIndices) = map(axes1, iter.indices)
size(iter::LinearIndices) = map(unsafe_length, iter.indices)
function getindex(iter::LinearIndices, i::Int)
@_inline_meta
Expand Down
Loading