diff --git a/base/promotion.jl b/base/promotion.jl index 72257f8ba5a3d..3a74930fceeca 100644 --- a/base/promotion.jl +++ b/base/promotion.jl @@ -302,18 +302,12 @@ promote_type(T) = T promote_type(T, S, U) = (@inline; promote_type(promote_type(T, S), U)) promote_type(T, S, U, V...) = (@inline; afoldl(promote_type, promote_type(T, S, U), V...)) -promote_type(::Type{Bottom}, ::Type{Bottom}) = Bottom -promote_type(::Type{T}, ::Type{T}) where {T} = T -promote_type(::Type{T}, ::Type{Bottom}) where {T} = T -promote_type(::Type{Bottom}, ::Type{T}) where {T} = T - function promote_type(::Type{T}, ::Type{S}) where {T,S} @inline # Try promote_rule in both orders. Typically only one is defined, # and there is a fallback returning Bottom below, so the common case is # promote_type(T, S) => - # promote_result(T, S, result, Bottom) => - # typejoin(result, Bottom) => result + # promote_result(T, S, result, Bottom) => result promote_result(T, S, promote_rule(T,S), promote_rule(S,T)) end @@ -326,7 +320,10 @@ it for new types as appropriate. """ function promote_rule end -promote_rule(::Type, ::Type) = Bottom +# add a level of indirection to avoid method ambiguities +promote_rule(T::Type, S::Type) = _promote_rule(T, S) +_promote_rule(::Type, ::Type) = Bottom +_promote_rule(::Type{T}, ::Type{T}) where {T} = T # Define some methods to avoid needing to enumerate unrelated possibilities when presented # with Type{<:T}, and return a value in general accordance with the result given by promote_type promote_rule(::Type{Bottom}, slurp...) = Bottom @@ -334,7 +331,23 @@ 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)) +# 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`s were ", + "detected with either argument being a possible result."))) +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) + # 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)) diff --git a/test/core.jl b/test/core.jl index 7e4f655222ea5..5d7e3d1b070a6 100644 --- a/test/core.jl +++ b/test/core.jl @@ -8389,3 +8389,19 @@ f_call_me() = invoke(f_invoke_me, f_invoke_me_ci) f_invalidate_me() = 2 @test_throws ErrorException invoke(f_invoke_me, f_invoke_me_ci) @test_throws ErrorException f_call_me() + +@testset "promote_rule and promote_result overloads" begin + struct PromoteA end + struct PromoteB 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) + end + + @testset "promote_rule overload for identical types" begin + Base.promote_rule(::Type{PromoteA}, ::Type{PromoteA}) = PromoteB + @test promote_type(PromoteA, PromoteA) == PromoteB + end +end