Skip to content

Commit

Permalink
Add err global to REPL to store most recent errors (JuliaLang#40642)
Browse files Browse the repository at this point in the history
  • Loading branch information
BioTurboNick committed Nov 19, 2021
1 parent d730703 commit 2c2e724
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 21 deletions.
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ Standard library changes
argument have a type more specific than `Any`; use SHIFT-TAB instead of TAB
to allow any compatible methods.

* New `err` global variable in `Main` set when an expression throws an exception, akin to `ans`. Typing `err` reprints
the exception information.

#### SparseArrays

#### Dates
Expand Down
33 changes: 21 additions & 12 deletions base/client.jl
Original file line number Diff line number Diff line change
Expand Up @@ -84,27 +84,33 @@ end

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

function display_error(io::IO, er, bt)
istrivialerror(stack::ExceptionStack) =
length(stack) == 1 && length(stack[1].backtrace) 1
# frame 1 = top level; assumes already went through scrub_repl_backtrace

function display_error(io::IO, stack::ExceptionStack)
printstyled(io, "ERROR: "; bold=true, color=Base.error_color())
bt = scrub_repl_backtrace(bt)
showerror(IOContext(io, :limit => true), er, bt, backtrace = bt!==nothing)
show_exception_stack(IOContext(io, :limit => true), stack)
println(io)
end
function display_error(io::IO, stack::ExceptionStack)
display_error(stack::ExceptionStack) = display_error(stderr, stack)

# these forms are depended on by packages outside Julia
function display_error(io::IO, exception, backtrace)
printstyled(io, "ERROR: "; bold=true, color=Base.error_color())
bt = Any[ (x[1], scrub_repl_backtrace(x[2])) for x in stack ]
show_exception_stack(IOContext(io, :limit => true), bt)
showerror(IOContext(io, :limit => true), er, bt, backtrace = bt!==nothing)
println(io)
end
display_error(stack::ExceptionStack) = display_error(stderr, stack)
display_error(er, bt=nothing) = display_error(stderr, er, bt)

function eval_user_input(errio, @nospecialize(ast), show_value::Bool)
Expand All @@ -117,6 +123,8 @@ function eval_user_input(errio, @nospecialize(ast), show_value::Bool)
print(color_normal)
end
if lasterr !== nothing
lasterr = scrub_repl_backtrace(lasterr)
istrivialerror(lasterr) || ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, lasterr)
invokelatest(display_error, errio, lasterr)
errcount = 0
lasterr = nothing
Expand All @@ -143,7 +151,8 @@ function eval_user_input(errio, @nospecialize(ast), show_value::Bool)
@error "SYSTEM: display_error(errio, lasterr) caused an error"
end
errcount += 1
lasterr = current_exceptions()
lasterr = scrub_repl_backtrace(current_exceptions())
ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, lasterr)
if errcount > 2
@error "It is likely that something important is broken, and Julia will not be able to continue normally" errcount
break
Expand Down Expand Up @@ -260,7 +269,7 @@ function exec_options(opts)
try
load_julia_startup()
catch
invokelatest(display_error, current_exceptions())
invokelatest(display_error, scrub_repl_backtrace(current_exceptions()))
!(repl || is_interactive::Bool) && exit(1)
end
end
Expand Down Expand Up @@ -294,7 +303,7 @@ function exec_options(opts)
try
include(Main, PROGRAM_FILE)
catch
invokelatest(display_error, current_exceptions())
invokelatest(display_error, scrub_repl_backtrace(current_exceptions()))
if !is_interactive::Bool
exit(1)
end
Expand Down Expand Up @@ -496,7 +505,7 @@ function _start()
try
exec_options(JLOptions())
catch
invokelatest(display_error, current_exceptions())
invokelatest(display_error, scrub_repl_backtrace(current_exceptions()))
exit(1)
end
if is_interactive && get(stdout, :color, false)
Expand Down
4 changes: 2 additions & 2 deletions base/task.jl
Original file line number Diff line number Diff line change
Expand Up @@ -489,13 +489,13 @@ function errormonitor(t::Task)
try # try to display the failure atomically
errio = IOContext(PipeBuffer(), errs::IO)
emphasize(errio, "Unhandled Task ")
display_error(errio, current_exceptions(t))
display_error(errio, scrub_repl_backtrace(current_exceptions(t)))
write(errs, errio)
catch
try # try to display the secondary error atomically
errio = IOContext(PipeBuffer(), errs::IO)
print(errio, "\nSYSTEM: caught exception while trying to print a failed Task notice: ")
display_error(errio, current_exceptions())
display_error(errio, scrub_repl_backtrace(current_exceptions()))
write(errs, errio)
flush(errs)
# and then the actual error, as best we can
Expand Down
4 changes: 1 addition & 3 deletions contrib/generate_precompile.jl
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@ precompile(Tuple{typeof(Base.recursive_prefs_merge), Base.Dict{String, Any}})
precompile(Tuple{typeof(isassigned), Core.SimpleVector, Int})
precompile(Tuple{typeof(getindex), Core.SimpleVector, Int})
precompile(Tuple{typeof(Base.Experimental.register_error_hint), Any, Type})
precompile(Tuple{typeof(Base.display_error), MethodError, Vector{Union{Ptr{Nothing}, Base.InterpreterIP}}})
precompile(Tuple{typeof(Base.display_error), ErrorException})
precompile(Tuple{typeof(Base.display_error), BoundsError})
precompile(Tuple{typeof(Base.display_error), Base.ExceptionStack})
precompile(Tuple{Core.kwftype(typeof(Type)), NamedTuple{(:sizehint,), Tuple{Int}}, Type{IOBuffer}})
precompile(Base.CoreLogging.current_logger_for_env, (Base.CoreLogging.LogLevel, String, Module))
precompile(Base.CoreLogging.current_logger_for_env, (Base.CoreLogging.LogLevel, Symbol, Module))
Expand Down
6 changes: 5 additions & 1 deletion stdlib/REPL/src/REPL.jl
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,8 @@ function print_response(errio::IO, response, show_value::Bool, have_color::Bool,
try
Base.sigatomic_end()
if iserr
val = Base.scrub_repl_backtrace(val)
Base.istrivialerror(val) || ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, val)
Base.invokelatest(Base.display_error, errio, val)
else
if val !== nothing && show_value
Expand All @@ -305,7 +307,9 @@ function print_response(errio::IO, response, show_value::Bool, have_color::Bool,
println(errio) # an error during printing is likely to leave us mid-line
println(errio, "SYSTEM (REPL): showing an error caused an error")
try
Base.invokelatest(Base.display_error, errio, current_exceptions())
excs = Base.scrub_repl_backtrace(current_exceptions())
ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, excs)
Base.invokelatest(Base.display_error, errio, excs)
catch e
# at this point, only print the name of the type as a Symbol to
# minimize the possibility of further errors.
Expand Down
43 changes: 40 additions & 3 deletions stdlib/REPL/test/repl.jl
Original file line number Diff line number Diff line change
Expand Up @@ -885,13 +885,13 @@ end

# Test containers in error messages are limited #18726
let io = IOBuffer()
Base.display_error(io,
try
Base.display_error(io, Base.ExceptionStack(Any[(exception =
(try
[][trues(6000)]
@assert false
catch e
e
end, [])
end), backtrace = [])]))
@test length(String(take!(io))) < 1500
end

Expand Down Expand Up @@ -1366,3 +1366,40 @@ end
@test isempty(mods)
end
end

# err should reprint error if deeper than top-level
fake_repl() do stdin_write, stdout_read, repl
repltask = @async begin
REPL.run_repl(repl)
end
# initialize `err` to `nothing`
write(stdin_write, "global err = nothing\n")
readline(stdout_read)
readline(stdout_read) == "\e[0m"
readuntil(stdout_read, "julia> ", keep=true)
# 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) == ""
readuntil(stdout_read, "julia> ", keep=true)
# check that top-level error did not change `err`
write(stdin_write, "err\n")
readline(stdout_read)
@test readline(stdout_read) == "\e[0m"
readuntil(stdout_read, "julia> ", keep=true)
# generate deeper error
write(stdin_write, "foo() = foobar\n")
readline(stdout_read)
readuntil(stdout_read, "julia> ", keep=true)
write(stdin_write, "foo()\n")
readline(stdout_read)
@test readline(stdout_read) == "\e[0mERROR: UndefVarError: foobar not defined"
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) == "Stacktrace:"
end

0 comments on commit 2c2e724

Please sign in to comment.