Skip to content

Commit 422d05d

Browse files
authored
Add MethodError hints for functions in other modules (#58715)
When a MethodError occurs, check if functions with the same name exist in other modules (particularly those of the argument types). This helps users discover that they may need to import a function or ensure multiple functions are the same generic function. - For Base functions: suggests importing (e.g., "You may have intended to import Base.length") - For other modules: suggests they may be intended as the same generic function - Shows all matches from relevant modules in sorted order - Uses modulesof! to properly handle all type structures including unions Fixes #58682
1 parent a812f03 commit 422d05d

File tree

2 files changed

+41
-7
lines changed

2 files changed

+41
-7
lines changed

base/errorshow.jl

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -327,13 +327,33 @@ function showerror(io::IO, ex::MethodError)
327327
if ft <: AbstractArray
328328
print(io, "\nIn case you're trying to index into the array, use square brackets [] instead of parentheses ().")
329329
end
330-
# Check for local functions that shadow methods in Base
331-
let name = ft.name.singletonname
332-
if f_is_function && isdefined(Base, name)
333-
basef = getfield(Base, name)
334-
if basef !== f && hasmethod(basef, arg_types)
335-
print(io, "\nYou may have intended to import ")
336-
show_unquoted(io, Expr(:., :Base, QuoteNode(name)))
330+
# Check for functions with the same name in other modules
331+
if f_is_function && ex.world != typemax(UInt)
332+
let name = ft.name.singletonname
333+
modules_to_check = Set{Module}()
334+
push!(modules_to_check, Base)
335+
for T in san_arg_types_param
336+
modulesof!(modules_to_check, T)
337+
end
338+
339+
# Check all modules (sorted for consistency)
340+
sorted_modules = sort!(collect(modules_to_check), by=nameof)
341+
for mod in sorted_modules
342+
if isdefined(mod, name)
343+
candidate = getfield(mod, name)
344+
if candidate !== f && hasmethod(candidate, arg_types; world=ex.world)
345+
if mod === Base
346+
print(io, "\nYou may have intended to import ")
347+
show_unquoted(io, Expr(:., :Base, QuoteNode(name)))
348+
else
349+
print(io, "\nThe definition in ")
350+
show_unquoted(io, mod)
351+
print(io, " may have intended to extend ")
352+
f_module = parentmodule(ft)
353+
show_unquoted(io, Expr(:., f_module, QuoteNode(name)))
354+
end
355+
end
356+
end
337357
end
338358
end
339359
end

test/errorshow.jl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,6 +1019,20 @@ for (func,str) in ((TestMethodShadow.:+,":+"), (TestMethodShadow.:(==),":(==)"),
10191019
@test occursin("You may have intended to import Base.$str", sprint(Base.showerror, ex))
10201020
end
10211021

1022+
# Test hint for functions in modules of argument types (issue #58682)
1023+
module TestModuleHint
1024+
struct Bar end
1025+
length(x::Bar) = 42
1026+
end
1027+
let ex = try
1028+
# Call Base.length on TestModuleHint.Bar - should suggest importing TestModuleHint.length
1029+
length(TestModuleHint.Bar())
1030+
catch e
1031+
e
1032+
end::MethodError
1033+
@test occursin("may have intended to extend", sprint(Base.showerror, ex))
1034+
end
1035+
10221036
# Test that implementation detail of include() is hidden from the user by default
10231037
let bt = try
10241038
@noinline include("testhelpers/include_error.jl")

0 commit comments

Comments
 (0)