Incorrect `method overwritten` warning with optional kwargs..?

My package exposes this caller method, whose behaviour may be controlled by consumers provided they specialize f:

TestPkg/src/TestPkg.jl

module TestPkg

# Entry point for users.
caller(a) = f(a; b = 1)
export caller

# Extension point for users.
f(a; b = 0) = f(a)
f(a) = println("default for $a (ignored kwargs)")

end 

Here is how users opt into either the default or the specialized versions:

using TestPkg

TestPkg.f(a::Int) = println("specialized for $a without using b")
TestPkg.f(a::String; b = 0) = println("specialized for $a using b = $b")

caller(5)
caller("a")
caller([]) 
specialized for 5 without using b
specialized for a using b = 1
default for Any[] (ignored kwargs)

This behaviour is what I expect, but the following warning is issued:

WARNING: Method definition f(Any) in module TestPkg at ./TestPkg/src/TestPkg.jl:8 overwritten at ./TestPkg/src/TestPkg.jl:9.

I can’t find any satisfactory fix:

  • Changing the extension point to:
    f(a) = println("default for $a (ignored kwargs)")
    
    breaks user code:
    ERROR: LoadError: MethodError: no method matching f(::Int64; b::Int64)
    Closest candidates are:
    f(::Int64) got unsupported keyword argument "b"
    
  • Changing the extension point to:
    f(a; b = 0) = println("default for $a (ignored kwargs)")
    
    fixes the warning without error, but it breaks the expected behaviour in a subtle way:
    default for 5 (ignored kwargs) # <- /!\ Should be specialized.
    specialized for a using b = 1
    default for Any[] (ignored kwargs)
    

Is there any way out of this? If not, maybe the warning is a false positive in this situation?

Keyword arguments don’t participate in dispatch, so this is expected behavior. Ignoring keyword arguments could be done by providing a method taking arbitrary keyword args, like f(a; kws...) with haskey(kws, ...) checks in your provided fallback.

2 Likes

Thank you for feedback @Sukera. I understand that dispatch do not happen on keyword args, but using f(a; kw...) as a fallback breaks the observable behaviour here, because the first eventual caller(5) is not resolved to the specialized version (like in the second failed attempt in the OP).

So, it seems that I cannot fix the warning without breaking user code, right?

What I’m saying is that dispatch wise, there is no difference between f(a::T; b=...) and f(a::T) for the same T. Julia simply does not make a difference between the two, so if a user of yours previously tried to distinguish methods like that (e.g. for T == String), they would have experienced broken or unexpected behavior, even if you could “fix” the warning on your end.

So, from that POV, I’m not sure there’s a way around breaking your API and provide a different way to opt into some more specialized version of your code.

2 Likes

Understood, the API was a mistake then. I’ll try to fix it instead of fixing the warning. Thank you : )

1 Like