diff --git a/NEWS.md b/NEWS.md index cd0799ca681b82..5762c168a5d9ec 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,6 +6,7 @@ New language features * The `extrema` function now accepts a function argument in the same manner as `minimum` and `maximum` ([#30323]). +* `hasmethod` can now check for matching keyword argument names ([#30712]). Multi-threading changes ----------------------- diff --git a/base/reflection.jl b/base/reflection.jl index d201ecdff72c94..b0a6c4e4660242 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -1084,17 +1084,38 @@ function parentmodule(@nospecialize(f), @nospecialize(types)) end """ - hasmethod(f, Tuple type; world = typemax(UInt)) -> Bool + hasmethod(f, t::Type{<:Tuple}[, kwnames]; world=typemax(UInt)) -> Bool Determine whether the given generic function has a method matching the given `Tuple` of argument types with the upper bound of world age given by `world`. +If a tuple of keyword argument names `kwnames` is provided, this also checks +whether the method of `f` matching `t` has the given keyword argument names. +If the matching method accepts a variable number of keyword arguments, e.g. +with `kwargs...`, the method's keyword arguments must be a subset of the +provided names. Otherwise the provided names must be a subset of the method's +keyword arguments. + See also [`applicable`](@ref). +!!! compat "Julia 1.2" + Providing keyword argument names requires Julia 1.2 or later. + # Examples ```jldoctest julia> hasmethod(length, Tuple{Array}) true + +julia> hasmethod(sum, Tuple{Function, Array}, (:dims,)) +true + +julia> hasmethod(sum, Tuple{Function, Array}, (:apples, :bananas)) +false + +julia> g(; xs...) = 4; + +julia> hasmethod(g, Tuple{}, (:a, :b, :c, :d)) # g accepts arbitrary kwargs +true ``` """ function hasmethod(@nospecialize(f), @nospecialize(t); world = typemax(UInt)) @@ -1103,6 +1124,18 @@ function hasmethod(@nospecialize(f), @nospecialize(t); world = typemax(UInt)) return ccall(:jl_method_exists, Cint, (Any, Any, UInt), typeof(f).name.mt, t, world) != 0 end +function hasmethod(@nospecialize(f), @nospecialize(t), kwnames::Tuple{Vararg{Symbol}}; world=typemax(UInt)) + hasmethod(f, t, world=world) || return false + isempty(kwnames) && return true + m = which(f, t) + max_world(m) <= world || return false + kws = kwarg_decl(m, Core.kwftype(typeof(f))) + for kw in kws + endswith(String(kw), "...") && return true + end + issubset(kwnames, kws) +end + """ isambiguous(m1, m2; ambiguous_bottom=false) -> Bool diff --git a/test/reflection.jl b/test/reflection.jl index a74e21f12a27a1..1f2a6b73e6ef48 100644 --- a/test/reflection.jl +++ b/test/reflection.jl @@ -796,6 +796,27 @@ Base.delete_method(m) end +module HasmethodKwargs +using Test +f(x::Int; y=3) = x + y +@test hasmethod(f, Tuple{Int}) +@test hasmethod(f, Tuple{Int}, ()) +@test hasmethod(f, Tuple{Int}, (:y,)) +@test !hasmethod(f, Tuple{Int}, (:jeff,)) +@test !hasmethod(f, Tuple{Int}, (:y,), world=typemin(UInt)) +g(; b, c, a) = a + b + c +h(; kwargs...) = 4 +for gh = (g, h) + @test hasmethod(gh, Tuple{}) + @test hasmethod(gh, Tuple{}, ()) + @test hasmethod(gh, Tuple{}, (:a,)) + @test hasmethod(gh, Tuple{}, (:a, :b)) + @test hasmethod(gh, Tuple{}, (:a, :b, :c)) +end +@test !hasmethod(g, Tuple{}, (:a, :b, :c, :d)) +@test hasmethod(h, Tuple{}, (:a, :b, :c, :d)) +end + # issue #26267 module M26267 import Test