 Parametric method type matching issue

Consider this function

``````foo(x::T, y::S) where {T <: Integer, S <: T} = 1
``````

1. `y` has a type of `S`
2. type `S` should be a subtype of `T`
3. type `T` should be a subtype of `Integer`

But, I can do

``````julia> foo(1, 0x00)
1
``````

`T` is `Int64`, `S` is `UInt8`, but `S` is not a subtype of `T`. So, why would it dispatch? It seems that it’s enforcing `S` to be a subtype of `Integer` rather than a subtype of `T`.

What would be a subtype of `T`? If `T` is concrete there are no subtypes apart from `T` itself.

Yes, that would be the behavior that I expect i.e. `S == T`, when `T` is concrete.

Technically, `Union{}` or `Base.Bottom` is the bottom type for all types but that not useful in this context.

No. T is undefined.

`T` is known when the method is called. Isn’t it dynamic?

No. There are infinite number of `T` that can match so `T` is not known at any time.

``````julia> foo(x::T, y::S) where {T <: Integer, S <: T} = (T, S)
foo (generic function with 1 method)

julia> foo(1, 0x00)
ERROR: UndefVarError: T not defined
Stacktrace:
 foo(::Int64, ::UInt8) at ./REPL:1
 top-level scope at none:0
``````

What does this mean?

At the call site, I’m passing two arguments and they have type `Int64` and `UInt8` respectively. If I just match them to `foo`'s signature then `T` would be `Int64` and `S` would be `UInt8`, except that it does not satisfy the where condition properly because `Int64 <: Integer` but `UInt8 <: Int64` is not true.

I see that `T` is not defined in your example above but I don’t understand why it complains about that. Can you elaborate why there are infinite number of `T` that can match?

When I said dynamic, I was just referring to dynamic dispatch where at runtime the types of all arguments are known.

But

``````julia> foo(x::T, y::S) where {T <: Integer, S <: T} = (T, S)
foo (generic function with 1 method)

julia> foo(1,1)
(Int64, Int64)
``````

No `T` could be anything that is a supertype of `Int64` and `S` could be anything that is a supertype of `UInt8`, there are infinite many of those.

It’s the same reason `f(::T, ::Vector{T}) where T` can be called with `f(1, [])`. You can’t say in this case that you first match the `::T` to `T = Int` since that’ll conflict with the `::Vector{T}`. In this case `T = Any` in the end.

Because there are infinite number of types that is supertype of `Int64`.

I believe you are confused about the special rule about covariant type parameters. I don’t know what’s the exact definition of the rule right now (in corner cases, like when `x` and `y` have the same time shown in @giordano’s post ) but it says that if a type parameter only appears in covariant location then it must be a concrete type. This is so that `(::T, ::T) where T` won’t be the same as `(::Any, ::Any)` and `(::Tuple{T,T}) where T` won’t be the same as `(::Tuple{Any,Any})`. The rule does not apply since you are using the type parameter elsewhere in this case.

1. Dynamic dispatch means the dispatch is done at runtime, it’s in contrast to static dispatch and both are purely optimization concept and has nothing to do with the semantics. In fact, if not for all your calls are in the global scope, the dispatch is certainly going to be static.
2. The types of the argument of course must be know by dispatch time (which must be no latter than runtime) but that does not mean anything about type parameters. They don’t need to be assigned.
2 Likes

Thanks for the additional details. I have found the documentation about Diagonal Types and it has cleared up some of my misunderstanding.

How about this? Even though `T` is undefined, `S` is determined to be `UInt8`… what makes it possible to return `S` here?

``````julia> goo(x::T, y::S) where {T <: Integer, S <: T} = S
goo (generic function with 1 method)

julia> goo(1, 0x00)
UInt8
``````
1 Like

Because S can only be a concrete type here