Skip to content

Commit a99f379

Browse files
committed
Fully deprecate partial linear indexing. Fixes #14770.
1 parent 2bb2727 commit a99f379

6 files changed

+137
-94
lines changed

base/abstractarray.jl

+34-30
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ unsafe_indices(r::Range) = (OneTo(unsafe_length(r)),) # Ranges use checked_sub f
6969
"""
7070
linearindices(A)
7171
72-
Returns a `UnitRange` specifying the valid range of indices for `A[i]`
72+
Returns a `AbstractUnitRange` specifying the valid range of indices for `A[i]`
7373
where `i` is an `Int`. For arrays with conventional indexing (indices
7474
start at 1), or any multidimensional array, this is `1:length(A)`;
7575
however, for one-dimensional arrays with unconventional indices, this
@@ -89,6 +89,9 @@ julia> extrema(b)
8989
"""
9090
linearindices(A) = (@_inline_meta; OneTo(_length(A)))
9191
linearindices(A::AbstractVector) = (@_inline_meta; indices1(A))
92+
linearindices(inds::Tuple{Vararg{AbstractUnitRange}}) =
93+
(@_inline_meta; OneTo(prod(map(unsafe_length, inds))))
94+
9295
eltype(::Type{<:AbstractArray{E}}) where {E} = E
9396
elsize{T}(::AbstractArray{T}) = sizeof(T)
9497

@@ -363,21 +366,7 @@ function checkbounds_indices(::Type{Bool}, IA::Tuple{Any}, I::Tuple{Any})
363366
end
364367
function checkbounds_indices(::Type{Bool}, IA::Tuple, I::Tuple{Any})
365368
@_inline_meta
366-
checkbounds_linear_indices(Bool, IA, I[1])
367-
end
368-
function checkbounds_linear_indices(::Type{Bool}, IA::Tuple, i)
369-
@_inline_meta
370-
if checkindex(Bool, IA[1], i)
371-
return true
372-
elseif checkindex(Bool, OneTo(trailingsize(IA)), i) # partial linear indexing
373-
partial_linear_indexing_warning_lookup(length(IA))
374-
return true # TODO: Return false after the above function is removed in deprecated.jl
375-
end
376-
return false
377-
end
378-
function checkbounds_linear_indices(::Type{Bool}, IA::Tuple, i::Union{Slice,Colon})
379-
partial_linear_indexing_warning_lookup(length(IA))
380-
true
369+
checkbounds_indices(Bool, (linearindices(IA),), I)
381370
end
382371
checkbounds_indices(::Type{Bool}, ::Tuple, ::Tuple{}) = true
383372

@@ -824,18 +813,32 @@ _getindex(::LinearIndexing, A::AbstractArray, I...) = error("indexing $(typeof(A
824813

825814
## LinearFast Scalar indexing: canonical method is one Int
826815
_getindex(::LinearFast, A::AbstractArray, i::Int) = (@_propagate_inbounds_meta; getindex(A, i))
827-
_getindex(::LinearFast, A::AbstractArray) = (@_propagate_inbounds_meta; getindex(A, _to_linear_index(A)))
816+
_getindex(::LinearFast, A::AbstractArray) = (@_propagate_inbounds_meta; getindex(A, 1))
828817
function _getindex(::LinearFast, A::AbstractArray, I::Int...)
829818
@_inline_meta
830819
@boundscheck checkbounds(A, I...) # generally _to_linear_index requires bounds checking
831820
@inbounds r = getindex(A, _to_linear_index(A, I...))
832821
r
833822
end
823+
_to_linear_index(A::AbstractVector) = (@_inline_meta; first(indices1(A)))
824+
_to_linear_index(A::AbstractArray) = 1
834825
_to_linear_index(A::AbstractArray, i::Int) = i
835-
_to_linear_index(A::AbstractVector, i::Int, I::Int...) = i # TODO: DEPRECATE FOR #14770
836826
_to_linear_index{T,N}(A::AbstractArray{T,N}, I::Vararg{Int,N}) = (@_inline_meta; sub2ind(A, I...))
837-
_to_linear_index(A::AbstractArray) = 1 # TODO: DEPRECATE FOR #14770
838-
_to_linear_index(A::AbstractArray, I::Int...) = (@_inline_meta; sub2ind(A, I...)) # TODO: DEPRECATE FOR #14770
827+
function _to_linear_index{T,N}(A::AbstractArray{T,N}, I::Int...)
828+
@_inline_meta
829+
J, Jrem = IteratorsMD.split(I, Val{N})
830+
_to_linear_index(A, J, Jrem)
831+
end
832+
# It's safe to drop Jrem, because we've already bounds-checked
833+
_to_linear_index(A::AbstractVector, J::Tuple, Jrem::Tuple) = (@_inline_meta; J[1])
834+
_to_linear_index(A::AbstractArray, J::Tuple, Jrem::Tuple) = (@_inline_meta; sub2ind(A, J...))
835+
# TODO: uncomment when deprecation period for partial linear indexing has ended
836+
# function _to_linear_index(A::AbstractVector, J::Tuple, Jrem::Tuple{})
837+
# throw(ArgumentError("partial linear indexing is not allowed. Use `reshape(A, Val{$(length(J))})` to make the dimensionality of the array match the number of indices"))
838+
# end
839+
# function _to_linear_index(A::AbstractArray, J::Tuple, Jrem::Tuple{})
840+
# throw(ArgumentError("partial linear indexing is not allowed. Use `reshape(A, Val{$(length(J))})` to make the dimensionality of the array match the number of indices"))
841+
# end
839842

840843
## LinearSlow Scalar indexing: Canonical method is full dimensionality of Ints
841844
_getindex(::LinearSlow, A::AbstractArray) = (@_propagate_inbounds_meta; getindex(A, _to_subscript_indices(A)...))
@@ -847,16 +850,18 @@ function _getindex(::LinearSlow, A::AbstractArray, I::Int...)
847850
end
848851
_getindex{T,N}(::LinearSlow, A::AbstractArray{T,N}, I::Vararg{Int, N}) = (@_propagate_inbounds_meta; getindex(A, I...))
849852
_to_subscript_indices(A::AbstractArray, i::Int) = (@_inline_meta; _unsafe_ind2sub(A, i))
850-
_to_subscript_indices{T,N}(A::AbstractArray{T,N}) = (@_inline_meta; fill_to_length((), 1, Val{N})) # TODO: DEPRECATE FOR #14770
851-
_to_subscript_indices{T}(A::AbstractArray{T,0}) = () # TODO: REMOVE FOR #14770
852-
_to_subscript_indices{T}(A::AbstractArray{T,0}, i::Int) = () # TODO: REMOVE FOR #14770
853-
_to_subscript_indices{T}(A::AbstractArray{T,0}, I::Int...) = () # TODO: DEPRECATE FOR #14770
854-
function _to_subscript_indices{T,N}(A::AbstractArray{T,N}, I::Int...) # TODO: DEPRECATE FOR #14770
853+
_to_subscript_indices{T,N}(A::AbstractArray{T,N}) = (@_inline_meta; map(first, indices(A)))
854+
_to_subscript_indices{T}(A::AbstractArray{T,0}, I::Int...) = ()
855+
function _to_subscript_indices{T,N}(A::AbstractArray{T,N}, I::Int...)
855856
@_inline_meta
856-
J, _ = IteratorsMD.split(I, Val{N}) # (maybe) drop any trailing indices
857-
sz = _remaining_size(J, size(A)) # compute trailing size (overlapping the final index)
858-
(front(J)..., _unsafe_ind2sub(sz, last(J))...) # (maybe) extend the last index
859-
end
857+
J, Jrem = IteratorsMD.split(I, Val{N})
858+
_to_subscript_indices(A, J, Jrem)
859+
end
860+
_to_subscript_indices(A, J::Tuple, Jrem::Tuple) = J # already bounds-checked, safe to drop Jrem
861+
# TODO: uncomment when deprecation period for partial linear indexing has ended
862+
# function _to_subscript_indices(A::AbstractArray, J::Tuple, Jrem::Tuple{})
863+
# throw(ArgumentError("partial linear indexing is not allowed. Use `reshape(A, Val{$(length(J))})` to make the dimensionality of the array match the number of indices"))
864+
# end
860865
_to_subscript_indices{T,N}(A::AbstractArray{T,N}, I::Vararg{Int,N}) = I
861866
_remaining_size(::Tuple{Any}, t::Tuple) = t
862867
_remaining_size(h::Tuple, t::Tuple) = (@_inline_meta; _remaining_size(tail(h), tail(t)))
@@ -926,7 +931,6 @@ end
926931

927932
get(A::AbstractArray, I::Range, default) = get!(similar(A, typeof(default), index_shape(I)), A, I, default)
928933

929-
# TODO: DEPRECATE FOR #14770 (just the partial linear indexing part)
930934
function get!{T}(X::AbstractArray{T}, A::AbstractArray, I::RangeVecIntList, default::T)
931935
fill!(X, default)
932936
dst, src = indcopy(size(A), I)

base/array.jl

+5-2
Original file line numberDiff line numberDiff line change
@@ -471,8 +471,9 @@ done(a::Array,i) = (@_inline_meta; i == length(a)+1)
471471
## Indexing: getindex ##
472472

473473
# This is more complicated than it needs to be in order to get Win64 through bootstrap
474+
getindex{T}(A::Array{T,0}) = arrayref(A, 1)
474475
getindex(A::Array, i1::Int) = arrayref(A, i1)
475-
getindex(A::Array, i1::Int, i2::Int, I::Int...) = (@_inline_meta; arrayref(A, i1, i2, I...)) # TODO: REMOVE FOR #14770
476+
getindex{T,N}(A::Array{T,N}, I::Vararg{Int,N}) = (@_inline_meta; arrayref(A, I...))
476477

477478
# Faster contiguous indexing using copy! for UnitRange and Colon
478479
function getindex(A::Array, I::UnitRange{Int})
@@ -500,8 +501,9 @@ function getindex{S}(A::Array{S}, I::Range{Int})
500501
end
501502

502503
## Indexing: setindex! ##
504+
setindex!{T}(A::Array{T,0}, x) = arrayset(A, convert(T,x)::T, 1)
503505
setindex!{T}(A::Array{T}, x, i1::Int) = arrayset(A, convert(T,x)::T, i1)
504-
setindex!{T}(A::Array{T}, x, i1::Int, i2::Int, I::Int...) = (@_inline_meta; arrayset(A, convert(T,x)::T, i1, i2, I...)) # TODO: REMOVE FOR #14770
506+
setindex!{T,N}(A::Array{T,N}, x, I::Vararg{Int,N}) = (@_inline_meta; arrayset(A, convert(T,x)::T, I...))
505507

506508
# These are redundant with the abstract fallbacks but needed for bootstrap
507509
function setindex!(A::Array, x, I::AbstractVector{Int})
@@ -548,6 +550,7 @@ function setindex!{T}(A::Array{T}, X::Array{T}, c::Colon)
548550
end
549551

550552
setindex!(A::Array, x::Number, ::Colon) = fill!(A, x)
553+
setindex!{T}(A::Array{T,0}, x::Number) = fill!(A, x) # ambiguity resolution
551554
setindex!{T, N}(A::Array{T, N}, x::Number, ::Vararg{Colon, N}) = fill!(A, x)
552555

553556
# efficiently grow an array

base/deprecated.jl

+14-36
Original file line numberDiff line numberDiff line change
@@ -1032,43 +1032,19 @@ isempty(::Task) = error("isempty not defined for Tasks")
10321032
end
10331033

10341034
# Deprecate partial linear indexing
1035-
function partial_linear_indexing_warning_lookup(nidxs_remaining)
1036-
# We need to figure out how many indices were passed for a sensible deprecation warning
1037-
opts = JLOptions()
1038-
if opts.depwarn > 0
1039-
# Find the caller -- this is very expensive so we don't want to do it twice
1040-
bt = backtrace()
1041-
found = false
1042-
call = StackTraces.UNKNOWN
1043-
caller = StackTraces.UNKNOWN
1044-
for frame in bt
1045-
lkups = StackTraces.lookup(frame)
1046-
for caller in lkups
1047-
if caller === StackTraces.UNKNOWN
1048-
continue
1049-
end
1050-
found && @goto found
1051-
if caller.func in (:getindex, :setindex!, :view)
1052-
found = true
1053-
call = caller
1054-
end
1055-
end
1056-
end
1057-
@label found
1058-
fn = "`reshape`"
1059-
if call != StackTraces.UNKNOWN && !isnull(call.linfo)
1060-
# Try to grab the number of dimensions in the parent array
1061-
mi = get(call.linfo)
1062-
args = mi.specTypes.parameters
1063-
if length(args) >= 2 && args[2] <: AbstractArray
1064-
fn = "`reshape(A, Val{$(ndims(args[2]) - nidxs_remaining + 1)})`"
1065-
end
1066-
end
1067-
_depwarn("Partial linear indexing is deprecated. Use $fn to make the dimensionality of the array match the number of indices.", opts, bt, caller)
1068-
end
1035+
check_oneto(A::AbstractArray) = check_oneto(indices(A))
1036+
check_oneto(inds::Tuple{Vararg{OneTo}}) = nothing
1037+
check_oneto(inds::Tuple{AbstractUnitRange,Vararg{AbstractUnitRange}}) = error("partial linear indexing is not supported for arrays with non-1 indexing")
1038+
function _to_linear_index(A::AbstractArray, J::Tuple, Jrem::Tuple{})
1039+
check_oneto(A)
1040+
depwarn("partial linear indexing is deprecated. Use `reshape(A, Val{$(length(J))})` to make the dimensionality of the array match the number of indices", :_to_linear_index)
1041+
sub2ind(A, J...)
10691042
end
1070-
function partial_linear_indexing_warning(n)
1071-
depwarn("Partial linear indexing is deprecated. Use `reshape(A, Val{$n})` to make the dimensionality of the array match the number of indices.", (:getindex, :setindex!, :view))
1043+
function _to_subscript_indices(A::AbstractArray, J::Tuple, Jrem::Tuple{})
1044+
check_oneto(A)
1045+
depwarn("partial linear indexing is deprecated. Use `reshape(A, Val{$(length(J))})` to make the dimensionality of the array match the number of indices", :_to_subscript_indices)
1046+
sz = _remaining_size(J, indices(A)) # compute trailing size (overlapping the final index)
1047+
(front(J)..., _unsafe_ind2sub(sz, last(J))...)
10721048
end
10731049

10741050
# Deprecate Array(T, dims...) in favor of proper type constructors
@@ -1262,6 +1238,8 @@ for f in (:airyai, :airyaiprime, :airybi, :airybiprime, :airyaix, :airyaiprimex,
12621238
end
12631239

12641240
# END 0.6 deprecations
1241+
# IMPORTANT: when the 0.6 deprecations are removed, uncomment error messages for
1242+
# _to_linear_index and _to_subscript_indices in abstractarray.jl
12651243

12661244
# BEGIN 1.0 deprecations
12671245
# END 1.0 deprecations

base/multidimensional.jl

+2
Original file line numberDiff line numberDiff line change
@@ -197,8 +197,10 @@ end
197197
# Indexing into Array with mixtures of Integers and CartesianIndices is
198198
# extremely performance-sensitive. While the abstract fallbacks support this,
199199
# codegen has extra support for SIMDification that sub2ind doesn't (yet) support
200+
@propagate_inbounds getindex(A::Array, I::Integer...) = _getindex(LinearFast(), A, to_indices(A, I)...)
200201
@propagate_inbounds getindex(A::Array, i1::Union{Integer, CartesianIndex}, I::Union{Integer, CartesianIndex}...) =
201202
A[to_indices(A, (i1, I...))...]
203+
@propagate_inbounds setindex!(A::Array, v, I::Integer...) = _setindex!(LinearFast(), A, v, to_indices(A, I)...)
202204
@propagate_inbounds setindex!(A::Array, v, i1::Union{Integer, CartesianIndex}, I::Union{Integer, CartesianIndex}...) =
203205
(A[to_indices(A, (i1, I...))...] = v; A)
204206

test/abstractarray.jl

+64-19
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ Base.setindex!{T}(A::TSlow{T,5}, v, i1::Int, i2::Int, i3::Int, i4::Int, i5::Int)
241241
(A.data[(i1,i2,i3,i4,i5)] = v)
242242

243243
const can_inline = Base.JLOptions().can_inline != 0
244+
const depwarns_not_error = Base.JLOptions().depwarn < 2
244245
function test_scalar_indexing{T}(::Type{T}, shape, ::Type{TestAbstractArray})
245246
N = prod(shape)
246247
A = reshape(collect(1:N), shape)
@@ -263,27 +264,44 @@ function test_scalar_indexing{T}(::Type{T}, shape, ::Type{TestAbstractArray})
263264
end
264265
end
265266
# Test linear indexing and partial linear indexing
267+
@test A == B
266268
i=0
267269
for i1 = 1:length(B)
268270
i += 1
269271
@test A[i1] == B[i1] == i
270272
end
271-
i=0
272-
for i2 = 1:size(B, 2)
273-
for i1 = 1:size(B, 1)
274-
i += 1
275-
@test A[i1,i2] == B[i1,i2] == i
273+
# TODO (#14770): run only for ndims(A) <= 2, elim. redirect_stderr stuff
274+
if ndims(A) <= 2 || depwarns_not_error
275+
filename = tempname()
276+
open(filename, "w") do f
277+
redirect_stderr(f) do
278+
i=0
279+
for i2 = 1:size(B, 2)
280+
for i1 = 1:size(B, 1)
281+
i += 1
282+
@test A[i1,i2] == B[i1,i2] == i
283+
end
284+
end
285+
end
276286
end
287+
rm(filename)
277288
end
278-
@test A == B
279-
i=0
280-
for i3 = 1:size(B, 3)
281-
for i2 = 1:size(B, 2)
282-
for i1 = 1:size(B, 1)
283-
i += 1
284-
@test A[i1,i2,i3] == B[i1,i2,i3] == i
289+
if ndims(A) <= 3 || depwarns_not_error
290+
filename = tempname()
291+
open(filename, "w") do f
292+
redirect_stderr(f) do
293+
i=0
294+
for i3 = 1:size(B, 3)
295+
for i2 = 1:size(B, 2)
296+
for i1 = 1:size(B, 1)
297+
i += 1
298+
@test A[i1,i2,i3] == B[i1,i2,i3] == i
299+
end
300+
end
301+
end
285302
end
286303
end
304+
rm(filename)
287305
end
288306
# Test zero-dimensional accesses
289307
@test A[] == B[] == A[1] == B[1] == 1
@@ -369,22 +387,49 @@ function test_vector_indexing{T}(::Type{T}, shape, ::Type{TestAbstractArray})
369387
# Test adding dimensions with matrices
370388
idx1 = rand(1:size(A, 1), 3)
371389
idx2 = rand(1:size(A, 2), 4, 5)
372-
@test B[idx1, idx2] == A[idx1, idx2] == reshape(A[idx1, vec(idx2)], 3, 4, 5) == reshape(B[idx1, vec(idx2)], 3, 4, 5)
373-
@test B[1, idx2] == A[1, idx2] == reshape(A[1, vec(idx2)], 4, 5) == reshape(B[1, vec(idx2)], 4, 5)
390+
# #14770
391+
if ndims(A) <= 2 || depwarns_not_error
392+
filename = tempname()
393+
open(filename, "w") do f
394+
redirect_stderr(f) do
395+
@test B[idx1, idx2] == A[idx1, idx2] == reshape(A[idx1, vec(idx2)], 3, 4, 5) == reshape(B[idx1, vec(idx2)], 3, 4, 5)
396+
@test B[1, idx2] == A[1, idx2] == reshape(A[1, vec(idx2)], 4, 5) == reshape(B[1, vec(idx2)], 4, 5)
397+
end
398+
end
399+
rm(filename)
400+
end
374401

375402
# test removing dimensions with 0-d arrays
376403
idx0 = reshape([rand(1:size(A, 1))])
377-
@test B[idx0, idx2] == A[idx0, idx2] == reshape(A[idx0[], vec(idx2)], 4, 5) == reshape(B[idx0[], vec(idx2)], 4, 5)
378-
# @test B[reshape([end]), reshape([end])] == A[reshape([end]), reshape([end])] == reshape([A[end,end]]) == reshape([B[end,end]]) # TODO: Re-enable after partial linear indexing deprecation
404+
# TODO (#14770): run only for ndims(A) <= 2, elim. redirect_stderr stuff
405+
if ndims(A) <= 2 || depwarns_not_error
406+
filename = tempname()
407+
open(filename, "w") do f
408+
redirect_stderr(f) do
409+
@test B[idx0, idx2] == A[idx0, idx2] == reshape(A[idx0[], vec(idx2)], 4, 5) == reshape(B[idx0[], vec(idx2)], 4, 5)
410+
# @test B[reshape([end]), reshape([end])] == A[reshape([end]), reshape([end])] == reshape([A[end,end]]) == reshape([B[end,end]]) # TODO: Re-enable after partial linear indexing deprecation
411+
end
412+
end
413+
rm(filename)
414+
end
379415

380416
# test logical indexing
381417
mask = bitrand(shape)
382418
@test B[mask] == A[mask] == B[find(mask)] == A[find(mask)] == find(mask)
383419
@test B[vec(mask)] == A[vec(mask)] == find(mask)
384420
mask1 = bitrand(size(A, 1))
385-
mask2 = bitrand(size(A, 2))
386-
@test B[mask1, mask2] == A[mask1, mask2] == B[find(mask1), find(mask2)]
387-
@test B[mask1, 1] == A[mask1, 1] == find(mask1)
421+
mask2 = bitrand(prod(Base.tail(size(A))))
422+
# TODO (#14770): run only for ndims(A) <= 2, elim. redirect_stderr stuff
423+
if ndims(A) <= 2 || depwarns_not_error
424+
filename = tempname()
425+
open(filename, "w") do f
426+
redirect_stderr(f) do
427+
@test B[mask1, mask2] == A[mask1, mask2] == B[find(mask1), find(mask2)]
428+
@test B[mask1, 1] == A[mask1, 1] == find(mask1)
429+
end
430+
end
431+
rm(filename)
432+
end
388433
end
389434

390435
function test_primitives{T}(::Type{T}, shape, ::Type{TestAbstractArray})

test/subarray.jl

+18-7
Original file line numberDiff line numberDiff line change
@@ -126,17 +126,26 @@ function test_linear(A::ANY, B::ANY)
126126
end
127127

128128
# "mixed" means 2 indexes even for N-dimensional arrays
129+
# TODO (#14770): eliminate this test when deprecations removed
130+
const depwarns_not_error = Base.JLOptions().depwarn < 2
129131
test_mixed{T}(::AbstractArray{T,1}, ::Array) = nothing
130132
test_mixed{T}(::AbstractArray{T,2}, ::Array) = nothing
131133
test_mixed(A, B::Array) = _test_mixed(A, reshape(B, size(A)))
132134
function _test_mixed(A::ANY, B::ANY)
133135
m = size(A, 1)
134136
n = size(A, 2)
135137
isgood = true
136-
for j = 1:n, i = 1:m
137-
if A[i,j] != B[i,j]
138-
isgood = false
139-
break
138+
if depwarns_not_error
139+
filename = tempname()
140+
open(filename, "w") do f
141+
redirect_stderr(f) do
142+
for j = 1:n, i = 1:m
143+
if A[i,j] != B[i,j]
144+
isgood = false
145+
break
146+
end
147+
end
148+
end
140149
end
141150
end
142151
if !isgood
@@ -229,9 +238,11 @@ function runviews(SB::AbstractArray, indexN, indexNN, indexNNN)
229238
ndims(SB) > 3 && i3 isa Colon && continue # TODO: Re-enable once Colon no longer spans partial trailing dimensions
230239
runsubarraytests(SB, i1, i2, i3)
231240
end
232-
for i2 in indexN, i1 in indexN
233-
i2 isa Colon && continue # TODO: Re-enable once Colon no longer spans partial trailing dimensions
234-
runsubarraytests(SB, i1, i2)
241+
if depwarns_not_error
242+
for i2 in indexN, i1 in indexN
243+
i2 isa Colon && continue # TODO: Re-enable once Colon no longer spans partial trailing dimensions
244+
runsubarraytests(SB, i1, i2)
245+
end
235246
end
236247
for i1 in indexNNN
237248
runsubarraytests(SB, i1)

0 commit comments

Comments
 (0)