diff --git a/base/docs/helpdb/Base.jl b/base/docs/helpdb/Base.jl index eea1362e7b5b7..dd9315a18a4c8 100644 --- a/base/docs/helpdb/Base.jl +++ b/base/docs/helpdb/Base.jl @@ -6908,7 +6908,7 @@ EnvHash method_exists(f, Tuple type) -> Bool Determine whether the given generic function has a method matching the given -[`Tuple`](:obj:`Tuple`) of argument types. +[`Tuple`](:obj:`Tuple`) of argument types. See also `@method_exists`. ```jldoctest julia> method_exists(length, Tuple{Array}) diff --git a/base/exports.jl b/base/exports.jl index 97f15e908be3a..77f1b84571e09 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -1358,6 +1358,7 @@ export @which, @edit, @functionloc, + @method_exists, @less, @code_typed, @code_warntype, diff --git a/base/reflection.jl b/base/reflection.jl index 4e75ba95222ef..f4f8e43ff7ed4 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -440,6 +440,40 @@ function method_exists(f::ANY, t::ANY) return ccall(:jl_method_exists, Cint, (Any, Any), typeof(f).name.mt, t) != 0 end +# For static evaluation of method_exists, when it evaluates to true. +# We don't evaluate it at compile-time when false, because someone +# might define the missing method later. +""" + @method_exists(f, tt) + +Determine whether the given generic function `f` has a method matching +the given tuple `tt` of argument types. When used in a function, if +the method exists at the time the function is compiled, this statement +simply gets replaced by `true` (and hence has no runtime overhead). If +the function does not exist, then it is equivalent to the function +form, `method_exists`. + +```jldoctest +julia> @method_exists(length, Tuple{Array}) +true +``` +""" +:@method_exists + +macro method_exists(f, tt) + exmt = :(typeof($f).name.mt) # get the method table for f + # Form the tuple-type of the function + argument types + if tt.head == :curly && tt.args[1] == :Tuple + extt = :(Tuple{typeof($f), $(tt.args[2:end]...)}) + elseif tt.head == :tuple + extt = :(Tuple{typeof($f), $(tt.args...)}) + else + error("expected a tuple or tuple-type") + end + # Issue the ccall + esc(:(ccall(:jl_method_exists, Cint, (Any, Any), $exmt, $extt) != 0)) +end + function isambiguous(m1::Method, m2::Method) ti = typeintersect(m1.sig, m2.sig) ti === Bottom && return false diff --git a/doc/stdlib/base.rst b/doc/stdlib/base.rst index e4aeea4d14470..9bcd811ad5404 100644 --- a/doc/stdlib/base.rst +++ b/doc/stdlib/base.rst @@ -626,13 +626,24 @@ Generic Functions .. Docstring generated from Julia source - Determine whether the given generic function has a method matching the given :obj:`Tuple` of argument types. + Determine whether the given generic function has a method matching the given :obj:`Tuple` of argument types. See also ``@method_exists``\ . .. doctest:: julia> method_exists(length, Tuple{Array}) true +.. function:: @method_exists(f, tt) + + .. Docstring generated from Julia source + + Determine whether the given generic function ``f`` has a method matching the given tuple ``tt`` of argument types. When used in a function, if the method exists at the time the function is compiled, this statement simple gets replaced by ``true`` (and hence has no runtime overhead). If the function does not exist, then it is equivalent to the function form, ``method_exists``\ . + + .. doctest:: + + julia> @method_exists(length, Tuple{Array}) + true + .. function:: applicable(f, args...) -> Bool .. Docstring generated from Julia source diff --git a/src/ccall.cpp b/src/ccall.cpp index f33e919e80326..89ecbcf797694 100644 --- a/src/ccall.cpp +++ b/src/ccall.cpp @@ -1295,6 +1295,21 @@ static jl_cgval_t emit_ccall(jl_value_t **args, size_t nargs, jl_codectx_t *ctx) false, args[2], rt, static_rt, ctx); } } + if (fptr == (void(*)(void))&jl_method_exists || + ((f_lib==NULL || (intptr_t)f_lib==2) + && f_name && !strcmp(f_name, "jl_method_exists"))) { + assert(nargt == 2); + jl_value_t *mt = static_eval(args[4], ctx, false, false); + jl_value_t *usertypes = args[6]; + if (mt != NULL && jl_is_mtable(mt) && jl_is_tuple_type(usertypes)) { + int exists = jl_method_exists((jl_methtable_t*)mt, (jl_tupletype_t*)usertypes); + if (exists) { + JL_GC_POP(); + return mark_or_box_ccall_result(ConstantInt::get(T_int32, exists), + false, args[2], rt, static_rt, ctx); + } + } + } if (fptr == (void(*)(void))&jl_function_ptr || ((f_lib==NULL || (intptr_t)f_lib==2) && f_name && !strcmp(f_name, "jl_function_ptr"))) { diff --git a/src/codegen.cpp b/src/codegen.cpp index fade5478f78f0..ce8e0b1ffaa7f 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -1603,13 +1603,19 @@ jl_value_t *jl_static_eval(jl_value_t *ex, void *ctx_, jl_module_t *mod, jl_value_t *f = jl_static_eval(jl_exprarg(e,0),ctx,mod,linfo,sparams,allow_alloc); if (f) { if (jl_array_dim0(e->args) == 3 && f==jl_builtin_getfield) { - m = (jl_module_t*)jl_static_eval(jl_exprarg(e,1),ctx,mod,linfo,sparams,allow_alloc); + jl_value_t* e1 = jl_static_eval(jl_exprarg(e,1),ctx,mod,linfo,sparams,allow_alloc); s = (jl_sym_t*)jl_static_eval(jl_exprarg(e,2),ctx,mod,linfo,sparams,allow_alloc); - if (m && jl_is_module(m) && s && jl_is_symbol(s)) { - jl_binding_t *b = jl_get_binding(m, s); - if (b && b->constp) { - if (b->deprecated) cg_bdw(b, ctx); - return b->value; + if (s && jl_is_symbol(s)) { + if (e1 && jl_is_module(e1)) { + m = (jl_module_t*)e1; + jl_binding_t *b = jl_get_binding(m, s); + if (b && b->constp) { + if (b->deprecated) cg_bdw(b, ctx); + return b->value; + } + } + else if (e1 && (jl_is_datatype(e1) || jl_is_typename(e1))) { + return jl_get_field(e1, jl_symbol_name(s)); } } } diff --git a/src/julia.h b/src/julia.h index b60dca6dae4cb..092ca22862f2b 100644 --- a/src/julia.h +++ b/src/julia.h @@ -1209,6 +1209,9 @@ STATIC_INLINE jl_function_t *jl_get_function(jl_module_t *m, const char *name) JL_DLLEXPORT void jl_module_run_initializer(jl_module_t *m); int jl_is_submodule(jl_module_t *child, jl_module_t *parent); +// methods +JL_DLLEXPORT int jl_method_exists(jl_methtable_t *mt, jl_tupletype_t *types); + // eq hash tables JL_DLLEXPORT jl_array_t *jl_eqtable_put(jl_array_t *h, void *key, void *val); JL_DLLEXPORT jl_value_t *jl_eqtable_get(jl_array_t *h, void *key, diff --git a/test/reflection.jl b/test/reflection.jl index 31abbdf509b5d..ab15f3049a0b1 100644 --- a/test/reflection.jl +++ b/test/reflection.jl @@ -439,3 +439,44 @@ fLargeTable() = 4 # issue #15280 function f15280(x) end @test functionloc(f15280)[2] > 0 + +# Static evaluation of method_exists (#16422) +meth16422(x::Int, y::Int) = 0 +function static_meth_exists_tt() + # As a tuple-type + if @method_exists(meth16422, Tuple{Int,Int}) + return true + end + false +end +@test static_meth_exists_tt() +io = IOBuffer() +code_llvm(io, static_meth_exists_tt, ()) +str = takebuf_string(io) +@test contains(str, "top:\n ret i1 true\n}") + +function static_meth_exists_t() + # As a tuple + if @method_exists(meth16422, (Int,Int)) + return true + end + false +end +@test static_meth_exists_t() +io = IOBuffer() +code_llvm(io, static_meth_exists_t, ()) +str = takebuf_string(io) +@test contains(str, "top:\n ret i1 true\n}") + +function static_meth_not_exists() + if @method_exists(meth16422, Tuple{Int,String}) + return true + end + false +end +@test !static_meth_not_exists() +code_llvm(io, static_meth_not_exists, ()) +str = takebuf_string(io) +@test !contains(str, "ret i1 true") && !contains(str, "ret i1 false") +meth16422(x::Int, y::String) = 1 +@test static_meth_not_exists() # define it later, should be true