Skip to content

Commit

Permalink
fixup! deprecate unsafe_length for length
Browse files Browse the repository at this point in the history
  • Loading branch information
vtjnash committed Jun 4, 2021
1 parent efede49 commit d728d7f
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 39 deletions.
12 changes: 10 additions & 2 deletions base/checked.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ module Checked

export checked_neg, checked_abs, checked_add, checked_sub, checked_mul,
checked_div, checked_rem, checked_fld, checked_mod, checked_cld,
add_with_overflow, sub_with_overflow, mul_with_overflow
checked_length, add_with_overflow, sub_with_overflow, mul_with_overflow

import Core.Intrinsics:
checked_sadd_int, checked_ssub_int, checked_smul_int, checked_sdiv_int,
checked_srem_int,
checked_uadd_int, checked_usub_int, checked_umul_int, checked_udiv_int,
checked_urem_int
import ..no_op_err, ..@_inline_meta, ..@_noinline_meta
import ..no_op_err, ..@_inline_meta, ..@_noinline_meta, ..checked_length

# define promotion behavior for checked operations
checked_add(x::Integer, y::Integer) = checked_add(promote(x,y)...)
Expand Down Expand Up @@ -349,4 +349,12 @@ The overflow protection may impose a perceptible performance penalty.
"""
checked_cld(x::T, y::T) where {T<:Integer} = cld(x, y) # Base.cld already checks

"""
Base.checked_length(r)
Calculates `length(r)`, but may check for overflow errors where applicable when
the result doesn't fit into `Union{eltype(r),Int}`.
"""
checked_length(r) = length(r) # for most things, length doesn't error

end
4 changes: 2 additions & 2 deletions base/deprecated.jl
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,8 @@ end
@deprecate cat_shape(dims, shape::Tuple{}, shapes::Tuple...) cat_shape(dims, shapes) false
cat_shape(dims, shape::Tuple{}) = () # make sure `cat_shape(dims, ())` do not recursively calls itself

@deprecate unsafe_indices(A) axes(A)
@deprecate unsafe_length(r) length(r)
@deprecate unsafe_indices(A) axes(A) false
@deprecate unsafe_length(r) length(r) false

# END 1.6 deprecations

Expand Down
106 changes: 80 additions & 26 deletions base/range.jl
Original file line number Diff line number Diff line change
Expand Up @@ -631,46 +631,100 @@ firstindex(::UnitRange) = 1
firstindex(::StepRange) = 1
firstindex(::LinRange) = 1

length(r::AbstractUnitRange) = Integer(last(r) - first(r) + step(r))
# n.b. checked_length for these is defined iff checked_add and checked_sub are
# defined between the relevant types
function checked_length(r::OrdinalRange{T}) where T
s = step(r)
# s != 0, by construction, but avoids the division error later
start = first(r)
if s == zero(s) || isempty(r)
return Integer(start - start + zero(s))
end
return Integer(div(checked_add(checked_sub(last(r), start), s, s)))
end

function checked_length(r::AbstractUnitRange{T}) where T
# compiler optimization: remove dead cases from above
if isempty(r)
return Integer(first(r) - first(r))
end
a = Integer(checked_add(checked_sub(last(r), first(r))))
return a + one(a)
end

function length(r::OrdinalRange{T}) where T
s = step(r)
# s != 0, by construction, but avoids the division error later
start = first(r)
if s == zero(s) || isempty(r)
return Integer(start - start + zero(s))
end
return Integer(div(last(r) - start + s, s))
end

function length(r::AbstractUnitRange{T}) where T
@_inline_meta
a = Integer(last(r) - first(r)) # even when isempty, by construction (with overflow)
return a + one(a)
end

length(r::OneTo) = Integer(r.stop - zero(r.stop))
length(r::StepRangeLen) = r.len
length(r::LinRange) = r.len
length(r::StepRange) = Integer(div(r.stop - r.start + r.step, r.step))

# compile optimizations for which promote_type(T, Int) = T
let bigint = Union{Int,UInt,Int64,UInt64,Int128,UInt128}
let bigints = Union{Int, UInt, Int64, UInt64, Int128, UInt128}
global length
function length(r::StepRange{T}) where T<:bigint
step = r.step
diff = r.stop - r.start
step == 0 && return zero(T) # unreachable, by construction, but avoids the error case here later
# compile optimization for which promote_type(T, Int) == T
length(r::OneTo{T}) where {T<:bigints} = r.stop
# slightly more accurate length and checked_length in extreme cases
# (near typemax) for types with known `unsigned` functions
function length(r::OrdinalRange{T}) where T<:bigints
s = step(r)
s == zero(s) && return zero(T) # unreachable, by construction, but avoids the error case here later
isempty(r) && return zero(T)
if -1 <= step <= 1 || step == -step || step isa Unsigned # n.b. !(step isa T)
# if |step| > 1, diff might have overflowed, but unsigned(diff)÷step should
# therefore still be valid (if the result is representable at all)
return div(diff, step) % T + one(T)
elseif step < 0
return div(unsigned(-diff), -step) % T + one(T)
diff = last(r) - first(r)
# if |s| > 1, diff might have overflowed, but unsigned(diff)÷s should
# therefore still be valid (if the result is representable at all)
# n.b. !(s isa T)
if s isa Unsigned || -1 <= s <= 1 || s == -s
a = div(diff, s)
elseif s < 0
a = div(unsigned(-diff), -s) % typeof(diff)
else
return div(unsigned(diff), step) % T + one(T)
a = div(unsigned(diff), s) % typeof(diff)
end
return Integer(a) + one(a)
end

function length(r::AbstractUnitRange{T}) where T<:bigint
@_inline_meta
return last(r) - first(r) + one(T) # even when isempty, by construction (with overflow)
function checked_length(r::OrdinalRange{T}) where T<:bigints
s = step(r)
s == zero(s) && return zero(T) # unreachable, by construction, but avoids the error case here later
isempty(r) && return zero(T)
stop, start = last(r), first(r)
# n.b. !(s isa T)
if s > 1
diff = stop - start
a = convert(T, div(unsigned(diff), s))
elseif s < -1
diff = start - stop
a = convert(T, div(unsigned(diff), -s))
elseif s > 0
a = div(checked_sub(stop, start), s)
else
a = div(checked_sub(start, stop), -s)
end
return checked_add(a, one(a))
end
length(r::OneTo{T}) where {T<:bigint} = r.stop
end

# some special cases to favor default Int type
let smallint = (Int === Int64 ?
Union{Int8,UInt8,Int16,UInt16,Int32,UInt32} :
Union{Int8,UInt8,Int16,UInt16})
let smallints = (Int === Int64 ?
Union{Int8, UInt8, Int16, UInt16, Int32, UInt32} :
Union{Int8, UInt8, Int16, UInt16})
global length
length(r::StepRange{<:smallint}) = div(Int(r.stop) - Int(r.start) + r.step, r.step) # n.b. !(step isa T)
length(r::AbstractUnitRange{<:smallint}) = Int(last(r)) - Int(first(r)) + 1
length(r::OneTo{<:smallint}) = Int(r.stop)
# n.b. !(step isa T)
length(r::OrdinalRange{<:smallints}) = div(Int(last(r)) - Int(first(r)) + step(r), step(r))
length(r::AbstractUnitRange{<:smallints}) = Int(last(r)) - Int(first(r)) + 1
length(r::OneTo{<:smallints}) = Int(r.stop)
end

first(r::OrdinalRange{T}) where {T} = convert(T, r.start)
Expand Down
15 changes: 6 additions & 9 deletions test/testhelpers/Furlongs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Furlong{p}(x::Furlong{q}) where {p,q} = (@assert(p==q); Furlong{p,typeof(x.val)}
Furlong{p,T}(x::Furlong{q}) where {T,p,q} = (@assert(p==q); Furlong{p,T}(T(x.val)))

Base.promote_type(::Type{Furlong{p,T}}, ::Type{Furlong{p,S}}) where {p,T,S} =
(Base.@_pure_meta; Furlong{p,promote_type(T,S)})
Furlong{p,promote_type(T,S)}

Base.one(x::Furlong{p,T}) where {p,T} = one(T)
Base.one(::Type{Furlong{p,T}}) where {p,T} = one(T)
Expand All @@ -38,14 +38,12 @@ Base.floatmax(::Type{Furlong{p,T}}) where {p,T<:AbstractFloat} = Furlong{p}(floa
Base.floatmax(::Furlong{p,T}) where {p,T<:AbstractFloat} = floatmax(Furlong{p,T})
Base.conj(x::Furlong{p,T}) where {p,T} = Furlong{p,T}(conj(x.val))

# convert Furlong exponent p to a canonical form. This
# is not type stable, but it doesn't matter since it is used
# at compile time (in generated functions), not runtime
# convert Furlong exponent p to a canonical form
canonical_p(p) = isinteger(p) ? Int(p) : Rational{Int}(p)

Base.abs(x::Furlong{p}) where {p} = Furlong{p}(abs(x.val))
@generated Base.abs2(x::Furlong{p}) where {p} = :(Furlong{$(canonical_p(2p))}(abs2(x.val)))
@generated Base.inv(x::Furlong{p}) where {p} = :(Furlong{$(canonical_p(-p))}(inv(x.val)))
Base.abs2(x::Furlong{p}) where {p} = Furlong{canonical_p(2p)}(abs2(x.val))
Base.inv(x::Furlong{p}) where {p} = Furlong{canonical_p(-p)}(inv(x.val))

for f in (:isfinite, :isnan, :isreal, :isinf)
@eval Base.$f(x::Furlong) = $f(x.val)
Expand All @@ -64,11 +62,10 @@ end
for op in (:(==), :(!=), :<, :<=, :isless, :isequal)
@eval $op(x::Furlong{p}, y::Furlong{p}) where {p} = $op(x.val, y.val)
end
# generated functions to allow type inference of the value of the exponent:
for (f,op) in ((:_plus,:+),(:_minus,:-),(:_times,:*),(:_div,://))
@eval @generated function $f(v::T, ::Furlong{p}, ::Union{Furlong{q},Val{q}}) where {T,p,q}
@eval function $f(v::T, ::Furlong{p}, ::Union{Furlong{q},Val{q}}) where {T,p,q}
s = $op(p, q)
:(Furlong{$(canonical_p(s)),$T}(v))
Furlong{canonical_p(s),T}(v)
end
end
for (op,eop) in ((:*, :_plus), (:/, :_minus), (://, :_minus), (:div, :_minus))
Expand Down

0 comments on commit d728d7f

Please sign in to comment.