From d728d7f932fc08ea2a54ad7a04c3f9666c6496ba Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Thu, 3 Jun 2021 11:57:27 -0400 Subject: [PATCH] fixup! deprecate unsafe_length for length --- base/checked.jl | 12 +++- base/deprecated.jl | 4 +- base/range.jl | 106 ++++++++++++++++++++++++++--------- test/testhelpers/Furlongs.jl | 15 ++--- 4 files changed, 98 insertions(+), 39 deletions(-) diff --git a/base/checked.jl b/base/checked.jl index 840015861923f..65f3d4234045e 100644 --- a/base/checked.jl +++ b/base/checked.jl @@ -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)...) @@ -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 diff --git a/base/deprecated.jl b/base/deprecated.jl index 901512092e974..3ed9ffa9c34e8 100644 --- a/base/deprecated.jl +++ b/base/deprecated.jl @@ -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 diff --git a/base/range.jl b/base/range.jl index 73b6602026234..4c4237cad0637 100644 --- a/base/range.jl +++ b/base/range.jl @@ -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) diff --git a/test/testhelpers/Furlongs.jl b/test/testhelpers/Furlongs.jl index 73d23a39d2d7b..f3583a532215a 100644 --- a/test/testhelpers/Furlongs.jl +++ b/test/testhelpers/Furlongs.jl @@ -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) @@ -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) @@ -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))