Skip to content

Commit 224ca8c

Browse files
committed
prevent stack overflow in 2-arg promote_type
Don't use recursion, delete the `promote_result` function. Use loop known to terminate, because of a hardcoded limit on the iteration count. Closes JuliaLang#57507, this PR encompasses the fixes from that PR and is more comprehensive. Fixes JuliaLang#13193
1 parent 2dd4cdf commit 224ca8c

File tree

2 files changed

+68
-17
lines changed

2 files changed

+68
-17
lines changed

base/promotion.jl

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -302,19 +302,44 @@ promote_type(T) = T
302302
promote_type(T, S, U) = (@inline; promote_type(promote_type(T, S), U))
303303
promote_type(T, S, U, V...) = (@inline; afoldl(promote_type, promote_type(T, S, U), V...))
304304

305-
promote_type(::Type{Bottom}, ::Type{Bottom}) = Bottom
306-
promote_type(::Type{T}, ::Type{T}) where {T} = T
307-
promote_type(::Type{T}, ::Type{Bottom}) where {T} = T
308-
promote_type(::Type{Bottom}, ::Type{T}) where {T} = T
309-
310305
function promote_type(::Type{T}, ::Type{S}) where {T,S}
311-
@inline
312-
# Try promote_rule in both orders. Typically only one is defined,
313-
# and there is a fallback returning Bottom below, so the common case is
314-
# promote_type(T, S) =>
315-
# promote_result(T, S, result, Bottom) =>
316-
# typejoin(result, Bottom) => result
317-
promote_result(T, S, promote_rule(T,S), promote_rule(S,T))
306+
@_terminates_locally_meta
307+
normalized_type(::Type{Typ}) where {Typ} = Typ
308+
types_are_identical(::Type{A}, ::Type{B}) where {A,B} = A === B
309+
is_bottom(::Type{Typ}) where {Typ} = Typ <: Bottom
310+
left = T
311+
right = S
312+
for _ 1:1000
313+
if types_are_identical(left, right) || is_bottom(left) || is_bottom(right)
314+
break
315+
end
316+
# Try `promote_rule` in both orders.
317+
a = normalized_type(promote_rule(left, right))
318+
b = normalized_type(promote_rule(right, left))
319+
loop_is_detected_1 = types_are_identical(left, a) && types_are_identical(right, b)
320+
loop_is_detected_2 = types_are_identical(left, b) && types_are_identical(right, a)
321+
if loop_is_detected_1 || loop_is_detected_2
322+
let s = LazyString("`promote_type(", T, ", ", S, ")` failed, there are conflicting `promote_rule` definitions for types ", a, ", ", b)
323+
throw(ArgumentError(s))
324+
end
325+
end
326+
if is_bottom(a) && is_bottom(b)
327+
# If no `promote_rule` is defined, both directions give `Bottom`. In that
328+
# case use `typejoin` on the original types.
329+
return typejoin(left, right)
330+
end
331+
left = a
332+
right = b
333+
end
334+
if (left === right) || is_bottom(left)
335+
right
336+
elseif is_bottom(right)
337+
left
338+
else
339+
let s = LazyString("`promote_type(", T, ", ", S, ")` failed, ended up with (", left, ", ", right, "), check for faulty `promote_rule` methods")
340+
throw(ArgumentError(s))
341+
end
342+
end
318343
end
319344

320345
"""
@@ -334,11 +359,6 @@ promote_rule(::Type{Bottom}, ::Type{Bottom}, slurp...) = Bottom # not strictly n
334359
promote_rule(::Type{Bottom}, ::Type{T}, slurp...) where {T} = T
335360
promote_rule(::Type{T}, ::Type{Bottom}, slurp...) where {T} = T
336361

337-
promote_result(::Type,::Type,::Type{T},::Type{S}) where {T,S} = (@inline; promote_type(T,S))
338-
# If no promote_rule is defined, both directions give Bottom. In that
339-
# case use typejoin on the original types instead.
340-
promote_result(::Type{T},::Type{S},::Type{Bottom},::Type{Bottom}) where {T,S} = (@inline; typejoin(T, S))
341-
342362
"""
343363
promote(xs...)
344364

test/core.jl

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2028,6 +2028,37 @@ g4731() = f4731()
20282028
@test f4731() == ""
20292029
@test g4731() == ""
20302030

2031+
@testset "type promotion" begin
2032+
@testset "conflicting promote_rule error, PR #57507" begin
2033+
struct PR57507A end
2034+
struct PR57507B end
2035+
struct PR57507C end
2036+
@testset "error with conflicting promote_rules" begin
2037+
Base.promote_rule(::Type{PR57507A}, ::Type{PR57507B}) = PR57507A
2038+
Base.promote_rule(::Type{PR57507B}, ::Type{PR57507A}) = PR57507B
2039+
@test_throws ArgumentError promote_type(PR57507A, PR57507B)
2040+
@test_throws ArgumentError promote_type(PR57507B, PR57507A)
2041+
end
2042+
@testset "unambiguous cases" begin
2043+
@test PR57507A === @inferred promote_type(PR57507A, PR57507A)
2044+
@test PR57507B === @inferred promote_type(PR57507B, PR57507B)
2045+
Base.promote_rule(::Type{PR57507C}, ::Type{PR57507A}) = PR57507C
2046+
Base.promote_rule(::Type{PR57507B}, ::Type{PR57507C}) = PR57507C
2047+
@test PR57507C === @inferred promote_type(PR57507A, PR57507C)
2048+
@test PR57507C === @inferred promote_type(PR57507C, PR57507B)
2049+
end
2050+
end
2051+
@testset "issue #13193" begin
2052+
struct Issue13193_SIQuantity{T<:Number} <: Number end
2053+
Base.promote_rule(::Type{Issue13193_SIQuantity{T}}, ::Type{Issue13193_SIQuantity{S}}) where {T, S} = Issue13193_SIQuantity{promote_type(T,S)}
2054+
Base.promote_rule(::Type{Issue13193_SIQuantity{T}}, ::Type{S}) where {T, S<:Number} = Issue13193_SIQuantity{promote_type(T,S)}
2055+
struct Issue13193_Interval{T<:Number} <: Number end
2056+
Base.promote_rule(::Type{Issue13193_Interval{T}}, ::Type{Issue13193_Interval{S}}) where {T, S} = Issue13193_Interval{promote_type(T,S)}
2057+
Base.promote_rule(::Type{Issue13193_Interval{T}}, ::Type{S}) where {T, S<:Number} = Issue13193_Interval{promote_type(T,S)}
2058+
@test_throws ArgumentError promote_type(Issue13193_Interval{Int}, Issue13193_SIQuantity{Int})
2059+
end
2060+
end
2061+
20312062
# issue #4675
20322063
f4675(x::StridedArray...) = 1
20332064
f4675(x::StridedArray{T}...) where {T} = 2

0 commit comments

Comments
 (0)