diff --git a/base/promotion.jl b/base/promotion.jl index 72257f8ba5a3d..247a96f75c9a3 100644 --- a/base/promotion.jl +++ b/base/promotion.jl @@ -334,10 +334,25 @@ promote_rule(::Type{Bottom}, ::Type{Bottom}, slurp...) = Bottom # not strictly n promote_rule(::Type{Bottom}, ::Type{T}, slurp...) where {T} = T promote_rule(::Type{T}, ::Type{Bottom}, slurp...) where {T} = T +# if both the arguments are identical, or if both the orderings in promote_rule +# are defined to return identical results, we may return the result directly +promote_result(::Type{T},::Type{T},::Type{T},::Type{T}) where {T} = T +promote_result(::Type,::Type,::Type{T},::Type{T}) where {T} = T +# If only one promote_rule is defined, use the definition directly +promote_result(::Type,::Type,::Type{T},::Type{Bottom}) where {T} = T +promote_result(::Type,::Type,::Type{Bottom},::Type{T}) where {T} = T +# if multiple promote_rules are defined, try to promote the results promote_result(::Type,::Type,::Type{T},::Type{S}) where {T,S} = (@inline; promote_type(T,S)) # If no promote_rule is defined, both directions give Bottom. In that # case use typejoin on the original types instead. promote_result(::Type{T},::Type{S},::Type{Bottom},::Type{Bottom}) where {T,S} = (@inline; typejoin(T, S)) +# avoid recursion if the types don't change under promote_rule, and throw an informative error instead +function _throw_promote_type_fail(A::Type, B::Type) + throw(ArgumentError(LazyString("promote_type(", A, ", ", B, ") failed, as conflicting promote_rule definitions were ", + "detected with both ", A, " and ", B, " being possible results."))) +end +promote_result(::Type{T},::Type{S},::Type{T},::Type{S}) where {T,S} = _throw_promote_type_fail(T, S) +promote_result(::Type{T},::Type{S},::Type{S},::Type{T}) where {T,S} = _throw_promote_type_fail(T, S) """ promote(xs...) diff --git a/test/core.jl b/test/core.jl index fd607cc86f1d5..ccc79a536cf83 100644 --- a/test/core.jl +++ b/test/core.jl @@ -8471,3 +8471,26 @@ module GlobalAssign57446 (@__MODULE__).theglobal = 1 @test theglobal == 1 end + +@testset "conflicting promote_rule error" begin + struct PromoteA end + struct PromoteB end + struct PromoteC end + + @testset "error with conflicting promote_rules" begin + Base.promote_rule(::Type{PromoteA}, ::Type{PromoteB}) = PromoteA + Base.promote_rule(::Type{PromoteB}, ::Type{PromoteA}) = PromoteB + @test_throws ArgumentError promote_type(PromoteA, PromoteB) + @test_throws ArgumentError promote_type(PromoteB, PromoteA) + end + @testset "unambiguous cases" begin + @test promote_type(PromoteA, PromoteA) == PromoteA + @test promote_type(PromoteB, PromoteB) == PromoteB + + Base.promote_rule(::Type{PromoteC}, ::Type{PromoteA}) = PromoteC + Base.promote_rule(::Type{PromoteB}, ::Type{PromoteC}) = PromoteC + + @test promote_type(PromoteA, PromoteC) == PromoteC + @test promote_type(PromoteC, PromoteB) == PromoteC + end +end