From f7ece4dae54c04e99fdab96eb22f52d73d0504a8 Mon Sep 17 00:00:00 2001 From: Alex Arslan Date: Sun, 13 Jan 2019 18:22:36 -0800 Subject: [PATCH] Add a keyword name checking method to hasmethod With this change, `hasmethod` now accepts a tuple of symbols corresponding to keyword argument names to check for when finding a matching method. --- NEWS.md | 1 + base/reflection.jl | 34 +++++++++++++++++++++++++++++++++- test/reflection.jl | 21 +++++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index cd0799ca681b8..5762c168a5d9e 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 d201ecdff72c9..a5c05ea6701df 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -1084,17 +1084,37 @@ 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...`, any names given in `kwnames` are considered valid. 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 +1123,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 a74e21f12a27a..1f2a6b73e6ef4 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