diff --git a/Project.toml b/Project.toml index d8f13f0cd..bcba2a612 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "Compat" uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" -version = "3.33.0" +version = "3.34.0" [deps] Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" diff --git a/README.md b/README.md index 1fc08484f..b9b6aa20d 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,11 @@ changes in `julia`. ## Supported features +* The function `current_exceptions()` has been added to get the current + exception stack. Julia-1.0 lacks runtime support for full execption stacks, + so we return only the most recent exception in that case. ([#29901]) (since + Compat 3.34) + * The methods `Base.include(::Function, ::Module, path)` and `Base.include_string(::Function, ::Module, code[, filename])` have been added. ([#34595]) (since Compat 3.33) diff --git a/src/Compat.jl b/src/Compat.jl index 465aa20ae..f1d93c222 100644 --- a/src/Compat.jl +++ b/src/Compat.jl @@ -1049,6 +1049,59 @@ if VERSION < v"1.5.0-DEV.263" end end +# https://github.com/JuliaLang/julia/pull/29901 +if VERSION < v"1.7.0-DEV.1106" + struct ExceptionStack <: AbstractArray{Any,1} + stack + end + + if VERSION >= v"1.1" + function current_exceptions(task=current_task(); backtrace=true) + stack = Base.catch_stack(task, include_bt=backtrace) + ExceptionStack(Any[(exception=x[1],backtrace=x[2]) for x in stack]) + end + else + # There's no exception stack in 1.0, but we can fall back to returning + # the (single) current exception and backtrace instead. + @eval function current_exceptions(task=current_task(); backtrace=true) + bt = catch_backtrace() + # `exc = Expr(:the_exception)` is the lowering for `catch exc` + exc = isempty(bt) ? nothing : $(Expr(:the_exception)) + ExceptionStack(isempty(bt) ? Any[] : Any[(exception=exc, backtrace=bt)]) + end + @eval function the_stack() + $(Expr(:the_exception)), catch_backtrace() + end + end + + Base.size(s::ExceptionStack) = size(s.stack) + Base.getindex(s::ExceptionStack, i::Int) = s.stack[i] + + function show_exception_stack(io::IO, stack) + # Display exception stack with the top of the stack first. This ordering + # means that the user doesn't have to scroll up in the REPL to discover the + # root cause. + nexc = length(stack) + for i = nexc:-1:1 + if nexc != i + printstyled(io, "\ncaused by: ", color=Base.error_color()) + end + exc, bt = stack[i] + showerror(io, exc, bt, backtrace = bt!==nothing) + i == 1 || println(io) + end + end + + function Base.show(io::IO, ::MIME"text/plain", stack::ExceptionStack) + nexc = length(stack) + printstyled(io, nexc, "-element ExceptionStack", nexc == 0 ? "" : ":\n") + show_exception_stack(io, stack) + end + Base.show(io::IO, stack::ExceptionStack) = show(io, MIME("text/plain"), stack) + + export current_exceptions +end + include("iterators.jl") include("deprecated.jl") diff --git a/test/runtests.jl b/test/runtests.jl index 297ab674b..f62ab2d3d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1109,3 +1109,38 @@ end @test m.y === 3 @test !isdefined(m, :x) end + +# https://github.com/JuliaLang/julia/pull/29901 +@testset "current_exceptions" begin + # Display of errors which cause more than one entry on the exception stack + excs = try + try + __not_a_binding__ + catch + 1 รท 0 # Generate error while handling error + end + catch + current_exceptions() + end + + if VERSION >= v"1.1" + @test typeof.(first.(excs)) == [UndefVarError, DivideError] + + @test occursin(r""" + 2-element ExceptionStack: + DivideError: integer division error + Stacktrace:.* + + caused by: UndefVarError: __not_a_binding__ not defined + Stacktrace:.* + """s, sprint(show, excs)) + else + # Due to runtime limitations, julia-1.0 only retains the last exception + @test typeof.(first.(excs)) == [DivideError] + @test occursin(r""" + 1-element ExceptionStack: + DivideError: integer division error + Stacktrace:.* + """, sprint(show, excs)) + end +end