Skip to content

Commit 0cad721

Browse files
gdalleDilumAluthgevtjnash
authored
fix: trigger error hints on exception supertypes too (#59843)
Fixes #58367 Co-authored-by: Dilum Aluthge <dilum@aluthge.com> Co-authored-by: Jameson Nash <vtjnash@gmail.com>
1 parent 74f3126 commit 0cad721

File tree

2 files changed

+54
-14
lines changed

2 files changed

+54
-14
lines changed

base/experimental.jl

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -295,18 +295,18 @@ Closest candidates are:
295295
`if isdefined(Base.Experimental, :register_error_hint) ... end` block.
296296
"""
297297
function register_error_hint(@nospecialize(handler), @nospecialize(exct::Type))
298-
list = get!(Vector{Any}, _hint_handlers, exct)
299-
push!(list, handler)
298+
list = get!(Vector{Any}, _hint_handlers, Core.typename(exct))
299+
push!(list, (exct, handler))
300300
return nothing
301301
end
302302

303-
const _hint_handlers = IdDict{Type,Vector{Any}}()
303+
const _hint_handlers = IdDict{Core.TypeName,Vector{Any}}()
304304

305305
"""
306306
Experimental.show_error_hints(io, ex, args...)
307307
308308
Invoke all handlers from [`Experimental.register_error_hint`](@ref) for the particular
309-
exception type `typeof(ex)`. `args` must contain any other arguments expected by
309+
exception type `typeof(ex)` and all of its supertypes. `args` must contain any other arguments expected by
310310
the handler for that type.
311311
312312
!!! compat "Julia 1.5"
@@ -316,15 +316,20 @@ the handler for that type.
316316
"""
317317
function show_error_hints(io, ex, args...)
318318
@nospecialize
319-
hinters = get(_hint_handlers, typeof(ex), nothing)
320-
isnothing(hinters) && return
321-
for handler in hinters
322-
try
323-
@invokelatest handler(io, ex, args...)
324-
catch
325-
tn = typeof(handler).name
326-
@error "Hint-handler $handler for $(typeof(ex)) in $(tn.module) caused an error" exception=current_exceptions()
319+
ex_supertype = typeof(ex)
320+
while ex_supertype != Any
321+
hinters = get(_hint_handlers, Core.typename(ex_supertype), Any[])
322+
for (exct, handler) in hinters
323+
ex isa exct || continue
324+
try
325+
# TODO: deal with handlers accepting different signatures?
326+
@invokelatest handler(io, ex, args...)
327+
catch
328+
tn = typeof(handler).name
329+
@error "Hint-handler $handler for $(ex_supertype) in $(tn.module) caused an error" exception=current_exceptions()
330+
end
327331
end
332+
ex_supertype = supertype(ex_supertype)
328333
end
329334
end
330335

test/errorshow.jl

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -740,7 +740,7 @@ let err_str
740740
@test occursin(Regex("MethodError: no method matching one\\(::.*HasNoOne; value::$(Int)\\)"), err_str)
741741
@test occursin("`one` doesn't take keyword arguments, that would be silly", err_str)
742742
end
743-
pop!(Base.Experimental._hint_handlers[MethodError]) # order is undefined, don't copy this
743+
pop!(Base.Experimental._hint_handlers[Core.typename(MethodError)]) # order is undefined, don't copy this
744744

745745
function busted_hint(io, exc, notarg) # wrong number of args
746746
print(io, "\nI don't have a hint for you, sorry")
@@ -752,7 +752,7 @@ catch ex
752752
io = IOBuffer()
753753
@test_logs (:error, "Hint-handler busted_hint for DomainError in $(@__MODULE__) caused an error") showerror(io, ex)
754754
end
755-
pop!(Base.Experimental._hint_handlers[DomainError]) # order is undefined, don't copy this
755+
pop!(Base.Experimental._hint_handlers[Core.typename(DomainError)]) # order is undefined, don't copy this
756756

757757
struct ANumber <: Number end
758758
let err_str = @except_str ANumber()(3 + 4) MethodError
@@ -1476,3 +1476,38 @@ let err_str
14761476
err_str = @except_str f56325(1,2) MethodError
14771477
@test occursin("The anonymous function", err_str)
14781478
end
1479+
1480+
# Test that error hints catch abstract exception supertypes (issue #58367)
1481+
1482+
module Hinterland
1483+
1484+
abstract type AbstractHintableException <: Exception end
1485+
struct ConcreteHintableException <: AbstractHintableException end
1486+
gonnathrow() = throw(ConcreteHintableException())
1487+
1488+
function Base.showerror(io::IO, exc::ConcreteHintableException)
1489+
print(io, "This is my exception")
1490+
Base.Experimental.show_error_hints(io, exc)
1491+
end
1492+
1493+
function __init__()
1494+
Base.Experimental.register_error_hint(ConcreteHintableException) do io, exc
1495+
print(io, "\nThis hint caught my concrete exception type")
1496+
end
1497+
Base.Experimental.register_error_hint(AbstractHintableException) do io, exc
1498+
print(io, "\nThis other hint caught my abstract exception supertype")
1499+
end
1500+
end
1501+
1502+
end
1503+
1504+
@testset "Hints for abstract exception supertypes" begin
1505+
exc = try
1506+
Hinterland.gonnathrow()
1507+
catch e
1508+
e
1509+
end
1510+
exc_print = sprint(Base.showerror, exc)
1511+
@test occursin("This hint caught my concrete exception type", exc_print)
1512+
@test occursin("This other hint caught my abstract exception supertype", exc_print)
1513+
end

0 commit comments

Comments
 (0)