From b9857935ee0acc43def346e791d2a9f4f26cb8f1 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Tue, 31 Oct 2023 16:31:10 +0000 Subject: [PATCH] add verbose UndefVarError messages Record the 'scope' of the variable that was undefined (the Module, or a descriptive word such as :local or :static_parameter). Add that scope to the error message, and expand the hint suggestions added by the REPL to include more specific advice on common mistakes: - forgetting to set an initial value - forgetting to import a global - creating a local of the same name as a global - not matching a static parameter in a signature subtype Fixes #17062 (although more could probably be done to search for typos using REPL.string_distance and getting the method from stacktrace) Fixes #18877 Fixes #25263 Fixes #35126 Fixes #39280 Fixes #41728 Fixes #48731 Fixes #49917 Fixes #50369 --- base/boot.jl | 3 ++ base/docs/basedocs.jl | 7 ++-- base/errorshow.jl | 10 +++++ base/logging.jl | 4 +- doc/src/manual/control-flow.md | 7 ++-- doc/src/manual/distributed-computing.md | 4 +- doc/src/manual/faq.md | 6 +-- doc/src/manual/metaprogramming.md | 4 +- doc/src/manual/modules.md | 6 +-- doc/src/manual/variables-and-scoping.md | 13 ++++--- src/ast.c | 2 + src/builtins.c | 2 +- src/codegen.cpp | 27 +++++++------ src/interpreter.c | 10 ++--- src/julia.h | 3 +- src/julia_internal.h | 1 + src/module.c | 9 +---- src/rtutils.c | 26 +++++++++++-- stdlib/REPL/src/REPLCompletions.jl | 50 +++++++++++++++++++++++-- stdlib/REPL/test/repl.jl | 8 ++-- stdlib/REPL/test/replcompletions.jl | 10 +++-- stdlib/Test/test/runtests.jl | 2 +- test/ccall.jl | 4 +- test/client.jl | 6 +-- test/compiler/inference.jl | 2 +- test/compiler/inline.jl | 2 +- test/core.jl | 34 ++++++++--------- test/corelogging.jl | 8 ++-- test/errorshow.jl | 7 +++- test/subtype.jl | 2 +- test/syntax.jl | 4 +- test/testdefs.jl | 3 ++ 32 files changed, 186 insertions(+), 100 deletions(-) diff --git a/base/boot.jl b/base/boot.jl index 98eceb1414ecc..8859f7f96297d 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -334,6 +334,9 @@ struct StackOverflowError <: Exception end struct UndefRefError <: Exception end struct UndefVarError <: Exception var::Symbol + scope # a Module or Symbol or other object describing the context where this variable was looked for (e.g. Main or :local or :static_parameter) + UndefVarError(var::Symbol) = new(var) + UndefVarError(var::Symbol, @nospecialize scope) = new(var, scope) end struct ConcurrencyViolationError <: Exception msg::AbstractString diff --git a/base/docs/basedocs.jl b/base/docs/basedocs.jl index 071e5b38e3585..87f568a420b8d 100644 --- a/base/docs/basedocs.jl +++ b/base/docs/basedocs.jl @@ -1818,14 +1818,14 @@ In these examples, `a` is a [`Rational`](@ref), which has two fields. nfields """ - UndefVarError(var::Symbol) + UndefVarError(var::Symbol, [scope]) A symbol in the current scope is not defined. # Examples ```jldoctest julia> a -ERROR: UndefVarError: `a` not defined +ERROR: UndefVarError: `a` not defined in `Main` julia> a = 1; @@ -2346,7 +2346,8 @@ See also [`setproperty!`](@ref Base.setproperty!) and [`getglobal`](@ref) julia> module M end; julia> M.a # same as `getglobal(M, :a)` -ERROR: UndefVarError: `a` not defined +ERROR: UndefVarError: `a` not defined in `M` +Suggestion: check for spelling errors or missing imports. No global of this name exists in this module. julia> setglobal!(M, :a, 1) 1 diff --git a/base/errorshow.jl b/base/errorshow.jl index dd6cd75e57c6b..0440e2073ead0 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -170,6 +170,16 @@ showerror(io::IO, ex::UndefKeywordError) = function showerror(io::IO, ex::UndefVarError) print(io, "UndefVarError: `$(ex.var)` not defined") + if isdefined(ex, :scope) + scope = ex.scope + if scope isa Module + print(io, " in `$scope`") + elseif scope === :static_parameter + print(io, " in static parameter matching") + else + print(io, " in $scope scope") + end + end Experimental.show_error_hints(io, ex) end diff --git a/base/logging.jl b/base/logging.jl index 208774bcecf38..7150299efa647 100644 --- a/base/logging.jl +++ b/base/logging.jl @@ -335,12 +335,12 @@ function logmsg_code(_module, file, line, level, message, exs...) checkerrors = nothing for kwarg in reverse(log_data.kwargs) if isa(kwarg.args[2].args[1], Symbol) - checkerrors = Expr(:if, Expr(:isdefined, kwarg.args[2]), checkerrors, Expr(:call, Expr(:core, :UndefVarError), QuoteNode(kwarg.args[2].args[1]))) + checkerrors = Expr(:if, Expr(:isdefined, kwarg.args[2]), checkerrors, Expr(:call, Expr(:core, :UndefVarError), QuoteNode(kwarg.args[2].args[1]), QuoteNode(:local))) end end if isa(message, Symbol) message = esc(message) - checkerrors = Expr(:if, Expr(:isdefined, message), checkerrors, Expr(:call, Expr(:core, :UndefVarError), QuoteNode(message.args[1]))) + checkerrors = Expr(:if, Expr(:isdefined, message), checkerrors, Expr(:call, Expr(:core, :UndefVarError), QuoteNode(message.args[1]), QuoteNode(:local))) end logrecord = quote let err = $checkerrors diff --git a/doc/src/manual/control-flow.md b/doc/src/manual/control-flow.md index 84b7f141dfd13..5eb2bd24fb076 100644 --- a/doc/src/manual/control-flow.md +++ b/doc/src/manual/control-flow.md @@ -139,7 +139,7 @@ julia> test(1,2) x is less than y. julia> test(2,1) -ERROR: UndefVarError: `relation` not defined +ERROR: UndefVarError: `relation` not defined in local scope Stacktrace: [1] test(::Int64, ::Int64) at ./none:7 ``` @@ -458,7 +458,7 @@ julia> for j = 1:3 3 julia> j -ERROR: UndefVarError: `j` not defined +ERROR: UndefVarError: `j` not defined in `Main` ``` ```jldoctest @@ -862,7 +862,8 @@ end else foo end - ERROR: UndefVarError: `foo` not defined + ERROR: UndefVarError: `foo` not defined in `Main` + Suggestion: check for spelling errors or missing imports. No global of this name exists in this module. ``` Use the [`local` keyword](@ref local-scope) outside the `try` block to make the variable accessible from anywhere within the outer scope. diff --git a/doc/src/manual/distributed-computing.md b/doc/src/manual/distributed-computing.md index 83d4f14808a8f..dd372ccbc53d4 100644 --- a/doc/src/manual/distributed-computing.md +++ b/doc/src/manual/distributed-computing.md @@ -158,7 +158,7 @@ julia> rand2(2,2) 1.15119 0.918912 julia> fetch(@spawnat :any rand2(2,2)) -ERROR: RemoteException(2, CapturedException(UndefVarError(Symbol("#rand2")) +ERROR: RemoteException(2, CapturedException(UndefVarError(Symbol("#rand2")))) Stacktrace: [...] ``` @@ -209,7 +209,7 @@ MyType(7) julia> fetch(@spawnat 2 MyType(7)) ERROR: On worker 2: -UndefVarError: `MyType` not defined +UndefVarError: `MyType` not defined in `Main` ⋮ julia> fetch(@spawnat 2 DummyModule.MyType(7)) diff --git a/doc/src/manual/faq.md b/doc/src/manual/faq.md index 5ac88bbc98bf0..ccea44f922a8e 100644 --- a/doc/src/manual/faq.md +++ b/doc/src/manual/faq.md @@ -725,7 +725,7 @@ julia> module Foo julia> Foo.foo() ERROR: On worker 2: -UndefVarError: `Foo` not defined +UndefVarError: `Foo` not defined in `Main` Stacktrace: [...] ``` @@ -746,7 +746,7 @@ julia> @everywhere module Foo julia> Foo.foo() ERROR: On worker 2: -UndefVarError: `gvar` not defined +UndefVarError: `gvar` not defined in `Main.Foo` Stacktrace: [...] ``` @@ -782,7 +782,7 @@ bar (generic function with 1 method) julia> remotecall_fetch(bar, 2) ERROR: On worker 2: -UndefVarError: `#bar` not defined +UndefVarError: `#bar` not defined in `Main` [...] julia> anon_bar = ()->1 diff --git a/doc/src/manual/metaprogramming.md b/doc/src/manual/metaprogramming.md index 4936e4fbe550f..b1623ff8591b0 100644 --- a/doc/src/manual/metaprogramming.md +++ b/doc/src/manual/metaprogramming.md @@ -379,7 +379,7 @@ julia> ex = :(a + b) :(a + b) julia> eval(ex) -ERROR: UndefVarError: `b` not defined +ERROR: UndefVarError: `b` not defined in `Main` [...] julia> a = 1; b = 2; @@ -397,7 +397,7 @@ julia> ex = :(x = 1) :(x = 1) julia> x -ERROR: UndefVarError: `x` not defined +ERROR: UndefVarError: `x` not defined in `Main` julia> eval(ex) 1 diff --git a/doc/src/manual/modules.md b/doc/src/manual/modules.md index 4be08edc56f38..10da581c2a8b3 100644 --- a/doc/src/manual/modules.md +++ b/doc/src/manual/modules.md @@ -288,7 +288,7 @@ julia> using .A, .B julia> f WARNING: both B and A export "f"; uses of it in module Main must be qualified -ERROR: UndefVarError: `f` not defined +ERROR: UndefVarError: `f` not defined in `Main` ``` Here, Julia cannot decide which `f` you are referring to, so you have to make a choice. The following solutions are commonly used: @@ -404,7 +404,7 @@ x = 0 module Sub using ..TestPackage -z = y # ERROR: UndefVarError: `y` not defined +z = y # ERROR: UndefVarError: `y` not defined in `Main` end y = 1 @@ -420,7 +420,7 @@ For similar reasons, you cannot use a cyclic ordering: module A module B -using ..C # ERROR: UndefVarError: `C` not defined +using ..C # ERROR: UndefVarError: `C` not defined in `Main.A` end module C diff --git a/doc/src/manual/variables-and-scoping.md b/doc/src/manual/variables-and-scoping.md index c763d62680091..9d2d68244a65b 100644 --- a/doc/src/manual/variables-and-scoping.md +++ b/doc/src/manual/variables-and-scoping.md @@ -90,7 +90,8 @@ julia> module B julia> module D b = a # errors as D's global scope is separate from A's end; -ERROR: UndefVarError: `a` not defined +ERROR: UndefVarError: `a` not defined in `D` +Suggestion: check for spelling errors or missing imports. No global of this name exists in this module. ``` If a top-level expression contains a variable declaration with keyword `local`, @@ -187,7 +188,7 @@ julia> greet() hello julia> x # global -ERROR: UndefVarError: `x` not defined +ERROR: UndefVarError: `x` not defined in `Main` ``` Inside of the `greet` function, the assignment `x = "hello"` causes `x` to be a new local variable @@ -256,7 +257,7 @@ julia> sum_to(10) 55 julia> s # global -ERROR: UndefVarError: `s` not defined +ERROR: UndefVarError: `s` not defined in `Main` ``` Since `s` is local to the function `sum_to`, calling the function has no effect on the global @@ -343,7 +344,7 @@ hello hello julia> x -ERROR: UndefVarError: `x` not defined +ERROR: UndefVarError: `x` not defined in `Main` ``` Since the global `x` is not defined when the `for` loop is evaluated, the first clause of the soft @@ -408,7 +409,7 @@ julia> code = """ julia> include_string(Main, code) ┌ Warning: Assignment to `s` in soft scope is ambiguous because a global variable by the same name exists: `s` will be treated as a new local. Disambiguate by using `local s` to suppress this warning or `global s` to assign to the existing global variable. └ @ string:4 -ERROR: LoadError: UndefVarError: `s` not defined +ERROR: LoadError: UndefVarError: `s` not defined in local scope ``` Here we use [`include_string`](@ref), to evaluate `code` as though it were the contents of a file. @@ -559,7 +560,7 @@ julia> let x = 1, z println("z: $z") # errors as z has not been assigned yet but is local end x: 1, y: -1 -ERROR: UndefVarError: `z` not defined +ERROR: UndefVarError: `z` not defined in local scope ``` The assignments are evaluated in order, with each right-hand side evaluated in the scope before diff --git a/src/ast.c b/src/ast.c index 4647dd9f85a63..9e22369d836b9 100644 --- a/src/ast.c +++ b/src/ast.c @@ -60,6 +60,7 @@ JL_DLLEXPORT jl_sym_t *jl_thunk_sym; JL_DLLEXPORT jl_sym_t *jl_foreigncall_sym; JL_DLLEXPORT jl_sym_t *jl_as_sym; JL_DLLEXPORT jl_sym_t *jl_global_sym; +JL_DLLEXPORT jl_sym_t *jl_local_sym; JL_DLLEXPORT jl_sym_t *jl_list_sym; JL_DLLEXPORT jl_sym_t *jl_dot_sym; JL_DLLEXPORT jl_sym_t *jl_newvar_sym; @@ -322,6 +323,7 @@ void jl_init_common_symbols(void) jl_opaque_closure_method_sym = jl_symbol("opaque_closure_method"); jl_const_sym = jl_symbol("const"); jl_global_sym = jl_symbol("global"); + jl_local_sym = jl_symbol("local"); jl_thunk_sym = jl_symbol("thunk"); jl_toplevel_sym = jl_symbol("toplevel"); jl_dot_sym = jl_symbol("."); diff --git a/src/builtins.c b/src/builtins.c index bed8bbc2b0216..b2733818abcb0 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -669,7 +669,7 @@ static jl_value_t *do_apply(jl_value_t **args, uint32_t nargs, jl_value_t *itera } } if (extra && iterate == NULL) { - jl_undefined_var_error(jl_symbol("iterate")); + jl_undefined_var_error(jl_symbol("iterate"), NULL); } // allocate space for the argument array and gc roots for it // based on our previous estimates diff --git a/src/codegen.cpp b/src/codegen.cpp index 8cc2634d55aa0..1a45ce14b8892 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -833,8 +833,10 @@ static const auto jltypeerror_func = new JuliaFunction<>{ }; static const auto jlundefvarerror_func = new JuliaFunction<>{ XSTR(jl_undefined_var_error), - [](LLVMContext &C) { return FunctionType::get(getVoidTy(C), - {PointerType::get(JuliaType::get_jlvalue_ty(C), AddressSpace::CalleeRooted)}, false); }, + [](LLVMContext &C) { + Type *T = PointerType::get(JuliaType::get_jlvalue_ty(C), AddressSpace::CalleeRooted); + return FunctionType::get(getVoidTy(C), {T, T}, false); + }, get_attrs_noreturn, }; static const auto jlhasnofield_func = new JuliaFunction<>{ @@ -1954,7 +1956,7 @@ static jl_returninfo_t get_specsig_function(jl_codectx_t &ctx, Module *M, Value static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval = -1); static Value *global_binding_pointer(jl_codectx_t &ctx, jl_module_t *m, jl_sym_t *s, jl_binding_t **pbnd, bool assign); -static jl_cgval_t emit_checked_var(jl_codectx_t &ctx, Value *bp, jl_sym_t *name, bool isvol, MDNode *tbaa); +static jl_cgval_t emit_checked_var(jl_codectx_t &ctx, Value *bp, jl_sym_t *name, jl_value_t *scope, bool isvol, MDNode *tbaa); static jl_cgval_t emit_sparam(jl_codectx_t &ctx, size_t i); static Value *emit_condition(jl_codectx_t &ctx, const jl_cgval_t &condV, const Twine &msg); static Value *get_current_task(jl_codectx_t &ctx); @@ -3076,7 +3078,7 @@ static jl_cgval_t emit_globalref(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t * } } // todo: use type info to avoid undef check - return emit_checked_var(ctx, bp, name, false, ctx.tbaa().tbaa_binding); + return emit_checked_var(ctx, bp, name, (jl_value_t*)mod, false, ctx.tbaa().tbaa_binding); } static bool emit_globalset(jl_codectx_t &ctx, jl_module_t *mod, jl_sym_t *sym, const jl_cgval_t &rval_info, AtomicOrdering Order) @@ -4869,15 +4871,16 @@ static jl_cgval_t emit_call(jl_codectx_t &ctx, jl_expr_t *ex, jl_value_t *rt, bo // --- accessing and assigning variables --- -static void undef_var_error_ifnot(jl_codectx_t &ctx, Value *ok, jl_sym_t *name) +static void undef_var_error_ifnot(jl_codectx_t &ctx, Value *ok, jl_sym_t *name, jl_value_t *scope) { ++EmittedUndefVarErrors; BasicBlock *err = BasicBlock::Create(ctx.builder.getContext(), "err", ctx.f); BasicBlock *ifok = BasicBlock::Create(ctx.builder.getContext(), "ok"); ctx.builder.CreateCondBr(ok, ifok, err); ctx.builder.SetInsertPoint(err); - ctx.builder.CreateCall(prepare_call(jlundefvarerror_func), - mark_callee_rooted(ctx, literal_pointer_val(ctx, (jl_value_t*)name))); + ctx.builder.CreateCall(prepare_call(jlundefvarerror_func), { + mark_callee_rooted(ctx, literal_pointer_val(ctx, (jl_value_t*)name)), + mark_callee_rooted(ctx, literal_pointer_val(ctx, scope))}); ctx.builder.CreateUnreachable(); ifok->insertInto(ctx.f); ctx.builder.SetInsertPoint(ifok); @@ -4965,7 +4968,7 @@ static Value *global_binding_pointer(jl_codectx_t &ctx, jl_module_t *m, jl_sym_t return julia_binding_gv(ctx, b); } -static jl_cgval_t emit_checked_var(jl_codectx_t &ctx, Value *bp, jl_sym_t *name, bool isvol, MDNode *tbaa) +static jl_cgval_t emit_checked_var(jl_codectx_t &ctx, Value *bp, jl_sym_t *name, jl_value_t *scope, bool isvol, MDNode *tbaa) { LoadInst *v = ctx.builder.CreateAlignedLoad(ctx.types().T_prjlvalue, bp, Align(sizeof(void*))); setName(ctx.emission_context, v, jl_symbol_name(name) + StringRef(".checked")); @@ -4976,7 +4979,7 @@ static jl_cgval_t emit_checked_var(jl_codectx_t &ctx, Value *bp, jl_sym_t *name, jl_aliasinfo_t ai = jl_aliasinfo_t::fromTBAA(ctx, tbaa); ai.decorateInst(v); } - undef_var_error_ifnot(ctx, ctx.builder.CreateIsNotNull(v), name); + undef_var_error_ifnot(ctx, ctx.builder.CreateIsNotNull(v), name, scope); return mark_julia_type(ctx, v, true, jl_any_type); } @@ -5002,7 +5005,7 @@ static jl_cgval_t emit_sparam(jl_codectx_t &ctx, size_t i) sparam = (jl_unionall_t*)sparam->body; assert(jl_is_unionall(sparam)); } - undef_var_error_ifnot(ctx, isnull, sparam->var->name); + undef_var_error_ifnot(ctx, isnull, sparam->var->name, (jl_value_t*)jl_static_parameter_sym); return mark_julia_type(ctx, sp, true, jl_any_type); } @@ -5160,7 +5163,7 @@ static jl_cgval_t emit_varinfo(jl_codectx_t &ctx, jl_varinfo_t &vi, jl_sym_t *va } if (isnull) { setName(ctx.emission_context, isnull, jl_symbol_name(varname) + StringRef("_is_null")); - undef_var_error_ifnot(ctx, isnull, varname); + undef_var_error_ifnot(ctx, isnull, varname, (jl_value_t*)jl_local_sym); } return v; } @@ -5756,7 +5759,7 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_ literal_pointer_val(ctx, jl_undefref_exception)); } else { - undef_var_error_ifnot(ctx, cond, var); + undef_var_error_ifnot(ctx, cond, var, (jl_value_t*)jl_local_sym); } return ghostValue(ctx, jl_nothing_type); } diff --git a/src/interpreter.c b/src/interpreter.c index 00ea5a50934a6..f6000b6aeca92 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -147,7 +147,7 @@ jl_value_t *jl_eval_global_var(jl_module_t *m, jl_sym_t *e) { jl_value_t *v = jl_get_global(m, e); if (v == NULL) - jl_undefined_var_error(e); + jl_undefined_var_error(e, (jl_value_t*)m); return v; } @@ -155,7 +155,7 @@ jl_value_t *jl_eval_globalref(jl_globalref_t *g) { jl_value_t *v = jl_get_globalref_value(g); if (v == NULL) - jl_undefined_var_error(g->name); + jl_undefined_var_error(g->name, (jl_value_t*)g->mod); return v; } @@ -191,7 +191,7 @@ static jl_value_t *eval_value(jl_value_t *e, interpreter_state *s) jl_error("access to invalid slot number"); jl_value_t *v = s->locals[n - 1]; if (v == NULL) - jl_undefined_var_error((jl_sym_t*)jl_array_ptr_ref(src->slotnames, n - 1)); + jl_undefined_var_error((jl_sym_t*)jl_array_ptr_ref(src->slotnames, n - 1), (jl_value_t*)jl_local_sym); return v; } if (jl_is_quotenode(e)) { @@ -268,7 +268,7 @@ static jl_value_t *eval_value(jl_value_t *e, interpreter_state *s) if (var == jl_getfield_undefref_sym) jl_throw(jl_undefref_exception); else - jl_undefined_var_error(var); + jl_undefined_var_error(var, (jl_value_t*)jl_local_sym); } return jl_nothing; } @@ -307,7 +307,7 @@ static jl_value_t *eval_value(jl_value_t *e, interpreter_state *s) if (s->sparam_vals && n <= jl_svec_len(s->sparam_vals)) { jl_value_t *sp = jl_svecref(s->sparam_vals, n - 1); if (jl_is_typevar(sp) && !s->preevaluation) - jl_undefined_var_error(((jl_tvar_t*)sp)->name); + jl_undefined_var_error(((jl_tvar_t*)sp)->name, (jl_value_t*)jl_static_parameter_sym); return sp; } // static parameter val unknown needs to be an error for ccall diff --git a/src/julia.h b/src/julia.h index 1a8e62f9dcf9d..b2f6ab8b7a62f 100644 --- a/src/julia.h +++ b/src/julia.h @@ -1877,7 +1877,6 @@ JL_DLLEXPORT int jl_get_module_max_methods(jl_module_t *m); // get binding for reading JL_DLLEXPORT jl_binding_t *jl_get_binding(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var); JL_DLLEXPORT jl_binding_t *jl_get_binding_or_error(jl_module_t *m, jl_sym_t *var); -JL_DLLEXPORT jl_binding_t *jl_get_binding_if_bound(jl_module_t *m, jl_sym_t *var); JL_DLLEXPORT jl_value_t *jl_module_globalref(jl_module_t *m, jl_sym_t *var); JL_DLLEXPORT jl_value_t *jl_get_binding_type(jl_module_t *m, jl_sym_t *var); // get binding for assignment @@ -1953,7 +1952,7 @@ JL_DLLEXPORT void JL_NORETURN jl_type_error_rt(const char *fname, const char *context, jl_value_t *ty JL_MAYBE_UNROOTED, jl_value_t *got JL_MAYBE_UNROOTED); -JL_DLLEXPORT void JL_NORETURN jl_undefined_var_error(jl_sym_t *var); +JL_DLLEXPORT void JL_NORETURN jl_undefined_var_error(jl_sym_t *var, jl_value_t *scope JL_MAYBE_UNROOTED); JL_DLLEXPORT void JL_NORETURN jl_has_no_field_error(jl_sym_t *type_name, jl_sym_t *var); JL_DLLEXPORT void JL_NORETURN jl_atomic_error(char *str); JL_DLLEXPORT void JL_NORETURN jl_bounds_error(jl_value_t *v JL_MAYBE_UNROOTED, diff --git a/src/julia_internal.h b/src/julia_internal.h index 09a963258d566..2c0c13d0bb343 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -1532,6 +1532,7 @@ extern JL_DLLEXPORT jl_sym_t *jl_thunk_sym; extern JL_DLLEXPORT jl_sym_t *jl_foreigncall_sym; extern JL_DLLEXPORT jl_sym_t *jl_as_sym; extern JL_DLLEXPORT jl_sym_t *jl_global_sym; +extern JL_DLLEXPORT jl_sym_t *jl_local_sym; extern JL_DLLEXPORT jl_sym_t *jl_list_sym; extern JL_DLLEXPORT jl_sym_t *jl_dot_sym; extern JL_DLLEXPORT jl_sym_t *jl_newvar_sym; diff --git a/src/module.c b/src/module.c index a5b967dc67735..04ddb4f4fb3ae 100644 --- a/src/module.c +++ b/src/module.c @@ -426,13 +426,6 @@ static jl_binding_t *jl_resolve_owner(jl_binding_t *b/*optional*/, jl_module_t * return b2; } -JL_DLLEXPORT jl_binding_t *jl_get_binding_if_bound(jl_module_t *m, jl_sym_t *var) -{ - jl_binding_t *b = jl_get_module_binding(m, var, 0); - return b == NULL ? NULL : jl_atomic_load_relaxed(&b->owner); -} - - // get the current likely owner of binding when accessing m.var, without resolving the binding (it may change later) JL_DLLEXPORT jl_binding_t *jl_binding_owner(jl_module_t *m, jl_sym_t *var) { @@ -467,7 +460,7 @@ JL_DLLEXPORT jl_binding_t *jl_get_binding_or_error(jl_module_t *m, jl_sym_t *var { jl_binding_t *b = jl_get_binding(m, var); if (b == NULL) - jl_undefined_var_error(var); + jl_undefined_var_error(var, (jl_value_t*)m); // XXX: this only considers if the original is deprecated, not the binding in m if (b->deprecated) jl_binding_deprecation_warning(m, var, b); diff --git a/src/rtutils.c b/src/rtutils.c index e3cb1af50c676..5fbba42c15b02 100644 --- a/src/rtutils.c +++ b/src/rtutils.c @@ -127,11 +127,29 @@ JL_DLLEXPORT void JL_NORETURN jl_type_error(const char *fname, jl_type_error_rt(fname, "", expected, got); } -JL_DLLEXPORT void JL_NORETURN jl_undefined_var_error(jl_sym_t *var) +JL_DLLEXPORT void JL_NORETURN jl_undefined_var_error(jl_sym_t *var, jl_value_t *scope) { - if (!jl_undefvarerror_type) - jl_errorf("UndefVarError(%s)", jl_symbol_name(var)); - jl_throw(jl_new_struct(jl_undefvarerror_type, var)); + if (!jl_undefvarerror_type) { + const char *s1 = ""; + const char *s2 = ""; + if (scope) { + if (jl_is_symbol(scope)) { + s1 = ", :"; + s2 = jl_symbol_name((jl_sym_t*)scope); + } + else if (jl_is_module(scope)) { + s1 = ", module "; + s2 = jl_symbol_name(((jl_module_t*)scope)->name); + } + else { + s1 = ", "; + s2 = "unknown scope"; + } + } + jl_errorf("UndefVarError(%s%s%s)", jl_symbol_name(var), s1, s2); + } + JL_GC_PUSH1(&scope); + jl_throw(jl_new_struct(jl_undefvarerror_type, var, scope)); } JL_DLLEXPORT void JL_NORETURN jl_has_no_field_error(jl_sym_t *type_name, jl_sym_t *var) diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index f8a367b8342da..35deb0277406c 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -1293,16 +1293,60 @@ end function UndefVarError_hint(io::IO, ex::UndefVarError) var = ex.var if var === :or - print(io, "\nsuggestion: Use `||` for short-circuiting boolean OR.") + print(io, "\nSuggestion: Use `||` for short-circuiting boolean OR.") elseif var === :and - print(io, "\nsuggestion: Use `&&` for short-circuiting boolean AND.") + print(io, "\nSuggestion: Use `&&` for short-circuiting boolean AND.") elseif var === :help println(io) # Show friendly help message when user types help or help() and help is undefined show(io, MIME("text/plain"), Base.Docs.parsedoc(Base.Docs.keywords[:help])) elseif var === :quit - print(io, "\nsuggestion: To exit Julia, use Ctrl-D, or type exit() and press enter.") + print(io, "\nSuggestion: To exit Julia, use Ctrl-D, or type exit() and press enter.") end + if isdefined(ex, :scope) + scope = ex.scope + if scope isa Module + bnd = ccall(:jl_get_module_binding, Any, (Any, Any, Cint), scope, var, true)::Core.Binding + if isdefined(bnd, :owner) + owner = bnd.owner + if owner === bnd + print(io, "\nSuggestion: add an appropriate import or assignment. This global was declared but not assigned.") + end + else + owner = ccall(:jl_binding_owner, Ptr{Cvoid}, (Any, Any), scope, var) + if C_NULL == owner + print(io, "\nSuggestion: check for spelling errors or missing imports. No global of this name exists in this module.") + owner = bnd + else + owner = unsafe_pointer_to_objref(owner)::Core.Binding + end + end + if owner !== bnd + # this could use jl_binding_dbgmodule for the exported location in the message too + print(io, "\nSuggestion: this global was defined as `$(owner.globalref)` but not assigned a value.") + end + elseif scope === :static_parameter + print(io, "\nSuggestion: run Test.detect_unbound_args to detect method arguments that do not fully constrain a type parameter.") + elseif scope === :local + print(io, "\nSuggestion: check for an assignment to a local variable that shadows a global of the same name.") + end + else + scope = undef + end + warnfor(m, var) = Base.isbindingresolved(m, var) && isdefined(m, var) && (print(io, "\nHint: a global variable of this name also exists in $m."); true) + if scope !== Base && !warnfor(Base, var) + warned = false + for m in Base.loaded_modules_order + m === Core && continue + m === Base && continue + m === Main && continue + m === scope && continue + warned = warnfor(m, var) || warned + end + warned = warned || warnfor(Core, var) + warned = warned || warnfor(Main, var) + end + nothing end function __init__() diff --git a/stdlib/REPL/test/repl.jl b/stdlib/REPL/test/repl.jl index 48284a5e89161..0e5f1c5f60c26 100644 --- a/stdlib/REPL/test/repl.jl +++ b/stdlib/REPL/test/repl.jl @@ -6,6 +6,8 @@ using Random import REPL.LineEdit using Markdown +empty!(Base.Experimental._hint_handlers) # unregister error hints so they can be tested separately + @test isassigned(Base.REPL_MODULE_REF) const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test") @@ -1491,7 +1493,7 @@ fake_repl() do stdin_write, stdout_read, repl # generate top-level error write(stdin_write, "foobar\n") readline(stdout_read) - @test readline(stdout_read) == "\e[0mERROR: UndefVarError: `foobar` not defined" + @test readline(stdout_read) == "\e[0mERROR: UndefVarError: `foobar` not defined in `Main`" @test readline(stdout_read) == "" readuntil(stdout_read, "julia> ", keep=true) # check that top-level error did not change `err` @@ -1506,13 +1508,13 @@ fake_repl() do stdin_write, stdout_read, repl readuntil(stdout_read, "julia> ", keep=true) write(stdin_write, "foo()\n") readline(stdout_read) - @test readline(stdout_read) == "\e[0mERROR: UndefVarError: `foobar` not defined" + @test readline(stdout_read) == "\e[0mERROR: UndefVarError: `foobar` not defined in `Main`" readuntil(stdout_read, "julia> ", keep=true) # check that deeper error did set `err` write(stdin_write, "err\n") readline(stdout_read) @test readline(stdout_read) == "\e[0m1-element ExceptionStack:" - @test readline(stdout_read) == "UndefVarError: `foobar` not defined" + @test readline(stdout_read) == "UndefVarError: `foobar` not defined in `Main`" @test readline(stdout_read) == "Stacktrace:" readuntil(stdout_read, "\n\n", keep=true) readuntil(stdout_read, "julia> ", keep=true) diff --git a/stdlib/REPL/test/replcompletions.jl b/stdlib/REPL/test/replcompletions.jl index 1560d322b375d..5b98052e7de7e 100644 --- a/stdlib/REPL/test/replcompletions.jl +++ b/stdlib/REPL/test/replcompletions.jl @@ -4,12 +4,14 @@ using REPL.REPLCompletions using Test using Random using REPL - @testset "Check symbols previously not shown by REPL.doc_completions()" begin + +@testset "Check symbols previously not shown by REPL.doc_completions()" begin symbols = ["?","=","[]","[","]","{}","{","}",";","","'","&&","||","julia","Julia","new","@var_str"] - for i in symbols - @test i ∈ REPL.doc_completions(i, Main) - end + for i in symbols + @test i ∈ REPL.doc_completions(i, Main) end +end + let ex = quote module CompletionFoo using Random diff --git a/stdlib/Test/test/runtests.jl b/stdlib/Test/test/runtests.jl index 92ca8900882ce..1f93db2b5ed72 100644 --- a/stdlib/Test/test/runtests.jl +++ b/stdlib/Test/test/runtests.jl @@ -77,7 +77,7 @@ end @test 1234 === @test_nowarn(1234) @test 5678 === @test_warn("WARNING: foo", begin println(stderr, "WARNING: foo"); 5678; end) let a - @test_throws UndefVarError(:a) a + @test_throws UndefVarError(:a, :local) a @test_nowarn a = 1 @test a === 1 end diff --git a/test/ccall.jl b/test/ccall.jl index 6e8269a36225d..3647173eb9290 100644 --- a/test/ccall.jl +++ b/test/ccall.jl @@ -1477,7 +1477,7 @@ end # issue #20835 @test_throws(ErrorException("could not evaluate ccall argument type (it might depend on a local variable)"), eval(:(f20835(x) = ccall(:fn, Cvoid, (Ptr{typeof(x)},), x)))) -@test_throws(UndefVarError(:Something_not_defined_20835), +@test_throws(UndefVarError(:Something_not_defined_20835, @__MODULE__), eval(:(f20835(x) = ccall(:fn, Something_not_defined_20835, (Ptr{typeof(x)},), x)))) @test isempty(methods(f20835)) @@ -1838,7 +1838,7 @@ ccall_lazy_lib_name(x) = ccall((:testUcharX, compute_lib_name()), Int32, (UInt8, @test ccall_lazy_lib_name(0) == 0 @test ccall_lazy_lib_name(3) == 1 ccall_with_undefined_lib() = ccall((:time, xx_nOt_DeFiNeD_xx), Cint, (Ptr{Cvoid},), C_NULL) -@test_throws UndefVarError(:xx_nOt_DeFiNeD_xx) ccall_with_undefined_lib() +@test_throws UndefVarError(:xx_nOt_DeFiNeD_xx, @__MODULE__) ccall_with_undefined_lib() @testset "transcode for UInt8 and UInt16" begin a = [UInt8(1), UInt8(2), UInt8(3)] diff --git a/test/client.jl b/test/client.jl index b2877001d235e..61fe7d5093474 100644 --- a/test/client.jl +++ b/test/client.jl @@ -12,14 +12,14 @@ nested_error_pattern = r""" ERROR: DivideError: integer division error Stacktrace:.* - caused by: UndefVarError: `__not_a_binding__` not defined + caused by: UndefVarError: `__not_a_binding__` not defined in `Main` Stacktrace:.* """s @testset "display_error" begin # Display of errors which cause more than one entry on the exception stack excs = try - eval(nested_error_expr) + Core.eval(Main, nested_error_expr) catch Base.current_exceptions() end @@ -31,7 +31,7 @@ nested_error_pattern = r""" DivideError: integer division error Stacktrace:.* - caused by: UndefVarError: `__not_a_binding__` not defined + caused by: UndefVarError: `__not_a_binding__` not defined in `Main` Stacktrace:.* """s, sprint(show, excs)) end diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index bfb78d6c61bfd..20fdaaf297d82 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -731,7 +731,7 @@ for (codetype, all_ssa) in Any[ test_inferred_static(codetype, all_ssa) end @test f18679() === () -@test_throws UndefVarError(:any_undef_global) g18679() +@test_throws UndefVarError(:any_undef_global, @__MODULE__) g18679() @test h18679() === nothing diff --git a/test/compiler/inline.jl b/test/compiler/inline.jl index f3c29e4c2e970..2ad18e3374d7f 100644 --- a/test/compiler/inline.jl +++ b/test/compiler/inline.jl @@ -71,7 +71,7 @@ function bar12620() foo_inl(i==1) end end -@test_throws UndefVarError(:y) bar12620() +@test_throws UndefVarError(:y, :local) bar12620() # issue #16165 @inline f16165(x) = (x = UInt(x) + 1) diff --git a/test/core.jl b/test/core.jl index 739ea182cd4f3..72b9f435d21a1 100644 --- a/test/core.jl +++ b/test/core.jl @@ -552,7 +552,7 @@ function i18408() return (x -> i) end let f = i18408() - @test_throws UndefVarError(:i) f(0) + @test_throws UndefVarError(:i, :local) f(0) end # issue #23558 @@ -612,7 +612,7 @@ begin global f7234_cnt += -10000 end end -@test_throws UndefVarError(:glob_x2) f7234_a() +@test_throws UndefVarError(:glob_x2, :local) f7234_a() @test f7234_cnt == 1 begin global glob_x2 = 24 @@ -622,7 +622,7 @@ begin global f7234_cnt += -10000 end end -@test_throws UndefVarError(:glob_x2) f7234_b() +@test_throws UndefVarError(:glob_x2, :local) f7234_b() @test f7234_cnt == 2 # globals can accessed if declared for i = 1:2 @@ -737,11 +737,11 @@ function f21900() global f21900_cnt += -1000 nothing end -@test_throws UndefVarError(:x_global_undefined_error) f21900() +@test_throws UndefVarError(:x_global_undefined_error, @__MODULE__) f21900() @test f21900_cnt == 1 # use @eval so this runs as a toplevel scope block -@test_throws UndefVarError(:foo21900) @eval begin +@test_throws UndefVarError(:foo21900, @__MODULE__) @eval begin for i21900 = 1:10 local bar21900 for j21900 = 1:10 @@ -754,7 +754,7 @@ end @test !@isdefined(foo21900) @test !@isdefined(bar21900) bar21900 = 0 -@test_throws UndefVarError(:foo21900) @eval begin +@test_throws UndefVarError(:foo21900, @__MODULE__) @eval begin for i21900 = 1:10 global bar21900 for j21900 = 1:10 @@ -4176,7 +4176,7 @@ let foo(x::Union{T, Nothing}, y::Union{T, Nothing}) where {T} = 1 end let foo(x::Union{T, Nothing}, y::Union{T, Nothing}) where {T} = T @test foo(1, nothing) === Int - @test_throws UndefVarError(:T) foo(nothing, nothing) + @test_throws UndefVarError(:T, :static_parameter) foo(nothing, nothing) end module TestMacroGlobalFunction @@ -4230,14 +4230,14 @@ foo9677(x::Array) = invoke(foo9677, Tuple{AbstractArray}, x) # issue #6846 f6846() = (please6846; 2) -@test_throws UndefVarError(:please6846) f6846() +@test_throws UndefVarError(:please6846, @__MODULE__) f6846() module M6846 macro f() return esc(:(please6846; 2)) end end -@test_throws UndefVarError(:please6846) @M6846.f() +@test_throws UndefVarError(:please6846, @__MODULE__) @M6846.f() # issue #14758 @test isa(@eval(f14758(; $([]...)) = ()), Function) @@ -4940,7 +4940,7 @@ function trigger14878() w.ext[:14878] = B14878(junk) # global junk not defined! return w end -@test_throws UndefVarError(:junk) trigger14878() +@test_throws UndefVarError(:junk, @__MODULE__) trigger14878() # issue #1090 function f1090(x)::Int @@ -5258,9 +5258,9 @@ GC.enable(true) @test isa(which(bad_tvars, ()), Method) @test bad_tvars() === 1 @test_warn "declares type variable T but does not use it" @eval bad_tvars2() where {T} = T -@test_throws UndefVarError(:T) bad_tvars2() +@test_throws UndefVarError(:T, :static_parameter) bad_tvars2() missing_tvar(::T...) where {T} = T -@test_throws UndefVarError(:T) missing_tvar() +@test_throws UndefVarError(:T, :static_parameter) missing_tvar() @test missing_tvar(1) === Int @test missing_tvar(1, 2, 3) === Int @test_throws MethodError missing_tvar(1, 2, "3") @@ -5876,7 +5876,7 @@ function f_unused_undefined_sp(::T...) where T T return 0 end -@test_throws UndefVarError(:T) f_unused_undefined_sp() +@test_throws UndefVarError(:T, :static_parameter) f_unused_undefined_sp() # note: the constant `5` here should be > DataType.ninitialized. # This tests that there's no crash due to accessing Type.body.layout. @@ -6866,7 +6866,7 @@ end # issue #21004 const PTuple_21004{N,T} = NTuple{N,VecElement{T}} @test_throws ArgumentError("too few elements for tuple type $PTuple_21004") PTuple_21004(1) -@test_throws UndefVarError(:T) PTuple_21004_2{N,T} = NTuple{N, VecElement{T}}(1) +@test_throws UndefVarError(:T, :static_parameter) PTuple_21004_2{N,T} = NTuple{N, VecElement{T}}(1) #issue #22792 foo_22792(::Type{<:Union{Int8,Int,UInt}}) = 1; @@ -7164,7 +7164,7 @@ end c28399 = 42 @test g28399(0)() == 42 @test g28399(1)() == 42 -@test_throws UndefVarError(:__undef_28399__) f28399() +@test_throws UndefVarError(:__undef_28399__, @__MODULE__) f28399() # issue #28445 mutable struct foo28445 @@ -7960,14 +7960,14 @@ code_typed(f47476, (Int, Int, Vararg{Union{Int, NTuple{2,Int}}},)) code_typed(f47476, (Int, Int, Int, Vararg{Union{Int, NTuple{2,Int}}},)) code_typed(f47476, (Int, Int, Int, Int, Vararg{Union{Int, NTuple{2,Int}}},)) @test f47476(1, 2, 3, 4, 5, 6, (7, 8)) === 2 -@test_throws UndefVarError(:N) f47476(1, 2, 3, 4, 5, 6, 7) +@test_throws UndefVarError(:N, :static_parameter) f47476(1, 2, 3, 4, 5, 6, 7) vect47476(::Type{T}) where {T} = T @test vect47476(Type{Type{Type{Int32}}}) === Type{Type{Type{Int32}}} @test vect47476(Type{Type{Type{Int64}}}) === Type{Type{Type{Int64}}} g47476(::Union{Nothing,Int,Val{T}}...) where {T} = T -@test_throws UndefVarError(:T) g47476(nothing, 1, nothing, 2, nothing, 3, nothing, 4, nothing, 5) +@test_throws UndefVarError(:T, :static_parameter) g47476(nothing, 1, nothing, 2, nothing, 3, nothing, 4, nothing, 5) @test g47476(nothing, 1, nothing, 2, nothing, 3, nothing, 4, nothing, 5, Val(6)) === 6 let spec = only(methods(g47476)).specializations::Core.SimpleVector @test !isempty(spec) diff --git a/test/corelogging.jl b/test/corelogging.jl index 9626f48e4b407..778e70aecd406 100644 --- a/test/corelogging.jl +++ b/test/corelogging.jl @@ -103,12 +103,12 @@ end logmsg = (function() @info msg x=y end, function() @info msg x=y z=1+1 end)[i] @test_logs (Error, Test.Ignored(), Test.Ignored(), :logevent_error) catch_exceptions=true logmsg() - @test_throws UndefVarError(:msg) collect_test_logs(logmsg) - @test (only(collect_test_logs(logmsg, catch_exceptions=true)[1]).kwargs[:exception]::Tuple{UndefVarError, Vector})[1] === UndefVarError(:msg) + @test_throws UndefVarError(:msg, :local) collect_test_logs(logmsg) + @test (only(collect_test_logs(logmsg, catch_exceptions=true)[1]).kwargs[:exception]::Tuple{UndefVarError, Vector})[1] === UndefVarError(:msg, :local) msg = "the msg" @test_logs (Error, Test.Ignored(), Test.Ignored(), :logevent_error) catch_exceptions=true logmsg() - @test_throws UndefVarError(:y) collect_test_logs(logmsg) - @test (only(collect_test_logs(logmsg, catch_exceptions=true)[1]).kwargs[:exception]::Tuple{UndefVarError, Vector})[1] === UndefVarError(:y) + @test_throws UndefVarError(:y, :local) collect_test_logs(logmsg) + @test (only(collect_test_logs(logmsg, catch_exceptions=true)[1]).kwargs[:exception]::Tuple{UndefVarError, Vector})[1] === UndefVarError(:y, :local) y = "the y" @test_logs (Info,"the msg") logmsg() @test only(collect_test_logs(logmsg)[1]).kwargs[:x] === "the y" diff --git a/test/errorshow.jl b/test/errorshow.jl index 04af56fb208af..1b9d46ca5f4bd 100644 --- a/test/errorshow.jl +++ b/test/errorshow.jl @@ -5,6 +5,9 @@ using Random, LinearAlgebra # For curmod_* include("testenv.jl") +# re-register only the error hints that are being tested here ( +Base.Experimental.register_error_hint(Base.noncallable_number_hint_handler, MethodError) +Base.Experimental.register_error_hint(Base.string_concatenation_hint_handler, MethodError) @testset "SystemError" begin err = try; systemerror("reason", Cint(0)); false; catch ex; ex; end::SystemError @@ -350,7 +353,7 @@ let undefvar err_str = @except_str Vector{Any}(undef, 1)[1] UndefRefError @test err_str == "UndefRefError: access to undefined reference" err_str = @except_str undefvar UndefVarError - @test err_str == "UndefVarError: `undefvar` not defined" + @test err_str == "UndefVarError: `undefvar` not defined in local scope" err_str = @except_str read(IOBuffer(), UInt8) EOFError @test err_str == "EOFError: read end of file" err_str = @except_str Dict()[:doesnotexist] KeyError @@ -501,7 +504,7 @@ let @test (@macroexpand @fastmath + ) == :(Base.FastMath.add_fast) @test (@macroexpand @fastmath min(1) ) == :(Base.FastMath.min_fast(1)) let err = try; @macroexpand @doc "" f() = @x; catch ex; ex; end - @test err == UndefVarError(Symbol("@x")) + @test err == UndefVarError(Symbol("@x"), @__MODULE__) end @test (@macroexpand @seven_dollar $bar) == 7 x = 2 diff --git a/test/subtype.jl b/test/subtype.jl index 289c31c475792..9a1dd62f4f9c4 100644 --- a/test/subtype.jl +++ b/test/subtype.jl @@ -1520,7 +1520,7 @@ f26453(x::T,y::T) where {S,T>:S} = 0 @test f26453(1,2) == 0 @test f26453(1,"") == 0 g26453(x::T,y::T) where {S,T>:S} = T -@test_throws UndefVarError(:T) g26453(1,1) +@test_throws UndefVarError(:T, :static_parameter) g26453(1,1) @test issub_strict((Tuple{T,T} where T), (Tuple{T,T} where {S,T>:S})) # issue #27632 diff --git a/test/syntax.jl b/test/syntax.jl index a2f8ed76373b6..106a8f296ced2 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -1706,7 +1706,7 @@ end @test Meta.parse("(a...)") == Expr(Symbol("..."), :a) # #19324 -@test_throws UndefVarError(:x) eval(:(module M19324 +@test_throws UndefVarError(:x, :local) eval(:(module M19324 x=1 for i=1:10 x += i @@ -1923,7 +1923,7 @@ function capture_with_conditional_label() return y->x end let f = capture_with_conditional_label() # should not throw - @test_throws UndefVarError(:x) f(0) + @test_throws UndefVarError(:x, :local) f(0) end # `_` should not create a global (or local) diff --git a/test/testdefs.jl b/test/testdefs.jl index a1d4107a502bd..e8f62858d1cbb 100644 --- a/test/testdefs.jl +++ b/test/testdefs.jl @@ -5,6 +5,9 @@ using Test, Random function runtests(name, path, isolate=true; seed=nothing) old_print_setting = Test.TESTSET_PRINT_ENABLE[] Test.TESTSET_PRINT_ENABLE[] = false + # remove all hint_handlers, so that errorshow tests are not changed by which packages have been loaded on this worker already + # packages that call register_error_hint should also call this again, and then re-add any hooks they want to test + empty!(Base.Experimental._hint_handlers) try if isolate # Simple enough to type and random enough so that no one will hard