From d0853759f18862f57664adcf92b19dfb51a61a3f Mon Sep 17 00:00:00 2001 From: Keno Fischer Date: Sat, 9 Nov 2024 04:20:34 +0000 Subject: [PATCH] WIP: Make world-age increments explicit This PR introduces a new, toplevel-only, syntax form `:worldinc` that semantically represents the effect of raising the current task's world age to the latest world for the remainder of the current toplevel evaluation (that context being an entry to `eval` or a module expression). For detailed motivation on why this is desirable, see #55145, which I won't repeat here, but the gist is that we never really defined when world-age increments and worse are inconsistent about it. This is something we need to figure out now, because the bindings partition work will make world age even more observable via bindings. Having created a mechanism for world age increments, the big question is one of policy, i.e. when should these world age increments be inserted. Several reasonable options exist: 1. After world-age affecting syntax constructs (as proprosed in #55145) 2. Option 1 + some reasonable additional cases that people rely on 3. Before any top level `call` expression 4. Before any expression at toplevel whatsover As in example, case, consider `a == a` at toplevel. Depending on the semantics that could either be the same as in local scope, or each of the four world age dependent lookups (three binding lookups, one method lookup could occur in a different world age). The general tradeoff here is between the risk of exposing the user to confusing world age errors and our ability to optimize top-level code (in general, any :worldinc statement will require us to fully pessimize or recompile all following code). This PR basically implements option 2 with the following semantics: 1. The interpreter explicit raises the world age only at `:worldinc` exprs or after `:module` exprs. 2. The frontend inserts `:worldinc` after all struct definitions, method definitions, `using` and `import. 3. The `@eval` macro inserts a worldinc following the call to `eval` if at toplevel 4. A literal (syntactic) call to `include` gains an implicit `worldinc`. Of these the fourth is probably the most questionable, but is necessary to make this non-breaking for most code patterns. Perhaps it would have been better to make `include` a macro from the beginning (esp because it already has semantics that look a little like reaching into the calling module), but that ship has sailed. Unfortunately, I don't see any good intermediate options between this PR and option #3 above. I think option #3 is closes to what we have right now, but if we were to choose it and actually fix the soundness issues, I expect that we would be destroying all performance of global-scope code. For this reason, I would like to try to make the version in this PR work, even if the semantics are a little ugly. The biggest pattern that this PR does not catch is: ``` eval(:(f() = 1)) f() ``` We could apply the same `include` special case to eval, but given the existence of `@eval` which allows addressing this at the macro level, I decided not to. We can decide which way we want to go on this based on what the package ecosystem looks like. --- base/boot.jl | 6 +- base/essentials.jl | 14 +- base/sysimg.jl | 8 + base/tuple.jl | 2 +- src/ast.c | 2 + src/interpreter.c | 8 +- src/jlfrontend.scm | 2 +- src/julia-syntax.scm | 44 ++++- src/julia_internal.h | 1 + src/toplevel.c | 21 +- stdlib/Logging/test/runtests.jl | 2 +- stdlib/REPL/src/REPL.jl | 1 + stdlib/REPL/test/replcompletions.jl | 273 +++++++++++++------------- stdlib/Serialization/test/runtests.jl | 2 +- stdlib/Test/src/Test.jl | 27 ++- test/backtrace.jl | 2 + test/ccall.jl | 6 +- test/core.jl | 14 +- test/deprecation_exec.jl | 1 + test/error.jl | 1 + test/llvmcall.jl | 1 + test/rebinding.jl | 1 + test/reflection.jl | 14 ++ test/syntax.jl | 11 +- 24 files changed, 293 insertions(+), 171 deletions(-) diff --git a/base/boot.jl b/base/boot.jl index 5d40191ecab211..626e28aa8b94d0 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -259,18 +259,21 @@ else const UInt = UInt32 end -function iterate end function Typeof end ccall(:jl_toplevel_eval_in, Any, (Any, Any), Core, quote (f::typeof(Typeof))(x) = ($(_expr(:meta,:nospecialize,:x)); isa(x,Type) ? Type{x} : typeof(x)) end) +function iterate end + macro nospecialize(x) _expr(:meta, :nospecialize, x) end Expr(@nospecialize args...) = _expr(args...) +macro worldinc() Expr(:worldinc) end + _is_internal(__module__) = __module__ === Core # can be used in place of `@assume_effects :total` (supposed to be used for bootstrapping) macro _total_meta() @@ -518,6 +521,7 @@ eval(Core, quote InterConditional(slot::Int, @nospecialize(thentype), @nospecialize(elsetype)) = $(Expr(:new, :InterConditional, :slot, :thentype, :elsetype)) MethodMatch(@nospecialize(spec_types), sparams::SimpleVector, method::Method, fully_covers::Bool) = $(Expr(:new, :MethodMatch, :spec_types, :sparams, :method, :fully_covers)) end) +@worldinc const NullDebugInfo = DebugInfo(:none) diff --git a/base/essentials.jl b/base/essentials.jl index 64fbaea95d4e7b..0550eb62bb632a 100644 --- a/base/essentials.jl +++ b/base/essentials.jl @@ -470,10 +470,20 @@ Evaluate an expression with values interpolated into it using `eval`. If two arguments are provided, the first is the module to evaluate in. """ macro eval(ex) - return Expr(:escape, Expr(:call, GlobalRef(Core, :eval), __module__, Expr(:quote, ex))) + g = ccall(:jl_gensym, Ref{Symbol}, ()) + return Expr(:let, Expr(:(=), g, + Expr(:escape, Expr(:call, GlobalRef(Core, :eval), __module__, Expr(:quote, ex)))), + Expr(:block, + Expr(:var"worldinc-if-toplevel"), + g)) end macro eval(mod, ex) - return Expr(:escape, Expr(:call, GlobalRef(Core, :eval), mod, Expr(:quote, ex))) + g = ccall(:jl_gensym, Ref{Symbol}, ()) + return Expr(:let, Expr(:(=), g, + Expr(:escape, Expr(:call, GlobalRef(Core, :eval), mod, Expr(:quote, ex)))), + Expr(:block, + Expr(:var"worldinc-if-toplevel"), + g)) end # use `@eval` here to directly form `:new` expressions avoid implicit `convert`s diff --git a/base/sysimg.jl b/base/sysimg.jl index ccc8ef38e81bcf..da9b56df47a5bf 100644 --- a/base/sysimg.jl +++ b/base/sysimg.jl @@ -6,6 +6,7 @@ using .Base # Set up Main module using Base.MainInclude # ans, err, and sometimes Out +Core.@worldinc # These definitions calls Base._include rather than Base.include to get # one-frame stacktraces for the common case of using include(fname) in Main. @@ -29,6 +30,13 @@ actually evaluates `mapexpr(expr)`. If it is omitted, `mapexpr` defaults to [`i Use [`Base.include`](@ref) to evaluate a file into another module. +!!! note + Julia's syntax lowering recognizes an explicit call to a literal `include` + at top-level and inserts an implicit `@Core.worldinc` to make any include'd + definitions visible to subsequent code. Note however that this recognition + is *syntactic*. I.e. assigning `const myinclude = include` may require + and explicit `@Core.worldinc` call after `myinclude`. + !!! compat "Julia 1.5" Julia 1.5 is required for passing the `mapexpr` argument. """ diff --git a/base/tuple.jl b/base/tuple.jl index fc213410cfd7c6..d9e8739d3b90bf 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -60,7 +60,7 @@ end function _setindex(v, i::Integer, args::Vararg{Any,N}) where {N} @inline - return ntuple(j -> ifelse(j == i, v, args[j]), Val{N}()) + return ntuple(j -> ifelse(j == i, v, args[j]), Val{N}())::NTuple{N, Any} end diff --git a/src/ast.c b/src/ast.c index ea1de429a946ce..51dc6d4680c8c9 100644 --- a/src/ast.c +++ b/src/ast.c @@ -119,6 +119,7 @@ JL_DLLEXPORT jl_sym_t *jl_release_sym; JL_DLLEXPORT jl_sym_t *jl_acquire_release_sym; JL_DLLEXPORT jl_sym_t *jl_sequentially_consistent_sym; JL_DLLEXPORT jl_sym_t *jl_uninferred_sym; +JL_DLLEXPORT jl_sym_t *jl_worldinc_sym; static const uint8_t flisp_system_image[] = { #include @@ -461,6 +462,7 @@ void jl_init_common_symbols(void) jl_acquire_release_sym = jl_symbol("acquire_release"); jl_sequentially_consistent_sym = jl_symbol("sequentially_consistent"); jl_uninferred_sym = jl_symbol("uninferred"); + jl_worldinc_sym = jl_symbol("worldinc"); } JL_DLLEXPORT void jl_lisp_prompt(void) diff --git a/src/interpreter.c b/src/interpreter.c index f9d981687c631b..a85627268894d0 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -463,8 +463,6 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip, s->ip = ip; if (ip >= ns) jl_error("`body` expression must terminate in `return`. Use `block` instead."); - if (toplevel) - ct->world_age = jl_atomic_load_acquire(&jl_world_counter); jl_value_t *stmt = jl_array_ptr_ref(stmts, ip); assert(!jl_is_phinode(stmt)); size_t next_ip = ip + 1; @@ -643,6 +641,9 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip, jl_eval_const_decl(s->module, jl_exprarg(stmt, 0), val); s->locals[jl_source_nslots(s->src) + s->ip] = jl_nothing; } + else if (head == jl_worldinc_sym) { + ct->world_age = jl_atomic_load_acquire(&jl_world_counter); + } else if (jl_is_toplevel_only_expr(stmt)) { jl_toplevel_eval(s->module, stmt); } @@ -887,10 +888,7 @@ jl_value_t *NOINLINE jl_interpret_toplevel_thunk(jl_module_t *m, jl_code_info_t s->mi = NULL; s->ci = NULL; JL_GC_ENABLEFRAME(s); - jl_task_t *ct = jl_current_task; - size_t last_age = ct->world_age; jl_value_t *r = eval_body(stmts, s, 0, 1); - ct->world_age = last_age; JL_GC_POP(); return r; } diff --git a/src/jlfrontend.scm b/src/jlfrontend.scm index 808af18ebfdbdf..3d46940d6fcbbc 100644 --- a/src/jlfrontend.scm +++ b/src/jlfrontend.scm @@ -139,7 +139,7 @@ (define (toplevel-only-expr? e) (and (pair? e) - (or (memq (car e) '(toplevel line module import using export public + (or (memq (car e) '(toplevel line module export public error incomplete)) (and (memq (car e) '(global const)) (every symbol? (cdr e)))))) diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index b48cb48bf0b790..f4c09129e2321a 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -1026,6 +1026,7 @@ ;; otherwise do an assignment to trigger an error (const (globalref (thismodule) ,name) ,name))) (const (globalref (thismodule) ,name) ,name)) + (worldinc) (call (core _typebody!) ,name (call (core svec) ,@field-types)) (null))) ;; "inner" constructors @@ -1076,6 +1077,7 @@ (call (core _equiv_typedef) (globalref (thismodule) ,name) ,name)) (null) (const (globalref (thismodule) ,name) ,name)) + (worldinc) (null)))))) (define (primitive-type-def-expr n name params super) @@ -1096,6 +1098,7 @@ (call (core _equiv_typedef) (globalref (thismodule) ,name) ,name)) (null) (const (globalref (thismodule) ,name) ,name)) + (worldinc) (null)))))) ;; take apart a type signature, e.g. T{X} <: S{Y} @@ -1210,7 +1213,7 @@ (cond ((and (length= e 2) (or (symbol? name) (globalref? name))) (if (not (valid-name? name)) (error (string "invalid function name \"" name "\""))) - `(method ,name)) + `(block (method ,name) (worldinc) (unnecessary ,name))) ((not (pair? name)) e) ((eq? (car name) 'call) (let* ((raw-typevars (or where '())) @@ -2733,6 +2736,9 @@ ((and (eq? (identifier-name f) '^) (length= e 4) (integer? (cadddr e))) (expand-forms `(call (top literal_pow) ,f ,(caddr e) (call (call (core apply_type) (top Val) ,(cadddr e)))))) + ((eq? f 'include) + (let ((r (make-ssavalue))) + `(block (= ,r ,(map expand-forms e)) (worldinc-if-toplevel) ,r))) (else (map expand-forms e)))) (map expand-forms e))) @@ -4114,7 +4120,8 @@ f(x) = yt(x) `(lambda ,(cadr lam2) (,(clear-capture-bits (car vis)) ,@(cdr vis)) - ,body))))) + ,body))) + (worldinc))) (else (let* ((exprs (lift-toplevel (convert-lambda lam2 '|#anon| #t '() #f parsed-method-stack))) (top-stmts (cdr exprs)) @@ -4122,7 +4129,8 @@ f(x) = yt(x) `(toplevel-butfirst (block ,@sp-inits (method ,(cadr e) ,(cl-convert sig fname lam namemap defined toplevel interp opaq parsed-method-stack globals locals) - ,(julia-bq-macro newlam))) + ,(julia-bq-macro newlam)) + (worldinc)) ,@top-stmts)))) ;; local case - lift to a new type at top level @@ -4261,7 +4269,8 @@ f(x) = yt(x) `(toplevel-butfirst (null) ,@sp-inits - ,@mk-method) + ,@mk-method + (worldinc)) (begin (put! defined name #t) `(toplevel-butfirst @@ -4269,7 +4278,8 @@ f(x) = yt(x) ,@typedef ,@(map (lambda (v) `(moved-local ,v)) moved-vars) ,@sp-inits - ,@mk-method)))))))) + ,@mk-method + (worldinc))))))))) ((lambda) ;; happens inside (thunk ...) and generated function bodies (for-each (lambda (vi) (vinfo:set-asgn! vi #t)) (list-tail (car (lam:vinfo e)) (length (lam:args e)))) @@ -4501,6 +4511,7 @@ f(x) = yt(x) ((struct_type) "\"struct\" expression") ((method) "method definition") ((set_binding_type!) (string "type declaration for global \"" (deparse (cadr e)) "\"")) + ((worldinc) "World age increment") (else (string "\"" h "\" expression")))) (if (not (null? (cadr lam))) (error (string (head-to-text (car e)) " not at top level")))) @@ -4952,7 +4963,12 @@ f(x) = yt(x) (else (emit temp))))) ;; top level expressions - ((thunk module) + ((thunk) + (check-top-level e) + (emit e) + (if tail (emit-return tail '(null))) + '(null)) + ((module) (check-top-level e) (emit e) (if tail (emit-return tail '(null))) @@ -4968,8 +4984,22 @@ f(x) = yt(x) (if tail (emit-return tail val)) val)) + ((worldinc-if-toplevel) + (if (null? (cadr lam)) + (emit `(worldinc))) + '(null)) + + ((import using) + (check-top-level e) + (emit e) + (emit `(worldinc)) + (let ((have-ret? (and (pair? code) (pair? (car code)) (eq? (caar code) 'return)))) + (if (and tail (not have-ret?)) + (emit-return tail '(null)))) + '(null)) + ;; other top level expressions - ((import using export public) + ((export public worldinc) (check-top-level e) (emit e) (let ((have-ret? (and (pair? code) (pair? (car code)) (eq? (caar code) 'return)))) diff --git a/src/julia_internal.h b/src/julia_internal.h index 5eb99be9e333f2..31819410b32595 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -1841,6 +1841,7 @@ extern JL_DLLEXPORT jl_sym_t *jl_release_sym; extern JL_DLLEXPORT jl_sym_t *jl_acquire_release_sym; extern JL_DLLEXPORT jl_sym_t *jl_sequentially_consistent_sym; extern JL_DLLEXPORT jl_sym_t *jl_uninferred_sym; +extern JL_DLLEXPORT jl_sym_t *jl_worldinc_sym; JL_DLLEXPORT enum jl_memory_order jl_get_atomic_order(jl_sym_t *order, char loading, char storing); JL_DLLEXPORT enum jl_memory_order jl_get_atomic_order_checked(jl_sym_t *order, char loading, char storing); diff --git a/src/toplevel.c b/src/toplevel.c index 45143f99a178c6..0e652481fc1943 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -225,9 +225,7 @@ static jl_value_t *jl_eval_module_expr(jl_module_t *parent_module, jl_expr_t *ex for (int i = 0; i < jl_array_nrows(exprs); i++) { // process toplevel form - ct->world_age = jl_atomic_load_acquire(&jl_world_counter); form = jl_expand_stmt_with_loc(jl_array_ptr_ref(exprs, i), newm, filename, lineno); - ct->world_age = jl_atomic_load_acquire(&jl_world_counter); (void)jl_toplevel_eval_flex(newm, form, 1, 1, &filename, &lineno); } ct->world_age = last_age; @@ -607,7 +605,8 @@ int jl_is_toplevel_only_expr(jl_value_t *e) JL_NOTSAFEPOINT ((jl_expr_t*)e)->head == jl_const_sym || ((jl_expr_t*)e)->head == jl_toplevel_sym || ((jl_expr_t*)e)->head == jl_error_sym || - ((jl_expr_t*)e)->head == jl_incomplete_sym); + ((jl_expr_t*)e)->head == jl_incomplete_sym || + ((jl_expr_t*)e)->head == jl_worldinc_sym); } int jl_needs_lowering(jl_value_t *e) JL_NOTSAFEPOINT @@ -864,6 +863,7 @@ JL_DLLEXPORT jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_val if (head == jl_module_sym) { jl_value_t *val = jl_eval_module_expr(m, ex); + ct->world_age = jl_atomic_load_acquire(&jl_world_counter); JL_GC_POP(); return val; } @@ -919,6 +919,9 @@ JL_DLLEXPORT jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_val jl_eval_errorf(m, *toplevel_filename, *toplevel_lineno, "syntax: malformed \"using\" statement"); } + if (!expanded) { + ct->world_age = jl_atomic_load_acquire(&jl_world_counter); + } JL_GC_POP(); return jl_nothing; } @@ -967,6 +970,12 @@ JL_DLLEXPORT jl_value_t *jl_toplevel_eval_flex(jl_module_t *JL_NONNULL m, jl_val jl_eval_errorf(m, *toplevel_filename, *toplevel_lineno, "syntax: malformed \"import\" statement"); } + if (!expanded) { + // To avoid having to roundtrip every `using` expression through + // lowering, just to add the world-age increment effect, do it + // manually here. + ct->world_age = jl_atomic_load_acquire(&jl_world_counter); + } JL_GC_POP(); return jl_nothing; } @@ -1111,8 +1120,11 @@ JL_DLLEXPORT jl_value_t *jl_toplevel_eval_in(jl_module_t *m, jl_value_t *ex) jl_value_t *v = NULL; int last_lineno = jl_lineno; const char *last_filename = jl_filename; + jl_task_t *ct = jl_current_task; jl_lineno = 1; jl_filename = "none"; + size_t last_age = ct->world_age; + ct->world_age = jl_atomic_load_relaxed(&jl_world_counter); JL_TRY { v = jl_toplevel_eval(m, ex); } @@ -1123,6 +1135,7 @@ JL_DLLEXPORT jl_value_t *jl_toplevel_eval_in(jl_module_t *m, jl_value_t *ex) } jl_lineno = last_lineno; jl_filename = last_filename; + ct->world_age = last_age; assert(v); return v; } @@ -1170,6 +1183,7 @@ static jl_value_t *jl_parse_eval_all(jl_module_t *module, jl_value_t *text, int last_lineno = jl_lineno; const char *last_filename = jl_filename; size_t last_age = ct->world_age; + ct->world_age = jl_atomic_load_acquire(&jl_world_counter); int lineno = 0; jl_lineno = 0; const char *filename_str = jl_string_data(filename); @@ -1187,7 +1201,6 @@ static jl_value_t *jl_parse_eval_all(jl_module_t *module, jl_value_t *text, } expression = jl_expand_with_loc_warn(expression, module, jl_string_data(filename), lineno); - ct->world_age = jl_atomic_load_acquire(&jl_world_counter); result = jl_toplevel_eval_flex(module, expression, 1, 1, &filename_str, &lineno); } } diff --git a/stdlib/Logging/test/runtests.jl b/stdlib/Logging/test/runtests.jl index 176860fcdec63f..2fedbde5570787 100644 --- a/stdlib/Logging/test/runtests.jl +++ b/stdlib/Logging/test/runtests.jl @@ -285,7 +285,7 @@ end AboveMaxLevel === Logging.AboveMaxLevel end """) - @test m.run() + @test invokelatest(m.run) end @testset "custom log macro" begin diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index e3a58ec362d89b..04c58c50e608f3 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -367,6 +367,7 @@ function eval_user_input(@nospecialize(ast), backend::REPLBackend, mod::Module) for xf in backend.ast_transforms ast = Base.invokelatest(xf, ast) end + toplevel_eval_with_hooks(mod, Expr(:worldinc)) value = toplevel_eval_with_hooks(mod, ast) backend.in_eval = false setglobal!(Base.MainInclude, :ans, value) diff --git a/stdlib/REPL/test/replcompletions.jl b/stdlib/REPL/test/replcompletions.jl index 1355f74c9bfff4..b2595678844864 100644 --- a/stdlib/REPL/test/replcompletions.jl +++ b/stdlib/REPL/test/replcompletions.jl @@ -12,150 +12,151 @@ using REPL end end -let ex = quote - module CompletionFoo - using Random - import Test - - mutable struct Test_y - yy - end - mutable struct Test_x - xx :: Test_y - end - type_test = Test_x(Test_y(1)) - (::Test_y)() = "", "" - unicode_αβγ = Test_y(1) +let ex = + quote + module CompletionFoo + using Random + import Test + + mutable struct Test_y + yy + end + mutable struct Test_x + xx :: Test_y + end + type_test = Test_x(Test_y(1)) + (::Test_y)() = "", "" + unicode_αβγ = Test_y(1) - Base.:(+)(x::Test_x, y::Test_y) = Test_x(Test_y(x.xx.yy + y.yy)) - module CompletionFoo2 + Base.:(+)(x::Test_x, y::Test_y) = Test_x(Test_y(x.xx.yy + y.yy)) + module CompletionFoo2 - end - const bar = 1 - foo() = bar - macro foobar() - :() - end - macro barfoo(ex) - ex - end - macro error_expanding() - error("cannot expand @error_expanding") - :() - end - macro error_lowering_conditional(a) - if isa(a, Number) - return a end - throw(AssertionError("Not a Number")) - :() - end - macro error_throwing() - return quote - error("@error_throwing throws an error") + const bar = 1 + foo() = bar + macro foobar() + :() + end + macro barfoo(ex) + ex + end + macro error_expanding() + error("cannot expand @error_expanding") + :() + end + macro error_lowering_conditional(a) + if isa(a, Number) + return a + end + throw(AssertionError("Not a Number")) + :() + end + macro error_throwing() + return quote + error("@error_throwing throws an error") + end end - end - primitive type NonStruct 8 end - Base.propertynames(::NonStruct) = (:a, :b, :c) - x = reinterpret(NonStruct, 0x00) + primitive type NonStruct 8 end + Base.propertynames(::NonStruct) = (:a, :b, :c) + x = reinterpret(NonStruct, 0x00) - # Support non-Dict AbstractDicts, #19441 - mutable struct CustomDict{K, V} <: AbstractDict{K, V} - mydict::Dict{K, V} - end + # Support non-Dict AbstractDicts, #19441 + mutable struct CustomDict{K, V} <: AbstractDict{K, V} + mydict::Dict{K, V} + end - Base.keys(d::CustomDict) = collect(keys(d.mydict)) - Base.length(d::CustomDict) = length(d.mydict) + Base.keys(d::CustomDict) = collect(keys(d.mydict)) + Base.length(d::CustomDict) = length(d.mydict) - # Support AbstractDict with unknown length, #55931 - struct NoLengthDict{K,V} <: AbstractDict{K,V} - dict::Dict{K,V} - NoLengthDict{K,V}() where {K,V} = new(Dict{K,V}()) - end - Base.iterate(d::NoLengthDict, s...) = iterate(d.dict, s...) - Base.IteratorSize(::Type{<:NoLengthDict}) = Base.SizeUnknown() - Base.eltype(::Type{NoLengthDict{K,V}}) where {K,V} = Pair{K,V} - Base.setindex!(d::NoLengthDict, v, k) = d.dict[k] = v - - test(x::T, y::T) where {T<:Real} = pass - test(x::Real, y::Real) = pass - test(x::AbstractArray{T}, y) where {T<:Real} = pass - test(args...) = pass - - test1(x::Type{Float64}) = pass - - test2(x::AbstractString) = pass - test2(x::Char) = pass - test2(x::Cmd) = pass - - test3(x::AbstractArray{Int}, y::Int) = pass - test3(x::AbstractArray{Float64}, y::Float64) = pass - - test4(x::AbstractString, y::AbstractString) = pass - test4(x::AbstractString, y::Regex) = pass - - test5(x::Array{Bool,1}) = pass - test5(x::BitArray{1}) = pass - test5(x::Float64) = pass - const a=x->x - test6()=[a, a] - test7() = rand(Bool) ? 1 : 1.0 - 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 - - test!12() = pass - - kwtest(; x=1, y=2, w...) = pass - kwtest2(a; x=1, y=2, w...) = pass - kwtest3(a::Number; length, len2, foobar, kwargs...) = pass - kwtest3(a::Real; another!kwarg, len2) = pass - kwtest3(a::Integer; namedarg, foobar, slurp...) = pass - kwtest4(a::AbstractString; _a1b, x23) = pass - kwtest4(a::String; _a1b, xαβγ) = pass - kwtest4(a::SubString; x23, _something) = pass - kwtest5(a::Int, b, x...; somekwarg, somekotherkwarg) = pass - kwtest5(a::Char, b; xyz) = pass - - const named = (; len2=3) - const fmsoebelkv = (; len2=3) - - array = [1, 1] - varfloat = 0.1 - - const tuple = (1, 2) - - test_y_array=[(@__MODULE__).Test_y(rand()) for i in 1:10] - test_dict = Dict("abc"=>1, "abcd"=>10, :bar=>2, :bar2=>9, Base=>3, - occursin=>4, `ls`=>5, 66=>7, 67=>8, ("q",3)=>11, - "α"=>12, :α=>13) - test_customdict = CustomDict(test_dict) - - macro teststr_str(s) end - macro tϵsτstρ_str(s) end - macro testcmd_cmd(s) end - macro tϵsτcmδ_cmd(s) end - - var"complicated symbol with spaces" = 5 - - struct WeirdNames end - Base.propertynames(::WeirdNames) = (Symbol("oh no!"), Symbol("oh yes!")) - - # https://github.com/JuliaLang/julia/issues/52551#issuecomment-1858543413 - export exported_symbol - exported_symbol(::WeirdNames) = nothing + # Support AbstractDict with unknown length, #55931 + struct NoLengthDict{K,V} <: AbstractDict{K,V} + dict::Dict{K,V} + NoLengthDict{K,V}() where {K,V} = new(Dict{K,V}()) + end + Base.iterate(d::NoLengthDict, s...) = iterate(d.dict, s...) + Base.IteratorSize(::Type{<:NoLengthDict}) = Base.SizeUnknown() + Base.eltype(::Type{NoLengthDict{K,V}}) where {K,V} = Pair{K,V} + Base.setindex!(d::NoLengthDict, v, k) = d.dict[k] = v + + test(x::T, y::T) where {T<:Real} = pass + test(x::Real, y::Real) = pass + test(x::AbstractArray{T}, y) where {T<:Real} = pass + test(args...) = pass + + test1(x::Type{Float64}) = pass + + test2(x::AbstractString) = pass + test2(x::Char) = pass + test2(x::Cmd) = pass + + test3(x::AbstractArray{Int}, y::Int) = pass + test3(x::AbstractArray{Float64}, y::Float64) = pass + + test4(x::AbstractString, y::AbstractString) = pass + test4(x::AbstractString, y::Regex) = pass + + test5(x::Array{Bool,1}) = pass + test5(x::BitArray{1}) = pass + test5(x::Float64) = pass + const a=x->x + test6()=[a, a] + test7() = rand(Bool) ? 1 : 1.0 + 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 + + test!12() = pass + + kwtest(; x=1, y=2, w...) = pass + kwtest2(a; x=1, y=2, w...) = pass + kwtest3(a::Number; length, len2, foobar, kwargs...) = pass + kwtest3(a::Real; another!kwarg, len2) = pass + kwtest3(a::Integer; namedarg, foobar, slurp...) = pass + kwtest4(a::AbstractString; _a1b, x23) = pass + kwtest4(a::String; _a1b, xαβγ) = pass + kwtest4(a::SubString; x23, _something) = pass + kwtest5(a::Int, b, x...; somekwarg, somekotherkwarg) = pass + kwtest5(a::Char, b; xyz) = pass + + const named = (; len2=3) + const fmsoebelkv = (; len2=3) + + array = [1, 1] + varfloat = 0.1 + + const tuple = (1, 2) + + test_y_array=[(@__MODULE__).Test_y(rand()) for i in 1:10] + test_dict = Dict("abc"=>1, "abcd"=>10, :bar=>2, :bar2=>9, Base=>3, + occursin=>4, `ls`=>5, 66=>7, 67=>8, ("q",3)=>11, + "α"=>12, :α=>13) + test_customdict = CustomDict(test_dict) + + macro teststr_str(s) end + macro tϵsτstρ_str(s) end + macro testcmd_cmd(s) end + macro tϵsτcmδ_cmd(s) end + + var"complicated symbol with spaces" = 5 + + struct WeirdNames end + Base.propertynames(::WeirdNames) = (Symbol("oh no!"), Symbol("oh yes!")) + + # https://github.com/JuliaLang/julia/issues/52551#issuecomment-1858543413 + export exported_symbol + exported_symbol(::WeirdNames) = nothing end # module CompletionFoo test_repl_comp_dict = CompletionFoo.test_dict diff --git a/stdlib/Serialization/test/runtests.jl b/stdlib/Serialization/test/runtests.jl index a7d5023e1ec518..c2a14b68be2ed0 100644 --- a/stdlib/Serialization/test/runtests.jl +++ b/stdlib/Serialization/test/runtests.jl @@ -595,7 +595,7 @@ let f_data f_data = "N0pMBwAAAAA0MxMAAAAAAAAAAAEFIyM1IzYiAAAAABBYH04BBE1haW6bRCIAAAAAIgAAAABNTEy+AQIjNRUAI78jAQAAAAAAAAAfTgEETWFpbkQBAiM1AQdSRVBMWzJdvxBTH04BBE1haW6bRAMAAAAzLAAARkYiAAAAAE7BTBsVRsEWA1YkH04BBE1haW5EAQEqwCXAFgNWJB9OAQRNYWluRJ0ovyXBFgFVKMAVAAbBAQAAAAEAAAABAAAATsEVRr80EAEMTGluZUluZm9Ob2RlH04BBE1haW6bRB9OAQRNYWluRAECIzUBB1JFUExbMl2/vhW+FcEAAAAVRsGifX5MTExMTsEp" end f = deserialize(IOBuffer(base64decode(f_data))) - @test f(10,3) == 23 + @test invokelatest(f, 10,3) == 23 end # issue #33466, IdDict diff --git a/stdlib/Test/src/Test.jl b/stdlib/Test/src/Test.jl index cf906591b99627..e8c7d49d076aac 100644 --- a/stdlib/Test/src/Test.jl +++ b/stdlib/Test/src/Test.jl @@ -890,10 +890,17 @@ macro test_warn(msg, expr) quote let fname = tempname() try - ret = open(fname, "w") do f - redirect_stderr(f) do - $(esc(expr)) - end + f = open(fname, "w") + stdold = stderr + redirect_stderr(f) + ret = try + # We deliberately don't use the thunk versions of open/redirect + # to ensure that adding the macro does not change the toplevel-ness + # of the resulting expression. + $(esc(expr)) + finally + redirect_stderr(stdold) + close(f) end @test contains_warn(read(fname, String), $(esc(msg))) ret @@ -922,10 +929,14 @@ macro test_nowarn(expr) # here. let fname = tempname() try - ret = open(fname, "w") do f - redirect_stderr(f) do - $(esc(expr)) - end + f = open(fname, "w") + stdold = stderr + redirect_stderr(f) + ret = try + $(esc(expr)) + finally + redirect_stderr(stdold) + close(f) end stderr_content = read(fname, String) print(stderr, stderr_content) # this is helpful for debugging diff --git a/test/backtrace.jl b/test/backtrace.jl index 68873678df57b1..8b6a65e0a3ed66 100644 --- a/test/backtrace.jl +++ b/test/backtrace.jl @@ -24,6 +24,7 @@ eval(Expr(:function, Expr(:call, :test_inline_1), Expr(:call, :throw, "foo"), Expr(:meta, :pop_loc), Expr(:line, 99))))) +@Core.worldinc @test functionloc(test_inline_1) == ("backtrace.jl", 99) try @@ -49,6 +50,7 @@ eval(Expr(:function, Expr(:call, :test_inline_2), Expr(:call, :throw, "foo"), Expr(:meta, :pop_loc), Expr(:line, 99))))) +@Core.worldinc @test functionloc(test_inline_2) == ("backtrace.jl", 81) try diff --git a/test/ccall.jl b/test/ccall.jl index b10504de21abc3..4872069ecdf310 100644 --- a/test/ccall.jl +++ b/test/ccall.jl @@ -70,8 +70,10 @@ function gen_ccall_echo(x, T, U, ret=nothing) func_ex = :($ret($func_ex)) end @gensym func_name - @eval @noinline $func_name(x) = $func_ex - :($func_name($(esc(x)))) + quote + @noinline $(esc(func_name))(x) = $func_ex + $(esc(func_name))($(esc(x))) + end end macro ccall_echo_func(x, T, U) diff --git a/test/core.jl b/test/core.jl index 4b5a674ba44b3a..a301c6a02df874 100644 --- a/test/core.jl +++ b/test/core.jl @@ -2621,7 +2621,9 @@ end # issue #8338 let ex = Expr(:(=), :(f8338(x;y=4)), :(x*y)) eval(ex) - @test f8338(2) == 8 + invokelatest() do + @test f8338(2) == 8 + end end # call overloading (#2403) @@ -8332,3 +8334,13 @@ let s = mktemp() do path, io end @test strip(s) == "xxx = 42" end + +# `module` has an implicit world-age increment +let foo = eval(Expr(:toplevel, :(module BarModuleInc; struct FooModuleInc; end; end), :(BarModuleInc.FooModuleInc()))) + @Core.worldinc + @test foo == BarModuleInc.FooModuleInc() +end + +eval(:(module BarModuleInc; module BazModuleInc; struct FooModuleInc; end; end; const foo = BazModuleInc.FooModuleInc(); end)) +@Core.worldinc +@test BarModuleInc.foo == BarModuleInc.BazModuleInc.FooModuleInc() diff --git a/test/deprecation_exec.jl b/test/deprecation_exec.jl index 61ffcc2a59ac6e..92fb3d98cb015b 100644 --- a/test/deprecation_exec.jl +++ b/test/deprecation_exec.jl @@ -68,6 +68,7 @@ begin # @deprecate ex = :(module M22845; import ..DeprecationTests: bar; bar(x::Number) = x + 3; end) @test_warn "importing deprecated binding" eval(ex) + @Core.worldinc @test @test_nowarn(DeprecationTests.bar(4)) == 7 @test @test_warn "`f1` is deprecated, use `f` instead." f1() diff --git a/test/error.jl b/test/error.jl index 8657c707207792..550d7ae634e120 100644 --- a/test/error.jl +++ b/test/error.jl @@ -93,6 +93,7 @@ end @testset "MethodError for methods without line numbers" begin try eval(Expr(:function, :(f44319()), 0)) + @Core.worldinc f44319(1) catch e s = sprint(showerror, e) diff --git a/test/llvmcall.jl b/test/llvmcall.jl index c83ac05b1ec481..cb87ae3386597c 100644 --- a/test/llvmcall.jl +++ b/test/llvmcall.jl @@ -158,6 +158,7 @@ module ObjLoadTest end @test_throws(ErrorException("@ccallable was already defined for this method name"), @eval @ccallable Cvoid jl_the_callback(not_the_method::Int) = "other") + @Core.worldinc # Make sure everything up until here gets compiled @test jl_the_callback() === nothing @test jl_the_callback(1) == "other" diff --git a/test/rebinding.jl b/test/rebinding.jl index c93c34be7a75c6..0396bbb1fb4248 100644 --- a/test/rebinding.jl +++ b/test/rebinding.jl @@ -15,6 +15,7 @@ module Rebinding @test Base.binding_kind(@__MODULE__, :Foo) == Base.BINDING_KIND_CONST @test !contains(repr(x), "@world") Base.delete_binding(@__MODULE__, :Foo) + @Core.worldinc @test Base.binding_kind(@__MODULE__, :Foo) == Base.BINDING_KIND_GUARD @test contains(repr(x), "@world") diff --git a/test/reflection.jl b/test/reflection.jl index 8c701acb9c09de..0c2c330a598732 100644 --- a/test/reflection.jl +++ b/test/reflection.jl @@ -777,6 +777,7 @@ faz1(x) = foo1(x) @test faz1(1.0) == 1 m = first(methods(bar1, Tuple{Int})) Base.delete_method(m) +@Core.worldinc @test bar1(1) == 1 @test bar1(1.0) == 1 @test foo1(1) == 1 @@ -793,6 +794,7 @@ faz2(x) = foo2(x) @test foo2(1.0) == 1 m = first(methods(bar2, Tuple{Int})) Base.delete_method(m) +@Core.worldinc @test bar2(1.0) == 1 @test bar2(1) == 1 @test foo2(1) == 1 @@ -809,6 +811,7 @@ faz3(x) = foo3(x) @test bar3(1.0) == 1 m = first(methods(bar3, Tuple{Int})) Base.delete_method(m) +@Core.worldinc @test bar3(1) == 1 @test bar3(1.0) == 1 @test foo3(1) == 1 @@ -823,6 +826,7 @@ foo4(x) = bar4(x) faz4(x) = foo4(x) m = first(methods(bar4, Tuple{Int})) Base.delete_method(m) +@Core.worldinc @test bar4(1) == 1 @test bar4(1.0) == 1 @test foo4(1) == 1 @@ -846,12 +850,15 @@ log = Ref{String}() @test f48802!(log, 1) == 2 @test log[] == "default" addmethod_48802() +@Core.worldinc @test f48802!(log, 1) == 2 @test log[] == "specialized" Base.delete_method(which(f48802!, Tuple{Any, Int})) +@Core.worldinc @test f48802!(log, 1) == 2 @test log[] == "default" addmethod_48802() +@Core.worldinc @test f48802!(log, 1) == 2 @test log[] == "specialized" @@ -862,6 +869,7 @@ fookw(y::Int) = 2 @test fookw(1) == 2 m = collect(methods(fookw))[2] Base.delete_method(m) +@Core.worldinc @test fookw(1) == 2 @test_throws MethodError fookw("string") @@ -873,12 +881,14 @@ end @test foomany(Int32(5), "hello", 3.2) == "hello" m = first(methods(foomany, Tuple{Int32, String, Float64})) Base.delete_method(m) +@Core.worldinc @test_throws MethodError foomany(Int32(5), "hello", 3.2) struct EmptyType end Base.convert(::Type{EmptyType}, x::Integer) = EmptyType() m = first(methods(convert, Tuple{Type{EmptyType}, Integer})) Base.delete_method(m) +@Core.worldinc @test_throws MethodError convert(EmptyType, 1) # parametric methods @@ -886,6 +896,7 @@ parametric(A::Array{T,N}, i::Vararg{Int,N}) where {T,N} = N @test parametric(rand(2,2), 1, 1) == 2 m = first(methods(parametric)) Base.delete_method(m) +@Core.worldinc @test_throws MethodError parametric(rand(2,2), 1, 1) # Deletion and ambiguity detection @@ -893,10 +904,12 @@ foo(::Int, ::Int) = 1 foo(::Real, ::Int) = 2 foo(::Int, ::Real) = 3 Base.delete_method(first(methods(foo))) +@Core.worldinc @test_throws MethodError foo(1, 1) foo(::Int, ::Int) = 1 foo(1, 1) Base.delete_method(first(methods(foo))) +@Core.worldinc @test_throws MethodError foo(1, 1) # multiple deletions and ambiguities @@ -918,6 +931,7 @@ uambig(::Union{Float64,Nothing}) = 2 @test_throws MethodError uambig(nothing) m = which(uambig, Tuple{Int}) Base.delete_method(m) +@Core.worldinc @test_throws MethodError uambig(1) @test uambig(1.0) == 2 @test uambig(nothing) == 2 diff --git a/test/syntax.jl b/test/syntax.jl index c19721b5c54b3a..9309d42f5bceb8 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -553,7 +553,14 @@ for (str, tag) in Dict("" => :none, "\"" => :string, "#=" => :comment, "'" => :c end # meta nodes for optional positional arguments -let src = Meta.lower(Main, :(@inline f(p::Int=2) = 3)).args[1].code[end-2].args[3] +let code = Meta.lower(Main, :(@inline f(p::Int=2) = 3)).args[1].code + local src + for i = length(code):-1:1 + if Meta.isexpr(code[i], :method) + src = code[i].args[3] + break + end + end @test Core.Compiler.is_declared_inline(src) end @@ -578,6 +585,7 @@ let thismodule = @__MODULE__, @test isa(ex, Expr) @test !isdefined(M16096, :foo16096) local_foo16096 = Core.eval(@__MODULE__, ex) + Core.@worldinc @test local_foo16096(2.0) == 1 @test !@isdefined foo16096 @test !@isdefined it @@ -3102,6 +3110,7 @@ end ex = Expr(:block) ex.args = fill!(Vector{Any}(undef, 700000), 1) f = eval(Expr(:function, :(), ex)) + @Core.worldinc @test f() == 1 ex = Expr(:vcat) ex.args = fill!(Vector{Any}(undef, 600000), 1)