diff --git a/NEWS.md b/NEWS.md index c86332d51a338..886602c3c6845 100644 --- a/NEWS.md +++ b/NEWS.md @@ -322,6 +322,10 @@ Library improvements * `logging` can now be used to redirect `info`, `warn`, and `error` messages either universally or on a per-module/function basis ([#16213]). + * New function `Base.invokelatest(f, args...)` to call the latest version + of a function in circumstances where an older version may be called + instead (e.g. in a function calling `eval`) ([#19784]). + * A new `iszero(x)` function was added, to quickly check whether `x` is zero (or is all zeros, for an array) ([#19950]). diff --git a/base/essentials.jl b/base/essentials.jl index b5af539f7edf0..145ea6bb50486 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -337,3 +337,15 @@ function vector_any(xs::ANY...) end isempty(itr) = done(itr, start(itr)) + +""" + invokelatest(f, args...) + +Calls `f(args...)`, but guarantees that the most recent method of `f` +will be executed. This is useful in specialized circumstances, +e.g. long-running event loops or callback functions that may +call obsolete versions of a function `f`. +(The drawback is that `invokelatest` is somewhat slower than calling +`f` directly, and the type of the result cannot be inferred by the compiler.) +""" +invokelatest(f, args...) = Core._apply_latest(f, args) diff --git a/base/socket.jl b/base/socket.jl index 57a83640a2a57..14bb412a94406 100644 --- a/base/socket.jl +++ b/base/socket.jl @@ -589,18 +589,18 @@ function uv_getaddrinfocb(req::Ptr{Void}, status::Cint, addrinfo::Ptr{Void}) cb = unsafe_pointer_to_objref(data)::Function pop!(callback_dict,cb) # using pop forces an error if cb not in callback_dict if status != 0 || addrinfo == C_NULL - cb(UVError("uv_getaddrinfocb received an unexpected status code", status)) + invokelatest(cb, UVError("uv_getaddrinfocb received an unexpected status code", status)) else freeaddrinfo = addrinfo while addrinfo != C_NULL sockaddr = ccall(:jl_sockaddr_from_addrinfo, Ptr{Void}, (Ptr{Void},), addrinfo) if ccall(:jl_sockaddr_is_ip4, Int32, (Ptr{Void},), sockaddr) == 1 - cb(IPv4(ntoh(ccall(:jl_sockaddr_host4, UInt32, (Ptr{Void},), sockaddr)))) + invokelatest(cb, IPv4(ntoh(ccall(:jl_sockaddr_host4, UInt32, (Ptr{Void},), sockaddr)))) break #elseif ccall(:jl_sockaddr_is_ip6, Int32, (Ptr{Void},), sockaddr) == 1 # host = Vector{UInt128}(1) # scope_id = ccall(:jl_sockaddr_host6, UInt32, (Ptr{Void}, Ptr{UInt128}), sockaddr, host) - # cb(IPv6(ntoh(host[1]))) + # invokelatest(cb, IPv6(ntoh(host[1]))) # break end addrinfo = ccall(:jl_next_from_addrinfo, Ptr{Void}, (Ptr{Void},), addrinfo) diff --git a/doc/src/manual/methods.md b/doc/src/manual/methods.md index 330ec3a7746b8..1b46b99ab47c7 100644 --- a/doc/src/manual/methods.md +++ b/doc/src/manual/methods.md @@ -455,17 +455,12 @@ In the example above, we see that the "current world" (in which the method `newf is one greater than the task-local "runtime world" that was fixed when the execution of `tryeval` started. Sometimes it is necessary to get around this (for example, if you are implementing the above REPL). -Well, don't despair, since there's an easy solution: just call `eval` a second time. -For example, here we create a zero-argument closure over `ans` and `eval` a call to it: +Fortunately, there is an easy solution: call the function using [`Base.invokelatest`](@ref): ```jldoctest julia> function tryeval2() - ans = (@eval newfun2() = 1) - res = eval(Expr(:call, - function() - return ans() + 1 - end)) - return res + @eval newfun2() = 2 + Base.invokelatest(newfun2) end tryeval2 (generic function with 1 method) diff --git a/doc/src/stdlib/base.md b/doc/src/stdlib/base.md index 770ab55f9131b..c19859b07cf98 100644 --- a/doc/src/stdlib/base.md +++ b/doc/src/stdlib/base.md @@ -121,6 +121,7 @@ Base.instances Base.method_exists Core.applicable Core.invoke +Base.invokelatest Base.:(|>) Base.:(∘) ``` diff --git a/src/builtin_proto.h b/src/builtin_proto.h index 001cf160ea15a..754e9824ac4b0 100644 --- a/src/builtin_proto.h +++ b/src/builtin_proto.h @@ -23,6 +23,7 @@ DECLARE_BUILTIN(throw); DECLARE_BUILTIN(is); DECLARE_BUILTIN(typeof); DECLARE_BUILTIN(sizeof); DECLARE_BUILTIN(issubtype); DECLARE_BUILTIN(isa); DECLARE_BUILTIN(_apply); DECLARE_BUILTIN(_apply_pure); +DECLARE_BUILTIN(_apply_latest); DECLARE_BUILTIN(isdefined); DECLARE_BUILTIN(nfields); DECLARE_BUILTIN(tuple); DECLARE_BUILTIN(svec); DECLARE_BUILTIN(getfield); DECLARE_BUILTIN(setfield); diff --git a/src/builtins.c b/src/builtins.c index e117eae5dd54e..e7f29e0051e73 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -454,6 +454,18 @@ JL_CALLABLE(jl_f__apply_pure) return ret; } +// this is like `_apply`, but always runs in the newest world +JL_CALLABLE(jl_f__apply_latest) +{ + jl_ptls_t ptls = jl_get_ptls_states(); + size_t last_age = ptls->world_age; + if (!ptls->in_pure_callback) + ptls->world_age = jl_world_counter; + jl_value_t *ret = jl_f__apply(NULL, args, nargs); + ptls->world_age = last_age; + return ret; +} + // eval ----------------------------------------------------------------------- JL_DLLEXPORT jl_value_t *jl_toplevel_eval_in(jl_module_t *m, jl_value_t *ex) @@ -1089,6 +1101,7 @@ void jl_init_primitives(void) add_builtin_func("apply_type", jl_f_apply_type); add_builtin_func("_apply", jl_f__apply); add_builtin_func("_apply_pure", jl_f__apply_pure); + add_builtin_func("_apply_latest", jl_f__apply_latest); add_builtin_func("_expr", jl_f__expr); add_builtin_func("svec", jl_f_svec); diff --git a/src/codegen.cpp b/src/codegen.cpp index 178f2675e3c44..e55424d47170e 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -6761,6 +6761,7 @@ static void init_julia_llvm_env(Module *m) builtin_func_map[jl_f_typeassert] = jlcall_func_to_llvm("jl_f_typeassert", &jl_f_typeassert, m); builtin_func_map[jl_f__apply] = jlcall_func_to_llvm("jl_f__apply", &jl_f__apply, m); builtin_func_map[jl_f__apply_pure] = jlcall_func_to_llvm("jl_f__apply_pure", &jl_f__apply_pure, m); + builtin_func_map[jl_f__apply_latest] = jlcall_func_to_llvm("jl_f__apply_latest", &jl_f__apply_latest, m); builtin_func_map[jl_f_throw] = jlcall_func_to_llvm("jl_f_throw", &jl_f_throw, m); builtin_func_map[jl_f_tuple] = jlcall_func_to_llvm("jl_f_tuple", &jl_f_tuple, m); builtin_func_map[jl_f_svec] = jlcall_func_to_llvm("jl_f_svec", &jl_f_svec, m); diff --git a/src/dump.c b/src/dump.c index 7a32a63715d83..63fb83a042d91 100644 --- a/src/dump.c +++ b/src/dump.c @@ -77,7 +77,7 @@ static htable_t fptr_to_id; static const jl_fptr_t id_to_fptrs[] = { NULL, NULL, jl_f_throw, jl_f_is, jl_f_typeof, jl_f_issubtype, jl_f_isa, - jl_f_typeassert, jl_f__apply, jl_f__apply_pure, jl_f_isdefined, + jl_f_typeassert, jl_f__apply, jl_f__apply_pure, jl_f__apply_latest, jl_f_isdefined, jl_f_tuple, jl_f_svec, jl_f_intrinsic_call, jl_f_invoke_kwsorter, jl_f_getfield, jl_f_setfield, jl_f_fieldtype, jl_f_nfields, jl_f_arrayref, jl_f_arrayset, jl_f_arraysize, jl_f_apply_type, diff --git a/test/misc.jl b/test/misc.jl index f03edaa1ea34b..197cd00f0fa86 100644 --- a/test/misc.jl +++ b/test/misc.jl @@ -669,3 +669,12 @@ if Bool(parse(Int,(get(ENV, "JULIA_TESTFULL", "0")))) @test_throws StackOverflowError f_19433(+, 1, 2) end end + +# invokelatest function for issue #19774 +issue19774(x) = 1 +let foo() = begin + eval(:(issue19774(x::Int) = 2)) + return Base.invokelatest(issue19774, 0) + end + @test foo() == 2 +end