diff --git a/NEWS.md b/NEWS.md index ebe550653a5b8..e73f7f31dc204 100644 --- a/NEWS.md +++ b/NEWS.md @@ -51,6 +51,7 @@ Compiler/Runtime improvements calls ([#43239]). * Abstract callsite can now be inlined or statically resolved as far as the callsite has a single matching method ([#43113]). +* Builtin function are now a bit more like generic functions, and can be enumerated with `methods` ([#43865]). Command-line option changes --------------------------- diff --git a/base/compiler/typeinfer.jl b/base/compiler/typeinfer.jl index 47951336035ef..42af383157c13 100644 --- a/base/compiler/typeinfer.jl +++ b/base/compiler/typeinfer.jl @@ -958,20 +958,36 @@ function typeinf_ext_toplevel(interp::AbstractInterpreter, linfo::MethodInstance return src end -function return_type(@nospecialize(f), @nospecialize(t)) +function return_type(@nospecialize(f), t::DataType) # this method has a special tfunc world = ccall(:jl_get_tls_world_age, UInt, ()) - return ccall(:jl_call_in_typeinf_world, Any, (Ptr{Ptr{Cvoid}}, Cint), Any[_return_type, f, t, world], 4) + args = Any[_return_type, NativeInterpreter(world), Tuple{Core.Typeof(f), t.parameters...}] + return ccall(:jl_call_in_typeinf_world, Any, (Ptr{Ptr{Cvoid}}, Cint), args, length(args)) end -_return_type(@nospecialize(f), @nospecialize(t), world) = _return_type(NativeInterpreter(world), f, t) +function return_type(@nospecialize(f), t::DataType, world::UInt) + return return_type(Tuple{Core.Typeof(f), t.parameters...}, world) +end + +function return_type(t::DataType) + world = ccall(:jl_get_tls_world_age, UInt, ()) + return return_type(t, world) +end + +function return_type(t::DataType, world::UInt) + args = Any[_return_type, NativeInterpreter(world), t] + return ccall(:jl_call_in_typeinf_world, Any, (Ptr{Ptr{Cvoid}}, Cint), args, length(args)) +end -function _return_type(interp::AbstractInterpreter, @nospecialize(f), @nospecialize(t)) +function _return_type(interp::AbstractInterpreter, t::DataType) rt = Union{} + f = singleton_type(t.parameters[1]) if isa(f, Builtin) - rt = builtin_tfunction(interp, f, Any[t.parameters...], nothing) + args = Any[t.parameters...] + popfirst!(args) + rt = builtin_tfunction(interp, f, args, nothing) rt = widenconst(rt) else - for match in _methods(f, t, -1, get_world_counter(interp))::Vector + for match in _methods_by_ftype(t, -1, get_world_counter(interp))::Vector match = match::MethodMatch ty = typeinf_type(interp, match.method, match.spec_types, match.sparams) ty === nothing && return Any diff --git a/base/methodshow.jl b/base/methodshow.jl index 937ff02a1786d..18bfdaa6164d0 100644 --- a/base/methodshow.jl +++ b/base/methodshow.jl @@ -224,6 +224,7 @@ function show(io::IO, m::Method) file, line = updated_methodloc(m) print(io, " at ", file, ":", line) end + nothing end function show_method_list_header(io::IO, ms::MethodList, namefmt::Function) @@ -232,24 +233,27 @@ function show_method_list_header(io::IO, ms::MethodList, namefmt::Function) hasname = isdefined(mt.module, name) && typeof(getfield(mt.module, name)) <: Function n = length(ms) - if mt.module === Core && n == 0 && mt.defs === nothing && mt.cache !== nothing - # try to detect Builtin - print(io, "# built-in function; no methods") + m = n==1 ? "method" : "methods" + print(io, "# $n $m") + sname = string(name) + namedisplay = namefmt(sname) + if hasname + what = (startswith(sname, '@') ? + "macro" + : mt.module === Core && last(ms).sig === Tuple ? + "builtin function" + : # else + "generic function") + print(io, " for ", what, " ", namedisplay) + elseif '#' in sname + print(io, " for anonymous function ", namedisplay) + elseif mt === _TYPE_NAME.mt + print(io, " for type constructor") else - m = n==1 ? "method" : "methods" - print(io, "# $n $m") - sname = string(name) - namedisplay = namefmt(sname) - if hasname - what = startswith(sname, '@') ? "macro" : "generic function" - print(io, " for ", what, " ", namedisplay) - elseif '#' in sname - print(io, " for anonymous function ", namedisplay) - elseif mt === _TYPE_NAME.mt - print(io, " for type constructor") - end - print(io, ":") + print(io, " for callable object") end + n > 0 && print(io, ":") + nothing end function show_method_table(io::IO, ms::MethodList, max::Int=-1, header::Bool=true) @@ -267,7 +271,7 @@ function show_method_table(io::IO, ms::MethodList, max::Int=-1, header::Bool=tru last_shown_line_infos === nothing || empty!(last_shown_line_infos) for meth in ms - if max==-1 || n a comprehension triggers too many invalidations via _collect, so collect the methods manually @@ -988,15 +977,12 @@ function methods(@nospecialize(f), @nospecialize(t), end methods(@nospecialize(f), @nospecialize(t), mod::Module) = methods(f, t, (mod,)) -methods(f::Core.Builtin) = MethodList(Method[], typeof(f).name.mt) - function methods_including_ambiguous(@nospecialize(f), @nospecialize(t)) tt = signature_type(f, t) world = get_world_counter() min = RefValue{UInt}(typemin(UInt)) max = RefValue{UInt}(typemax(UInt)) - ms = _methods_by_ftype(tt, nothing, -1, world, true, min, max, Ptr{Int32}(C_NULL)) - isa(ms, Bool) && return ms + ms = _methods_by_ftype(tt, nothing, -1, world, true, min, max, Ptr{Int32}(C_NULL))::Vector return MethodList(Method[(m::Core.MethodMatch).method for m in ms], typeof(f).name.mt) end @@ -1213,9 +1199,6 @@ function code_typed(@nospecialize(f), @nospecialize(types=default_tt(f)); debuginfo::Symbol=:default, world = get_world_counter(), interp = Core.Compiler.NativeInterpreter(world)) - if isa(f, Core.Builtin) - throw(ArgumentError("argument is not a generic function")) - end if isa(f, Core.OpaqueClosure) return code_typed_opaque_closure(f; optimize, debuginfo, interp) end @@ -1262,18 +1245,18 @@ function code_typed_by_type(@nospecialize(tt#=::Type=#); throw(ArgumentError("'debuginfo' must be either :source or :none")) end tt = to_tuple_type(tt) - matches = _methods_by_ftype(tt, -1, world) - if matches === false - error("signature does not correspond to a generic function") - end + matches = _methods_by_ftype(tt, -1, world)::Vector asts = [] - for match in matches::Vector + for match in matches match = match::Core.MethodMatch meth = func_for_method_checked(match.method, tt, match.sparams) (code, ty) = Core.Compiler.typeinf_code(interp, meth, match.spec_types, match.sparams, optimize) - code === nothing && error("inference not successful") # inference disabled? - debuginfo === :none && remove_linenums!(code) - push!(asts, code => ty) + if code === nothing + push!(asts, meth => Any) + else + debuginfo === :none && remove_linenums!(code) + push!(asts, code => ty) + end end return asts end @@ -1295,9 +1278,6 @@ end function return_types(@nospecialize(f), @nospecialize(types=default_tt(f)), interp=Core.Compiler.NativeInterpreter()) ccall(:jl_is_in_pure_context, Bool, ()) && error("code reflection cannot be used from generated functions") - if isa(f, Core.Builtin) - throw(ArgumentError("argument is not a generic function")) - end types = to_tuple_type(types) rt = [] world = get_world_counter() @@ -1305,8 +1285,7 @@ function return_types(@nospecialize(f), @nospecialize(types=default_tt(f)), inte match = match::Core.MethodMatch meth = func_for_method_checked(match.method, types, match.sparams) ty = Core.Compiler.typeinf_type(interp, meth, match.spec_types, match.sparams) - ty === nothing && error("inference not successful") # inference disabled? - push!(rt, ty) + push!(rt, something(ty, Any)) end return rt end @@ -1318,9 +1297,6 @@ Print type-inferred and optimized code for `f` given argument types `types`, prepending each line with its cost as estimated by the compiler's inlining engine. """ function print_statement_costs(io::IO, @nospecialize(f), @nospecialize(t); kwargs...) - if isa(f, Core.Builtin) - throw(ArgumentError("argument is not a generic function")) - end tt = signature_type(f, t) print_statement_costs(io, tt; kwargs...) end @@ -1328,27 +1304,27 @@ end function print_statement_costs(io::IO, @nospecialize(tt#=::Type=#); world = get_world_counter(), interp = Core.Compiler.NativeInterpreter(world)) - matches = _methods_by_ftype(tt, -1, world) - if matches === false - error("signature does not correspond to a generic function") - end + matches = _methods_by_ftype(tt, -1, world)::Vector params = Core.Compiler.OptimizationParams(interp) cst = Int[] - for match in matches::Vector + for match in matches match = match::Core.MethodMatch meth = func_for_method_checked(match.method, tt, match.sparams) - (code, ty) = Core.Compiler.typeinf_code(interp, meth, match.spec_types, match.sparams, true) - code === nothing && error("inference not successful") # inference disabled? - empty!(cst) - resize!(cst, length(code.code)) - maxcost = Core.Compiler.statement_costs!(cst, code.code, code, Any[match.sparams...], false, params) - nd = ndigits(maxcost) println(io, meth) - irshow_config = IRShow.IRShowConfig() do io, linestart, idx - print(io, idx > 0 ? lpad(cst[idx], nd+1) : " "^(nd+1), " ") - return "" + (code, ty) = Core.Compiler.typeinf_code(interp, meth, match.spec_types, match.sparams, true) + if code === nothing + println(io, " inference not successful") + else + empty!(cst) + resize!(cst, length(code.code)) + maxcost = Core.Compiler.statement_costs!(cst, code.code, code, Any[match.sparams...], false, params) + nd = ndigits(maxcost) + irshow_config = IRShow.IRShowConfig() do io, linestart, idx + print(io, idx > 0 ? lpad(cst[idx], nd+1) : " "^(nd+1), " ") + return "" + end + IRShow.show_ir(io, code, irshow_config) end - IRShow.show_ir(io, code, irshow_config) println(io) end end @@ -1377,9 +1353,6 @@ If `types` is an abstract type, then the method that would be called by `invoke` See also: [`parentmodule`](@ref), and `@which` and `@edit` in [`InteractiveUtils`](@ref man-interactive-utils). """ function which(@nospecialize(f), @nospecialize(t)) - if isa(f, Core.Builtin) - throw(ArgumentError("argument is not a generic function")) - end t = to_tuple_type(t) tt = signature_type(f, t) return which(tt) diff --git a/src/builtins.c b/src/builtins.c index 900a3c44dc4ec..b5368ad36a164 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -1771,8 +1771,9 @@ static void add_builtin(const char *name, jl_value_t *v) jl_fptr_args_t jl_get_builtin_fptr(jl_value_t *b) { assert(jl_isa(b, (jl_value_t*)jl_builtin_type)); - jl_typemap_entry_t *entry = (jl_typemap_entry_t*)jl_atomic_load_relaxed(&jl_gf_mtable(b)->cache); - jl_code_instance_t *ci = jl_atomic_load_relaxed(&entry->func.linfo->cache); + jl_typemap_entry_t *entry = (jl_typemap_entry_t*)jl_atomic_load_relaxed(&jl_gf_mtable(b)->defs); + jl_method_instance_t *mi = jl_atomic_load_relaxed(&entry->func.method->unspecialized); + jl_code_instance_t *ci = jl_atomic_load_relaxed(&mi->cache); return jl_atomic_load_relaxed(&ci->specptr.fptr1); } diff --git a/src/gf.c b/src/gf.c index 27b19e50c84ea..b111c3f2c0b86 100644 --- a/src/gf.c +++ b/src/gf.c @@ -101,6 +101,8 @@ static int speccache_eq(size_t idx, const void *ty, jl_svec_t *data, uint_t hv) // get or create the MethodInstance for a specialization JL_DLLEXPORT jl_method_instance_t *jl_specializations_get_linfo(jl_method_t *m JL_PROPAGATES_ROOT, jl_value_t *type, jl_svec_t *sparams) { + if (m->sig == (jl_value_t*)jl_anytuple_type && m->unspecialized) + return m->unspecialized; // handle builtin methods jl_value_t *ut = jl_is_unionall(type) ? jl_unwrap_unionall(type) : type; JL_TYPECHK(specializations, datatype, ut); uint_t hv = ((jl_datatype_t*)ut)->hash; @@ -224,9 +226,17 @@ jl_datatype_t *jl_mk_builtin_func(jl_datatype_t *dt, const char *name, jl_fptr_a m->nargs = 2; m->sig = (jl_value_t*)jl_anytuple_type; m->slot_syms = jl_an_empty_string; + m->nospecialize = 0; + m->nospecialize = ~m->nospecialize; + jl_methtable_t *mt = dt->name->mt; jl_typemap_entry_t *newentry = NULL; JL_GC_PUSH2(&m, &newentry); + + newentry = jl_typemap_alloc(jl_anytuple_type, NULL, jl_emptysvec, + (jl_value_t*)m, 1, ~(size_t)0); + jl_typemap_insert(&mt->defs, (jl_value_t*)mt, newentry, 0); + jl_method_instance_t *mi = jl_get_specialized(m, (jl_value_t*)jl_anytuple_type, jl_emptysvec); m->unspecialized = mi; jl_gc_wb(m, mi); @@ -238,7 +248,6 @@ jl_datatype_t *jl_mk_builtin_func(jl_datatype_t *dt, const char *name, jl_fptr_a codeinst->specptr.fptr1 = fptr; codeinst->invoke = jl_fptr_args; - jl_methtable_t *mt = dt->name->mt; newentry = jl_typemap_alloc(jl_anytuple_type, NULL, jl_emptysvec, (jl_value_t*)mi, 1, ~(size_t)0); jl_typemap_insert(&mt->cache, (jl_value_t*)mt, newentry, 0); @@ -424,15 +433,13 @@ static int get_method_unspec_list(jl_typemap_entry_t *def, void *closure) return 1; } -static void foreach_mtable_in_module( +static int foreach_mtable_in_module( jl_module_t *m, - void (*visit)(jl_methtable_t *mt, void *env), - void *env, - jl_array_t **visited) + int (*visit)(jl_methtable_t *mt, void *env), + void *env) { size_t i; void **table = m->bindings.table; - *visited = jl_eqtable_put(*visited, (jl_value_t*)m, jl_true, NULL); for (i = 1; i < m->bindings.size; i += 2) { if (table[i] != HT_NOTFOUND) { jl_binding_t *b = (jl_binding_t*)table[i]; @@ -440,58 +447,67 @@ static void foreach_mtable_in_module( jl_value_t *v = jl_unwrap_unionall(b->value); if (jl_is_datatype(v)) { jl_typename_t *tn = ((jl_datatype_t*)v)->name; - if (tn->module == m && tn->name == b->name) { + if (tn->module == m && tn->name == b->name && tn->wrapper == b->value) { jl_methtable_t *mt = tn->mt; if (mt != NULL && (jl_value_t*)mt != jl_nothing && mt != jl_type_type_mt && mt != jl_nonfunction_mt) { - visit(mt, env); + if (!visit(mt, env)) + return 0; } } } else if (jl_is_module(v)) { jl_module_t *child = (jl_module_t*)v; - if (child != m && child->parent == m && child->name == b->name && - !jl_eqtable_get(*visited, v, NULL)) { + if (child != m && child->parent == m && child->name == b->name) { // this is the original/primary binding for the submodule - foreach_mtable_in_module(child, visit, env, visited); + if (!foreach_mtable_in_module(child, visit, env)) + return 0; } } } } } + return 1; } -void jl_foreach_reachable_mtable(void (*visit)(jl_methtable_t *mt, void *env), void *env) +int jl_foreach_reachable_mtable(int (*visit)(jl_methtable_t *mt, void *env), void *env) { - jl_array_t *visited = jl_alloc_vec_any(16); - jl_array_t *mod_array = NULL; - JL_GC_PUSH2(&visited, &mod_array); - mod_array = jl_get_loaded_modules(); - visit(jl_type_type_mt, env); - visit(jl_nonfunction_mt, env); + if (!visit(jl_type_type_mt, env)) + return 0; + if (!visit(jl_nonfunction_mt, env)) + return 0; + jl_array_t *mod_array = jl_get_loaded_modules(); if (mod_array) { + JL_GC_PUSH1(&mod_array); int i; for (i = 0; i < jl_array_len(mod_array); i++) { jl_module_t *m = (jl_module_t*)jl_array_ptr_ref(mod_array, i); assert(jl_is_module(m)); - if (!jl_eqtable_get(visited, (jl_value_t*)m, NULL)) - foreach_mtable_in_module(m, visit, env, &visited); + if (m->parent == m) // some toplevel modules (really just Base) aren't actually + if (!foreach_mtable_in_module(m, visit, env)) { + JL_GC_POP(); + return 0; + } } + JL_GC_POP(); } else { - foreach_mtable_in_module(jl_main_module, visit, env, &visited); - foreach_mtable_in_module(jl_core_module, visit, env, &visited); + if (!foreach_mtable_in_module(jl_main_module, visit, env)) + return 0; + if (!foreach_mtable_in_module(jl_core_module, visit, env)) + return 0; } - JL_GC_POP(); + return 1; } -static void reset_mt_caches(jl_methtable_t *mt, void *env) +static int reset_mt_caches(jl_methtable_t *mt, void *env) { // removes all method caches - if (mt->defs != jl_nothing) { // make sure not to reset builtin functions + if (!mt->frozen) { // make sure not to reset builtin functions mt->leafcache = (jl_array_t*)jl_an_empty_vec_any; mt->cache = jl_nothing; } jl_typemap_visitor(mt->defs, get_method_unspec_list, env); + return 1; } @@ -558,7 +574,7 @@ jl_value_t *jl_nth_slot_type(jl_value_t *sig, size_t i) JL_NOTSAFEPOINT // return 1; //} -static jl_value_t *ml_matches(jl_methtable_t *mt, int offs, +static jl_value_t *ml_matches(jl_methtable_t *mt, jl_tupletype_t *type, int lim, int include_ambiguous, int intersections, size_t world, int cache_result, size_t *min_valid, size_t *max_valid, int *ambig); @@ -577,6 +593,10 @@ static void jl_compilation_sig( // so assume the caller was intelligent about calling us return; } + if (definition->sig == (jl_value_t*)jl_anytuple_type && definition->unspecialized) { + *newparams = jl_anytuple_type->parameters; // handle builtin methods + return; + } jl_value_t *decl = definition->sig; assert(jl_is_tuple_type(tt)); @@ -796,6 +816,8 @@ JL_DLLEXPORT int jl_isa_compileable_sig( if (!jl_is_datatype(type) || jl_has_free_typevars((jl_value_t*)type)) return 0; + if (definition->sig == (jl_value_t*)jl_anytuple_type && definition->unspecialized) + return jl_egal((jl_value_t*)type, definition->sig); // handle builtin methods size_t i, np = jl_nparams(type); size_t nargs = definition->nargs; // == jl_nparams(jl_unwrap_unionall(decl)); @@ -1044,7 +1066,7 @@ static jl_method_instance_t *cache_method( // now examine what will happen if we chose to use this sig in the cache size_t min_valid2 = 1; size_t max_valid2 = ~(size_t)0; - temp = ml_matches(mt, 0, compilationsig, MAX_UNSPECIALIZED_CONFLICTS, 1, 1, world, 0, &min_valid2, &max_valid2, NULL); + temp = ml_matches(mt, compilationsig, MAX_UNSPECIALIZED_CONFLICTS, 1, 1, world, 0, &min_valid2, &max_valid2, NULL); int guards = 0; if (temp == jl_false) { cache_with_orig = 1; @@ -1893,9 +1915,9 @@ JL_DLLEXPORT jl_value_t *jl_matching_methods(jl_tupletype_t *types, jl_value_t * return (jl_value_t*)jl_an_empty_vec_any; if (mt == jl_nothing) mt = (jl_value_t*)jl_method_table_for(unw); - if ((jl_value_t*)mt == jl_nothing) - return jl_false; // indeterminate - ml_matches can't deal with this case - return ml_matches((jl_methtable_t*)mt, 0, types, lim, include_ambiguous, 1, world, 1, min_valid, max_valid, ambig); + if (mt == jl_nothing) + mt = NULL; + return ml_matches((jl_methtable_t*)mt, types, lim, include_ambiguous, 1, world, 1, min_valid, max_valid, ambig); } jl_method_instance_t *jl_get_unspecialized(jl_method_instance_t *method JL_PROPAGATES_ROOT) @@ -2493,8 +2515,8 @@ static jl_method_match_t *_gf_invoke_lookup(jl_value_t *types JL_PROPAGATES_ROOT return NULL; jl_methtable_t *mt = jl_method_table_for(unw); if ((jl_value_t*)mt == jl_nothing) - return NULL; - jl_value_t *matches = ml_matches(mt, 0, (jl_tupletype_t*)types, 1, 0, 0, world, 1, min_valid, max_valid, NULL); + mt = NULL; + jl_value_t *matches = ml_matches(mt, (jl_tupletype_t*)types, 1, 0, 0, world, 1, min_valid, max_valid, NULL); if (matches == jl_false || jl_array_len(matches) != 1) return NULL; jl_method_match_t *matc = (jl_method_match_t*)jl_array_ptr_ref(matches, 0); @@ -2723,6 +2745,11 @@ static int ml_matches_visitor(jl_typemap_entry_t *ml, struct typemap_intersectio return 1; } +static int ml_mtable_visitor(jl_methtable_t *mt, void *env) +{ + return jl_typemap_intersection_visitor(mt->defs, 0, (struct typemap_intersection_env*)env); +} + // This is the collect form of calling jl_typemap_intersection_visitor // with optimizations to skip fully shadowed methods. // @@ -2733,15 +2760,12 @@ static int ml_matches_visitor(jl_typemap_entry_t *ml, struct typemap_intersectio // fully-covers is a Bool indicating subtyping, though temporarily it may be // tri-values, with `nothing` indicating a match that is not a subtype, but // which is dominated by one which is (and thus should be excluded unless ambiguous) -static jl_value_t *ml_matches(jl_methtable_t *mt, int offs, +static jl_value_t *ml_matches(jl_methtable_t *mt, jl_tupletype_t *type, int lim, int include_ambiguous, int intersections, size_t world, int cache_result, size_t *min_valid, size_t *max_valid, int *ambig) { int has_ambiguity = 0; - jl_typemap_t *defs = mt->defs; - if (defs == jl_nothing) // special-case: ignore builtin functions - return jl_an_empty_vec_any; jl_value_t *unw = jl_unwrap_unionall((jl_value_t*)type); assert(jl_is_datatype(unw)); size_t l = jl_svec_len(((jl_datatype_t*)unw)->parameters); @@ -2761,64 +2785,75 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, int offs, jl_value_t *isect2 = NULL; JL_GC_PUSH6(&env.t, &env.matc, &env.match.env, &search.env, &env.match.ti, &isect2); - // check the leaf cache if this type can be in there - if (((jl_datatype_t*)unw)->isdispatchtuple) { - jl_array_t *leafcache = jl_atomic_load_relaxed(&mt->leafcache); - jl_typemap_entry_t *entry = lookup_leafcache(leafcache, (jl_value_t*)type, world); - if (entry) { - jl_method_instance_t *mi = entry->func.linfo; - jl_method_t *meth = mi->def.method; - if (!jl_is_unionall(meth->sig)) { - env.match.env = jl_emptysvec; - env.match.ti = unw; - } - else if (jl_egal((jl_value_t*)type, mi->specTypes)) { - env.match.env = mi->sparam_vals; - env.match.ti = mi->specTypes; - } - else { - // this just calls jl_subtype_env (since we know that `type <: meth->sig` by transitivity) - env.match.ti = jl_type_intersection_env((jl_value_t*)type, (jl_value_t*)meth->sig, &env.match.env); - } - env.matc = make_method_match((jl_tupletype_t*)env.match.ti, - env.match.env, meth, FULLY_COVERS); - env.t = (jl_value_t*)jl_alloc_vec_any(1); - jl_array_ptr_set(env.t, 0, env.matc); - if (*min_valid < entry->min_world) - *min_valid = entry->min_world; - if (*max_valid > entry->max_world) - *max_valid = entry->max_world; - JL_GC_POP(); - return env.t; - } - } - // then check the full cache if it seems profitable - if (((jl_datatype_t*)unw)->isdispatchtuple) { - jl_typemap_entry_t *entry = jl_typemap_assoc_by_type(jl_atomic_load_relaxed(&mt->cache), &search, jl_cachearg_offset(mt), /*subtype*/1); - if (entry && (((jl_datatype_t*)unw)->isdispatchtuple || entry->guardsigs == jl_emptysvec)) { - jl_method_instance_t *mi = entry->func.linfo; - jl_method_t *meth = mi->def.method; - if (!jl_is_unionall(meth->sig) && ((jl_datatype_t*)unw)->isdispatchtuple) { - env.match.env = jl_emptysvec; - env.match.ti = unw; + if (mt) { + // check the leaf cache if this type can be in there + if (((jl_datatype_t*)unw)->isdispatchtuple) { + jl_array_t *leafcache = jl_atomic_load_relaxed(&mt->leafcache); + jl_typemap_entry_t *entry = lookup_leafcache(leafcache, (jl_value_t*)type, world); + if (entry) { + jl_method_instance_t *mi = entry->func.linfo; + jl_method_t *meth = mi->def.method; + if (!jl_is_unionall(meth->sig)) { + env.match.env = jl_emptysvec; + env.match.ti = unw; + } + else if (jl_egal((jl_value_t*)type, mi->specTypes)) { + env.match.env = mi->sparam_vals; + env.match.ti = mi->specTypes; + } + else { + // this just calls jl_subtype_env (since we know that `type <: meth->sig` by transitivity) + env.match.ti = jl_type_intersection_env((jl_value_t*)type, (jl_value_t*)meth->sig, &env.match.env); + } + env.matc = make_method_match((jl_tupletype_t*)env.match.ti, + env.match.env, meth, FULLY_COVERS); + env.t = (jl_value_t*)jl_alloc_vec_any(1); + jl_array_ptr_set(env.t, 0, env.matc); + if (*min_valid < entry->min_world) + *min_valid = entry->min_world; + if (*max_valid > entry->max_world) + *max_valid = entry->max_world; + JL_GC_POP(); + return env.t; } - else { - // this just calls jl_subtype_env (since we know that `type <: meth->sig` by transitivity) - env.match.ti = jl_type_intersection_env((jl_value_t*)type, (jl_value_t*)meth->sig, &env.match.env); + } + // then check the full cache if it seems profitable + if (((jl_datatype_t*)unw)->isdispatchtuple) { + jl_typemap_entry_t *entry = jl_typemap_assoc_by_type(jl_atomic_load_relaxed(&mt->cache), &search, jl_cachearg_offset(mt), /*subtype*/1); + if (entry && (((jl_datatype_t*)unw)->isdispatchtuple || entry->guardsigs == jl_emptysvec)) { + jl_method_instance_t *mi = entry->func.linfo; + jl_method_t *meth = mi->def.method; + if (!jl_is_unionall(meth->sig) && ((jl_datatype_t*)unw)->isdispatchtuple) { + env.match.env = jl_emptysvec; + env.match.ti = unw; + } + else { + // this just calls jl_subtype_env (since we know that `type <: meth->sig` by transitivity) + env.match.ti = jl_type_intersection_env((jl_value_t*)type, (jl_value_t*)meth->sig, &env.match.env); + } + env.matc = make_method_match((jl_tupletype_t*)env.match.ti, + env.match.env, meth, FULLY_COVERS); + env.t = (jl_value_t*)jl_alloc_vec_any(1); + jl_array_ptr_set(env.t, 0, env.matc); + if (*min_valid < entry->min_world) + *min_valid = entry->min_world; + if (*max_valid > entry->max_world) + *max_valid = entry->max_world; + JL_GC_POP(); + return env.t; } - env.matc = make_method_match((jl_tupletype_t*)env.match.ti, - env.match.env, meth, FULLY_COVERS); - env.t = (jl_value_t*)jl_alloc_vec_any(1); - jl_array_ptr_set(env.t, 0, env.matc); - *min_valid = entry->min_world; - *max_valid = entry->max_world; + } + if (!jl_typemap_intersection_visitor(mt->defs, 0, &env.match)) { JL_GC_POP(); - return env.t; + return jl_false; } } - if (!jl_typemap_intersection_visitor(defs, offs, &env.match)) { - JL_GC_POP(); - return jl_false; + else { + // else: scan everything + if (!jl_foreach_reachable_mtable(ml_mtable_visitor, &env.match)) { + JL_GC_POP(); + return jl_false; + } } *min_valid = env.min_valid; *max_valid = env.max_valid; @@ -3180,7 +3215,7 @@ static jl_value_t *ml_matches(jl_methtable_t *mt, int offs, jl_array_del_end((jl_array_t*)env.t, len - j); len = j; } - if (cache_result && ((jl_datatype_t*)unw)->isdispatchtuple) { // cache_result parameter keeps this from being recursive + if (mt && cache_result && ((jl_datatype_t*)unw)->isdispatchtuple) { // cache_result parameter keeps this from being recursive if (len == 1 && !has_ambiguity) { env.matc = (jl_method_match_t*)jl_array_ptr_ref(env.t, 0); jl_method_t *meth = env.matc->method; diff --git a/src/jlapi.c b/src/jlapi.c index 4e1d92e241c04..3ab01c5def7f4 100644 --- a/src/jlapi.c +++ b/src/jlapi.c @@ -173,7 +173,7 @@ JL_DLLEXPORT const char *jl_string_ptr(jl_value_t *s) return jl_string_data(s); } -JL_DLLEXPORT jl_value_t *jl_call(jl_function_t *f, jl_value_t **args, int32_t nargs) +JL_DLLEXPORT jl_value_t *jl_call(jl_function_t *f, jl_value_t **args, uint32_t nargs) { jl_value_t *v; jl_task_t *ct = jl_current_task; diff --git a/src/julia.h b/src/julia.h index 1f1186606d8cc..9b7e93dda7119 100644 --- a/src/julia.h +++ b/src/julia.h @@ -307,11 +307,11 @@ typedef struct _jl_method_t { // the default recusion relation. jl_value_t *recursion_relation; - int32_t nargs; - int32_t called; // bit flags: whether each of the first 8 arguments is called - int32_t nospecialize; // bit flags: which arguments should not be specialized - int32_t nkw; // # of leading arguments that are actually keyword arguments - // of another method. + uint32_t nargs; + uint32_t called; // bit flags: whether each of the first 8 arguments is called + uint32_t nospecialize; // bit flags: which arguments should not be specialized + uint32_t nkw; // # of leading arguments that are actually keyword arguments + // of another method. uint8_t isva; uint8_t pure; uint8_t is_for_opaque_closure; @@ -1781,7 +1781,7 @@ STATIC_INLINE jl_value_t *jl_apply(jl_value_t **args, uint32_t nargs) return jl_apply_generic(args[0], &args[1], nargs - 1); } -JL_DLLEXPORT jl_value_t *jl_call(jl_function_t *f JL_MAYBE_UNROOTED, jl_value_t **args, int32_t nargs); +JL_DLLEXPORT jl_value_t *jl_call(jl_function_t *f JL_MAYBE_UNROOTED, jl_value_t **args, uint32_t nargs); JL_DLLEXPORT jl_value_t *jl_call0(jl_function_t *f JL_MAYBE_UNROOTED); JL_DLLEXPORT jl_value_t *jl_call1(jl_function_t *f JL_MAYBE_UNROOTED, jl_value_t *a JL_MAYBE_UNROOTED); JL_DLLEXPORT jl_value_t *jl_call2(jl_function_t *f JL_MAYBE_UNROOTED, jl_value_t *a JL_MAYBE_UNROOTED, jl_value_t *b JL_MAYBE_UNROOTED); diff --git a/src/julia_internal.h b/src/julia_internal.h index e5ba0b23a2a66..c76d133d270f7 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -607,7 +607,7 @@ jl_value_t *replace_nth_field(jl_datatype_t *st, jl_value_t *v, size_t i, jl_val jl_expr_t *jl_exprn(jl_sym_t *head, size_t n); jl_function_t *jl_new_generic_function(jl_sym_t *name, jl_module_t *module); jl_function_t *jl_new_generic_function_with_supertype(jl_sym_t *name, jl_module_t *module, jl_datatype_t *st); -void jl_foreach_reachable_mtable(void (*visit)(jl_methtable_t *mt, void *env), void *env); +int jl_foreach_reachable_mtable(int (*visit)(jl_methtable_t *mt, void *env), void *env); void jl_init_main_module(void); JL_DLLEXPORT int jl_is_submodule(jl_module_t *child, jl_module_t *parent) JL_NOTSAFEPOINT; jl_array_t *jl_get_loaded_modules(void); diff --git a/src/precompile.c b/src/precompile.c index df0a3aa897587..5a43dc45f094e 100644 --- a/src/precompile.c +++ b/src/precompile.c @@ -294,9 +294,10 @@ static int compile_all_enq__(jl_typemap_entry_t *ml, void *env) } -static void compile_all_enq_(jl_methtable_t *mt, void *env) +static int compile_all_enq_(jl_methtable_t *mt, void *env) { jl_typemap_visitor(mt->defs, compile_all_enq__, env); + return 1; } static void jl_compile_all_defs(void) @@ -363,9 +364,9 @@ static int precompile_enq_all_specializations__(jl_typemap_entry_t *def, void *c return 1; } -static void precompile_enq_all_specializations_(jl_methtable_t *mt, void *env) +static int precompile_enq_all_specializations_(jl_methtable_t *mt, void *env) { - jl_typemap_visitor(mt->defs, precompile_enq_all_specializations__, env); + return jl_typemap_visitor(mt->defs, precompile_enq_all_specializations__, env); } static void *jl_precompile(int all) diff --git a/src/staticdata.c b/src/staticdata.c index 9291d2ba90f09..68fc08d317a8c 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -1678,9 +1678,9 @@ static int strip_all_codeinfos__(jl_typemap_entry_t *def, void *_env) return 1; } -static void strip_all_codeinfos_(jl_methtable_t *mt, void *_env) +static int strip_all_codeinfos_(jl_methtable_t *mt, void *_env) { - jl_typemap_visitor(mt->defs, strip_all_codeinfos__, NULL); + return jl_typemap_visitor(mt->defs, strip_all_codeinfos__, NULL); } static void jl_strip_all_codeinfos(void) diff --git a/stdlib/InteractiveUtils/src/codeview.jl b/stdlib/InteractiveUtils/src/codeview.jl index 33793f2b8bb93..0df2f83c45ed8 100644 --- a/stdlib/InteractiveUtils/src/codeview.jl +++ b/stdlib/InteractiveUtils/src/codeview.jl @@ -62,6 +62,11 @@ function code_warntype(io::IO, @nospecialize(f), @nospecialize(t=Base.default_tt debuginfo = Base.IRShow.debuginfo(debuginfo) lineprinter = Base.IRShow.__debuginfo[debuginfo] for (src, rettype) in code_typed(f, t; optimize, kwargs...) + if !(src isa Core.CodeInfo) + println(io, src) + println(io, " failed to infer") + continue + end lambda_io::IOContext = io p = src.parent nargs::Int = 0 diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index 1d7fbc8a85a5d..3120bb1122bff 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -9,6 +9,10 @@ using Base: propertynames, something abstract type Completion end +struct TextCompletion <: Completion + text::String +end + struct KeywordCompletion <: Completion keyword::String end @@ -37,10 +41,7 @@ struct FieldCompletion <: Completion end struct MethodCompletion <: Completion - func - input_types::Type method::Method - orig_method::Union{Nothing,Method} # if `method` is a keyword method, keep the original method for sensible printing end struct BslashCompletion <: Completion @@ -58,7 +59,9 @@ end # interface definition function Base.getproperty(c::Completion, name::Symbol) - if name === :keyword + if name === :text + return getfield(c, :text)::String + elseif name === :keyword return getfield(c, :keyword)::String elseif name === :path return getfield(c, :path)::String @@ -84,13 +87,14 @@ function Base.getproperty(c::Completion, name::Symbol) return getfield(c, name) end +_completion_text(c::TextCompletion) = c.text _completion_text(c::KeywordCompletion) = c.keyword _completion_text(c::PathCompletion) = c.path _completion_text(c::ModuleCompletion) = c.mod _completion_text(c::PackageCompletion) = c.package _completion_text(c::PropertyCompletion) = string(c.property) _completion_text(c::FieldCompletion) = string(c.field) -_completion_text(c::MethodCompletion) = sprint(io -> show(io, isnothing(c.orig_method) ? c.method : c.orig_method::Method)) +_completion_text(c::MethodCompletion) = repr(c.method) _completion_text(c::BslashCompletion) = c.bslash _completion_text(c::ShellCompletion) = c.text _completion_text(c::DictCompletion) = c.key @@ -125,7 +129,7 @@ function filtered_mod_names(ffunc::Function, mod::Module, name::AbstractString, end # REPL Symbol Completions -function complete_symbol(sym::String, ffunc, context_module::Module=Main) +function complete_symbol(sym::String, @nospecialize(ffunc), context_module::Module=Main) mod = context_module name = sym @@ -335,9 +339,25 @@ function find_start_brace(s::AbstractString; c_start='(', c_end=')') in_single_quotes = false in_double_quotes = false in_back_ticks = false + in_comment = 0 while i <= ncodeunits(r) c, i = iterate(r, i) - if !in_single_quotes && !in_double_quotes && !in_back_ticks + if c == '#' && i <= ncodeunits(r) && iterate(r, i)[1] == '=' + c, i = iterate(r, i) # consume '=' + new_comments = 1 + # handle #=#=#=#, by counting =# pairs + while i <= ncodeunits(r) && iterate(r, i)[1] == '#' + c, i = iterate(r, i) # consume '#' + iterate(r, i)[1] == '=' || break + c, i = iterate(r, i) # consume '=' + new_comments += 1 + end + if c == '=' + in_comment += new_comments + else + in_comment -= new_comments + end + elseif !in_single_quotes && !in_double_quotes && !in_back_ticks && in_comment == 0 if c == c_start braces += 1 elseif c == c_end @@ -350,15 +370,31 @@ function find_start_brace(s::AbstractString; c_start='(', c_end=')') in_back_ticks = true end else - if !in_back_ticks && !in_double_quotes && + if in_single_quotes && c == '\'' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\' - in_single_quotes = !in_single_quotes - elseif !in_back_ticks && !in_single_quotes && + in_single_quotes = false + elseif in_double_quotes && c == '"' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\' - in_double_quotes = !in_double_quotes - elseif !in_single_quotes && !in_double_quotes && + in_double_quotes = false + elseif in_back_ticks && c == '`' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\' - in_back_ticks = !in_back_ticks + in_back_ticks = false + elseif in_comment > 0 && + c == '=' && i <= ncodeunits(r) && iterate(r, i)[1] == '#' + # handle =#=#=#=, by counting #= pairs + c, i = iterate(r, i) # consume '#' + old_comments = 1 + while i <= ncodeunits(r) && iterate(r, i)[1] == '=' + c, i = iterate(r, i) # consume '=' + iterate(r, i)[1] == '#' || break + c, i = iterate(r, i) # consume '#' + old_comments += 1 + end + if c == '#' + in_comment -= old_comments + else + in_comment += old_comments + end end end braces == 1 && break @@ -375,62 +411,48 @@ end # will show it consist of Expr, QuoteNode's and Symbol's which all needs to # be handled differently to iterate down to get the value of whitespace_chars. function get_value(sym::Expr, fn) + if sym.head === :quote || sym.head === :inert + return sym.args[1], true + end sym.head !== :. && return (nothing, false) for ex in sym.args + ex, found = get_value(ex, fn) + !found && return (nothing, false) fn, found = get_value(ex, fn) !found && return (nothing, false) end return (fn, true) end get_value(sym::Symbol, fn) = isdefined(fn, sym) ? (getfield(fn, sym), true) : (nothing, false) -get_value(sym::QuoteNode, fn) = isdefined(fn, sym.value) ? (getfield(fn, sym.value), true) : (nothing, false) +get_value(sym::QuoteNode, fn) = (sym.value, true) get_value(sym::GlobalRef, fn) = get_value(sym.name, sym.mod) get_value(sym, fn) = (sym, true) # Return the type of a getfield call expression function get_type_getfield(ex::Expr, fn::Module) length(ex.args) == 3 || return Any, false # should never happen, but just for safety - obj, x = ex.args[2:3] + fld, found = get_value(ex.args[3], fn) + fld isa Symbol || return Any, false + obj = ex.args[2] objt, found = get_type(obj, fn) - objt isa DataType || return Any, false found || return Any, false - if x isa QuoteNode - fld = x.value - elseif isexpr(x, :quote) || isexpr(x, :inert) - fld = x.args[1] - else - fld = nothing # we don't know how to get the value of variable `x` here - end - fld isa Symbol || return Any, false + objt isa DataType || return Any, false hasfield(objt, fld) || return Any, false return fieldtype(objt, fld), true end -# Determines the return type with Base.return_types of a function call using the type information of the arguments. -function get_type_call(expr::Expr) +# Determines the return type with the Compiler of a function call using the type information of the arguments. +function get_type_call(expr::Expr, fn::Module) f_name = expr.args[1] - # The if statement should find the f function. How f is found depends on how f is referenced - if isa(f_name, GlobalRef) && isconst(f_name.mod,f_name.name) && isdefined(f_name.mod,f_name.name) - ft = typeof(eval(f_name)) - found = true - else - ft, found = get_type(f_name, Main) - end + f, found = get_type(f_name, fn) found || return (Any, false) # If the function f is not found return Any. args = Any[] - for ex in expr.args[2:end] # Find the type of the function arguments - typ, found = get_type(ex, Main) + for i in 2:length(expr.args) # Find the type of the function arguments + typ, found = get_type(expr.args[i], fn) found ? push!(args, typ) : push!(args, Any) end - # use _methods_by_ftype as the function is supplied as a type world = Base.get_world_counter() - matches = Base._methods_by_ftype(Tuple{ft, args...}, -1, world)::Vector - length(matches) == 1 || return (Any, false) - match = first(matches)::Core.MethodMatch - # Typeinference - interp = Core.Compiler.NativeInterpreter() - return_type = Core.Compiler.typeinf_type(interp, match.method, match.spec_types, match.sparams) - return_type === nothing && return (Any, false) + return_type = Core.Compiler.return_type(Tuple{f, args...}, world) return (return_type, true) end @@ -445,7 +467,7 @@ function try_get_type(sym::Expr, fn::Module) if a1 === :getfield || a1 === GlobalRef(Core, :getfield) return get_type_getfield(sym, fn) end - return get_type_call(sym) + return get_type_call(sym, fn) elseif sym.head === :thunk thk = sym.args[1] rt = ccall(:jl_infer_thunk, Any, (Any, Any), thk::Core.CodeInfo, fn) @@ -453,7 +475,7 @@ function try_get_type(sym::Expr, fn::Module) elseif sym.head === :ref # some simple cases of `expand` return try_get_type(Expr(:call, GlobalRef(Base, :getindex), sym.args...), fn) - elseif sym.head === :. && sym.args[2] isa QuoteNode # second check catches broadcasting + elseif sym.head === :. && sym.args[2] isa QuoteNode # second check catches broadcasting return try_get_type(Expr(:call, GlobalRef(Core, :getfield), sym.args...), fn) end return (Any, false) @@ -466,20 +488,18 @@ function get_type(sym::Expr, fn::Module) val, found = try_get_type(sym, fn) found && return val, found # https://github.com/JuliaLang/julia/issues/27184 - newsym = if isexpr(sym, :macrocall) + if isexpr(sym, :macrocall) _, found = get_type(first(sym.args), fn) found || return Any, false - try - Meta.lower(fn, sym) - catch e - e isa LoadError && return Any, false - # If e is not a LoadError then Meta.lower crashed in an unexpected way. - # Since this is not a specific to the user code but an internal error, - # rethrow the error to allow reporting it. - rethrow() - end - else + end + newsym = try Meta.lower(fn, sym) + catch e + e isa LoadError && return Any, false + # If e is not a LoadError then Meta.lower crashed in an unexpected way. + # Since this is not a specific to the user code but an internal error, + # rethrow the error to allow reporting it. + rethrow() end return try_get_type(newsym, fn) end @@ -495,37 +515,52 @@ function get_type(T, found::Bool, default_any::Bool) end # Method completion on function call expression that look like :(max(1)) +MAX_METHOD_COMPLETIONS = 40 function complete_methods(ex_org::Expr, context_module::Module=Main) - func, found = get_value(ex_org.args[1], context_module)::Tuple{Any,Bool} - !found && return Completion[] + out = Completion[] + funct, found = get_type(ex_org.args[1], context_module)::Tuple{Any,Bool} + !found && return out args_ex, kwargs_ex = complete_methods_args(ex_org.args[2:end], ex_org, context_module, true, true) + push!(args_ex, Vararg{Any}) + complete_methods!(out, funct, args_ex, kwargs_ex, MAX_METHOD_COMPLETIONS::Int) - out = Completion[] - complete_methods!(out, func, args_ex, kwargs_ex) return out end +MAX_ANY_METHOD_COMPLETIONS = 10 function complete_any_methods(ex_org::Expr, callee_module::Module, context_module::Module, moreargs::Bool, shift::Bool) out = Completion[] args_ex, kwargs_ex = try + # this may throw, since we set default_any to false complete_methods_args(ex_org.args[2:end], ex_org, context_module, false, false) - catch + catch ex + ex isa ArgumentError || rethrow() return out end + moreargs && push!(args_ex, Vararg{Any}) + seen = Base.IdSet() for name in names(callee_module; all=true) if !Base.isdeprecated(callee_module, name) && isdefined(callee_module, name) func = getfield(callee_module, name) if !isa(func, Module) - complete_methods!(out, func, args_ex, kwargs_ex, moreargs) - elseif callee_module === Main::Module && isa(func, Module) + funct = Core.Typeof(func) + if !in(funct, seen) + push!(seen, funct) + complete_methods!(out, funct, args_ex, kwargs_ex, MAX_ANY_METHOD_COMPLETIONS::Int) + end + elseif callee_module === Main && isa(func, Module) callee_module2 = func for name in names(callee_module2) - if isdefined(callee_module2, name) + if !Base.isdeprecated(callee_module2, name) && isdefined(callee_module2, name) func = getfield(callee_module, name) if !isa(func, Module) - complete_methods!(out, func, args_ex, kwargs_ex, moreargs) + funct = Core.Typeof(func) + if !in(funct, seen) + push!(seen, funct) + complete_methods!(out, funct, args_ex, kwargs_ex, MAX_ANY_METHOD_COMPLETIONS::Int) + end end end end @@ -536,7 +571,8 @@ function complete_any_methods(ex_org::Expr, callee_module::Module, context_modul if !shift # Filter out methods where all arguments are `Any` filter!(out) do c - isa(c, REPLCompletions.MethodCompletion) || return true + isa(c, TextCompletion) && return false + isa(c, MethodCompletion) || return true sig = Base.unwrap_unionall(c.method.sig)::DataType return !all(T -> T === Any || T === Vararg{Any}, sig.parameters[2:end]) end @@ -547,7 +583,7 @@ end function complete_methods_args(funargs::Vector{Any}, ex_org::Expr, context_module::Module, default_any::Bool, allow_broadcasting::Bool) args_ex = Any[] - kwargs_ex = Pair{Symbol,Any}[] + kwargs_ex = false if allow_broadcasting && ex_org.head === :. && ex_org.args[2] isa Expr # handle broadcasting, but only handle number of arguments instead of # argument types @@ -557,13 +593,11 @@ function complete_methods_args(funargs::Vector{Any}, ex_org::Expr, context_modul else for ex in funargs if isexpr(ex, :parameters) - for x in ex.args - n, v = isexpr(x, :kw) ? (x.args...,) : (x, x) - push!(kwargs_ex, n => get_type(get_type(v, context_module)..., default_any)) + if !isempty(ex.args) + kwargs_ex = true end elseif isexpr(ex, :kw) - n, v = (ex.args...,) - push!(kwargs_ex, n => get_type(get_type(v, context_module)..., default_any)) + kwargs_ex = true else push!(args_ex, get_type(get_type(ex, context_module)..., default_any)) end @@ -572,34 +606,18 @@ function complete_methods_args(funargs::Vector{Any}, ex_org::Expr, context_modul return args_ex, kwargs_ex end -function complete_methods!(out::Vector{Completion}, @nospecialize(func), args_ex::Vector{Any}, kwargs_ex::Vector{Pair{Symbol,Any}}, moreargs::Bool=true) - ml = methods(func) +function complete_methods!(out::Vector{Completion}, @nospecialize(funct), args_ex::Vector{Any}, kwargs_ex::Bool, max_method_completions::Int) # Input types and number of arguments - if isempty(kwargs_ex) - t_in = Tuple{Core.Typeof(func), args_ex...} - na = length(t_in.parameters)::Int - orig_ml = fill(nothing, length(ml)) - else - isdefined(ml.mt, :kwsorter) || return out - kwfunc = ml.mt.kwsorter - kwargt = NamedTuple{(first.(kwargs_ex)...,), Tuple{last.(kwargs_ex)...}} - t_in = Tuple{Core.Typeof(kwfunc), kwargt, Core.Typeof(func), args_ex...} - na = length(t_in.parameters)::Int - orig_ml = ml # this method is supposed to be used for printing - ml = methods(kwfunc) - func = kwfunc + t_in = Tuple{funct, args_ex...} + m = Base._methods_by_ftype(t_in, nothing, max_method_completions, Base.get_world_counter(), + #=ambig=# true, Ref(typemin(UInt)), Ref(typemax(UInt)), Ptr{Int32}(C_NULL)) + if m === false + push!(out, TextCompletion(sprint(Base.show_signature_function, funct) * "( too many methods to show )")) end - if !moreargs - na = typemax(Int) - end - - for (method::Method, orig_method) in zip(ml, orig_ml) - ms = method.sig - - # Check if the method's type signature intersects the input types - if typeintersect(Base.rewrap_unionall(Tuple{(Base.unwrap_unionall(ms)::DataType).parameters[1 : min(na, end)]...}, ms), t_in) != Union{} - push!(out, MethodCompletion(func, t_in, method, orig_method)) - end + m isa Vector || return + for match in m + # TODO: if kwargs_ex, filter out methods without kwargs? + push!(out, MethodCompletion(match.method)) end end @@ -678,7 +696,7 @@ function bslash_completions(string::String, pos::Int) return (false, (Completion[], 0:-1, false)) end -function dict_identifier_key(str::String, tag::Symbol, context_module::Module = Main) +function dict_identifier_key(str::String, tag::Symbol, context_module::Module=Main) if tag === :string str_close = str*"\"" elseif tag === :cmd @@ -867,19 +885,21 @@ function completions(string::String, pos::Int, context_module::Module=Main, shif dotpos < startpos && (dotpos = startpos - 1) s = string[startpos:pos] comp_keywords && append!(suggestions, complete_keyword(s)) - # The case where dot and start pos is equal could look like: "(""*"").d","". or CompletionFoo.test_y_array[1].y - # This case can be handled by finding the beginning of the expression. This is done below. - if dotpos == startpos + # if the start of the string is a `.`, try to consume more input to get back to the beginning of the last expression + if 0 < startpos <= lastindex(string) && string[startpos] == '.' i = prevind(string, startpos) while 0 < i c = string[i] - if c in [')', ']'] - if c==')' - c_start='('; c_end=')' - elseif c==']' - c_start='['; c_end=']' + if c in (')', ']') + if c == ')' + c_start = '(' + c_end = ')' + elseif c == ']' + c_start = '[' + c_end = ']' end frange, end_of_identifier = find_start_brace(string[1:prevind(string, i)], c_start=c_start, c_end=c_end) + isempty(frange) && break # unbalanced parens startpos = first(frange) i = prevind(string, startpos) elseif c in ('\'', '\"', '\`') diff --git a/stdlib/REPL/test/replcompletions.jl b/stdlib/REPL/test/replcompletions.jl index 6cec88f025e83..61dbe2360dfc1 100644 --- a/stdlib/REPL/test/replcompletions.jl +++ b/stdlib/REPL/test/replcompletions.jl @@ -89,6 +89,18 @@ let ex = quote test8() = Any[1][1] test9(x::Char) = pass test9(x::Char, i::Int) = pass + + test10(a, x::Int...) = pass + test10(a::Integer, b::Integer, c) = pass + test10(a, y::Bool...) = pass + test10(a, d::Integer, z::Signed...) = pass + test10(s::String...) = pass + + test11(a::Integer, b, c) = pass + test11(u, v::Integer, w) = pass + test11(x::Int, y::Int, z) = pass + test11(_, _, s::String) = pass + kwtest(; x=1, y=2, w...) = pass kwtest2(a; x=1, y=2, w...) = pass @@ -380,11 +392,14 @@ let end # Test completion of methods with input concrete args and args where typeinference determine their type -let s = "CompletionFoo.test(1,1, " +let s = "CompletionFoo.test(1, 1, " c, r, res = test_complete(s) @test !res @test c[1] == string(first(methods(Main.CompletionFoo.test, Tuple{Int, Int}))) - @test length(c) == 3 + @test c[2] == string(first(methods(Main.CompletionFoo.test, Tuple{}))) # corresponding to the vararg + @test length(c) == 2 + # In particular, this checks that test(x::Real, y::Real) is not a valid completion + # since it is strictly less specific than test(x::T, y::T) where T @test r == 1:18 @test s[r] == "CompletionFoo.test" end @@ -402,6 +417,7 @@ let s = "CompletionFoo.test(1,1,1," c, r, res = test_complete(s) @test !res @test c[1] == string(first(methods(Main.CompletionFoo.test, Tuple{Any, Any, Any}))) + @test length(c) == 1 @test r == 1:18 @test s[r] == "CompletionFoo.test" end @@ -546,6 +562,15 @@ end # method completion with `?` (arbitrary method with given argument types) let s = "CompletionFoo.?([1,2,3], 2.0)" + c, r, res = test_complete(s) + @test !res + @test length(c) == 1 + @test occursin("test(x::AbstractArray{T}, y) where T<:Real", c[1]) + # In particular, this checks that test(args...) is not a valid completion + # since it is strictly less specific than test(x::AbstractArray{T}, y) +end + +let s = "CompletionFoo.?([1,2,3], 2.0" c, r, res = test_complete(s) @test !res @test any(str->occursin("test(x::AbstractArray{T}, y) where T<:Real", str), c) @@ -558,6 +583,7 @@ let s = "CompletionFoo.?('c')" c, r, res = test_complete(s) @test !res @test any(str->occursin("test9(x::Char)", str), c) + @test any(str->occursin("test10(a, ", str), c) @test !any(str->occursin("test9(x::Char, i::Int", str), c) end @@ -565,20 +591,31 @@ let s = "CompletionFoo.?('c'" c, r, res = test_complete(s) @test !res @test any(str->occursin("test9(x::Char)", str), c) + @test any(str->occursin("test10(a, ", str), c) @test any(str->occursin("test9(x::Char, i::Int", str), c) end let s = "CompletionFoo.?(false, \"a\", 3, " c, r, res = test_complete(s) @test !res - @test length(c) == 1 + @test length(c) == 2 @test occursin("test(args...)", c[1]) + @test occursin("test11(a::Integer, b, c)", c[2]) end let s = "CompletionFoo.?(false, \"a\", 3, " c, r, res = test_complete_noshift(s) @test !res - @test isempty(c) + @test length(c) == 1 + @test occursin("test11(a::Integer, b, c)", c[1]) +end + +let s = "CompletionFoo.?(\"a\", 3, " + c, r, res = test_complete(s) + @test !res + @test any(str->occursin("test10(a, x::$Int...)", str), c) + @test !any(str->occursin("test10(a, y::Bool...)", str), c) + @test !any(str->occursin("test10(s::String...)", str), c) end let s = "CompletionFoo.?()" @@ -592,11 +629,119 @@ end let s = "CompletionFoo.?()" c, r, res = test_complete_noshift(s) @test !res - @test isempty(c) + @test length(c) == 1 + @test occursin("test10(s::String...)", c[1]) end ################################################################# +# Test method completion with varargs +let s = "CompletionFoo.test10(z, Integer[]...," + c, r, res = test_complete(s) + @test !res + @test length(c) == 5 + @test all(startswith("test10("), c) + @test allunique(c) +end + +let s = "CompletionFoo.test10(3, Integer[]...," + c, r, res = test_complete(s) + @test !res + @test length(c) == 4 + @test all(startswith("test10("), c) + @test allunique(c) + @test !any(str->occursin("test10(s::String...)", str), c) +end + +let s = "CompletionFoo.test10(3, 4," + c, r, res = test_complete(s) + @test !res + @test length(c) == 3 + @test any(str->occursin("test10(a, x::$Int...)", str), c) + @test any(str->occursin("test10(a::Integer, b::Integer, c)", str), c) + @test any(str->occursin("test10(a, d::Integer, z::Signed...)", str), c) +end + +let s = "CompletionFoo.test10(3, 4, 5," + c, r, res = test_complete(s) + @test !res + @test length(c) == 3 + @test any(str->occursin("test10(a, x::$Int...)", str), c) + @test any(str->occursin("test10(a::Integer, b::Integer, c)", str), c) # show it even though the call would result in an ambiguity error + @test any(str->occursin("test10(a, d::Integer, z::Signed...)", str), c) + # the last one is not eliminated by specificity since the complete call could be + # test10(3, 4, 5, Int8(6)) for instance +end + +let s = "CompletionFoo.test10(z, z, 0, " + c, r, res = test_complete(s) + @test !res + @test length(c) == 3 + @test any(str->occursin("test10(a, x::$Int...)", str), c) + @test any(str->occursin("test10(a::Integer, b::Integer, c)", str), c) # show it even though the call would result in an ambiguity error + @test any(str->occursin("test10(a, d::Integer, z::Signed...)", str), c) +end + +let s = "CompletionFoo.test10(\"a\", Union{Signed,Bool,String}[3][1], " + c, r, res = test_complete(s) + @test !res + @test length(c) == 4 + @test all(startswith("test10("), c) + @test allunique(c) + @test !any(str->occursin("test10(a::Integer, b::Integer, c)", str), c) +end + +# Test method completion with ambiguity +let s = "CompletionFoo.test11(Integer[false][1], Integer[14][1], " + c, r, res = test_complete(s) + @test !res + @test length(c) == 4 + @test all(startswith("test11("), c) + @test allunique(c) +end + +let s = "CompletionFoo.test11(Integer[-7][1], Integer[0x6][1], 6," + c, r, res = test_complete(s) + @test !res + @test length(c) == 3 + @test any(str->occursin("test11(a::Integer, b, c)", str), c) + @test any(str->occursin("test11(u, v::Integer, w)", str), c) + @test any(str->occursin("test11(x::$Int, y::$Int, z)", str), c) +end + +let s = "CompletionFoo.test11(3, 4," + c, r, res = test_complete(s) + @test !res + @test length(c) == 4 + @test any(str->occursin("test11(x::$Int, y::$Int, z)", str), c) + @test any(str->occursin("test11(::Any, ::Any, s::String)", str), c) +end + +let s = "CompletionFoo.test11(0x8, 5," + c, r, res = test_complete(s) + @test !res + @test length(c) == 3 + @test any(str->occursin("test11(a::Integer, b, c)", str), c) + @test any(str->occursin("test11(u, v::Integer, w)", str), c) + @test any(str->occursin("test11(::Any, ::Any, s::String)", str), c) +end + +let s = "CompletionFoo.test11(0x8, 'c'," + c, r, res = test_complete(s) + @test !res + @test length(c) == 2 + @test any(str->occursin("test11(a::Integer, b, c)", str), c) + @test any(str->occursin("test11(::Any, ::Any, s::String)", str), c) +end + +let s = "CompletionFoo.test11('d', 3," + c, r, res = test_complete(s) + @test !res + @test length(c) == 2 + @test any(str->occursin("test11(u, v::Integer, w)", str), c) + @test any(str->occursin("test11(::Any, ::Any, s::String)", str), c) +end + # Test of inference based getfield completion let s = "(1+2im)." c,r = test_complete(s) @@ -1204,7 +1349,10 @@ let s = "test(1,1, " c, r, res = test_complete_foo(s) @test !res @test c[1] == string(first(methods(Main.CompletionFoo.test, Tuple{Int, Int}))) - @test length(c) == 3 + @test c[2] == string(first(methods(Main.CompletionFoo.test, Tuple{}))) # corresponding to the vararg + @test length(c) == 2 + # In particular, this checks that test(x::Real, y::Real) is not a valid completion + # since it is strictly less specific than test(x::T, y::T) where T @test r == 1:4 @test s[r] == "test" end @@ -1283,8 +1431,8 @@ end end end - c, r = test_complete_context("foo().rs[1].", m) - @test m.var ≠ 1 # getfield type completion should never execute `foo()` + c, r = test_complete_context("foo(#=#==#=##==#).rs[1].", m) + @test m.var === nothing # getfield type completion should never execute `foo()` @test length(c) == fieldcount(Regex) end @@ -1302,8 +1450,11 @@ end end c, r = test_complete_context("foo().r.", m) - # the current implementation of `REPL.REPLCompletions.completions(::String, ::Int, ::Module)` - # cuts off "foo().r." to `.r.`, and the getfield type completion doesn't work for this simpler case - @test_broken length(c) == fieldcount(Regex) + @test m.var === nothing # getfield type completion should never execute `foo()` + @test length(c) == fieldcount(Regex) + + c, r = test_complete_context("foo(#=#=# =#= =#).r.", m) + @test m.var === nothing # getfield type completion should never execute `foo()` + @test length(c) == fieldcount(Regex) end end diff --git a/stdlib/Test/src/Test.jl b/stdlib/Test/src/Test.jl index 6714ed1716b64..8f7d9e1690ee1 100644 --- a/stdlib/Test/src/Test.jl +++ b/stdlib/Test/src/Test.jl @@ -1684,12 +1684,12 @@ function detect_ambiguities(mods::Module...; end function examine(mt::Core.MethodTable) for m in Base.MethodList(mt) + m.sig == Tuple && continue # ignore Builtins is_in_mods(m.module, recursive, mods) || continue world = Base.get_world_counter() ambig = Ref{Int32}(0) - ms = Base._methods_by_ftype(m.sig, nothing, -1, world, true, Ref(typemin(UInt)), Ref(typemax(UInt)), ambig) + ms = Base._methods_by_ftype(m.sig, nothing, -1, world, true, Ref(typemin(UInt)), Ref(typemax(UInt)), ambig)::Vector ambig[] == 0 && continue - isa(ms, Bool) && continue for match2 in ms match2 = match2::Core.MethodMatch m2 = match2.method diff --git a/test/broadcast.jl b/test/broadcast.jl index 39f6a40a5b542..6d85ac8624ca8 100644 --- a/test/broadcast.jl +++ b/test/broadcast.jl @@ -516,7 +516,7 @@ Base.BroadcastStyle(::Type{T}) where {T<:AD2Dim} = AD2DimStyle() @test a .+ 1 .* 2 == @inferred(fadd2(aa)) @test a .* a' == @inferred(fprod(aa)) @test isequal(a .+ [missing; 1:9], fadd3(aa)) - @test Core.Compiler.return_type(fadd3, (typeof(aa),)) <: Array19745{<:Union{Float64, Missing}} + @test Core.Compiler.return_type(fadd3, Tuple{typeof(aa),}) <: Array19745{<:Union{Float64, Missing}} @test isa(aa .+ 1, Array19745) @test isa(aa .+ 1 .* 2, Array19745) @test isa(aa .* aa', Array19745) diff --git a/test/compiler/contextual.jl b/test/compiler/contextual.jl index f20e02d371070..e89b56e4bf6de 100644 --- a/test/compiler/contextual.jl +++ b/test/compiler/contextual.jl @@ -72,7 +72,7 @@ module MiniCassette end tt = Tuple{f, args...} - match = Base._method_by_ftype(tt, -1, typemax(UInt)) + match = Base._which(tt, typemax(UInt)) mi = Core.Compiler.specialize_method(match) # Unsupported in this mini-cassette @assert !mi.def.isva diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 9549f4ab5ff1d..fe49b2d6000af 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -1172,10 +1172,10 @@ end # issue #22875 -typeargs = (Type{Int},) +typeargs = Tuple{Type{Int},} @test Base.Core.Compiler.return_type((args...) -> one(args...), typeargs) === Int -typeargs = (Type{Int},Type{Int},Type{Int},Type{Int},Type{Int},Type{Int}) +typeargs = Tuple{Type{Int},Type{Int},Type{Int},Type{Int},Type{Int},Type{Int}} @test Base.Core.Compiler.return_type(promote_type, typeargs) === Type{Int} # demonstrate that inference must converge diff --git a/test/precompile.jl b/test/precompile.jl index 55ddb04aa42ee..f9522d5409536 100644 --- a/test/precompile.jl +++ b/test/precompile.jl @@ -242,7 +242,7 @@ precompile_test_harness(false) do dir # create a backedge that includes Type{Union{}}, to ensure lookup can handle that call_bottom() = show(stdout::IO, Union{}) - Core.Compiler.return_type(call_bottom, ()) + Core.Compiler.return_type(call_bottom, Tuple{}) # check that @ccallable works from precompiled modules Base.@ccallable Cint f35014(x::Cint) = x+Cint(1) diff --git a/test/reflection.jl b/test/reflection.jl index 70dfde07048c2..b1a5b6eb822a3 100644 --- a/test/reflection.jl +++ b/test/reflection.jl @@ -197,9 +197,9 @@ let @test TestMod7648.TestModSub9475 == which(@__MODULE__, :a9475) end -@test_throws ArgumentError("argument is not a generic function") which(===, Tuple{Int, Int}) -@test_throws ArgumentError("argument is not a generic function") code_typed(===, Tuple{Int, Int}) -@test_throws ArgumentError("argument is not a generic function") Base.return_types(===, Tuple{Int, Int}) +@test which(===, Tuple{Int, Int}) isa Method +@test length(code_typed(===, Tuple{Int, Int})) === 1 +@test only(Base.return_types(===, Tuple{Int, Int})) === Any module TestingExported using Test @@ -545,7 +545,6 @@ end # code_typed_by_type @test Base.code_typed_by_type(Tuple{Type{<:Val}})[1][2] == Val @test Base.code_typed_by_type(Tuple{typeof(sin), Float64})[1][2] === Float64 -@test_throws ErrorException("signature does not correspond to a generic function") Base.code_typed_by_type(Tuple{Any}) # New reflection methods in 0.6 struct ReflectionExample{T<:AbstractFloat, N} diff --git a/test/show.jl b/test/show.jl index bd33c445df593..ba9f227e53e52 100644 --- a/test/show.jl +++ b/test/show.jl @@ -1879,31 +1879,34 @@ end @test read(td.io, String) == "1" end -function _methodsstr(f) +function _methodsstr(@nospecialize f) buf = IOBuffer() show(buf, methods(f)) - String(take!(buf)) + return String(take!(buf)) end @testset "show function methods" begin - @test occursin("methods for generic function \"sin\":", _methodsstr(sin)) + @test occursin("methods for generic function \"sin\":\n", _methodsstr(sin)) end @testset "show macro methods" begin - @test startswith(_methodsstr(getfield(Base,Symbol("@show"))), "# 1 method for macro \"@show\":") + @test startswith(_methodsstr(getfield(Base,Symbol("@show"))), "# 1 method for macro \"@show\":\n") end @testset "show constructor methods" begin - @test occursin("methods for type constructor:\n", _methodsstr(Vector)) + @test occursin(" methods for type constructor:\n", _methodsstr(Vector)) end @testset "show builtin methods" begin - @test startswith(_methodsstr(typeof), "# built-in function; no methods") + @test startswith(_methodsstr(typeof), "# 1 method for builtin function \"typeof\":\n") end @testset "show callable object methods" begin - @test occursin("methods:", _methodsstr(:)) + @test occursin("methods for callable object:\n", _methodsstr(:)) end @testset "#20111 show for function" begin K20111(x) = y -> x @test startswith(_methodsstr(K20111(1)), "# 1 method for anonymous function") end +@testset "show non-callable object" begin + @test "# 0 methods for callable object" == _methodsstr(1.0f0) +end @generated f22798(x::Integer, y) = :x @testset "#22798" begin diff --git a/test/worlds.jl b/test/worlds.jl index 8a0c936d3df8d..a6cbed9560a8d 100644 --- a/test/worlds.jl +++ b/test/worlds.jl @@ -12,13 +12,13 @@ begin f265a(x::Any) = 1 @test g265a() == 1 @test Base.return_types(g265a, ()) == Any[Int] - @test Core.Compiler.return_type(g265a, ()) == Int + @test Core.Compiler.return_type(g265a, Tuple{}) == Int f265a(x::Any) = 2.0 @test g265a() == 2.0 @test Base.return_types(g265a, ()) == Any[Float64] - @test Core.Compiler.return_type(g265a, ()) == Float64 + @test Core.Compiler.return_type(g265a, Tuple{}) == Float64 end # test signature widening @@ -29,13 +29,13 @@ begin end @test g265b(1) == 1 @test Base.return_types(g265b, (Int,)) == Any[Int] - @test Core.Compiler.return_type(g265b, (Int,)) == Int + @test Core.Compiler.return_type(g265b, Tuple{Int,}) == Int f265b(x::Any) = 2.0 @test g265b(1) == 1 @test g265b(2) == 2.0 @test Base.return_types(g265b, (Int,)) == Any[Union{Int, Float64}] - @test Core.Compiler.return_type(g265b, (Int,)) == Union{Int, Float64} + @test Core.Compiler.return_type(g265b, Tuple{Int,}) == Union{Int, Float64} end # test signature narrowing @@ -44,13 +44,13 @@ begin f265c(x::Any) = 1 @test g265c() == 1 @test Base.return_types(g265c, ()) == Any[Int] - @test Core.Compiler.return_type(g265c, ()) == Int + @test Core.Compiler.return_type(g265c, Tuple{}) == Int f265c(x::Int) = 2.0 @test g265c() == 2.0 @test Base.return_types(g265c, ()) == Any[Float64] - @test Core.Compiler.return_type(g265c, ()) == Float64 + @test Core.Compiler.return_type(g265c, Tuple{}) == Float64 end # test constructor narrowing @@ -78,7 +78,7 @@ end @test_throws MethodError B265_(2) @test_throws MethodError B265_(3) @test Base.return_types(B265_, (Int,)) == Any[B265{Int}] -@test Core.Compiler.return_type(B265_, (Int,)) == B265{Int} +@test Core.Compiler.return_type(B265_, Tuple{Int,}) == B265{Int} # add new constructors B265(x::Float64, dummy::Nothing) = B265{Float64}(x, dummy) @@ -90,7 +90,7 @@ B265(x::Any, dummy::Nothing) = B265{UInt8}(x, dummy) @test (B265_(3)::B265{UInt8}).field1 === 0x03 @test B265{UInt8} <: only(Base.return_types(B265_, (Int,))) <: B265 -@test B265{UInt8} <: Core.Compiler.return_type(B265_, (Int,)) <: B265 +@test B265{UInt8} <: Core.Compiler.return_type(B265_, Tuple{Int,}) <: B265 # test oldworld call / inference @@ -136,15 +136,15 @@ f265(::Int) = 1 @test put_n_take!(tls_world_age, ()) == wc265 @test g265() == Int[1, 1, 1] -@test Core.Compiler.return_type(f265, (Any,)) == Union{Float64, Int} -@test Core.Compiler.return_type(f265, (Int,)) == Int -@test Core.Compiler.return_type(f265, (Float64,)) == Float64 +@test Core.Compiler.return_type(f265, Tuple{Any,}) == Union{Float64, Int} +@test Core.Compiler.return_type(f265, Tuple{Int,}) == Int +@test Core.Compiler.return_type(f265, Tuple{Float64,}) == Float64 @test put_n_take!(g265, ()) == Float64[1.0, 1.0, 1.0] -@test put_n_take!(Core.Compiler.return_type, (f265, (Any,))) == Float64 -@test put_n_take!(Core.Compiler.return_type, (f265, (Int,))) == Float64 -@test put_n_take!(Core.Compiler.return_type, (f265, (Float64,))) == Float64 -@test put_n_take!(Core.Compiler.return_type, (f265, (Float64,))) == Float64 +@test put_n_take!(Core.Compiler.return_type, (f265, Tuple{Any,})) == Float64 +@test put_n_take!(Core.Compiler.return_type, (f265, Tuple{Int,})) == Float64 +@test put_n_take!(Core.Compiler.return_type, (f265, Tuple{Float64,})) == Float64 +@test put_n_take!(Core.Compiler.return_type, (f265, Tuple{Float64,})) == Float64 # test that reflection ignores worlds @test Base.return_types(f265, (Any,)) == Any[Int, Float64]