Skip to content

Commit 40cc4ad

Browse files
authored
Move custom error hints to Experimental (#35679)
Closes #35671
1 parent 68c8708 commit 40cc4ad

File tree

6 files changed

+107
-96
lines changed

6 files changed

+107
-96
lines changed

NEWS.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ New language features
1717
Similarly, passing an `a.b` expression uses `b` as the keyword or field name ([#29333]).
1818

1919
* Packages can now provide custom hints to help users resolve errors by using the
20-
`register_error_hint` function. Packages that define custom exception types
21-
can support hints by calling `show_error_hints` from their `showerror` method. ([#35094])
20+
experimental `Base.Experimental.register_error_hint` function.
21+
Packages that define custom exception types can support hints by
22+
calling the `Base.Experimental.show_error_hints` from their
23+
`showerror` method. ([#35094])
2224

2325
* Support for Unicode 13.0.0 (via utf8proc 2.5) ([#35282]).
2426

base/errorshow.jl

Lines changed: 5 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -29,87 +29,6 @@ ERROR: MyException: test exception
2929
"""
3030
showerror(io::IO, ex) = show(io, ex)
3131

32-
"""
33-
register_error_hint(handler, exceptiontype)
34-
35-
Register a "hinting" function `handler(io, exception)` that can
36-
suggest potential ways for users to circumvent errors. `handler`
37-
should examine `exception` to see whether the conditions appropriate
38-
for a hint are met, and if so generate output to `io`.
39-
Packages should call `register_error_hint` from within their
40-
`__init__` function.
41-
42-
For specific exception types, `handler` is required to accept additional arguments:
43-
44-
- `MethodError`: provide `handler(io, exc::MethodError, argtypes, kwargs)`,
45-
which splits the combined arguments into positional and keyword arguments.
46-
47-
When issuing a hint, the output should typically start with `\\n`.
48-
49-
If you define custom exception types, your `showerror` method can
50-
support hints by calling [`show_error_hints`](@ref).
51-
52-
# Example
53-
54-
```
55-
julia> module Hinter
56-
57-
only_int(x::Int) = 1
58-
any_number(x::Number) = 2
59-
60-
function __init__()
61-
register_error_hint(MethodError) do io, exc, argtypes, kwargs
62-
if exc.f == only_int
63-
# Color is not necessary, this is just to show it's possible.
64-
print(io, "\\nDid you mean to call ")
65-
printstyled(io, "`any_number`?", color=:cyan)
66-
end
67-
end
68-
end
69-
70-
end
71-
```
72-
73-
Then if you call `Hinter.only_int` on something that isn't an `Int` (thereby triggering a `MethodError`), it issues the hint:
74-
75-
```
76-
julia> Hinter.only_int(1.0)
77-
ERROR: MethodError: no method matching only_int(::Float64)
78-
Did you mean to call `any_number`?
79-
Closest candidates are:
80-
...
81-
```
82-
83-
!!! compat "Julia 1.5"
84-
Custom error hints are available as of Julia 1.5.
85-
"""
86-
function register_error_hint(handler, exct::Type)
87-
list = get!(()->[], _hint_handlers, exct)
88-
push!(list, handler)
89-
return nothing
90-
end
91-
92-
const _hint_handlers = IdDict{Type,Vector{Any}}()
93-
94-
"""
95-
show_error_hints(io, ex, args...)
96-
97-
Invoke all handlers from [`register_error_hint`](@ref) for the particular
98-
exception type `typeof(ex)`. `args` must contain any other arguments expected by
99-
the handler for that type.
100-
"""
101-
function show_error_hints(io, ex, args...)
102-
hinters = get!(()->[], _hint_handlers, typeof(ex))
103-
for handler in hinters
104-
try
105-
Base.invokelatest(handler, io, ex, args...)
106-
catch err
107-
tn = typeof(handler).name
108-
@error "Hint-handler $handler for $(typeof(ex)) in $(tn.module) caused an error"
109-
end
110-
end
111-
end
112-
11332
show_index(io::IO, x::Any) = show(io, x)
11433
show_index(io::IO, x::Slice) = show_index(io, x.indices)
11534
show_index(io::IO, x::LogicalIndex) = show_index(io, x.mask)
@@ -138,7 +57,7 @@ function showerror(io::IO, ex::BoundsError)
13857
print(io, ']')
13958
end
14059
end
141-
show_error_hints(io, ex)
60+
Experimental.show_error_hints(io, ex)
14261
end
14362

14463
function showerror(io::IO, ex::TypeError)
@@ -162,7 +81,7 @@ function showerror(io::IO, ex::TypeError)
16281
end
16382
print(io, ctx, ", expected ", ex.expected, ", got ", targs...)
16483
end
165-
show_error_hints(io, ex)
84+
Experimental.show_error_hints(io, ex)
16685
end
16786

16887
function showerror(io::IO, ex, bt; backtrace=true)
@@ -201,7 +120,7 @@ function showerror(io::IO, ex::DomainError)
201120
if isdefined(ex, :msg)
202121
print(io, ":\n", ex.msg)
203122
end
204-
show_error_hints(io, ex)
123+
Experimental.show_error_hints(io, ex)
205124
nothing
206125
end
207126

@@ -257,7 +176,7 @@ function showerror(io::IO, ex::InexactError)
257176
print(io, "InexactError: ", ex.func, '(')
258177
nameof(ex.T) === ex.func || print(io, ex.T, ", ")
259178
print(io, ex.val, ')')
260-
show_error_hints(io, ex)
179+
Experimental.show_error_hints(io, ex)
261180
end
262181

263182
typesof(args...) = Tuple{Any[ Core.Typeof(a) for a in args ]...}
@@ -408,7 +327,7 @@ function showerror(io::IO, ex::MethodError)
408327
"\nYou can convert to a column vector with the vec() function.")
409328
end
410329
end
411-
show_error_hints(io, ex, arg_types_param, kwargs)
330+
Experimental.show_error_hints(io, ex, arg_types_param, kwargs)
412331
try
413332
show_method_candidates(io, ex, kwargs)
414333
catch ex

base/experimental.jl

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,4 +116,96 @@ macro optlevel(n::Int)
116116
return Expr(:meta, :optlevel, n)
117117
end
118118

119+
# UI features for errors
120+
121+
"""
122+
Experimental.register_error_hint(handler, exceptiontype)
123+
124+
Register a "hinting" function `handler(io, exception)` that can
125+
suggest potential ways for users to circumvent errors. `handler`
126+
should examine `exception` to see whether the conditions appropriate
127+
for a hint are met, and if so generate output to `io`.
128+
Packages should call `register_error_hint` from within their
129+
`__init__` function.
130+
131+
For specific exception types, `handler` is required to accept additional arguments:
132+
133+
- `MethodError`: provide `handler(io, exc::MethodError, argtypes, kwargs)`,
134+
which splits the combined arguments into positional and keyword arguments.
135+
136+
When issuing a hint, the output should typically start with `\\n`.
137+
138+
If you define custom exception types, your `showerror` method can
139+
support hints by calling [`Experimental.show_error_hints`](@ref).
140+
141+
# Example
142+
143+
```
144+
julia> module Hinter
145+
146+
only_int(x::Int) = 1
147+
any_number(x::Number) = 2
148+
149+
function __init__()
150+
Base.Experimental.register_error_hint(MethodError) do io, exc, argtypes, kwargs
151+
if exc.f == only_int
152+
# Color is not necessary, this is just to show it's possible.
153+
print(io, "\\nDid you mean to call ")
154+
printstyled(io, "`any_number`?", color=:cyan)
155+
end
156+
end
157+
end
158+
159+
end
160+
```
161+
162+
Then if you call `Hinter.only_int` on something that isn't an `Int` (thereby triggering a `MethodError`), it issues the hint:
163+
164+
```
165+
julia> Hinter.only_int(1.0)
166+
ERROR: MethodError: no method matching only_int(::Float64)
167+
Did you mean to call `any_number`?
168+
Closest candidates are:
169+
...
170+
```
171+
172+
!!! compat "Julia 1.5"
173+
Custom error hints are available as of Julia 1.5.
174+
!!! warning
175+
This interface is experimental and subject to change or removal without notice.
176+
To insulate yourself against changes, consider putting any registrations inside an
177+
`if isdefined(Base.Experimental, :register_error_hint) ... end` block.
178+
"""
179+
function register_error_hint(handler, exct::Type)
180+
list = get!(()->[], _hint_handlers, exct)
181+
push!(list, handler)
182+
return nothing
183+
end
184+
185+
const _hint_handlers = IdDict{Type,Vector{Any}}()
186+
187+
"""
188+
Experimental.show_error_hints(io, ex, args...)
189+
190+
Invoke all handlers from [`Experimental.register_error_hint`](@ref) for the particular
191+
exception type `typeof(ex)`. `args` must contain any other arguments expected by
192+
the handler for that type.
193+
194+
!!! compat "Julia 1.5"
195+
Custom error hints are available as of Julia 1.5.
196+
!!! warning
197+
This interface is experimental and subject to change or removal without notice.
198+
"""
199+
function show_error_hints(io, ex, args...)
200+
hinters = get!(()->[], _hint_handlers, typeof(ex))
201+
for handler in hinters
202+
try
203+
Base.invokelatest(handler, io, ex, args...)
204+
catch err
205+
tn = typeof(handler).name
206+
@error "Hint-handler $handler for $(typeof(ex)) in $(tn.module) caused an error"
207+
end
208+
end
209+
end
210+
119211
end

base/exports.jl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -697,10 +697,8 @@ export
697697
backtrace,
698698
catch_backtrace,
699699
error,
700-
register_error_hint,
701700
rethrow,
702701
retry,
703-
show_error_hints,
704702
systemerror,
705703

706704
# stack traces

doc/src/base/base.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -337,8 +337,8 @@ Base.backtrace
337337
Base.catch_backtrace
338338
Base.catch_stack
339339
Base.@assert
340-
Base.register_error_hint
341-
Base.show_error_hints
340+
Base.Experimental.register_error_hint
341+
Base.Experimental.show_error_hints
342342
Base.ArgumentError
343343
Base.AssertionError
344344
Core.BoundsError

test/errorshow.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,7 @@ function recommend_oneunit(io, ex, arg_types, kwargs)
597597
end
598598
end
599599
end
600-
@test register_error_hint(recommend_oneunit, MethodError) === nothing
600+
@test Base.Experimental.register_error_hint(recommend_oneunit, MethodError) === nothing
601601
let err_str
602602
err_str = @except_str one(HasNoOne()) MethodError
603603
@test occursin(r"MethodError: no method matching one\(::.*HasNoOne\)", err_str)
@@ -606,19 +606,19 @@ let err_str
606606
@test occursin(r"MethodError: no method matching one\(::.*HasNoOne; value=2\)", err_str)
607607
@test occursin("`one` doesn't take keyword arguments, that would be silly", err_str)
608608
end
609-
pop!(Base._hint_handlers[MethodError]) # order is undefined, don't copy this
609+
pop!(Base.Experimental._hint_handlers[MethodError]) # order is undefined, don't copy this
610610

611611
function busted_hint(io, exc, notarg) # wrong number of args
612612
print(io, "\nI don't have a hint for you, sorry")
613613
end
614-
@test register_error_hint(busted_hint, DomainError) === nothing
614+
@test Base.Experimental.register_error_hint(busted_hint, DomainError) === nothing
615615
try
616616
sqrt(-2)
617617
catch ex
618618
io = IOBuffer()
619619
@test_logs (:error, "Hint-handler busted_hint for DomainError in $(@__MODULE__) caused an error") showerror(io, ex)
620620
end
621-
pop!(Base._hint_handlers[DomainError]) # order is undefined, don't copy this
621+
pop!(Base.Experimental._hint_handlers[DomainError]) # order is undefined, don't copy this
622622

623623

624624
# issue #28442

0 commit comments

Comments
 (0)