Skip to content

Commit bf22b5a

Browse files
authored
Make tuple t[2:end] inferrable through constant prop (#31138)
Spurred by #31132, I was curious if our constant propagation was robust enough to handle a peephole optimization here -- and it is! This removes the need to export front/tail by simply allowing folks to get inferrable results when they spell these operations as `t[2:end]` and `t[1:end-1]`. I additionally allowed for `t[3:end]`, `t[1:end-2]`, and the trivial `t[1:end]` as I have used `tail(tail(t))` in the past.
1 parent af324c7 commit bf22b5a

File tree

3 files changed

+70
-11
lines changed

3 files changed

+70
-11
lines changed

base/range.jl

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -289,16 +289,19 @@ unitrange_last(start::T, stop::T) where {T} =
289289
convert(T,start-oneunit(stop-start)))
290290

291291
if isdefined(Main, :Base)
292-
function getindex(t::Tuple, r::AbstractUnitRange{<:Real})
293-
n = length(r)
294-
n == 0 && return ()
295-
a = Vector{eltype(t)}(undef, n)
296-
o = first(r) - 1
297-
for i = 1:n
298-
el = t[o + i]
299-
@inbounds a[i] = el
292+
# Constant-fold-able indexing into tuples to functionally expose Base.tail and Base.front
293+
function getindex(@nospecialize(t::Tuple), r::UnitRange)
294+
@_inline_meta
295+
r.start > r.stop && return ()
296+
if r.start == 1
297+
r.stop == length(t) && return t
298+
r.stop == length(t)-1 && return front(t)
299+
r.stop == length(t)-2 && return front(front(t))
300+
elseif r.stop == length(t)
301+
r.start == 2 && return tail(t)
302+
r.start == 3 && return tail(tail(t))
300303
end
301-
(a...,)
304+
return (eltype(t)[t[ri] for ri in r]...,)
302305
end
303306
end
304307

base/tuple.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ size(@nospecialize(t::Tuple), d::Integer) = (d == 1) ? length(t) : throw(Argumen
2323
axes(@nospecialize t::Tuple) = (OneTo(length(t)),)
2424
@eval getindex(@nospecialize(t::Tuple), i::Int) = getfield(t, i, $(Expr(:boundscheck)))
2525
@eval getindex(@nospecialize(t::Tuple), i::Real) = getfield(t, convert(Int, i), $(Expr(:boundscheck)))
26-
getindex(t::Tuple, r::AbstractArray{<:Any,1}) = ([t[ri] for ri in r]...,)
26+
getindex(t::Tuple, r::AbstractArray{<:Any,1}) = (eltype(t)[t[ri] for ri in r]...,)
2727
getindex(t::Tuple, b::AbstractArray{Bool,1}) = length(b) == length(t) ? getindex(t, findall(b)) : throw(BoundsError(t, b))
2828
getindex(t::Tuple, c::Colon) = t
2929

test/tuple.jl

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -512,4 +512,60 @@ end
512512

513513
@test Base.setindex((1, 2, 4), 4, true) === (4, 2, 4)
514514
@test_throws BoundsError Base.setindex((1, 2), 2, false)
515-
end
515+
end
516+
517+
@testset "inferrable range indexing with constant values" begin
518+
whole(t) = t[1:end]
519+
tail(t) = t[2:end]
520+
ttail(t) = t[3:end]
521+
front(t) = t[1:end-1]
522+
ffront(t) = t[1:end-2]
523+
524+
@test @inferred( whole(())) == ()
525+
@test @inferred( tail(())) == ()
526+
@test @inferred( ttail(())) == ()
527+
@test @inferred( front(())) == ()
528+
@test @inferred(ffront(())) == ()
529+
530+
@test @inferred( whole((1,))) == (1,)
531+
@test @inferred( tail((1,))) == ()
532+
@test @inferred( ttail((1,))) == ()
533+
@test @inferred( front((1,))) == ()
534+
@test @inferred(ffront((1,))) == ()
535+
536+
@test @inferred( whole((1,2.0))) == (1,2.0)
537+
@test @inferred( tail((1,2.0))) == (2.0,)
538+
@test @inferred( ttail((1,2.0))) == ()
539+
@test @inferred( front((1,2.0))) == (1.0,)
540+
@test @inferred(ffront((1,2.0))) == ()
541+
542+
@test @inferred( whole((1,2.0,3//1))) == (1,2.0,3//1)
543+
@test @inferred( tail((1,2.0,3//1))) == (2.0,3//1)
544+
@test @inferred( ttail((1,2.0,3//1))) == (3//1,)
545+
@test @inferred( front((1,2.0,3//1))) == (1,2.0)
546+
@test @inferred(ffront((1,2.0,3//1))) == (1,)
547+
548+
@test @inferred( whole((1,2.0,3//1,0x04))) == (1,2.0,3//1,0x04)
549+
@test @inferred( tail((1,2.0,3//1,0x04))) == (2.0,3//1,0x04)
550+
@test @inferred( ttail((1,2.0,3//1,0x04))) == (3//1,0x04)
551+
@test @inferred( front((1,2.0,3//1,0x04))) == (1,2.0,3//1)
552+
@test @inferred(ffront((1,2.0,3//1,0x04))) == (1,2.0)
553+
554+
@test (1,)[0:-1] == ()
555+
@test (1,)[1:0] == ()
556+
@test (1,)[2:1] == ()
557+
@test (1,2.0)[0:-1] == ()
558+
@test (1,2.0)[1:0] == ()
559+
@test (1,2.0)[2:1] == ()
560+
@test (1,2.0)[3:2] == ()
561+
562+
@test_throws BoundsError (1,)[2:2]
563+
@test_throws BoundsError (1,)[1:2]
564+
@test_throws BoundsError (1,)[0:1]
565+
@test_throws BoundsError (1,)[0:0]
566+
@test_throws BoundsError (1,2.0)[3:3]
567+
@test_throws BoundsError (1,2.0)[1:3]
568+
@test_throws BoundsError (1,2.0)[0:2]
569+
@test_throws BoundsError (1,2.0)[0:1]
570+
@test_throws BoundsError (1,2.0)[0:0]
571+
end

0 commit comments

Comments
 (0)