Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: make promote throw an error if no arguments can be changed #23491

Merged
merged 1 commit into from
Aug 31, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
--------------------

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 @@ -1752,6 +1752,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