Skip to content

Commit

Permalink
Updated MethodError to show closest candidates more reliably (JuliaLa…
Browse files Browse the repository at this point in the history
…ng#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)
  • Loading branch information
vtjnash authored Feb 10, 2024
1 parent f3d6904 commit b43edb7
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 67 deletions.
130 changes: 64 additions & 66 deletions base/errorshow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,8 @@ function show_method_candidates(io::IO, ex::MethodError, kwargs=[])
# functions methods and counting the number of matching arguments.
f = ex.f
ft = typeof(f)
lines = []
lines = String[]
line_score = Int[]
# These functions are special cased to only show if first argument is matched.
special = f === convert || f === getindex || f === setindex!
funcs = Tuple{Any,Vector{Any}}[(f, arg_types_param)]
Expand Down Expand Up @@ -512,85 +513,82 @@ function show_method_candidates(io::IO, ex::MethodError, kwargs=[])
end
end

if right_matches > 0 || length(arg_types_param) < 2
if length(t_i) < length(sig)
# If the methods args is longer than input then the method
# arguments is printed as not a match
for (k, sigtype) in enumerate(sig[length(t_i)+1:end])
sigtype = isvarargtype(sigtype) ? unwrap_unionall(sigtype) : sigtype
if Base.isvarargtype(sigtype)
sigstr = (unwrapva(sigtype::Core.TypeofVararg), "...")
else
sigstr = (sigtype,)
end
if !((min(length(t_i), length(sig)) == 0) && k==1)
print(iob, ", ")
end
if k == 1 && Base.isvarargtype(sigtype)
# There wasn't actually a mismatch - the method match failed for
# some other reason, e.g. world age. Just print the sigstr.
print(iob, sigstr...)
elseif get(io, :color, false)::Bool
let sigstr=sigstr
Base.with_output_color(Base.error_color(), iob) do iob
print(iob, "::", sigstr...)
end
end
else
print(iob, "!Matched::", sigstr...)
end
if length(t_i) < length(sig)
# If the methods args is longer than input then the method
# arguments is printed as not a match
for (k, sigtype) in enumerate(sig[length(t_i)+1:end])
sigtype = isvarargtype(sigtype) ? unwrap_unionall(sigtype) : sigtype
if Base.isvarargtype(sigtype)
sigstr = (unwrapva(sigtype::Core.TypeofVararg), "...")
else
sigstr = (sigtype,)
end
end
kwords = kwarg_decl(method)
if !isempty(kwords)
print(iob, "; ")
join(iob, kwords, ", ")
end
print(iob, ")")
show_method_params(iob0, tv)
file, line = updated_methodloc(method)
if file === nothing
file = string(method.file)
end
stacktrace_contract_userdir() && (file = contractuser(file))

if !isempty(kwargs)::Bool
unexpected = Symbol[]
if isempty(kwords) || !(any(endswith(string(kword), "...") for kword in kwords))
for (k, v) in kwargs
if !(k::Symbol in kwords)
push!(unexpected, k::Symbol)
if !((min(length(t_i), length(sig)) == 0) && k==1)
print(iob, ", ")
end
if k == 1 && Base.isvarargtype(sigtype)
# There wasn't actually a mismatch - the method match failed for
# some other reason, e.g. world age. Just print the sigstr.
print(iob, sigstr...)
elseif get(io, :color, false)::Bool
let sigstr=sigstr
Base.with_output_color(Base.error_color(), iob) do iob
print(iob, "::", sigstr...)
end
end
else
print(iob, "!Matched::", sigstr...)
end
if !isempty(unexpected)
Base.with_output_color(Base.error_color(), iob) do iob
plur = length(unexpected) > 1 ? "s" : ""
print(iob, " got unsupported keyword argument$plur \"", join(unexpected, "\", \""), "\"")
end
end
kwords = kwarg_decl(method)
if !isempty(kwords)
print(iob, "; ")
join(iob, kwords, ", ")
end
print(iob, ")")
show_method_params(iob0, tv)
file, line = updated_methodloc(method)
if file === nothing
file = string(method.file)
end
stacktrace_contract_userdir() && (file = contractuser(file))

if !isempty(kwargs)::Bool
unexpected = Symbol[]
if isempty(kwords) || !(any(endswith(string(kword), "...") for kword in kwords))
for (k, v) in kwargs
if !(k::Symbol in kwords)
push!(unexpected, k::Symbol)
end
end
end
if ex.world < reinterpret(UInt, method.primary_world)
print(iob, " (method too new to be called from this world context.)")
elseif ex.world > reinterpret(UInt, method.deleted_world)
print(iob, " (method deleted before this world age.)")
if !isempty(unexpected)
Base.with_output_color(Base.error_color(), iob) do iob
plur = length(unexpected) > 1 ? "s" : ""
print(iob, " got unsupported keyword argument$plur \"", join(unexpected, "\", \""), "\"")
end
end
println(iob)

m = parentmodule_before_main(method)
modulecolor = get!(() -> popfirst!(STACKTRACE_MODULECOLORS), STACKTRACE_FIXEDCOLORS, m)
print_module_path_file(iob, m, string(file), line; modulecolor, digit_align_width = 3)

# TODO: indicate if it's in the wrong world
push!(lines, (buf, right_matches))
end
if ex.world < reinterpret(UInt, method.primary_world)
print(iob, " (method too new to be called from this world context.)")
elseif ex.world > reinterpret(UInt, method.deleted_world)
print(iob, " (method deleted before this world age.)")
end
println(iob)

m = parentmodule_before_main(method)
modulecolor = get!(() -> popfirst!(STACKTRACE_MODULECOLORS), STACKTRACE_FIXEDCOLORS, m)
print_module_path_file(iob, m, string(file), line; modulecolor, digit_align_width = 3)
push!(lines, String(take!(buf)))
push!(line_score, -(right_matches * 2 + (length(arg_types_param) < 2 ? 1 : 0)))
end
end

if !isempty(lines) # Display up to three closest candidates
Base.with_output_color(:normal, io) do io
print(io, "\n\nClosest candidates are:")
sort!(lines, by = x -> -x[2])
permute!(lines, sortperm(line_score))
i = 0
for line in lines
println(io)
Expand All @@ -599,7 +597,7 @@ function show_method_candidates(io::IO, ex::MethodError, kwargs=[])
break
end
i += 1
print(io, String(take!(line[1])))
print(io, line)
end
println(io) # extra newline for spacing to stacktrace
end
Expand Down
2 changes: 2 additions & 0 deletions doc/src/manual/constructors.md
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,8 @@ The type `Point` exists, but no method is defined for this combination of argume
Closest candidates are:
Point(::T, !Matched::T) where T<:Real
@ Main none:1
Point(!Matched::Int64, !Matched::Float64)
@ Main none:1
Stacktrace:
[...]
Expand Down
2 changes: 2 additions & 0 deletions doc/src/manual/methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ The function `f` exists, but no method is defined for this combination of argume
Closest candidates are:
f(!Matched::Number, ::Number)
@ Main none:1
f(!Matched::Float64, !Matched::Float64)
@ Main none:1
Stacktrace:
[...]
Expand Down
9 changes: 8 additions & 1 deletion test/errorshow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,12 @@ Base.show_method_candidates(buf, Base.MethodError(method_c1,(1, "", "")))
Base.show_method_candidates(buf, Base.MethodError(method_c1,(1., "", "")))
@test occursin("\n\nClosest candidates are:\n method_c1(::Float64, ::AbstractString...)$cmod$cfile$c1line\n", String(take!(buf)))

# Have no matches so should return empty
# Have no matches, but still print up to 3
Base.show_method_candidates(buf, Base.MethodError(method_c1,(1, 1, 1)))
@test occursin("\n\nClosest candidates are:\n method_c1(!Matched::Float64, !Matched::AbstractString...)$cmod$cfile$c1line\n", String(take!(buf)))

function nomethodsfunc end
Base.show_method_candidates(buf, Base.MethodError(nomethodsfunc,(1, 1, 1)))
@test isempty(String(take!(buf)))

# matches the implicit constructor -> convert method
Expand Down Expand Up @@ -1161,3 +1165,6 @@ end
# issue #47559"
@test_throws("MethodError: no method matching invoke Returns(::Any, ::Val{N}) where N",
invoke(Returns, Tuple{Any,Val{N}} where N, 1, Val(1)))

f33793(x::Float32, y::Float32) = 1
@test_throws "\nClosest candidates are:\n f33793(!Matched::Float32, !Matched::Float32)\n" f33793(Float64(0.0), Float64(0.0))

0 comments on commit b43edb7

Please sign in to comment.