I'm not sure if this is a bug or not.
First, define the functions f
and g
as follows:
julia> function f(a::A, b::Type{B}) where A <: B where B
println("a: ", a)
println("b: ", b)
println("A: ", A)
println("B: ", B)
return nothing
end
f (generic function with 1 method)
julia> function g(c::C, d::Type{D}) where D <: C where C
println("c: ", c)
println("d: ", d)
println("D: ", D)
println("C: ", C)
return nothing
end
g (generic function with 1 method)
julia> methods(f)
# 1 method for generic function "f":
[1] f(a::A, b::Type{B}) where {B, A<:B} in Main at REPL[1]:2
julia> methods(g)
# 1 method for generic function "g":
[1] g(c::C, d::Type{D}) where {C, D<:C} in Main at REPL[2]:2
Calling f(1, Int)
gives the correct output:
julia> f(1, Int)
a: 1
b: Int64
A: Int64
B: Int64
Calling f(1, Signed)
gives the correct output:
julia> f(1, Signed)
a: 1
b: Signed
A: Int64
B: Signed
Calling f(1, String)
correctly gives a MethodError
, which makes sense because Int
is not a subtype of String
:
julia> f(1, String)
ERROR: MethodError: no method matching f(::Int64, ::Type{String})
Closest candidates are:
f(::A, ::Type{B}) where {B, A<:B} at REPL[1]:2
Stacktrace:
[1] top-level scope at REPL[10]:1
Calling g(1, Int)
gives the correct output:
julia> g(1, Int)
c: 1
d: Int64
D: Int64
C: Int64
Now, here is the possible bug. If I understand correctly, g(1, String)
should throw a MethodError
because String
is not a subtype of Int
. Instead, however, calling g(1, String)
calls the method with type signature g(::Int64, ::Type{String})
(a method that should not exist), and then throws the error ERROR: UndefVarError: C not defined
when trying to run the line println("C: ", C)
.
julia> g(1, String)
c: 1
d: String
D: String
ERROR: UndefVarError: C not defined
Stacktrace:
[1] g(::Int64, ::Type{String}) at ./REPL[2]:5
[2] top-level scope at REPL[20]:1
Why does the call g(1, String)
match the method g(c::C, d::Type{D}) where D <: C where C
? It seems to me that C
should be Int
and D
should be String
, and clearly it is not the case that String
is a subtype of Int
.
If the type of the first argument is constrained, then a MethodError
is thrown when String
is passed as the second argument. However, when Signed
is passed as the second argument then the type of the first argument is not defined.
julia> function h(e::E, f::Type{F}) where F <: E where E <: Integer
println("e: ", e)
println("f: ", f)
println("E: ", E)
println("F: ", F)
return nothing
end
h (generic function with 1 method)
julia> h(1,Int64)
e: 1
f: Int64
E: Int64
F: Int64
julia> h(1,String)
ERROR: MethodError: no method matching h(::Int64, ::Type{String})
Closest candidates are:
h(::E, ::Type{F}) where {E<:Integer, F<:E} at REPL[19]:2
Stacktrace:
[1] top-level scope at REPL[22]:1
julia> h(1,Signed)
e: 1
f: Signed
ERROR: UndefVarError: E not defined
Stacktrace:
[1] h(::Int64, ::Type{Signed}) at ./REPL[19]:4
[2] top-level scope at REPL[23]:1
Why does the call
g(1, String)
match the methodg(c::C, d::Type{D}) where D <: C where C
?
IIUC this is because any C
s.t. C >: Union{String,Int}
satisfies this constraint. For example, C = Any
is _a_ valid answer. Dispatch is decoupled from type parameter resolution and julia
does not try to find the smallest possible type and keep it unbound. If you want to auto-detect something like this, Test
package exports detect_unbound_args
.
Yes, that's it. The behavior may be unexpected here, but is in fact correct.
@tkf that explains it perfectly, thank you so much!
Most helpful comment
Yes, that's it. The behavior may be unexpected here, but is in fact correct.