I am having trouble understanding why the following code does not dispatch on Base.convert(::Type{T}, ::T) as I would expect:
abstract type SuperType end
struct MyType{T} <: SuperType end
function Base.convert(::Type{<:MyType}, t::SuperType)
println("Converting")
MyType{Int}()
end
convert(MyType{Int32}, MyType{Int32}())
# goes through the `convert` method we just added instead of `Base.convert(::Type{T}, ::T)`
Is there a reason for this behavior? It forces me to define Base.convert(::Type{T}, t::T) where {T<:SuperType} = t for the code to avoid bugs that rely on this invariant.
This behavior seems reasonable to me. The Base.convert(::Type{<:MyType}, t::SuperType) method that you defined is more specific than the Base.convert(::Type{T}, ::T) method. The general rule is that when two methods are both applicable, the more specific method is the one that will be called.
In fact, the Base.convert method that you defined explicitly contradicts the invariant that you’re trying to maintain. So you could define something like this instead:
function Base.convert(::Type{S}, x::T) where {S <: MyType, T <: SuperType}
println("Converting")
if S == T
x
else
MyType{Int}()
end
end
However, that still seems goofy, because then you have
Unfortunately the rules for method specificity are not given in the Julia manual. I think the details are rather complicated, so the developers do not want to codify a list of specificity rules by putting them in the manual. There’s a brief discussion of method specificity in the developer docs:
Thanks for your answers. I would have expected that the diagonal type specification (having (::Type{T}, ::T) where {T}) would have been more specific over anything else (unless a similar method whose T is further constrained was defined of course).
What the convert method does is not important, I admit that in this example it may not make much sense. In your code snippet, the line if S == T is the equivalent of Base.convert(::Type{T}, t::T) where {T<:SuperType} = t I mentioned above. I would have preferred to avoid any of both, but I guess this is just a corner case that is not so intuitive for method specificity rules.
I think subtype relationships have the highest priority when it comes to method specificity. Diagonal type restrictions are farther down on the list.
In your actual use case, are you defining a convert method, or is it some other function? Just wondering, because usually if you encounter a method error for convert, it doesn’t mean that you should implement a convert method—it means that you are doing something wrong further up the chain.