Skip to content

Commit b2b8ce8

Browse files
authored
Add stacktrace colors to showing MethodErrors and method lists (JuliaLang#45069)
* Add color pass to MethodError showing, like in stacktraces Co-authored-by: Sukera <[email protected]>
1 parent ef7498d commit b2b8ce8

File tree

20 files changed

+306
-164
lines changed

20 files changed

+306
-164
lines changed

NEWS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,4 +170,7 @@ External dependencies
170170
Tooling Improvements
171171
---------------------
172172

173+
* Printing of `MethodError` and methods (such as from `methods(my_func)`) are now prettified and color consistent with printing of methods
174+
in stacktraces. ([#45069])
175+
173176
<!--- generated by NEWS-update.jl: -->

base/deprecated.jl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,17 @@ old (generic function with 1 method)
3939
Calls to `@deprecate` without explicit type-annotations will define deprecated methods
4040
accepting arguments of type `Any`. To restrict deprecation to a specific signature, annotate
4141
the arguments of `old`. For example,
42-
```jldoctest; filter = r"in Main at.*"
42+
```jldoctest; filter = r"@ .*"
4343
julia> new(x::Int) = x;
4444
4545
julia> new(x::Float64) = 2x;
4646
4747
julia> @deprecate old(x::Int) new(x);
4848
4949
julia> methods(old)
50-
# 1 method for generic function "old":
51-
[1] old(x::Int64) in Main at deprecated.jl:70
50+
# 1 method for generic function "old" from Main:
51+
[1] old(x::Int64)
52+
@ deprecated.jl:94
5253
```
5354
will define and deprecate a method `old(x::Int)` that mirrors `new(x::Int)` but will not
5455
define nor deprecate the method `old(x::Float64)`.

base/errorshow.jl

Lines changed: 50 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ end
333333
striptype(::Type{T}) where {T} = T
334334
striptype(::Any) = nothing
335335

336-
function showerror_ambiguous(io::IO, meth, f, args)
336+
function showerror_ambiguous(io::IO, meths, f, args)
337337
print(io, "MethodError: ")
338338
show_signature_function(io, isa(f, Type) ? Type{f} : typeof(f))
339339
print(io, "(")
@@ -342,23 +342,25 @@ function showerror_ambiguous(io::IO, meth, f, args)
342342
print(io, "::", a)
343343
i < length(p) && print(io, ", ")
344344
end
345-
print(io, ") is ambiguous. Candidates:")
345+
println(io, ") is ambiguous.\n\nCandidates:")
346346
sigfix = Any
347-
for m in meth
348-
print(io, "\n ", m)
347+
for m in meths
348+
print(io, " ")
349+
show(io, m; digit_align_width=-2)
350+
println(io)
349351
sigfix = typeintersect(m.sig, sigfix)
350352
end
351353
if isa(unwrap_unionall(sigfix), DataType) && sigfix <: Tuple
352354
let sigfix=sigfix
353-
if all(m->morespecific(sigfix, m.sig), meth)
355+
if all(m->morespecific(sigfix, m.sig), meths)
354356
print(io, "\nPossible fix, define\n ")
355357
Base.show_tuple_as_call(io, :function, sigfix)
356358
else
357-
println(io)
358359
print(io, "To resolve the ambiguity, try making one of the methods more specific, or ")
359360
print(io, "adding a new method more specific than any of the existing applicable methods.")
360361
end
361362
end
363+
println(io)
362364
end
363365
nothing
364366
end
@@ -516,7 +518,7 @@ function show_method_candidates(io::IO, ex::MethodError, @nospecialize kwargs=()
516518
file = string(method.file)
517519
end
518520
stacktrace_contract_userdir() && (file = contractuser(file))
519-
print(iob, " at ", file, ":", line)
521+
520522
if !isempty(kwargs)::Bool
521523
unexpected = Symbol[]
522524
if isempty(kwords) || !(any(endswith(string(kword), "...") for kword in kwords))
@@ -538,6 +540,12 @@ function show_method_candidates(io::IO, ex::MethodError, @nospecialize kwargs=()
538540
elseif ex.world > reinterpret(UInt, method.deleted_world)
539541
print(iob, " (method deleted before this world age.)")
540542
end
543+
println(iob)
544+
545+
m = parentmodule_before_main(method.module)
546+
color = get!(() -> popfirst!(STACKTRACE_MODULECOLORS), STACKTRACE_FIXEDCOLORS, m)
547+
print_module_path_file(iob, m, string(file), line, color, 1)
548+
541549
# TODO: indicate if it's in the wrong world
542550
push!(lines, (buf, right_matches))
543551
end
@@ -546,7 +554,7 @@ function show_method_candidates(io::IO, ex::MethodError, @nospecialize kwargs=()
546554

547555
if !isempty(lines) # Display up to three closest candidates
548556
Base.with_output_color(:normal, io) do io
549-
print(io, "\nClosest candidates are:")
557+
print(io, "\n\nClosest candidates are:")
550558
sort!(lines, by = x -> -x[2])
551559
i = 0
552560
for line in lines
@@ -558,6 +566,7 @@ function show_method_candidates(io::IO, ex::MethodError, @nospecialize kwargs=()
558566
i += 1
559567
print(io, String(take!(line[1])))
560568
end
569+
println(io) # extra newline for spacing to stacktrace
561570
end
562571
end
563572
end
@@ -573,20 +582,17 @@ end
573582
# replace `sf` as needed.
574583
const update_stackframes_callback = Ref{Function}(identity)
575584

576-
const STACKTRACE_MODULECOLORS = [:magenta, :cyan, :green, :yellow]
585+
const STACKTRACE_MODULECOLORS = Iterators.Stateful(Iterators.cycle([:magenta, :cyan, :green, :yellow]))
577586
const STACKTRACE_FIXEDCOLORS = IdDict(Base => :light_black, Core => :light_black)
578587

579588
function show_full_backtrace(io::IO, trace::Vector; print_linebreaks::Bool)
580589
num_frames = length(trace)
581590
ndigits_max = ndigits(num_frames)
582591

583-
modulecolordict = copy(STACKTRACE_FIXEDCOLORS)
584-
modulecolorcycler = Iterators.Stateful(Iterators.cycle(STACKTRACE_MODULECOLORS))
585-
586592
println(io, "\nStacktrace:")
587593

588594
for (i, (frame, n)) in enumerate(trace)
589-
print_stackframe(io, i, frame, n, ndigits_max, modulecolordict, modulecolorcycler)
595+
print_stackframe(io, i, frame, n, ndigits_max, STACKTRACE_FIXEDCOLORS, STACKTRACE_MODULECOLORS)
590596
if i < num_frames
591597
println(io)
592598
print_linebreaks && println(io)
@@ -646,15 +652,12 @@ function show_reduced_backtrace(io::IO, t::Vector)
646652

647653
ndigits_max = ndigits(length(t))
648654

649-
modulecolordict = Dict{Module, Symbol}()
650-
modulecolorcycler = Iterators.Stateful(Iterators.cycle(STACKTRACE_MODULECOLORS))
651-
652655
push!(repeated_cycle, (0,0,0)) # repeated_cycle is never empty
653656
frame_counter = 1
654657
for i in 1:length(displayed_stackframes)
655658
(frame, n) = displayed_stackframes[i]
656659

657-
print_stackframe(io, frame_counter, frame, n, ndigits_max, modulecolordict, modulecolorcycler)
660+
print_stackframe(io, frame_counter, frame, n, ndigits_max, STACKTRACE_FIXEDCOLORS, STACKTRACE_MODULECOLORS)
658661

659662
if i < length(displayed_stackframes)
660663
println(io)
@@ -684,22 +687,24 @@ end
684687
# from `modulecolorcycler`.
685688
function print_stackframe(io, i, frame::StackFrame, n::Int, digit_align_width, modulecolordict, modulecolorcycler)
686689
m = Base.parentmodule(frame)
687-
if m !== nothing
688-
while parentmodule(m) !== m
689-
pm = parentmodule(m)
690-
pm == Main && break
691-
m = pm
692-
end
693-
if !haskey(modulecolordict, m)
694-
modulecolordict[m] = popfirst!(modulecolorcycler)
695-
end
696-
modulecolor = modulecolordict[m]
690+
modulecolor = if m !== nothing
691+
m = parentmodule_before_main(m)
692+
get!(() -> popfirst!(modulecolorcycler), modulecolordict, m)
697693
else
698-
modulecolor = :default
694+
:default
699695
end
700696
print_stackframe(io, i, frame, n, digit_align_width, modulecolor)
701697
end
702698

699+
# Gets the topmost parent module that isn't Main
700+
function parentmodule_before_main(m)
701+
while parentmodule(m) !== m
702+
pm = parentmodule(m)
703+
pm == Main && break
704+
m = pm
705+
end
706+
m
707+
end
703708

704709
# Print a stack frame where the module color is set manually with `modulecolor`.
705710
function print_stackframe(io, i, frame::StackFrame, n::Int, digit_align_width, modulecolor)
@@ -727,32 +732,33 @@ function print_stackframe(io, i, frame::StackFrame, n::Int, digit_align_width, m
727732
end
728733
println(io)
729734

730-
# @
731-
printstyled(io, " " ^ (digit_align_width + 2) * "@ ", color = :light_black)
735+
# @ Module path / file : line
736+
print_module_path_file(io, modul, file, line, modulecolor, digit_align_width)
737+
738+
# inlined
739+
printstyled(io, inlined ? " [inlined]" : "", color = :light_black)
740+
end
741+
742+
function print_module_path_file(io, modul, file, line, modulecolor = :light_black, digit_align_width = 0)
743+
printstyled(io, " " ^ (digit_align_width + 2) * "@", color = :light_black)
732744

733745
# module
734-
if modul !== nothing
735-
printstyled(io, modul, color = modulecolor)
746+
if modul !== nothing && modulecolor !== nothing
736747
print(io, " ")
748+
printstyled(io, modul, color = modulecolor)
737749
end
738750

739751
# filepath
740-
pathparts = splitpath(file)
741-
folderparts = pathparts[1:end-1]
742-
if !isempty(folderparts)
743-
printstyled(io, joinpath(folderparts...) * (Sys.iswindows() ? "\\" : "/"), color = :light_black)
744-
end
752+
stacktrace_expand_basepaths() && (file = something(find_source_file(file), file))
753+
stacktrace_contract_userdir() && (file = contractuser(file))
754+
print(io, " ")
755+
dir = dirname(file)
756+
!isempty(dir) && printstyled(io, dir, Filesystem.path_separator, color = :light_black)
745757

746758
# filename, separator, line
747-
# use escape codes for formatting, printstyled can't do underlined and color
748-
# codes are bright black (90) and underlined (4)
749-
printstyled(io, pathparts[end], ":", line; color = :light_black, underline = true)
750-
751-
# inlined
752-
printstyled(io, inlined ? " [inlined]" : "", color = :light_black)
759+
printstyled(io, basename(file), ":", line; color = :light_black, underline = true)
753760
end
754761

755-
756762
function show_backtrace(io::IO, t::Vector)
757763
if haskey(io, :last_shown_line_infos)
758764
empty!(io[:last_shown_line_infos])

base/methodshow.jl

Lines changed: 61 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,7 @@ function arg_decl_parts(m::Method, html=false)
5959
push!(tv, sig.var)
6060
sig = sig.body
6161
end
62-
file = m.file
63-
line = m.line
62+
file, line = updated_methodloc(m)
6463
argnames = method_argnames(m)
6564
if length(argnames) >= m.nargs
6665
show_env = ImmutableDict{Symbol, Any}()
@@ -206,34 +205,45 @@ function sym_to_string(sym)
206205
end
207206
end
208207

209-
function show(io::IO, m::Method)
208+
function show(io::IO, m::Method; modulecolor = :light_black, digit_align_width = -1)
210209
tv, decls, file, line = arg_decl_parts(m)
211210
sig = unwrap_unionall(m.sig)
212211
if sig === Tuple
213212
# Builtin
214-
print(io, m.name, "(...) in ", m.module)
215-
return
216-
end
217-
print(io, decls[1][2], "(")
218-
join(
219-
io,
220-
String[isempty(d[2]) ? d[1] : string(d[1], "::", d[2]) for d in decls[2:end]],
221-
", ",
222-
", ",
223-
)
224-
kwargs = kwarg_decl(m)
225-
if !isempty(kwargs)
226-
print(io, "; ")
227-
join(io, map(sym_to_string, kwargs), ", ", ", ")
228-
end
229-
print(io, ")")
230-
show_method_params(io, tv)
231-
print(io, " in ", m.module)
232-
if line > 0
233-
file, line = updated_methodloc(m)
234-
print(io, " at ", file, ":", line)
213+
print(io, m.name, "(...)")
214+
file = "none"
215+
line = 0
216+
else
217+
print(io, decls[1][2], "(")
218+
219+
# arguments
220+
for (i,d) in enumerate(decls[2:end])
221+
printstyled(io, d[1], color=:light_black)
222+
if !isempty(d[2])
223+
print(io, "::")
224+
print_type_bicolor(io, d[2], color=:bold, inner_color=:normal)
225+
end
226+
i < length(decls)-1 && print(io, ", ")
227+
end
228+
229+
kwargs = kwarg_decl(m)
230+
if !isempty(kwargs)
231+
print(io, "; ")
232+
for kw in kwargs
233+
skw = sym_to_string(kw)
234+
print(io, skw)
235+
if kw != last(kwargs)
236+
print(io, ", ")
237+
end
238+
end
239+
end
240+
print(io, ")")
241+
show_method_params(io, tv)
235242
end
236-
nothing
243+
244+
# module & file, re-using function from errorshow.jl
245+
println(io)
246+
print_module_path_file(io, m.module, string(file), line, modulecolor, digit_align_width+4)
237247
end
238248

239249
function show_method_list_header(io::IO, ms::MethodList, namefmt::Function)
@@ -253,16 +263,19 @@ function show_method_list_header(io::IO, ms::MethodList, namefmt::Function)
253263
"builtin function"
254264
: # else
255265
"generic function")
256-
print(io, " for ", what, " ", namedisplay)
266+
print(io, " for ", what, " ", namedisplay, " from ")
267+
268+
col = get!(() -> popfirst!(STACKTRACE_MODULECOLORS), STACKTRACE_FIXEDCOLORS, parentmodule_before_main(ms.mt.module))
269+
270+
printstyled(io, ms.mt.module, color=col)
257271
elseif '#' in sname
258272
print(io, " for anonymous function ", namedisplay)
259273
elseif mt === _TYPE_NAME.mt
260274
print(io, " for type constructor")
261275
else
262276
print(io, " for callable object")
263277
end
264-
n > 0 && print(io, ":")
265-
nothing
278+
!iszero(n) && print(io, ":")
266279
end
267280

268281
function show_method_table(io::IO, ms::MethodList, max::Int=-1, header::Bool=true)
@@ -279,12 +292,29 @@ function show_method_table(io::IO, ms::MethodList, max::Int=-1, header::Bool=tru
279292
last_shown_line_infos = get(io, :last_shown_line_infos, nothing)
280293
last_shown_line_infos === nothing || empty!(last_shown_line_infos)
281294

295+
modul = if mt === _TYPE_NAME.mt # type constructor
296+
which(ms.ms[1].module, ms.ms[1].name)
297+
else
298+
mt.module
299+
end
300+
301+
digit_align_width = length(string(max > 0 ? max : length(ms)))
302+
282303
for meth in ms
283304
if max == -1 || n < max
284305
n += 1
285306
println(io)
286-
print(io, "[$n] ")
287-
show(io, meth)
307+
308+
print(io, " ", lpad("[$n]", digit_align_width + 2), " ")
309+
310+
modulecolor = if meth.module == modul
311+
nothing
312+
else
313+
m = parentmodule_before_main(meth.module)
314+
get!(() -> popfirst!(STACKTRACE_MODULECOLORS), STACKTRACE_FIXEDCOLORS, m)
315+
end
316+
show(io, meth; modulecolor)
317+
288318
file, line = updated_methodloc(meth)
289319
if last_shown_line_infos !== nothing
290320
push!(last_shown_line_infos, (string(file), line))
@@ -374,7 +404,7 @@ function show(io::IO, ::MIME"text/html", m::Method)
374404
join(
375405
io,
376406
String[
377-
isempty(d[2]) ? d[1] : string(d[1], "::<b>", d[2], "</b>") for d in decls[2:end]
407+
isempty(d[2]) ? string(d[1]) : string(d[1], "::<b>", d[2] , "</b>") for d in decls[2:end]
378408
],
379409
", ",
380410
", ",

0 commit comments

Comments
 (0)