Skip to content

Commit b43edb7

Browse files
authored
Updated MethodError to show closest candidates more reliably (JuliaLang#53165)
Updated version of JuliaLang#33793. Always show up to 3 methods, even if no arguments types match on some of them, but rank ones with fewer arguments before those with more arguments. Closes JuliaLang#33793 Fixes JuliaLang#33793 Fixes JuliaLang#46236 Co-authored-by: Eric Wright <efwright@udel.edu> (this diff best viewed with whitespace ignored)
1 parent f3d6904 commit b43edb7

File tree

4 files changed

+76
-67
lines changed

4 files changed

+76
-67
lines changed

base/errorshow.jl

+64-66
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,8 @@ function show_method_candidates(io::IO, ex::MethodError, kwargs=[])
421421
# functions methods and counting the number of matching arguments.
422422
f = ex.f
423423
ft = typeof(f)
424-
lines = []
424+
lines = String[]
425+
line_score = Int[]
425426
# These functions are special cased to only show if first argument is matched.
426427
special = f === convert || f === getindex || f === setindex!
427428
funcs = Tuple{Any,Vector{Any}}[(f, arg_types_param)]
@@ -512,85 +513,82 @@ function show_method_candidates(io::IO, ex::MethodError, kwargs=[])
512513
end
513514
end
514515

515-
if right_matches > 0 || length(arg_types_param) < 2
516-
if length(t_i) < length(sig)
517-
# If the methods args is longer than input then the method
518-
# arguments is printed as not a match
519-
for (k, sigtype) in enumerate(sig[length(t_i)+1:end])
520-
sigtype = isvarargtype(sigtype) ? unwrap_unionall(sigtype) : sigtype
521-
if Base.isvarargtype(sigtype)
522-
sigstr = (unwrapva(sigtype::Core.TypeofVararg), "...")
523-
else
524-
sigstr = (sigtype,)
525-
end
526-
if !((min(length(t_i), length(sig)) == 0) && k==1)
527-
print(iob, ", ")
528-
end
529-
if k == 1 && Base.isvarargtype(sigtype)
530-
# There wasn't actually a mismatch - the method match failed for
531-
# some other reason, e.g. world age. Just print the sigstr.
532-
print(iob, sigstr...)
533-
elseif get(io, :color, false)::Bool
534-
let sigstr=sigstr
535-
Base.with_output_color(Base.error_color(), iob) do iob
536-
print(iob, "::", sigstr...)
537-
end
538-
end
539-
else
540-
print(iob, "!Matched::", sigstr...)
541-
end
516+
if length(t_i) < length(sig)
517+
# If the methods args is longer than input then the method
518+
# arguments is printed as not a match
519+
for (k, sigtype) in enumerate(sig[length(t_i)+1:end])
520+
sigtype = isvarargtype(sigtype) ? unwrap_unionall(sigtype) : sigtype
521+
if Base.isvarargtype(sigtype)
522+
sigstr = (unwrapva(sigtype::Core.TypeofVararg), "...")
523+
else
524+
sigstr = (sigtype,)
542525
end
543-
end
544-
kwords = kwarg_decl(method)
545-
if !isempty(kwords)
546-
print(iob, "; ")
547-
join(iob, kwords, ", ")
548-
end
549-
print(iob, ")")
550-
show_method_params(iob0, tv)
551-
file, line = updated_methodloc(method)
552-
if file === nothing
553-
file = string(method.file)
554-
end
555-
stacktrace_contract_userdir() && (file = contractuser(file))
556-
557-
if !isempty(kwargs)::Bool
558-
unexpected = Symbol[]
559-
if isempty(kwords) || !(any(endswith(string(kword), "...") for kword in kwords))
560-
for (k, v) in kwargs
561-
if !(k::Symbol in kwords)
562-
push!(unexpected, k::Symbol)
526+
if !((min(length(t_i), length(sig)) == 0) && k==1)
527+
print(iob, ", ")
528+
end
529+
if k == 1 && Base.isvarargtype(sigtype)
530+
# There wasn't actually a mismatch - the method match failed for
531+
# some other reason, e.g. world age. Just print the sigstr.
532+
print(iob, sigstr...)
533+
elseif get(io, :color, false)::Bool
534+
let sigstr=sigstr
535+
Base.with_output_color(Base.error_color(), iob) do iob
536+
print(iob, "::", sigstr...)
563537
end
564538
end
539+
else
540+
print(iob, "!Matched::", sigstr...)
565541
end
566-
if !isempty(unexpected)
567-
Base.with_output_color(Base.error_color(), iob) do iob
568-
plur = length(unexpected) > 1 ? "s" : ""
569-
print(iob, " got unsupported keyword argument$plur \"", join(unexpected, "\", \""), "\"")
542+
end
543+
end
544+
kwords = kwarg_decl(method)
545+
if !isempty(kwords)
546+
print(iob, "; ")
547+
join(iob, kwords, ", ")
548+
end
549+
print(iob, ")")
550+
show_method_params(iob0, tv)
551+
file, line = updated_methodloc(method)
552+
if file === nothing
553+
file = string(method.file)
554+
end
555+
stacktrace_contract_userdir() && (file = contractuser(file))
556+
557+
if !isempty(kwargs)::Bool
558+
unexpected = Symbol[]
559+
if isempty(kwords) || !(any(endswith(string(kword), "...") for kword in kwords))
560+
for (k, v) in kwargs
561+
if !(k::Symbol in kwords)
562+
push!(unexpected, k::Symbol)
570563
end
571564
end
572565
end
573-
if ex.world < reinterpret(UInt, method.primary_world)
574-
print(iob, " (method too new to be called from this world context.)")
575-
elseif ex.world > reinterpret(UInt, method.deleted_world)
576-
print(iob, " (method deleted before this world age.)")
566+
if !isempty(unexpected)
567+
Base.with_output_color(Base.error_color(), iob) do iob
568+
plur = length(unexpected) > 1 ? "s" : ""
569+
print(iob, " got unsupported keyword argument$plur \"", join(unexpected, "\", \""), "\"")
570+
end
577571
end
578-
println(iob)
579-
580-
m = parentmodule_before_main(method)
581-
modulecolor = get!(() -> popfirst!(STACKTRACE_MODULECOLORS), STACKTRACE_FIXEDCOLORS, m)
582-
print_module_path_file(iob, m, string(file), line; modulecolor, digit_align_width = 3)
583-
584-
# TODO: indicate if it's in the wrong world
585-
push!(lines, (buf, right_matches))
586572
end
573+
if ex.world < reinterpret(UInt, method.primary_world)
574+
print(iob, " (method too new to be called from this world context.)")
575+
elseif ex.world > reinterpret(UInt, method.deleted_world)
576+
print(iob, " (method deleted before this world age.)")
577+
end
578+
println(iob)
579+
580+
m = parentmodule_before_main(method)
581+
modulecolor = get!(() -> popfirst!(STACKTRACE_MODULECOLORS), STACKTRACE_FIXEDCOLORS, m)
582+
print_module_path_file(iob, m, string(file), line; modulecolor, digit_align_width = 3)
583+
push!(lines, String(take!(buf)))
584+
push!(line_score, -(right_matches * 2 + (length(arg_types_param) < 2 ? 1 : 0)))
587585
end
588586
end
589587

590588
if !isempty(lines) # Display up to three closest candidates
591589
Base.with_output_color(:normal, io) do io
592590
print(io, "\n\nClosest candidates are:")
593-
sort!(lines, by = x -> -x[2])
591+
permute!(lines, sortperm(line_score))
594592
i = 0
595593
for line in lines
596594
println(io)
@@ -599,7 +597,7 @@ function show_method_candidates(io::IO, ex::MethodError, kwargs=[])
599597
break
600598
end
601599
i += 1
602-
print(io, String(take!(line[1])))
600+
print(io, line)
603601
end
604602
println(io) # extra newline for spacing to stacktrace
605603
end

doc/src/manual/constructors.md

+2
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,8 @@ The type `Point` exists, but no method is defined for this combination of argume
379379
Closest candidates are:
380380
Point(::T, !Matched::T) where T<:Real
381381
@ Main none:1
382+
Point(!Matched::Int64, !Matched::Float64)
383+
@ Main none:1
382384
383385
Stacktrace:
384386
[...]

doc/src/manual/methods.md

+2
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@ The function `f` exists, but no method is defined for this combination of argume
173173
Closest candidates are:
174174
f(!Matched::Number, ::Number)
175175
@ Main none:1
176+
f(!Matched::Float64, !Matched::Float64)
177+
@ Main none:1
176178
177179
Stacktrace:
178180
[...]

test/errorshow.jl

+8-1
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,12 @@ Base.show_method_candidates(buf, Base.MethodError(method_c1,(1, "", "")))
7878
Base.show_method_candidates(buf, Base.MethodError(method_c1,(1., "", "")))
7979
@test occursin("\n\nClosest candidates are:\n method_c1(::Float64, ::AbstractString...)$cmod$cfile$c1line\n", String(take!(buf)))
8080

81-
# Have no matches so should return empty
81+
# Have no matches, but still print up to 3
8282
Base.show_method_candidates(buf, Base.MethodError(method_c1,(1, 1, 1)))
83+
@test occursin("\n\nClosest candidates are:\n method_c1(!Matched::Float64, !Matched::AbstractString...)$cmod$cfile$c1line\n", String(take!(buf)))
84+
85+
function nomethodsfunc end
86+
Base.show_method_candidates(buf, Base.MethodError(nomethodsfunc,(1, 1, 1)))
8387
@test isempty(String(take!(buf)))
8488

8589
# matches the implicit constructor -> convert method
@@ -1161,3 +1165,6 @@ end
11611165
# issue #47559"
11621166
@test_throws("MethodError: no method matching invoke Returns(::Any, ::Val{N}) where N",
11631167
invoke(Returns, Tuple{Any,Val{N}} where N, 1, Val(1)))
1168+
1169+
f33793(x::Float32, y::Float32) = 1
1170+
@test_throws "\nClosest candidates are:\n f33793(!Matched::Float32, !Matched::Float32)\n" f33793(Float64(0.0), Float64(0.0))

0 commit comments

Comments
 (0)