Skip to content

Commit 2c2e724

Browse files
authored
Add err global to REPL to store most recent errors (#40642)
1 parent d730703 commit 2c2e724

File tree

6 files changed

+72
-21
lines changed

6 files changed

+72
-21
lines changed

NEWS.md

+3
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ Standard library changes
111111
argument have a type more specific than `Any`; use SHIFT-TAB instead of TAB
112112
to allow any compatible methods.
113113

114+
* New `err` global variable in `Main` set when an expression throws an exception, akin to `ans`. Typing `err` reprints
115+
the exception information.
116+
114117
#### SparseArrays
115118

116119
#### Dates

base/client.jl

+21-12
Original file line numberDiff line numberDiff line change
@@ -84,27 +84,33 @@ end
8484

8585
function scrub_repl_backtrace(bt)
8686
if bt !== nothing && !(bt isa Vector{Any}) # ignore our sentinel value types
87-
bt = stacktrace(bt)
87+
bt = bt isa Vector{StackFrame} ? copy(bt) : stacktrace(bt)
8888
# remove REPL-related frames from interactive printing
8989
eval_ind = findlast(frame -> !frame.from_c && frame.func === :eval, bt)
9090
eval_ind === nothing || deleteat!(bt, eval_ind:length(bt))
9191
end
9292
return bt
9393
end
94+
scrub_repl_backtrace(stack::ExceptionStack) =
95+
ExceptionStack(Any[(;x.exception, backtrace = scrub_repl_backtrace(x.backtrace)) for x in stack])
9496

95-
function display_error(io::IO, er, bt)
97+
istrivialerror(stack::ExceptionStack) =
98+
length(stack) == 1 && length(stack[1].backtrace) 1
99+
# frame 1 = top level; assumes already went through scrub_repl_backtrace
100+
101+
function display_error(io::IO, stack::ExceptionStack)
96102
printstyled(io, "ERROR: "; bold=true, color=Base.error_color())
97-
bt = scrub_repl_backtrace(bt)
98-
showerror(IOContext(io, :limit => true), er, bt, backtrace = bt!==nothing)
103+
show_exception_stack(IOContext(io, :limit => true), stack)
99104
println(io)
100105
end
101-
function display_error(io::IO, stack::ExceptionStack)
106+
display_error(stack::ExceptionStack) = display_error(stderr, stack)
107+
108+
# these forms are depended on by packages outside Julia
109+
function display_error(io::IO, exception, backtrace)
102110
printstyled(io, "ERROR: "; bold=true, color=Base.error_color())
103-
bt = Any[ (x[1], scrub_repl_backtrace(x[2])) for x in stack ]
104-
show_exception_stack(IOContext(io, :limit => true), bt)
111+
showerror(IOContext(io, :limit => true), er, bt, backtrace = bt!==nothing)
105112
println(io)
106113
end
107-
display_error(stack::ExceptionStack) = display_error(stderr, stack)
108114
display_error(er, bt=nothing) = display_error(stderr, er, bt)
109115

110116
function eval_user_input(errio, @nospecialize(ast), show_value::Bool)
@@ -117,6 +123,8 @@ function eval_user_input(errio, @nospecialize(ast), show_value::Bool)
117123
print(color_normal)
118124
end
119125
if lasterr !== nothing
126+
lasterr = scrub_repl_backtrace(lasterr)
127+
istrivialerror(lasterr) || ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, lasterr)
120128
invokelatest(display_error, errio, lasterr)
121129
errcount = 0
122130
lasterr = nothing
@@ -143,7 +151,8 @@ function eval_user_input(errio, @nospecialize(ast), show_value::Bool)
143151
@error "SYSTEM: display_error(errio, lasterr) caused an error"
144152
end
145153
errcount += 1
146-
lasterr = current_exceptions()
154+
lasterr = scrub_repl_backtrace(current_exceptions())
155+
ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, lasterr)
147156
if errcount > 2
148157
@error "It is likely that something important is broken, and Julia will not be able to continue normally" errcount
149158
break
@@ -260,7 +269,7 @@ function exec_options(opts)
260269
try
261270
load_julia_startup()
262271
catch
263-
invokelatest(display_error, current_exceptions())
272+
invokelatest(display_error, scrub_repl_backtrace(current_exceptions()))
264273
!(repl || is_interactive::Bool) && exit(1)
265274
end
266275
end
@@ -294,7 +303,7 @@ function exec_options(opts)
294303
try
295304
include(Main, PROGRAM_FILE)
296305
catch
297-
invokelatest(display_error, current_exceptions())
306+
invokelatest(display_error, scrub_repl_backtrace(current_exceptions()))
298307
if !is_interactive::Bool
299308
exit(1)
300309
end
@@ -496,7 +505,7 @@ function _start()
496505
try
497506
exec_options(JLOptions())
498507
catch
499-
invokelatest(display_error, current_exceptions())
508+
invokelatest(display_error, scrub_repl_backtrace(current_exceptions()))
500509
exit(1)
501510
end
502511
if is_interactive && get(stdout, :color, false)

base/task.jl

+2-2
Original file line numberDiff line numberDiff line change
@@ -489,13 +489,13 @@ function errormonitor(t::Task)
489489
try # try to display the failure atomically
490490
errio = IOContext(PipeBuffer(), errs::IO)
491491
emphasize(errio, "Unhandled Task ")
492-
display_error(errio, current_exceptions(t))
492+
display_error(errio, scrub_repl_backtrace(current_exceptions(t)))
493493
write(errs, errio)
494494
catch
495495
try # try to display the secondary error atomically
496496
errio = IOContext(PipeBuffer(), errs::IO)
497497
print(errio, "\nSYSTEM: caught exception while trying to print a failed Task notice: ")
498-
display_error(errio, current_exceptions())
498+
display_error(errio, scrub_repl_backtrace(current_exceptions()))
499499
write(errs, errio)
500500
flush(errs)
501501
# and then the actual error, as best we can

contrib/generate_precompile.jl

+1-3
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,7 @@ precompile(Tuple{typeof(Base.recursive_prefs_merge), Base.Dict{String, Any}})
3232
precompile(Tuple{typeof(isassigned), Core.SimpleVector, Int})
3333
precompile(Tuple{typeof(getindex), Core.SimpleVector, Int})
3434
precompile(Tuple{typeof(Base.Experimental.register_error_hint), Any, Type})
35-
precompile(Tuple{typeof(Base.display_error), MethodError, Vector{Union{Ptr{Nothing}, Base.InterpreterIP}}})
36-
precompile(Tuple{typeof(Base.display_error), ErrorException})
37-
precompile(Tuple{typeof(Base.display_error), BoundsError})
35+
precompile(Tuple{typeof(Base.display_error), Base.ExceptionStack})
3836
precompile(Tuple{Core.kwftype(typeof(Type)), NamedTuple{(:sizehint,), Tuple{Int}}, Type{IOBuffer}})
3937
precompile(Base.CoreLogging.current_logger_for_env, (Base.CoreLogging.LogLevel, String, Module))
4038
precompile(Base.CoreLogging.current_logger_for_env, (Base.CoreLogging.LogLevel, Symbol, Module))

stdlib/REPL/src/REPL.jl

+5-1
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,8 @@ function print_response(errio::IO, response, show_value::Bool, have_color::Bool,
284284
try
285285
Base.sigatomic_end()
286286
if iserr
287+
val = Base.scrub_repl_backtrace(val)
288+
Base.istrivialerror(val) || ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, val)
287289
Base.invokelatest(Base.display_error, errio, val)
288290
else
289291
if val !== nothing && show_value
@@ -305,7 +307,9 @@ function print_response(errio::IO, response, show_value::Bool, have_color::Bool,
305307
println(errio) # an error during printing is likely to leave us mid-line
306308
println(errio, "SYSTEM (REPL): showing an error caused an error")
307309
try
308-
Base.invokelatest(Base.display_error, errio, current_exceptions())
310+
excs = Base.scrub_repl_backtrace(current_exceptions())
311+
ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, excs)
312+
Base.invokelatest(Base.display_error, errio, excs)
309313
catch e
310314
# at this point, only print the name of the type as a Symbol to
311315
# minimize the possibility of further errors.

stdlib/REPL/test/repl.jl

+40-3
Original file line numberDiff line numberDiff line change
@@ -885,13 +885,13 @@ end
885885

886886
# Test containers in error messages are limited #18726
887887
let io = IOBuffer()
888-
Base.display_error(io,
889-
try
888+
Base.display_error(io, Base.ExceptionStack(Any[(exception =
889+
(try
890890
[][trues(6000)]
891891
@assert false
892892
catch e
893893
e
894-
end, [])
894+
end), backtrace = [])]))
895895
@test length(String(take!(io))) < 1500
896896
end
897897

@@ -1366,3 +1366,40 @@ end
13661366
@test isempty(mods)
13671367
end
13681368
end
1369+
1370+
# err should reprint error if deeper than top-level
1371+
fake_repl() do stdin_write, stdout_read, repl
1372+
repltask = @async begin
1373+
REPL.run_repl(repl)
1374+
end
1375+
# initialize `err` to `nothing`
1376+
write(stdin_write, "global err = nothing\n")
1377+
readline(stdout_read)
1378+
readline(stdout_read) == "\e[0m"
1379+
readuntil(stdout_read, "julia> ", keep=true)
1380+
# generate top-level error
1381+
write(stdin_write, "foobar\n")
1382+
readline(stdout_read)
1383+
@test readline(stdout_read) == "\e[0mERROR: UndefVarError: foobar not defined"
1384+
@test readline(stdout_read) == ""
1385+
readuntil(stdout_read, "julia> ", keep=true)
1386+
# check that top-level error did not change `err`
1387+
write(stdin_write, "err\n")
1388+
readline(stdout_read)
1389+
@test readline(stdout_read) == "\e[0m"
1390+
readuntil(stdout_read, "julia> ", keep=true)
1391+
# generate deeper error
1392+
write(stdin_write, "foo() = foobar\n")
1393+
readline(stdout_read)
1394+
readuntil(stdout_read, "julia> ", keep=true)
1395+
write(stdin_write, "foo()\n")
1396+
readline(stdout_read)
1397+
@test readline(stdout_read) == "\e[0mERROR: UndefVarError: foobar not defined"
1398+
readuntil(stdout_read, "julia> ", keep=true)
1399+
# check that deeper error did set `err`
1400+
write(stdin_write, "err\n")
1401+
readline(stdout_read)
1402+
@test readline(stdout_read) == "\e[0m1-element ExceptionStack:"
1403+
@test readline(stdout_read) == "UndefVarError: foobar not defined"
1404+
@test readline(stdout_read) == "Stacktrace:"
1405+
end

0 commit comments

Comments
 (0)