If I understand correctly, what happens is:
-
Concreteis the same asConcrete{T} where {T <: AbstractType}, following the definition ofConcrete, where the upper bound of the typevar isAbstractType -
Concrete{T} where Tis the same asConcrete{<: Any}, by definition. - Hence, the signature
is_same(c1::Concrete, c2::Concrete)is more specific thanis_same(c1::Concrete{T}, c2::Concrete{T}) where T, and the call dispatches to that.
This can be confirmed by looking at the method table of is_same, where the methods are displayed from more to less specific:
julia> methods(is_same)
# 2 methods for generic function "is_same":
[1] is_same(c1::Concrete, c2::Concrete) in Main at REPL[4]:1
[2] is_same(c1::Concrete{T}, c2::Concrete{T}) where T in Main at REPL[5]:1
Relevant issues: