Skip to content

Commit 449c7a2

Browse files
authored
make UndefVarError messages more precise and informative (#51979)
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
1 parent 560ede5 commit 449c7a2

32 files changed

+186
-100
lines changed

base/boot.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,9 @@ struct StackOverflowError <: Exception end
335335
struct UndefRefError <: Exception end
336336
struct UndefVarError <: Exception
337337
var::Symbol
338+
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)
339+
UndefVarError(var::Symbol) = new(var)
340+
UndefVarError(var::Symbol, @nospecialize scope) = new(var, scope)
338341
end
339342
struct ConcurrencyViolationError <: Exception
340343
msg::AbstractString

base/docs/basedocs.jl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1818,14 +1818,14 @@ In these examples, `a` is a [`Rational`](@ref), which has two fields.
18181818
nfields
18191819

18201820
"""
1821-
UndefVarError(var::Symbol)
1821+
UndefVarError(var::Symbol, [scope])
18221822
18231823
A symbol in the current scope is not defined.
18241824
18251825
# Examples
18261826
```jldoctest
18271827
julia> a
1828-
ERROR: UndefVarError: `a` not defined
1828+
ERROR: UndefVarError: `a` not defined in `Main`
18291829
18301830
julia> a = 1;
18311831
@@ -2346,7 +2346,8 @@ See also [`setproperty!`](@ref Base.setproperty!) and [`getglobal`](@ref)
23462346
julia> module M end;
23472347
23482348
julia> M.a # same as `getglobal(M, :a)`
2349-
ERROR: UndefVarError: `a` not defined
2349+
ERROR: UndefVarError: `a` not defined in `M`
2350+
Suggestion: check for spelling errors or missing imports. No global of this name exists in this module.
23502351
23512352
julia> setglobal!(M, :a, 1)
23522353
1

base/errorshow.jl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,16 @@ showerror(io::IO, ex::UndefKeywordError) =
170170

171171
function showerror(io::IO, ex::UndefVarError)
172172
print(io, "UndefVarError: `$(ex.var)` not defined")
173+
if isdefined(ex, :scope)
174+
scope = ex.scope
175+
if scope isa Module
176+
print(io, " in `$scope`")
177+
elseif scope === :static_parameter
178+
print(io, " in static parameter matching")
179+
else
180+
print(io, " in $scope scope")
181+
end
182+
end
173183
Experimental.show_error_hints(io, ex)
174184
end
175185

base/logging.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -335,12 +335,12 @@ function logmsg_code(_module, file, line, level, message, exs...)
335335
checkerrors = nothing
336336
for kwarg in reverse(log_data.kwargs)
337337
if isa(kwarg.args[2].args[1], Symbol)
338-
checkerrors = Expr(:if, Expr(:isdefined, kwarg.args[2]), checkerrors, Expr(:call, Expr(:core, :UndefVarError), QuoteNode(kwarg.args[2].args[1])))
338+
checkerrors = Expr(:if, Expr(:isdefined, kwarg.args[2]), checkerrors, Expr(:call, Expr(:core, :UndefVarError), QuoteNode(kwarg.args[2].args[1]), QuoteNode(:local)))
339339
end
340340
end
341341
if isa(message, Symbol)
342342
message = esc(message)
343-
checkerrors = Expr(:if, Expr(:isdefined, message), checkerrors, Expr(:call, Expr(:core, :UndefVarError), QuoteNode(message.args[1])))
343+
checkerrors = Expr(:if, Expr(:isdefined, message), checkerrors, Expr(:call, Expr(:core, :UndefVarError), QuoteNode(message.args[1]), QuoteNode(:local)))
344344
end
345345
logrecord = quote
346346
let err = $checkerrors

doc/src/manual/control-flow.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ julia> test(1,2)
139139
x is less than y.
140140
141141
julia> test(2,1)
142-
ERROR: UndefVarError: `relation` not defined
142+
ERROR: UndefVarError: `relation` not defined in local scope
143143
Stacktrace:
144144
[1] test(::Int64, ::Int64) at ./none:7
145145
```
@@ -458,7 +458,7 @@ julia> for j = 1:3
458458
3
459459
460460
julia> j
461-
ERROR: UndefVarError: `j` not defined
461+
ERROR: UndefVarError: `j` not defined in `Main`
462462
```
463463

464464
```jldoctest
@@ -862,7 +862,8 @@ end
862862
else
863863
foo
864864
end
865-
ERROR: UndefVarError: `foo` not defined
865+
ERROR: UndefVarError: `foo` not defined in `Main`
866+
Suggestion: check for spelling errors or missing imports. No global of this name exists in this module.
866867
```
867868
Use the [`local` keyword](@ref local-scope) outside the `try` block to make the variable
868869
accessible from anywhere within the outer scope.

doc/src/manual/distributed-computing.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ julia> rand2(2,2)
158158
1.15119 0.918912
159159
160160
julia> fetch(@spawnat :any rand2(2,2))
161-
ERROR: RemoteException(2, CapturedException(UndefVarError(Symbol("#rand2"))
161+
ERROR: RemoteException(2, CapturedException(UndefVarError(Symbol("#rand2"))))
162162
Stacktrace:
163163
[...]
164164
```
@@ -209,7 +209,7 @@ MyType(7)
209209
210210
julia> fetch(@spawnat 2 MyType(7))
211211
ERROR: On worker 2:
212-
UndefVarError: `MyType` not defined
212+
UndefVarError: `MyType` not defined in `Main`
213213
214214
215215
julia> fetch(@spawnat 2 DummyModule.MyType(7))

doc/src/manual/faq.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -725,7 +725,7 @@ julia> module Foo
725725
726726
julia> Foo.foo()
727727
ERROR: On worker 2:
728-
UndefVarError: `Foo` not defined
728+
UndefVarError: `Foo` not defined in `Main`
729729
Stacktrace:
730730
[...]
731731
```
@@ -746,7 +746,7 @@ julia> @everywhere module Foo
746746
747747
julia> Foo.foo()
748748
ERROR: On worker 2:
749-
UndefVarError: `gvar` not defined
749+
UndefVarError: `gvar` not defined in `Main.Foo`
750750
Stacktrace:
751751
[...]
752752
```
@@ -782,7 +782,7 @@ bar (generic function with 1 method)
782782
783783
julia> remotecall_fetch(bar, 2)
784784
ERROR: On worker 2:
785-
UndefVarError: `#bar` not defined
785+
UndefVarError: `#bar` not defined in `Main`
786786
[...]
787787
788788
julia> anon_bar = ()->1

doc/src/manual/metaprogramming.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ julia> ex = :(a + b)
379379
:(a + b)
380380
381381
julia> eval(ex)
382-
ERROR: UndefVarError: `b` not defined
382+
ERROR: UndefVarError: `b` not defined in `Main`
383383
[...]
384384
385385
julia> a = 1; b = 2;
@@ -397,7 +397,7 @@ julia> ex = :(x = 1)
397397
:(x = 1)
398398
399399
julia> x
400-
ERROR: UndefVarError: `x` not defined
400+
ERROR: UndefVarError: `x` not defined in `Main`
401401
402402
julia> eval(ex)
403403
1

doc/src/manual/modules.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ julia> using .A, .B
288288
289289
julia> f
290290
WARNING: both B and A export "f"; uses of it in module Main must be qualified
291-
ERROR: UndefVarError: `f` not defined
291+
ERROR: UndefVarError: `f` not defined in `Main`
292292
```
293293

294294
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
404404

405405
module Sub
406406
using ..TestPackage
407-
z = y # ERROR: UndefVarError: `y` not defined
407+
z = y # ERROR: UndefVarError: `y` not defined in `Main`
408408
end
409409

410410
y = 1
@@ -420,7 +420,7 @@ For similar reasons, you cannot use a cyclic ordering:
420420
module A
421421

422422
module B
423-
using ..C # ERROR: UndefVarError: `C` not defined
423+
using ..C # ERROR: UndefVarError: `C` not defined in `Main.A`
424424
end
425425

426426
module C

doc/src/manual/variables-and-scoping.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ julia> module B
9090
julia> module D
9191
b = a # errors as D's global scope is separate from A's
9292
end;
93-
ERROR: UndefVarError: `a` not defined
93+
ERROR: UndefVarError: `a` not defined in `D`
94+
Suggestion: check for spelling errors or missing imports. No global of this name exists in this module.
9495
```
9596

9697
If a top-level expression contains a variable declaration with keyword `local`,
@@ -187,7 +188,7 @@ julia> greet()
187188
hello
188189
189190
julia> x # global
190-
ERROR: UndefVarError: `x` not defined
191+
ERROR: UndefVarError: `x` not defined in `Main`
191192
```
192193

193194
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)
256257
55
257258
258259
julia> s # global
259-
ERROR: UndefVarError: `s` not defined
260+
ERROR: UndefVarError: `s` not defined in `Main`
260261
```
261262

262263
Since `s` is local to the function `sum_to`, calling the function has no effect on the global
@@ -343,7 +344,7 @@ hello
343344
hello
344345
345346
julia> x
346-
ERROR: UndefVarError: `x` not defined
347+
ERROR: UndefVarError: `x` not defined in `Main`
347348
```
348349

349350
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 = """
408409
julia> include_string(Main, code)
409410
┌ 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.
410411
└ @ string:4
411-
ERROR: LoadError: UndefVarError: `s` not defined
412+
ERROR: LoadError: UndefVarError: `s` not defined in local scope
412413
```
413414

414415
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
559560
println("z: $z") # errors as z has not been assigned yet but is local
560561
end
561562
x: 1, y: -1
562-
ERROR: UndefVarError: `z` not defined
563+
ERROR: UndefVarError: `z` not defined in local scope
563564
```
564565

565566
The assignments are evaluated in order, with each right-hand side evaluated in the scope before

0 commit comments

Comments
 (0)