diff --git a/base/experimental.jl b/base/experimental.jl index 9c1b460080dbf..f43bdb2800b75 100644 --- a/base/experimental.jl +++ b/base/experimental.jl @@ -295,18 +295,18 @@ Closest candidates are: `if isdefined(Base.Experimental, :register_error_hint) ... end` block. """ function register_error_hint(@nospecialize(handler), @nospecialize(exct::Type)) - list = get!(Vector{Any}, _hint_handlers, exct) - push!(list, handler) + list = get!(Vector{Any}, _hint_handlers, Core.typename(exct)) + push!(list, (exct, handler)) return nothing end -const _hint_handlers = IdDict{Type,Vector{Any}}() +const _hint_handlers = IdDict{Core.TypeName,Vector{Any}}() """ Experimental.show_error_hints(io, ex, args...) Invoke all handlers from [`Experimental.register_error_hint`](@ref) for the particular -exception type `typeof(ex)`. `args` must contain any other arguments expected by +exception type `typeof(ex)` and all of its supertypes. `args` must contain any other arguments expected by the handler for that type. !!! compat "Julia 1.5" @@ -316,15 +316,20 @@ the handler for that type. """ function show_error_hints(io, ex, args...) @nospecialize - hinters = get(_hint_handlers, typeof(ex), nothing) - isnothing(hinters) && return - for handler in hinters - try - @invokelatest handler(io, ex, args...) - catch - tn = typeof(handler).name - @error "Hint-handler $handler for $(typeof(ex)) in $(tn.module) caused an error" exception=current_exceptions() + ex_supertype = typeof(ex) + while ex_supertype != Any + hinters = get(_hint_handlers, Core.typename(ex_supertype), Any[]) + for (exct, handler) in hinters + ex isa exct || continue + try + # TODO: deal with handlers accepting different signatures? + @invokelatest handler(io, ex, args...) + catch + tn = typeof(handler).name + @error "Hint-handler $handler for $(ex_supertype) in $(tn.module) caused an error" exception=current_exceptions() + end end + ex_supertype = supertype(ex_supertype) end end diff --git a/test/errorshow.jl b/test/errorshow.jl index d1d6b1650520b..941c5e2074f51 100644 --- a/test/errorshow.jl +++ b/test/errorshow.jl @@ -740,7 +740,7 @@ let err_str @test occursin(Regex("MethodError: no method matching one\\(::.*HasNoOne; value::$(Int)\\)"), err_str) @test occursin("`one` doesn't take keyword arguments, that would be silly", err_str) end -pop!(Base.Experimental._hint_handlers[MethodError]) # order is undefined, don't copy this +pop!(Base.Experimental._hint_handlers[Core.typename(MethodError)]) # order is undefined, don't copy this function busted_hint(io, exc, notarg) # wrong number of args print(io, "\nI don't have a hint for you, sorry") @@ -752,7 +752,7 @@ catch ex io = IOBuffer() @test_logs (:error, "Hint-handler busted_hint for DomainError in $(@__MODULE__) caused an error") showerror(io, ex) end -pop!(Base.Experimental._hint_handlers[DomainError]) # order is undefined, don't copy this +pop!(Base.Experimental._hint_handlers[Core.typename(DomainError)]) # order is undefined, don't copy this struct ANumber <: Number end let err_str = @except_str ANumber()(3 + 4) MethodError @@ -1476,3 +1476,38 @@ let err_str err_str = @except_str f56325(1,2) MethodError @test occursin("The anonymous function", err_str) end + +# Test that error hints catch abstract exception supertypes (issue #58367) + +module Hinterland + +abstract type AbstractHintableException <: Exception end +struct ConcreteHintableException <: AbstractHintableException end +gonnathrow() = throw(ConcreteHintableException()) + +function Base.showerror(io::IO, exc::ConcreteHintableException) + print(io, "This is my exception") + Base.Experimental.show_error_hints(io, exc) +end + +function __init__() + Base.Experimental.register_error_hint(ConcreteHintableException) do io, exc + print(io, "\nThis hint caught my concrete exception type") + end + Base.Experimental.register_error_hint(AbstractHintableException) do io, exc + print(io, "\nThis other hint caught my abstract exception supertype") + end +end + +end + +@testset "Hints for abstract exception supertypes" begin + exc = try + Hinterland.gonnathrow() + catch e + e + end + exc_print = sprint(Base.showerror, exc) + @test occursin("This hint caught my concrete exception type", exc_print) + @test occursin("This other hint caught my abstract exception supertype", exc_print) +end