From b9ce52f90e324102365944e28c47066c39dd13a9 Mon Sep 17 00:00:00 2001 From: Jeff Bezanson Date: Mon, 14 Aug 2017 17:11:25 -0400 Subject: [PATCH] make `promote` throw an error if no arguments can be changed. fixes #22801. --- NEWS.md | 5 ++++ base/dates/types.jl | 2 +- base/deprecated.jl | 2 ++ base/int.jl | 2 +- base/promotion.jl | 58 +++++++++++++++++++++--------------------- base/range.jl | 4 +-- base/twiceprecision.jl | 10 ++++---- 7 files changed, 45 insertions(+), 38 deletions(-) diff --git a/NEWS.md b/NEWS.md index 242d0df86f115..80bd7801cb4c2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -178,6 +178,11 @@ This section lists changes that do not have deprecation warnings. of the output was shrunk to fit the union of the type of each element in the input. ([#22696]) + * The `promote` function now raises an error if its arguments are of different types + and if attempting to convert them to a common type fails to change any of their types. + This avoids stack overflows in the common case of definitions like + `f(x, y) = f(promote(x, y)...)` ([#22801]). + Library improvements -------------------- diff --git a/base/dates/types.jl b/base/dates/types.jl index 12e3e62bf25f2..5294b50cbfdd7 100644 --- a/base/dates/types.jl +++ b/base/dates/types.jl @@ -340,7 +340,7 @@ Base.typemin(::Union{Time, Type{Time}}) = Time(0) Base.eltype(::Type{T}) where {T<:Period} = T Base.promote_rule(::Type{Date}, x::Type{DateTime}) = DateTime Base.isless(x::T, y::T) where {T<:TimeType} = isless(value(x), value(y)) -Base.isless(x::TimeType, y::TimeType) = isless(Base.promote_noncircular(x, y)...) +Base.isless(x::TimeType, y::TimeType) = isless(promote(x, y)...) (==)(x::T, y::T) where {T<:TimeType} = (==)(value(x), value(y)) function ==(a::Time, b::Time) return hour(a) == hour(b) && minute(a) == minute(b) && diff --git a/base/deprecated.jl b/base/deprecated.jl index d6b2c1e4e498a..0f137159b3c13 100644 --- a/base/deprecated.jl +++ b/base/deprecated.jl @@ -1752,6 +1752,8 @@ end @deprecate selectperm partialsortperm @deprecate selectperm! partialsortperm! +@deprecate promote_noncircular promote false + # END 0.7 deprecations # BEGIN 1.0 deprecations diff --git a/base/int.jl b/base/int.jl index 7650c6e5a1bb6..df3f4823f9512 100644 --- a/base/int.jl +++ b/base/int.jl @@ -84,7 +84,7 @@ signbit(x::Unsigned) = false flipsign(x::T, y::T) where {T<:BitSigned} = flipsign_int(x, y) flipsign(x::BitSigned, y::BitSigned) = flipsign_int(promote(x, y)...) % typeof(x) -flipsign(x::Signed, y::Signed) = convert(typeof(x), flipsign(promote_noncircular(x, y)...)) +flipsign(x::Signed, y::Signed) = convert(typeof(x), flipsign(promote(x, y)...)) flipsign(x::Signed, y::Float16) = flipsign(x, bitcast(Int16, y)) flipsign(x::Signed, y::Float32) = flipsign(x, bitcast(Int32, y)) flipsign(x::Signed, y::Float64) = flipsign(x, bitcast(Int64, y)) diff --git a/base/promotion.jl b/base/promotion.jl index d51f4811e9d61..57c18a2e00293 100644 --- a/base/promotion.jl +++ b/base/promotion.jl @@ -187,7 +187,8 @@ promote_result(::Type{T},::Type{S},::Type{Bottom},::Type{Bottom}) where {T,S} = """ promote(xs...) -Convert all arguments to their common promotion type (if any), and return them all (as a tuple). +Convert all arguments to a common type, and return them all (as a tuple). +If no arguments can be converted, an error is raised. # Examples ```jldoctest @@ -197,21 +198,19 @@ julia> promote(Int8(1), Float16(4.5), Float32(4.1)) """ function promote end -promote() = () -promote(x) = (x,) -function promote(x::T, y::S) where {T,S} +function _promote(x::T, y::S) where {T,S} @_inline_meta (convert(promote_type(T,S),x), convert(promote_type(T,S),y)) end promote_typeof(x) = typeof(x) promote_typeof(x, xs...) = (@_inline_meta; promote_type(typeof(x), promote_typeof(xs...))) -function promote(x, y, z) +function _promote(x, y, z) @_inline_meta (convert(promote_typeof(x,y,z), x), convert(promote_typeof(x,y,z), y), convert(promote_typeof(x,y,z), z)) end -function promote(x, y, zs...) +function _promote(x, y, zs...) @_inline_meta (convert(promote_typeof(x,y,zs...), x), convert(promote_typeof(x,y,zs...), y), @@ -240,39 +239,38 @@ promote_to_supertype(::Type{T}, ::Type{S}, ::Type{S}) where {T<:Number,S<:Number promote_to_supertype(::Type{T}, ::Type{S}, ::Type) where {T<:Number,S<:Number} = error("no promotion exists for ", T, " and ", S) -# promotion with a check for circularity. Can be used to catch what -# would otherwise become StackOverflowErrors. -function promote_noncircular(x, y) +promote() = () +promote(x) = (x,) + +function promote(x, y) @_inline_meta - px, py = promote(x, y) - not_all_sametype((x,px), (y,py)) + px, py = _promote(x, y) + not_sametype((x,y), (px,py)) px, py end -function promote_noncircular(x, y, z) +function promote(x, y, z) @_inline_meta - px, py, pz = promote(x, y, z) - not_all_sametype((x,px), (y,py), (z,pz)) + px, py, pz = _promote(x, y, z) + not_sametype((x,y,z), (px,py,pz)) px, py, pz end -function promote_noncircular(x, y, z, a...) - p = promote(x, y, z, a...) - not_all_sametype(map(identity, (x, y, z, a...), p)) +function promote(x, y, z, a...) + p = _promote(x, y, z, a...) + not_sametype((x, y, z, a...), p) p end -not_all_sametype(x, y) = nothing -not_all_sametype(x, y, z) = nothing -not_all_sametype(x::Tuple{S,S}, y::Tuple{T,T}) where {S,T} = sametype_error(x[1], y[1]) -not_all_sametype(x::Tuple{R,R}, y::Tuple{S,S}, z::Tuple{T,T}) where {R,S,T} = sametype_error(x[1], y[1], z[1]) -function not_all_sametype(::Tuple{R,R}, y::Tuple{S,S}, z::Tuple{T,T}, args...) where {R,S,T} - @_inline_meta - not_all_sametype(y, z, args...) -end -not_all_sametype() = error("promotion failed to change any input types") -function sametype_error(input...) + +promote(x::T, y::T, zs::T...) where {T} = (x, y, zs...) + +not_sametype(x::T, y::T) where {T} = sametype_error(x) + +not_sametype(x, y) = nothing + +function sametype_error(input) @_noinline_meta - error("circular method definition: promotion of types ", + error("promotion of types ", join(map(x->string(typeof(x)), input), ", ", " and "), - " failed to change any input types") + " failed to change any arguments") end +(x::Number, y::Number) = +(promote(x,y)...) @@ -389,3 +387,5 @@ minmax(x::Real) = (x, x) max(x::T, y::T) where {T<:Real} = select_value(y < x, x, y) min(x::T, y::T) where {T<:Real} = select_value(y < x, y, x) minmax(x::T, y::T) where {T<:Real} = y < x ? (y, x) : (x, y) + +flipsign(x::T, y::T) where {T<:Signed} = no_op_err("flipsign", T) diff --git a/base/range.jl b/base/range.jl index 5bf8a2896767c..ba90a98cbbb26 100644 --- a/base/range.jl +++ b/base/range.jl @@ -241,7 +241,7 @@ julia> linspace(1.3,2.9,9) 1.3:0.2:2.9 ``` """ -linspace(start, stop, len::Real=50) = linspace(promote_noncircular(start, stop)..., Int(len)) +linspace(start, stop, len::Real=50) = linspace(promote(start, stop)..., Int(len)) linspace(start::T, stop::T, len::Real=50) where {T} = linspace(start, stop, Int(len)) linspace(start::Real, stop::Real, len::Integer) = linspace(promote(start, stop)..., len) @@ -947,7 +947,7 @@ function _define_range_op(@nospecialize f) $f(r1::Union{StepRangeLen, OrdinalRange, LinSpace}, r2::Union{StepRangeLen, OrdinalRange, LinSpace}) = - $f(promote_noncircular(r1, r2)...) + $f(promote(r1, r2)...) end end _define_range_op(:+) diff --git a/base/twiceprecision.jl b/base/twiceprecision.jl index 1baf1b9a921bd..a4e8f055377e5 100644 --- a/base/twiceprecision.jl +++ b/base/twiceprecision.jl @@ -84,7 +84,7 @@ function add12(x::T, y::T) where {T} x, y = ifelse(abs(y) > abs(x), (y, x), (x, y)) canonicalize2(x, y) end -add12(x, y) = add12(promote_noncircular(x, y)...) +add12(x, y) = add12(promote(x, y)...) """ zhi, zlo = mul12(x, y) @@ -116,7 +116,7 @@ function mul12(x::T, y::T) where {T<:AbstractFloat} ifelse(iszero(h) | !isfinite(h), (h, h), canonicalize2(h, fma(x, y, -h))) end mul12(x::T, y::T) where {T} = (p = x * y; (p, zero(p))) -mul12(x, y) = mul12(promote_noncircular(x, y)...) +mul12(x, y) = mul12(promote(x, y)...) """ zhi, zlo = div12(x, y) @@ -152,7 +152,7 @@ function div12(x::T, y::T) where {T<:AbstractFloat} ifelse(iszero(r) | !isfinite(r), (r, r), (ldexp(rh, xe-ye), ldexp(rl, xe-ye))) end div12(x::T, y::T) where {T} = (p = x / y; (p, zero(p))) -div12(x, y) = div12(promote_noncircular(x, y)...) +div12(x, y) = div12(promote(x, y)...) ## TwicePrecision @@ -269,7 +269,7 @@ function +(x::TwicePrecision{T}, y::TwicePrecision{T}) where T s = abs(x.hi) > abs(y.hi) ? (((x.hi - r) + y.hi) + y.lo) + x.lo : (((y.hi - r) + x.hi) + x.lo) + y.lo TwicePrecision(canonicalize2(r, s)...) end -+(x::TwicePrecision, y::TwicePrecision) = +(promote_noncircular(x, y)...) ++(x::TwicePrecision, y::TwicePrecision) = +(promote(x, y)...) -(x::TwicePrecision, y::TwicePrecision) = x + (-y) -(x::TwicePrecision, y::Number) = x + (-y) @@ -292,7 +292,7 @@ function *(x::TwicePrecision{T}, y::TwicePrecision{T}) where {T} ret = TwicePrecision{T}(canonicalize2(zh, (x.hi * y.lo + x.lo * y.hi) + zl)...) ifelse(iszero(zh) | !isfinite(zh), TwicePrecision{T}(zh, zh), ret) end -*(x::TwicePrecision, y::TwicePrecision) = *(promote_noncircular(x, y)...) +*(x::TwicePrecision, y::TwicePrecision) = *(promote(x, y)...) function /(x::TwicePrecision, v::Number) x / TwicePrecision{typeof(x.hi/v)}(v)