Skip to content

Commit fc06291

Browse files
authored
Improve error message for which(fn, types) (#47369)
Error messages for `MethodError` are much more helpful in determining why the method was not successfully dispatched than simply "No unique matching method found." Fixes #47322
1 parent fda9321 commit fc06291

File tree

15 files changed

+94
-49
lines changed

15 files changed

+94
-49
lines changed

base/errorshow.jl

Lines changed: 40 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -246,14 +246,15 @@ end
246246
function showerror(io::IO, ex::MethodError)
247247
# ex.args is a tuple type if it was thrown from `invoke` and is
248248
# a tuple of the arguments otherwise.
249-
is_arg_types = isa(ex.args, DataType)
250-
arg_types = (is_arg_types ? ex.args : typesof(ex.args...))::DataType
249+
is_arg_types = !isa(ex.args, Tuple)
250+
arg_types = is_arg_types ? ex.args : typesof(ex.args...)
251+
arg_types_param::SimpleVector = (unwrap_unionall(arg_types)::DataType).parameters
252+
san_arg_types_param = Any[rewrap_unionall(a, arg_types) for a in arg_types_param]
251253
f = ex.f
252254
meth = methods_including_ambiguous(f, arg_types)
253255
if isa(meth, MethodList) && length(meth) > 1
254256
return showerror_ambiguous(io, meth, f, arg_types)
255257
end
256-
arg_types_param::SimpleVector = arg_types.parameters
257258
print(io, "MethodError: ")
258259
ft = typeof(f)
259260
f_is_function = false
@@ -262,10 +263,11 @@ function showerror(io::IO, ex::MethodError)
262263
f = (ex.args::Tuple)[2]
263264
ft = typeof(f)
264265
arg_types_param = arg_types_param[3:end]
266+
san_arg_types_param = san_arg_types_param[3:end]
265267
kwargs = pairs(ex.args[1])
266268
ex = MethodError(f, ex.args[3:end::Int], ex.world)
269+
arg_types = Tuple{arg_types_param...}
267270
end
268-
name = ft.name.mt.name
269271
if f === Base.convert && length(arg_types_param) == 2 && !is_arg_types
270272
f_is_function = true
271273
show_convert_error(io, ex, arg_types_param)
@@ -277,36 +279,28 @@ function showerror(io::IO, ex::MethodError)
277279
if ft <: Function && isempty(ft.parameters) && _isself(ft)
278280
f_is_function = true
279281
end
280-
print(io, "no method matching ")
282+
if is_arg_types
283+
print(io, "no method matching invoke ")
284+
else
285+
print(io, "no method matching ")
286+
end
281287
buf = IOBuffer()
282288
iob = IOContext(buf, io) # for type abbreviation as in #49795; some, like `convert(T, x)`, should not abbreviate
283289
show_signature_function(iob, isa(f, Type) ? Type{f} : typeof(f))
284-
print(iob, "(")
285-
for (i, typ) in enumerate(arg_types_param)
286-
print(iob, "::", typ)
287-
i == length(arg_types_param) || print(iob, ", ")
288-
end
289-
if !isempty(kwargs)
290-
print(iob, "; ")
291-
for (i, (k, v)) in enumerate(kwargs)
292-
print(iob, k, "::", typeof(v))
293-
i == length(kwargs)::Int || print(iob, ", ")
294-
end
295-
end
296-
print(iob, ")")
290+
show_tuple_as_call(iob, :function, arg_types; hasfirst=false, kwargs = !isempty(kwargs) ? Any[(k, typeof(v)) for (k, v) in kwargs] : nothing)
297291
str = String(take!(buf))
298292
str = type_limited_string_from_context(io, str)
299293
print(io, str)
300294
end
301295
# catch the two common cases of element-wise addition and subtraction
302-
if (f === Base.:+ || f === Base.:-) && length(arg_types_param) == 2
296+
if (f === Base.:+ || f === Base.:-) && length(san_arg_types_param) == 2
303297
# we need one array of numbers and one number, in any order
304-
if any(x -> x <: AbstractArray{<:Number}, arg_types_param) &&
305-
any(x -> x <: Number, arg_types_param)
298+
if any(x -> x <: AbstractArray{<:Number}, san_arg_types_param) &&
299+
any(x -> x <: Number, san_arg_types_param)
306300

307301
nounf = f === Base.:+ ? "addition" : "subtraction"
308302
varnames = ("scalar", "array")
309-
first, second = arg_types_param[1] <: Number ? varnames : reverse(varnames)
303+
first, second = san_arg_types_param[1] <: Number ? varnames : reverse(varnames)
310304
fstring = f === Base.:+ ? "+" : "-" # avoid depending on show_default for functions (invalidation)
311305
print(io, "\nFor element-wise $nounf, use broadcasting with dot syntax: $first .$fstring $second")
312306
end
@@ -315,17 +309,25 @@ function showerror(io::IO, ex::MethodError)
315309
print(io, "\nUse square brackets [] for indexing an Array.")
316310
end
317311
# Check for local functions that shadow methods in Base
318-
if f_is_function && isdefined(Base, name)
319-
basef = getfield(Base, name)
320-
if basef !== ex.f && hasmethod(basef, arg_types)
321-
print(io, "\nYou may have intended to import ")
322-
show_unquoted(io, Expr(:., :Base, QuoteNode(name)))
312+
let name = ft.name.mt.name
313+
if f_is_function && isdefined(Base, name)
314+
basef = getfield(Base, name)
315+
if basef !== f && hasmethod(basef, arg_types)
316+
print(io, "\nYou may have intended to import ")
317+
show_unquoted(io, Expr(:., :Base, QuoteNode(name)))
318+
end
323319
end
324320
end
325-
if (ex.world != typemax(UInt) && hasmethod(ex.f, arg_types) &&
326-
!hasmethod(ex.f, arg_types, world = ex.world))
321+
if (ex.world != typemax(UInt) && hasmethod(f, arg_types) &&
322+
!hasmethod(f, arg_types, world = ex.world))
327323
curworld = get_world_counter()
328324
print(io, "\nThe applicable method may be too new: running in world age $(ex.world), while current world is $(curworld).")
325+
elseif f isa Function
326+
print(io, "\nThe function `$f` exists, but no method is defined for this combination of argument types.")
327+
elseif f isa Type
328+
print(io, "\nThe type `$f` exists, but no method is defined for this combination of argument types when trying to construct it.")
329+
else
330+
print(io, "\nThe object of type `$(typeof(f))` exists, but no method is defined for this combination of argument types when trying to treat it as a callable object.")
329331
end
330332
if !is_arg_types
331333
# Check for row vectors used where a column vector is intended.
@@ -342,7 +344,7 @@ function showerror(io::IO, ex::MethodError)
342344
"\nYou can convert to a column vector with the vec() function.")
343345
end
344346
end
345-
Experimental.show_error_hints(io, ex, arg_types_param, kwargs)
347+
Experimental.show_error_hints(io, ex, san_arg_types_param, kwargs)
346348
try
347349
show_method_candidates(io, ex, kwargs)
348350
catch ex
@@ -354,16 +356,12 @@ end
354356
striptype(::Type{T}) where {T} = T
355357
striptype(::Any) = nothing
356358

357-
function showerror_ambiguous(io::IO, meths, f, args)
359+
function showerror_ambiguous(io::IO, meths, f, args::Type)
360+
@nospecialize f args
358361
print(io, "MethodError: ")
359362
show_signature_function(io, isa(f, Type) ? Type{f} : typeof(f))
360-
print(io, "(")
361-
p = args.parameters
362-
for (i,a) in enumerate(p)
363-
print(io, "::", a)
364-
i < length(p) && print(io, ", ")
365-
end
366-
println(io, ") is ambiguous.\n\nCandidates:")
363+
show_tuple_as_call(io, :var"", args, hasfirst=false)
364+
println(io, " is ambiguous.\n\nCandidates:")
367365
sigfix = Any
368366
for m in meths
369367
print(io, " ")
@@ -375,7 +373,7 @@ function showerror_ambiguous(io::IO, meths, f, args)
375373
let sigfix=sigfix
376374
if all(m->morespecific(sigfix, m.sig), meths)
377375
print(io, "\nPossible fix, define\n ")
378-
Base.show_tuple_as_call(io, :function, sigfix)
376+
show_tuple_as_call(io, :function, sigfix)
379377
else
380378
print(io, "To resolve the ambiguity, try making one of the methods more specific, or ")
381379
print(io, "adding a new method more specific than any of the existing applicable methods.")
@@ -401,9 +399,10 @@ stacktrace_contract_userdir()::Bool = Base.get_bool_env("JULIA_STACKTRACE_CONTRA
401399
stacktrace_linebreaks()::Bool = Base.get_bool_env("JULIA_STACKTRACE_LINEBREAKS", false) === true
402400

403401
function show_method_candidates(io::IO, ex::MethodError, @nospecialize kwargs=())
404-
is_arg_types = isa(ex.args, DataType)
402+
is_arg_types = !isa(ex.args, Tuple)
405403
arg_types = is_arg_types ? ex.args : typesof(ex.args...)
406-
arg_types_param = Any[arg_types.parameters...]
404+
arg_types_param = Any[(unwrap_unionall(arg_types)::DataType).parameters...]
405+
arg_types_param = Any[rewrap_unionall(a, arg_types) for a in arg_types_param]
407406
# Displays the closest candidates of the given function by looping over the
408407
# functions methods and counting the number of matching arguments.
409408
f = ex.f

base/experimental.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ Then if you call `Hinter.only_int` on something that isn't an `Int` (thereby tri
280280
```
281281
julia> Hinter.only_int(1.0)
282282
ERROR: MethodError: no method matching only_int(::Float64)
283+
The function `only_int` exists, but no method is defined for this combination of argument types.
283284
Did you mean to call `any_number`?
284285
Closest candidates are:
285286
...

base/namedtuple.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,7 @@ Base.Pairs{Symbol, Int64, Tuple{Symbol}, @NamedTuple{init::Int64}}
526526
527527
julia> sum("julia"; init=1)
528528
ERROR: MethodError: no method matching +(::Char, ::Char)
529+
The function `+` exists, but no method is defined for this combination of argument types.
529530
530531
Closest candidates are:
531532
+(::Any, ::Any, ::Any, ::Any...)

base/reflection.jl

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2192,7 +2192,17 @@ See also: [`parentmodule`](@ref), [`@which`](@ref Main.InteractiveUtils.@which),
21922192
"""
21932193
function which(@nospecialize(f), @nospecialize(t))
21942194
tt = signature_type(f, t)
2195-
return which(tt)
2195+
world = get_world_counter()
2196+
match, _ = Core.Compiler._findsup(tt, nothing, world)
2197+
if match === nothing
2198+
me = MethodError(f, t, world)
2199+
ee = ErrorException(sprint(io -> begin
2200+
println(io, "Calling invoke(f, t, args...) would throw:");
2201+
Base.showerror(io, me);
2202+
end))
2203+
throw(ee)
2204+
end
2205+
return match.method
21962206
end
21972207

21982208
"""

doc/src/manual/constructors.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,8 @@ Point{Float64}(1.0, 2.5)
293293
294294
julia> Point(1,2.5) ## implicit T ##
295295
ERROR: MethodError: no method matching Point(::Int64, ::Float64)
296+
The type `Point` exists, but no method is defined for this combination of argument types when trying to construct it.
297+
296298
Closest candidates are:
297299
Point(::T, ::T) where T<:Real at none:2
298300
@@ -372,6 +374,7 @@ However, other similar calls still don't work:
372374
```jldoctest parametric2
373375
julia> Point(1.5,2)
374376
ERROR: MethodError: no method matching Point(::Float64, ::Int64)
377+
The type `Point` exists, but no method is defined for this combination of argument types when trying to construct it.
375378
376379
Closest candidates are:
377380
Point(::T, !Matched::T) where T<:Real
@@ -556,6 +559,7 @@ julia> struct SummedArray{T<:Number,S<:Number}
556559
557560
julia> SummedArray(Int32[1; 2; 3], Int32(6))
558561
ERROR: MethodError: no method matching SummedArray(::Vector{Int32}, ::Int32)
562+
The type `SummedArray` exists, but no method is defined for this combination of argument types when trying to construct it.
559563
560564
Closest candidates are:
561565
SummedArray(::Vector{T}) where T

doc/src/manual/faq.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,7 @@ foo (generic function with 1 method)
804804
805805
julia> foo([1])
806806
ERROR: MethodError: no method matching foo(::Vector{Int64})
807+
The function `foo` exists, but no method is defined for this combination of argument types.
807808
808809
Closest candidates are:
809810
foo(!Matched::Vector{Real})

doc/src/manual/functions.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,7 @@ julia> args = [1, 2, 3]
730730
731731
julia> baz(args...)
732732
ERROR: MethodError: no method matching baz(::Int64, ::Int64, ::Int64)
733+
The function `baz` exists, but no method is defined for this combination of argument types.
733734
734735
Closest candidates are:
735736
baz(::Any, ::Any)

doc/src/manual/methods.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ Applying it to any other types of arguments will result in a [`MethodError`](@re
7676
```jldoctest fofxy
7777
julia> f(2.0, 3)
7878
ERROR: MethodError: no method matching f(::Float64, ::Int64)
79+
The function `f` exists, but no method is defined for this combination of argument types.
7980
8081
Closest candidates are:
8182
f(::Float64, !Matched::Float64)
@@ -86,6 +87,7 @@ Stacktrace:
8687
8788
julia> f(Float32(2.0), 3.0)
8889
ERROR: MethodError: no method matching f(::Float32, ::Float64)
90+
The function `f` exists, but no method is defined for this combination of argument types.
8991
9092
Closest candidates are:
9193
f(!Matched::Float64, ::Float64)
@@ -96,6 +98,7 @@ Stacktrace:
9698
9799
julia> f(2.0, "3.0")
98100
ERROR: MethodError: no method matching f(::Float64, ::String)
101+
The function `f` exists, but no method is defined for this combination of argument types.
99102
100103
Closest candidates are:
101104
f(::Float64, !Matched::Float64)
@@ -106,6 +109,7 @@ Stacktrace:
106109
107110
julia> f("2.0", "3.0")
108111
ERROR: MethodError: no method matching f(::String, ::String)
112+
The function `f` exists, but no method is defined for this combination of argument types.
109113
```
110114

111115
As you can see, the arguments must be precisely of type [`Float64`](@ref). Other numeric
@@ -164,6 +168,7 @@ and applying it will still result in a [`MethodError`](@ref):
164168
```jldoctest fofxy
165169
julia> f("foo", 3)
166170
ERROR: MethodError: no method matching f(::String, ::Int64)
171+
The function `f` exists, but no method is defined for this combination of argument types.
167172
168173
Closest candidates are:
169174
f(!Matched::Number, ::Number)
@@ -174,6 +179,7 @@ Stacktrace:
174179
175180
julia> f()
176181
ERROR: MethodError: no method matching f()
182+
The function `f` exists, but no method is defined for this combination of argument types.
177183
178184
Closest candidates are:
179185
f(!Matched::Float64, !Matched::Float64)
@@ -432,6 +438,7 @@ julia> myappend([1,2,3],4)
432438
433439
julia> myappend([1,2,3],2.5)
434440
ERROR: MethodError: no method matching myappend(::Vector{Int64}, ::Float64)
441+
The function `myappend` exists, but no method is defined for this combination of argument types.
435442
436443
Closest candidates are:
437444
myappend(::Vector{T}, !Matched::T) where T
@@ -449,6 +456,7 @@ julia> myappend([1.0,2.0,3.0],4.0)
449456
450457
julia> myappend([1.0,2.0,3.0],4)
451458
ERROR: MethodError: no method matching myappend(::Vector{Float64}, ::Int64)
459+
The function `myappend` exists, but no method is defined for this combination of argument types.
452460
453461
Closest candidates are:
454462
myappend(::Vector{T}, !Matched::T) where T
@@ -494,6 +502,7 @@ true
494502
495503
julia> same_type_numeric("foo", 2.0)
496504
ERROR: MethodError: no method matching same_type_numeric(::String, ::Float64)
505+
The function `same_type_numeric` exists, but no method is defined for this combination of argument types.
497506
498507
Closest candidates are:
499508
same_type_numeric(!Matched::T, ::T) where T<:Number
@@ -506,6 +515,7 @@ Stacktrace:
506515
507516
julia> same_type_numeric("foo", "bar")
508517
ERROR: MethodError: no method matching same_type_numeric(::String, ::String)
518+
The function `same_type_numeric` exists, but no method is defined for this combination of argument types.
509519
510520
julia> same_type_numeric(Int32(1), Int64(2))
511521
false
@@ -888,6 +898,7 @@ bar (generic function with 1 method)
888898
889899
julia> bar(1,2,3)
890900
ERROR: MethodError: no method matching bar(::Int64, ::Int64, ::Int64)
901+
The function `bar` exists, but no method is defined for this combination of argument types.
891902
892903
Closest candidates are:
893904
bar(::Any, ::Any, ::Any, !Matched::Any)
@@ -901,6 +912,7 @@ julia> bar(1,2,3,4)
901912
902913
julia> bar(1,2,3,4,5)
903914
ERROR: MethodError: no method matching bar(::Int64, ::Int64, ::Int64, ::Int64, ::Int64)
915+
The function `bar` exists, but no method is defined for this combination of argument types.
904916
905917
Closest candidates are:
906918
bar(::Any, ::Any, ::Any, ::Any)

doc/src/manual/types.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -713,10 +713,12 @@ For the default constructor, exactly one argument must be supplied for each fiel
713713
```jldoctest pointtype
714714
julia> Point{Float64}(1.0)
715715
ERROR: MethodError: no method matching Point{Float64}(::Float64)
716+
The type `Point{Float64}` exists, but no method is defined for this combination of argument types when trying to construct it.
716717
[...]
717718
718-
julia> Point{Float64}(1.0,2.0,3.0)
719+
julia> Point{Float64}(1.0, 2.0, 3.0)
719720
ERROR: MethodError: no method matching Point{Float64}(::Float64, ::Float64, ::Float64)
721+
The type `Point{Float64}` exists, but no method is defined for this combination of argument types when trying to construct it.
720722
[...]
721723
```
722724

@@ -748,6 +750,7 @@ to `Point` have the same type. When this isn't the case, the constructor will fa
748750
```jldoctest pointtype
749751
julia> Point(1,2.5)
750752
ERROR: MethodError: no method matching Point(::Int64, ::Float64)
753+
The type `Point` exists, but no method is defined for this combination of argument types when trying to construct it.
751754
752755
Closest candidates are:
753756
Point(::T, !Matched::T) where T
@@ -1413,6 +1416,8 @@ is raised:
14131416
```jldoctest; filter = r"Closest candidates.*"s
14141417
julia> supertype(Union{Float64,Int64})
14151418
ERROR: MethodError: no method matching supertype(::Type{Union{Float64, Int64}})
1419+
The function `supertype` exists, but no method is defined for this combination of argument types.
1420+
14161421
Closest candidates are:
14171422
[...]
14181423
```

stdlib/Test/docs/src/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ Error During Test
7373
Test threw an exception of type MethodError
7474
Expression: foo(:cat) == 1
7575
MethodError: no method matching length(::Symbol)
76+
The function `length` exists, but no method is defined for this combination of argument types.
77+
7678
Closest candidates are:
7779
length(::SimpleVector) at essentials.jl:256
7880
length(::Base.MethodList) at reflection.jl:521

0 commit comments

Comments
 (0)