Skip to content

Commit bc362de

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 866c890 commit bc362de

File tree

2 files changed

+76
-17
lines changed

2 files changed

+76
-17
lines changed

base/promotion.jl

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -307,19 +307,52 @@ promote_type(T) = T
307307
promote_type(T, S, U) = (@inline; promote_type(promote_type(T, S), U))
308308
promote_type(T, S, U, V...) = (@inline; afoldl(promote_type, promote_type(T, S, U), V...))
309309

310-
promote_type(::Type{Bottom}, ::Type{Bottom}) = Bottom
311-
promote_type(::Type{T}, ::Type{T}) where {T} = T
312-
promote_type(::Type{T}, ::Type{Bottom}) where {T} = T
313-
promote_type(::Type{Bottom}, ::Type{T}) where {T} = T
314-
315310
function promote_type(::Type{T}, ::Type{S}) where {T,S}
316-
@inline
317-
# Try promote_rule in both orders. Typically only one is defined,
318-
# and there is a fallback returning Bottom below, so the common case is
319-
# promote_type(T, S) =>
320-
# promote_result(T, S, result, Bottom) =>
321-
# typejoin(result, Bottom) => result
322-
promote_result(T, S, promote_rule(T,S), promote_rule(S,T))
311+
@_terminates_locally_meta
312+
normalized_type(::Type{Typ}) where {Typ} = Typ
313+
types_are_equal(::Type, ::Type) = false
314+
types_are_equal(::Type{Typ}, ::Type{Typ}) where {Typ} = true
315+
is_bottom(::Type) = false
316+
is_bottom(::Type{Bottom}) = true
317+
function throw_conflicting_promote_rules((@nospecialize i1::Type), (@nospecialize i2::Type), (@nospecialize left::Type), (@nospecialize right::Type))
318+
@noinline
319+
s = LazyString("`promote_type(", i1, ", ", i2, ")` failed, there are conflicting `promote_rule` definitions for types ", left, ", ", right)
320+
throw(ArgumentError(s))
321+
end
322+
function throw_gave_up((@nospecialize i1::Type), (@nospecialize i2::Type), (@nospecialize left::Type), (@nospecialize right::Type))
323+
@noinline
324+
s = LazyString("`promote_type(", i1, ", ", i2, ")` failed, ended up with (", left, ", ", right, "), check for faulty `promote_rule` methods")
325+
throw(ArgumentError(s))
326+
end
327+
left = T
328+
right = S
329+
for _ 1:1000 # guarantee local termination
330+
if types_are_equal(left, right) || is_bottom(left) || is_bottom(right)
331+
break
332+
end
333+
# Try `promote_rule` in both orders.
334+
a = normalized_type(promote_rule(left, right))
335+
b = normalized_type(promote_rule(right, left))
336+
loop_is_detected_1 = types_are_equal(left, a) && types_are_equal(right, b)
337+
loop_is_detected_2 = types_are_equal(left, b) && types_are_equal(right, a)
338+
if loop_is_detected_1 || loop_is_detected_2
339+
throw_conflicting_promote_rules(T, S, left, right)
340+
end
341+
if is_bottom(a) && is_bottom(b)
342+
# If no `promote_rule` is defined, both directions give `Bottom`. In that
343+
# case use `typejoin` on the original types.
344+
return typejoin(left, right)
345+
end
346+
left = a
347+
right = b
348+
end
349+
if types_are_equal(left, right) || is_bottom(left)
350+
right
351+
elseif is_bottom(right)
352+
left
353+
else
354+
throw_gave_up(T, S, left, right)
355+
end
323356
end
324357

325358
"""
@@ -339,11 +372,6 @@ promote_rule(::Type{Bottom}, ::Type{Bottom}, slurp...) = Bottom # not strictly n
339372
promote_rule(::Type{Bottom}, ::Type{T}, slurp...) where {T} = T
340373
promote_rule(::Type{T}, ::Type{Bottom}, slurp...) where {T} = T
341374

342-
promote_result(::Type,::Type,::Type{T},::Type{S}) where {T,S} = (@inline; promote_type(T,S))
343-
# If no promote_rule is defined, both directions give Bottom. In that
344-
# case use typejoin on the original types instead.
345-
promote_result(::Type{T},::Type{S},::Type{Bottom},::Type{Bottom}) where {T,S} = (@inline; typejoin(T, S))
346-
347375
"""
348376
promote(xs...)
349377

test/core.jl

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2019,6 +2019,37 @@ g4731() = f4731()
20192019
@test f4731() == ""
20202020
@test g4731() == ""
20212021

2022+
@testset "type promotion" begin
2023+
@testset "conflicting promote_rule error, PR #57507" begin
2024+
struct PR57507A end
2025+
struct PR57507B end
2026+
struct PR57507C end
2027+
@testset "error with conflicting promote_rules" begin
2028+
Base.promote_rule(::Type{PR57507A}, ::Type{PR57507B}) = PR57507A
2029+
Base.promote_rule(::Type{PR57507B}, ::Type{PR57507A}) = PR57507B
2030+
@test_throws ArgumentError promote_type(PR57507A, PR57507B)
2031+
@test_throws ArgumentError promote_type(PR57507B, PR57507A)
2032+
end
2033+
@testset "unambiguous cases" begin
2034+
@test PR57507A === @inferred promote_type(PR57507A, PR57507A)
2035+
@test PR57507B === @inferred promote_type(PR57507B, PR57507B)
2036+
Base.promote_rule(::Type{PR57507C}, ::Type{PR57507A}) = PR57507C
2037+
Base.promote_rule(::Type{PR57507B}, ::Type{PR57507C}) = PR57507C
2038+
@test PR57507C === @inferred promote_type(PR57507A, PR57507C)
2039+
@test PR57507C === @inferred promote_type(PR57507C, PR57507B)
2040+
end
2041+
end
2042+
@testset "issue #13193" begin
2043+
struct Issue13193_SIQuantity{T<:Number} <: Number end
2044+
Base.promote_rule(::Type{Issue13193_SIQuantity{T}}, ::Type{Issue13193_SIQuantity{S}}) where {T, S} = Issue13193_SIQuantity{promote_type(T,S)}
2045+
Base.promote_rule(::Type{Issue13193_SIQuantity{T}}, ::Type{S}) where {T, S<:Number} = Issue13193_SIQuantity{promote_type(T,S)}
2046+
struct Issue13193_Interval{T<:Number} <: Number end
2047+
Base.promote_rule(::Type{Issue13193_Interval{T}}, ::Type{Issue13193_Interval{S}}) where {T, S} = Issue13193_Interval{promote_type(T,S)}
2048+
Base.promote_rule(::Type{Issue13193_Interval{T}}, ::Type{S}) where {T, S<:Number} = Issue13193_Interval{promote_type(T,S)}
2049+
@test_throws ArgumentError promote_type(Issue13193_Interval{Int}, Issue13193_SIQuantity{Int})
2050+
end
2051+
end
2052+
20222053
# issue #4675
20232054
f4675(x::StridedArray...) = 1
20242055
f4675(x::StridedArray{T}...) where {T} = 2

0 commit comments

Comments
 (0)