Skip to content

Commit

Permalink
Merge pull request JuliaLang#23491 from JuliaLang/jb/noncircular_promote
Browse files Browse the repository at this point in the history
RFC: make `promote` throw an error if no arguments can be changed
  • Loading branch information
JeffBezanson authored Aug 31, 2017
2 parents ecf284d + b9ce52f commit 7e35ac6
Show file tree
Hide file tree
Showing 7 changed files with 45 additions and 38 deletions.
5 changes: 5 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,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
--------------------

Expand Down
2 changes: 1 addition & 1 deletion base/dates/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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) &&
Expand Down
2 changes: 2 additions & 0 deletions base/deprecated.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1712,6 +1712,8 @@ end
@deprecate selectperm partialsortperm
@deprecate selectperm! partialsortperm!

@deprecate promote_noncircular promote false

# END 0.7 deprecations

# BEGIN 1.0 deprecations
Expand Down
2 changes: 1 addition & 1 deletion base/int.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
58 changes: 29 additions & 29 deletions base/promotion.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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),
Expand Down Expand Up @@ -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)...)
Expand Down Expand Up @@ -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)
4 changes: 2 additions & 2 deletions base/range.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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(:+)
Expand Down
10 changes: 5 additions & 5 deletions base/twiceprecision.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down

0 comments on commit 7e35ac6

Please sign in to comment.