Skip to content

promote_rule: forbid method definitions which may cause ambiguity for unknown types #55866

@nsajko

Description

@nsajko

Suppose a type T is defined in a package. Further suppose that S is some type not owned by the same package, and T <: S. Defining either of the following method signatures should be forbidden, because it causes dispatch ambiguity for perfectly valid promote_rule definitions from third-party packages:

Base.promote_rule(::Type{  T}, ::Type{<:S})
Base.promote_rule(::Type{<:T}, ::Type{<:S})
Base.promote_rule(::Type{<:S}, ::Type{  T})
Base.promote_rule(::Type{<:S}, ::Type{<:T})

NB: Base doesn't respect this rule, though perhaps this is OK/less bad, given that Base is an implicit dependency of everything anyway. Examples:

promote_rule(::Type{BigInt}, ::Type{<:Integer}) in Base.GMP at gmp.jl:460
promote_rule(::Type{BigFloat}, ::Type{<:Real}) in Base.MPFR at mpfr.jl:366
promote_rule(::Type{Bool}, ::Type{T}) where T<:Number @ Base bool.jl:4
promote_rule(::Type{<:AbstractIrrational}, ::Type{T}) where T<:Real @ Base irrationals.jl:47
promote_rule(::Type{S}, ::Type{T}) where {S<:AbstractIrrational, T<:Number} @ Base irrationals.jl:48

Example code for ambiguity errors:

# package A
struct TA <: Integer end
Base.promote_rule(::Type{TA}, t::Type{<:Real}) = promote_type(Int, t)  # offending definition
Base.promote_rule(t::Type{<:Real}, ::Type{TA}) = promote_type(Int, t)  # offending definition

# package B
struct TB <: AbstractFloat end
Base.promote_rule(::Type{TB}, t::Type{<:Integer}) = promote_type(Float64, t)  # OK
julia> promote_type(TA, TB)
ERROR: MethodError: promote_rule(::Type{TB}, ::Type{TA}) is ambiguous.

Candidates:
  promote_rule(::Type{TB}, t::Type{<:Integer})
    @ Main REPL[5]:1
  promote_rule(t::Type{<:Real}, ::Type{TA})
    @ Main REPL[3]:1

Possible fix, define
  promote_rule(::Type{TB}, ::Type{TA})

Stacktrace:
 [1] promote_type(::Type{TA}, ::Type{TB})
   @ Base ./promotion.jl:318
 [2] top-level scope
   @ REPL[6]:1

julia> using Test

julia> detect_ambiguities(Main, recursive=true)
8-element Vector{Tuple{Method, Method}}:
 (promote_rule(t::Type{<:Real}, ::Type{TA}) @ Main REPL[3]:1, promote_rule(::Type{BigInt}, ::Type{<:Integer}) @ Base.GMP gmp.jl:480)
 (promote_rule(t::Type{<:Real}, ::Type{TA}) @ Main REPL[3]:1, promote_rule(::Type{Bool}, ::Type{T}) where T<:Number @ Base bool.jl:4)
 (promote_rule(t::Type{<:Real}, ::Type{TA}) @ Main REPL[3]:1, promote_rule(::Type{Rational{T}}, ::Type{S}) where {T<:Integer, S<:Integer} @ Base rational.jl:180)
 (promote_rule(t::Type{<:Real}, ::Type{TA}) @ Main REPL[3]:1, promote_rule(::Type{TB}, t::Type{<:Integer}) @ Main REPL[5]:1)
 (promote_rule(t::Type{<:Real}, ::Type{TA}) @ Main REPL[3]:1, promote_rule(::Type{<:AbstractIrrational}, ::Type{T}) where T<:Real @ Base irrationals.jl:47)
 (promote_rule(::Type{TA}, t::Type{<:Real}) @ Main REPL[2]:1, promote_rule(t::Type{<:Real}, ::Type{TA}) @ Main REPL[3]:1)
 (promote_rule(t::Type{<:Real}, ::Type{TA}) @ Main REPL[3]:1, promote_rule(::Type{S}, ::Type{T}) where {S<:AbstractIrrational, T<:Number} @ Base irrationals.jl:48)
 (promote_rule(t::Type{<:Real}, ::Type{TA}) @ Main REPL[3]:1, promote_rule(::Type{BigFloat}, ::Type{<:Real}) @ Base.MPFR mpfr.jl:478)

Metadata

Metadata

Assignees

No one assigned

    Labels

    designDesign of APIs or of the language itselfdocsThis change adds or pertains to documentation

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions