diff --git a/NEWS.md b/NEWS.md index ac9e84c88b9344..c08fe50152fe29 100644 --- a/NEWS.md +++ b/NEWS.md @@ -170,4 +170,7 @@ External dependencies Tooling Improvements --------------------- +* Printing of `MethodError` and methods (such as from `methods(my_func)`) are now prettified and color consistent with printing of methods + in stacktraces. ([#45069]) + diff --git a/base/deprecated.jl b/base/deprecated.jl index 65107d0968fee0..f3f127e81b795e 100644 --- a/base/deprecated.jl +++ b/base/deprecated.jl @@ -39,7 +39,7 @@ old (generic function with 1 method) Calls to `@deprecate` without explicit type-annotations will define deprecated methods accepting arguments of type `Any`. To restrict deprecation to a specific signature, annotate the arguments of `old`. For example, -```jldoctest; filter = r"in Main at.*" +```jldoctest; filter = r"@ .*" julia> new(x::Int) = x; julia> new(x::Float64) = 2x; @@ -47,8 +47,9 @@ julia> new(x::Float64) = 2x; julia> @deprecate old(x::Int) new(x); julia> methods(old) -# 1 method for generic function "old": -[1] old(x::Int64) in Main at deprecated.jl:70 +# 1 method for generic function "old" from Main: + [1] old(x::Int64) + @ deprecated.jl:94 ``` will define and deprecate a method `old(x::Int)` that mirrors `new(x::Int)` but will not define nor deprecate the method `old(x::Float64)`. diff --git a/base/errorshow.jl b/base/errorshow.jl index 4ae02885aa6182..aaf040cd71b8d7 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -333,7 +333,7 @@ end striptype(::Type{T}) where {T} = T striptype(::Any) = nothing -function showerror_ambiguous(io::IO, meth, f, args) +function showerror_ambiguous(io::IO, meths, f, args) print(io, "MethodError: ") show_signature_function(io, isa(f, Type) ? Type{f} : typeof(f)) print(io, "(") @@ -342,23 +342,25 @@ function showerror_ambiguous(io::IO, meth, f, args) print(io, "::", a) i < length(p) && print(io, ", ") end - print(io, ") is ambiguous. Candidates:") + println(io, ") is ambiguous.\n\nCandidates:") sigfix = Any - for m in meth - print(io, "\n ", m) + for m in meths + print(io, " ") + show(io, m; digit_align_width=-2) + println(io) sigfix = typeintersect(m.sig, sigfix) end if isa(unwrap_unionall(sigfix), DataType) && sigfix <: Tuple let sigfix=sigfix - if all(m->morespecific(sigfix, m.sig), meth) + if all(m->morespecific(sigfix, m.sig), meths) print(io, "\nPossible fix, define\n ") Base.show_tuple_as_call(io, :function, sigfix) else - println(io) print(io, "To resolve the ambiguity, try making one of the methods more specific, or ") print(io, "adding a new method more specific than any of the existing applicable methods.") end end + println(io) end nothing end @@ -516,7 +518,7 @@ function show_method_candidates(io::IO, ex::MethodError, @nospecialize kwargs=() file = string(method.file) end stacktrace_contract_userdir() && (file = contractuser(file)) - print(iob, " at ", file, ":", line) + if !isempty(kwargs)::Bool unexpected = Symbol[] 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=() elseif ex.world > reinterpret(UInt, method.deleted_world) print(iob, " (method deleted before this world age.)") end + println(iob) + + m = parentmodule_before_main(method.module) + color = get!(() -> popfirst!(STACKTRACE_MODULECOLORS), STACKTRACE_FIXEDCOLORS, m) + print_module_path_file(iob, m, string(file), line, color, 1) + # TODO: indicate if it's in the wrong world push!(lines, (buf, right_matches)) end @@ -546,7 +554,7 @@ function show_method_candidates(io::IO, ex::MethodError, @nospecialize kwargs=() if !isempty(lines) # Display up to three closest candidates Base.with_output_color(:normal, io) do io - print(io, "\nClosest candidates are:") + print(io, "\n\nClosest candidates are:") sort!(lines, by = x -> -x[2]) i = 0 for line in lines @@ -558,6 +566,7 @@ function show_method_candidates(io::IO, ex::MethodError, @nospecialize kwargs=() i += 1 print(io, String(take!(line[1]))) end + println(io) # extra newline for spacing to stacktrace end end end @@ -573,20 +582,17 @@ end # replace `sf` as needed. const update_stackframes_callback = Ref{Function}(identity) -const STACKTRACE_MODULECOLORS = [:magenta, :cyan, :green, :yellow] +const STACKTRACE_MODULECOLORS = Iterators.Stateful(Iterators.cycle([:magenta, :cyan, :green, :yellow])) const STACKTRACE_FIXEDCOLORS = IdDict(Base => :light_black, Core => :light_black) function show_full_backtrace(io::IO, trace::Vector; print_linebreaks::Bool) num_frames = length(trace) ndigits_max = ndigits(num_frames) - modulecolordict = copy(STACKTRACE_FIXEDCOLORS) - modulecolorcycler = Iterators.Stateful(Iterators.cycle(STACKTRACE_MODULECOLORS)) - println(io, "\nStacktrace:") for (i, (frame, n)) in enumerate(trace) - print_stackframe(io, i, frame, n, ndigits_max, modulecolordict, modulecolorcycler) + print_stackframe(io, i, frame, n, ndigits_max, STACKTRACE_FIXEDCOLORS, STACKTRACE_MODULECOLORS) if i < num_frames println(io) print_linebreaks && println(io) @@ -646,15 +652,12 @@ function show_reduced_backtrace(io::IO, t::Vector) ndigits_max = ndigits(length(t)) - modulecolordict = Dict{Module, Symbol}() - modulecolorcycler = Iterators.Stateful(Iterators.cycle(STACKTRACE_MODULECOLORS)) - push!(repeated_cycle, (0,0,0)) # repeated_cycle is never empty frame_counter = 1 for i in 1:length(displayed_stackframes) (frame, n) = displayed_stackframes[i] - print_stackframe(io, frame_counter, frame, n, ndigits_max, modulecolordict, modulecolorcycler) + print_stackframe(io, frame_counter, frame, n, ndigits_max, STACKTRACE_FIXEDCOLORS, STACKTRACE_MODULECOLORS) if i < length(displayed_stackframes) println(io) @@ -684,22 +687,24 @@ end # from `modulecolorcycler`. function print_stackframe(io, i, frame::StackFrame, n::Int, digit_align_width, modulecolordict, modulecolorcycler) m = Base.parentmodule(frame) - if m !== nothing - while parentmodule(m) !== m - pm = parentmodule(m) - pm == Main && break - m = pm - end - if !haskey(modulecolordict, m) - modulecolordict[m] = popfirst!(modulecolorcycler) - end - modulecolor = modulecolordict[m] + modulecolor = if m !== nothing + m = parentmodule_before_main(m) + get!(() -> popfirst!(modulecolorcycler), modulecolordict, m) else - modulecolor = :default + :default end print_stackframe(io, i, frame, n, digit_align_width, modulecolor) end +# Gets the topmost parent module that isn't Main +function parentmodule_before_main(m) + while parentmodule(m) !== m + pm = parentmodule(m) + pm == Main && break + m = pm + end + m +end # Print a stack frame where the module color is set manually with `modulecolor`. 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 end println(io) - # @ - printstyled(io, " " ^ (digit_align_width + 2) * "@ ", color = :light_black) + # @ Module path / file : line + print_module_path_file(io, modul, file, line, modulecolor, digit_align_width) + + # inlined + printstyled(io, inlined ? " [inlined]" : "", color = :light_black) +end + +function print_module_path_file(io, modul, file, line, modulecolor = :light_black, digit_align_width = 0) + printstyled(io, " " ^ (digit_align_width + 2) * "@", color = :light_black) # module - if modul !== nothing - printstyled(io, modul, color = modulecolor) + if modul !== nothing && modulecolor !== nothing print(io, " ") + printstyled(io, modul, color = modulecolor) end # filepath - pathparts = splitpath(file) - folderparts = pathparts[1:end-1] - if !isempty(folderparts) - printstyled(io, joinpath(folderparts...) * (Sys.iswindows() ? "\\" : "/"), color = :light_black) - end + stacktrace_expand_basepaths() && (file = something(find_source_file(file), file)) + stacktrace_contract_userdir() && (file = contractuser(file)) + print(io, " ") + dir = dirname(file) + !isempty(dir) && printstyled(io, dir, Filesystem.path_separator, color = :light_black) # filename, separator, line - # use escape codes for formatting, printstyled can't do underlined and color - # codes are bright black (90) and underlined (4) - printstyled(io, pathparts[end], ":", line; color = :light_black, underline = true) - - # inlined - printstyled(io, inlined ? " [inlined]" : "", color = :light_black) + printstyled(io, basename(file), ":", line; color = :light_black, underline = true) end - function show_backtrace(io::IO, t::Vector) if haskey(io, :last_shown_line_infos) empty!(io[:last_shown_line_infos]) diff --git a/base/methodshow.jl b/base/methodshow.jl index 1fe12d718457d0..d65dd9b00d5959 100644 --- a/base/methodshow.jl +++ b/base/methodshow.jl @@ -59,8 +59,7 @@ function arg_decl_parts(m::Method, html=false) push!(tv, sig.var) sig = sig.body end - file = m.file - line = m.line + file, line = updated_methodloc(m) argnames = method_argnames(m) if length(argnames) >= m.nargs show_env = ImmutableDict{Symbol, Any}() @@ -206,34 +205,45 @@ function sym_to_string(sym) end end -function show(io::IO, m::Method) +function show(io::IO, m::Method; modulecolor = :light_black, digit_align_width = -1) tv, decls, file, line = arg_decl_parts(m) sig = unwrap_unionall(m.sig) if sig === Tuple # Builtin - print(io, m.name, "(...) in ", m.module) - return - end - print(io, decls[1][2], "(") - join( - io, - String[isempty(d[2]) ? d[1] : string(d[1], "::", d[2]) for d in decls[2:end]], - ", ", - ", ", - ) - kwargs = kwarg_decl(m) - if !isempty(kwargs) - print(io, "; ") - join(io, map(sym_to_string, kwargs), ", ", ", ") - end - print(io, ")") - show_method_params(io, tv) - print(io, " in ", m.module) - if line > 0 - file, line = updated_methodloc(m) - print(io, " at ", file, ":", line) + print(io, m.name, "(...)") + file = "none" + line = 0 + else + print(io, decls[1][2], "(") + + # arguments + for (i,d) in enumerate(decls[2:end]) + printstyled(io, d[1], color=:light_black) + if !isempty(d[2]) + print(io, "::") + print_type_bicolor(io, d[2], color=:bold, inner_color=:normal) + end + i < length(decls)-1 && print(io, ", ") + end + + kwargs = kwarg_decl(m) + if !isempty(kwargs) + print(io, "; ") + for kw in kwargs + skw = sym_to_string(kw) + print(io, skw) + if kw != last(kwargs) + print(io, ", ") + end + end + end + print(io, ")") + show_method_params(io, tv) end - nothing + + # module & file, re-using function from errorshow.jl + println(io) + print_module_path_file(io, m.module, string(file), line, modulecolor, digit_align_width+4) end function show_method_list_header(io::IO, ms::MethodList, namefmt::Function) @@ -253,7 +263,11 @@ function show_method_list_header(io::IO, ms::MethodList, namefmt::Function) "builtin function" : # else "generic function") - print(io, " for ", what, " ", namedisplay) + print(io, " for ", what, " ", namedisplay, " from ") + + col = get!(() -> popfirst!(STACKTRACE_MODULECOLORS), STACKTRACE_FIXEDCOLORS, parentmodule_before_main(ms.mt.module)) + + printstyled(io, ms.mt.module, color=col) elseif '#' in sname print(io, " for anonymous function ", namedisplay) elseif mt === _TYPE_NAME.mt @@ -261,8 +275,7 @@ function show_method_list_header(io::IO, ms::MethodList, namefmt::Function) else print(io, " for callable object") end - n > 0 && print(io, ":") - nothing + !iszero(n) && print(io, ":") end 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 last_shown_line_infos = get(io, :last_shown_line_infos, nothing) last_shown_line_infos === nothing || empty!(last_shown_line_infos) + modul = if mt === _TYPE_NAME.mt # type constructor + which(ms.ms[1].module, ms.ms[1].name) + else + mt.module + end + + digit_align_width = length(string(max > 0 ? max : length(ms))) + for meth in ms if max == -1 || n < max n += 1 println(io) - print(io, "[$n] ") - show(io, meth) + + print(io, " ", lpad("[$n]", digit_align_width + 2), " ") + + modulecolor = if meth.module == modul + nothing + else + m = parentmodule_before_main(meth.module) + get!(() -> popfirst!(STACKTRACE_MODULECOLORS), STACKTRACE_FIXEDCOLORS, m) + end + show(io, meth; modulecolor) + file, line = updated_methodloc(meth) if last_shown_line_infos !== nothing push!(last_shown_line_infos, (string(file), line)) @@ -374,7 +404,7 @@ function show(io::IO, ::MIME"text/html", m::Method) join( io, String[ - isempty(d[2]) ? d[1] : string(d[1], "::", d[2], "") for d in decls[2:end] + isempty(d[2]) ? string(d[1]) : string(d[1], "::", d[2] , "") for d in decls[2:end] ], ", ", ", ", diff --git a/base/show.jl b/base/show.jl index 7e9fc76698d700..cfcf634582e717 100644 --- a/base/show.jl +++ b/base/show.jl @@ -2448,7 +2448,7 @@ function show_tuple_as_call(io::IO, name::Symbol, sig::Type; print_within_stacktrace(io, argnames[i]; color=:light_black) end print(io, "::") - print_type_stacktrace(env_io, sig[i]) + print_type_bicolor(env_io, sig[i]; use_color = get(io, :backtrace, false)) end if kwargs !== nothing print(io, "; ") @@ -2458,7 +2458,7 @@ function show_tuple_as_call(io::IO, name::Symbol, sig::Type; first = false print_within_stacktrace(io, k; color=:light_black) print(io, "::") - print_type_stacktrace(io, t) + print_type_bicolor(io, t; use_color = get(io, :backtrace, false)) end end print_within_stacktrace(io, ")", bold=true) @@ -2466,16 +2466,25 @@ function show_tuple_as_call(io::IO, name::Symbol, sig::Type; nothing end -function print_type_stacktrace(io, type; color=:normal) +function print_type_bicolor(io, type; kwargs...) str = sprint(show, type, context=io) + print_type_bicolor(io, str; kwargs...) +end + +function print_type_bicolor(io, str::String; color=:normal, inner_color=:light_black, use_color::Bool=true) i = findfirst('{', str) - if !get(io, :backtrace, false)::Bool + if !use_color # fix #41928 print(io, str) elseif i === nothing printstyled(io, str; color=color) else printstyled(io, str[1:prevind(str,i)]; color=color) - printstyled(io, str[i:end]; color=:light_black) + if endswith(str, "...") + printstyled(io, str[i:prevind(str,end,3)]; color=inner_color) + printstyled(io, "..."; color=color) + else + printstyled(io, str[i:end]; color=inner_color) + end end end diff --git a/doc/src/devdocs/inference.md b/doc/src/devdocs/inference.md index 68d63600f1bb1e..cce272f336a860 100644 --- a/doc/src/devdocs/inference.md +++ b/doc/src/devdocs/inference.md @@ -96,7 +96,8 @@ Each statement gets analyzed for its total cost in a function called as follows: ```jldoctest; filter=r"tuple.jl:\d+" julia> Base.print_statement_costs(stdout, map, (typeof(sqrt), Tuple{Int},)) # map(sqrt, (2,)) -map(f, t::Tuple{Any}) in Base at tuple.jl:179 +map(f, t::Tuple{Any}) + @ Base tuple.jl:273 0 1 ─ %1 = Base.getfield(_3, 1, true)::Int64 1 │ %2 = Base.sitofp(Float64, %1)::Float64 2 │ %3 = Base.lt_float(%2, 0.0)::Bool diff --git a/doc/src/manual/constructors.md b/doc/src/manual/constructors.md index 39d6d6bcaf0f51..dad96e374742e5 100644 --- a/doc/src/manual/constructors.md +++ b/doc/src/manual/constructors.md @@ -372,8 +372,13 @@ However, other similar calls still don't work: ```jldoctest parametric2 julia> Point(1.5,2) ERROR: MethodError: no method matching Point(::Float64, ::Int64) + Closest candidates are: - Point(::T, !Matched::T) where T<:Real at none:1 + Point(::T, !Matched::T) where T<:Real + @ Main none:1 + +Stacktrace: +[...] ``` For a more general way to make all such calls work sensibly, see [Conversion and Promotion](@ref conversion-and-promotion). @@ -550,8 +555,11 @@ julia> struct SummedArray{T<:Number,S<:Number} julia> SummedArray(Int32[1; 2; 3], Int32(6)) ERROR: MethodError: no method matching SummedArray(::Vector{Int32}, ::Int32) + Closest candidates are: - SummedArray(::Vector{T}) where T at none:4 + SummedArray(::Vector{T}) where T + @ Main none:4 + Stacktrace: [...] ``` diff --git a/doc/src/manual/faq.md b/doc/src/manual/faq.md index 1dc56e79dac521..b476e2b25b64ec 100644 --- a/doc/src/manual/faq.md +++ b/doc/src/manual/faq.md @@ -802,8 +802,13 @@ foo (generic function with 1 method) julia> foo([1]) ERROR: MethodError: no method matching foo(::Vector{Int64}) + Closest candidates are: - foo(!Matched::Vector{Real}) at none:1 + foo(!Matched::Vector{Real}) + @ Main none:1 + +Stacktrace: +[...] ``` This is because `Vector{Real}` is not a supertype of `Vector{Int}`! You can solve this problem with something diff --git a/doc/src/manual/functions.md b/doc/src/manual/functions.md index 2724fa32ec3821..c3012efa1d8b1d 100644 --- a/doc/src/manual/functions.md +++ b/doc/src/manual/functions.md @@ -695,8 +695,13 @@ julia> args = [1,2,3] julia> baz(args...) ERROR: MethodError: no method matching baz(::Int64, ::Int64, ::Int64) + Closest candidates are: - baz(::Any, ::Any) at none:1 + baz(::Any, ::Any) + @ Main none:1 + +Stacktrace: +[...] ``` As you can see, if the wrong number of elements are in the splatted container, then the function diff --git a/doc/src/manual/methods.md b/doc/src/manual/methods.md index fe7623a9592c66..6cbcc4fad6a65d 100644 --- a/doc/src/manual/methods.md +++ b/doc/src/manual/methods.md @@ -76,18 +76,33 @@ Applying it to any other types of arguments will result in a [`MethodError`](@re ```jldoctest fofxy julia> f(2.0, 3) ERROR: MethodError: no method matching f(::Float64, ::Int64) + Closest candidates are: - f(::Float64, !Matched::Float64) at none:1 + f(::Float64, !Matched::Float64) + @ Main none:1 + +Stacktrace: +[...] julia> f(Float32(2.0), 3.0) ERROR: MethodError: no method matching f(::Float32, ::Float64) + Closest candidates are: - f(!Matched::Float64, ::Float64) at none:1 + f(!Matched::Float64, ::Float64) + @ Main none:1 + +Stacktrace: +[...] julia> f(2.0, "3.0") ERROR: MethodError: no method matching f(::Float64, ::String) + Closest candidates are: - f(::Float64, !Matched::Float64) at none:1 + f(::Float64, !Matched::Float64) + @ Main none:1 + +Stacktrace: +[...] julia> f("2.0", "3.0") ERROR: MethodError: no method matching f(::String, ::String) @@ -149,14 +164,25 @@ and applying it will still result in a [`MethodError`](@ref): ```jldoctest fofxy julia> f("foo", 3) ERROR: MethodError: no method matching f(::String, ::Int64) + Closest candidates are: - f(!Matched::Number, ::Number) at none:1 + f(!Matched::Number, ::Number) + @ Main none:1 + +Stacktrace: +[...] julia> f() ERROR: MethodError: no method matching f() + Closest candidates are: - f(!Matched::Float64, !Matched::Float64) at none:1 - f(!Matched::Number, !Matched::Number) at none:1 + f(!Matched::Float64, !Matched::Float64) + @ Main none:1 + f(!Matched::Number, !Matched::Number) + @ Main none:1 + +Stacktrace: +[...] ``` You can easily see which methods exist for a function by entering the function object itself in @@ -172,9 +198,11 @@ of those methods are, use the [`methods`](@ref) function: ```jldoctest fofxy julia> methods(f) -# 2 methods for generic function "f": -[1] f(x::Float64, y::Float64) in Main at none:1 -[2] f(x::Number, y::Number) in Main at none:1 +# 2 methods for generic function "f" from Main: + [1] f(x::Float64, y::Float64) + @ none:1 + [2] f(x::Number, y::Number) + @ none:1 ``` which shows that `f` has two methods, one taking two `Float64` arguments and one taking arguments @@ -190,10 +218,13 @@ julia> f(x,y) = println("Whoa there, Nelly.") f (generic function with 3 methods) julia> methods(f) -# 3 methods for generic function "f": -[1] f(x::Float64, y::Float64) in Main at none:1 -[2] f(x::Number, y::Number) in Main at none:1 -[3] f(x, y) in Main at none:1 +# 3 methods for generic function "f" from Main: + [1] f(x::Float64, y::Float64) + @ none:1 + [2] f(x::Number, y::Number) + @ none:1 + [3] f(x, y) + @ none:1 julia> f("foo", 1) Whoa there, Nelly. @@ -256,11 +287,19 @@ julia> g(2, 3.0) 8.0 julia> g(2.0, 3.0) -ERROR: MethodError: g(::Float64, ::Float64) is ambiguous. Candidates: - g(x::Float64, y) in Main at none:1 - g(x, y::Float64) in Main at none:1 +ERROR: MethodError: g(::Float64, ::Float64) is ambiguous. + +Candidates: + g(x::Float64, y) + @ Main none:1 + g(x, y::Float64) + @ Main none:1 + Possible fix, define g(::Float64, ::Float64) + +Stacktrace: +[...] ``` Here the call `g(2.0, 3.0)` could be handled by either the `g(Float64, Any)` or the `g(Any, Float64)` @@ -347,8 +386,11 @@ julia> myappend([1,2,3],4) julia> myappend([1,2,3],2.5) ERROR: MethodError: no method matching myappend(::Vector{Int64}, ::Float64) + Closest candidates are: - myappend(::Vector{T}, !Matched::T) where T at none:1 + myappend(::Vector{T}, !Matched::T) where T + @ Main none:1 + Stacktrace: [...] @@ -361,8 +403,11 @@ julia> myappend([1.0,2.0,3.0],4.0) julia> myappend([1.0,2.0,3.0],4) ERROR: MethodError: no method matching myappend(::Vector{Float64}, ::Int64) + Closest candidates are: - myappend(::Vector{T}, !Matched::T) where T at none:1 + myappend(::Vector{T}, !Matched::T) where T + @ Main none:1 + Stacktrace: [...] ``` @@ -403,9 +448,15 @@ true julia> same_type_numeric("foo", 2.0) ERROR: MethodError: no method matching same_type_numeric(::String, ::Float64) + Closest candidates are: - same_type_numeric(!Matched::T, ::T) where T<:Number at none:1 - same_type_numeric(!Matched::Number, ::Number) at none:1 + same_type_numeric(!Matched::T, ::T) where T<:Number + @ Main none:1 + same_type_numeric(!Matched::Number, ::Number) + @ Main none:1 + +Stacktrace: +[...] julia> same_type_numeric("foo", "bar") ERROR: MethodError: no method matching same_type_numeric(::String, ::String) @@ -791,16 +842,26 @@ bar (generic function with 1 method) julia> bar(1,2,3) ERROR: MethodError: no method matching bar(::Int64, ::Int64, ::Int64) + Closest candidates are: - bar(::Any, ::Any, ::Any, !Matched::Any) at none:1 + bar(::Any, ::Any, ::Any, !Matched::Any) + @ Main none:1 + +Stacktrace: +[...] julia> bar(1,2,3,4) (1, 2, (3, 4)) julia> bar(1,2,3,4,5) ERROR: MethodError: no method matching bar(::Int64, ::Int64, ::Int64, ::Int64, ::Int64) + Closest candidates are: - bar(::Any, ::Any, ::Any, ::Any) at none:1 + bar(::Any, ::Any, ::Any, ::Any) + @ Main none:1 + +Stacktrace: +[...] ``` More usefully, it is possible to constrain varargs methods by a parameter. For example: diff --git a/doc/src/manual/types.md b/doc/src/manual/types.md index 862645f5a97277..4d0015a05c38a5 100644 --- a/doc/src/manual/types.md +++ b/doc/src/manual/types.md @@ -729,8 +729,13 @@ to `Point` have the same type. When this isn't the case, the constructor will fa ```jldoctest pointtype julia> Point(1,2.5) ERROR: MethodError: no method matching Point(::Int64, ::Float64) + Closest candidates are: - Point(::T, !Matched::T) where T at none:2 + Point(::T, !Matched::T) where T + @ Main none:2 + +Stacktrace: +[...] ``` Constructor methods to appropriately handle such mixed cases can be defined, but that will not diff --git a/stdlib/InteractiveUtils/test/highlighting.jl b/stdlib/InteractiveUtils/test/highlighting.jl index e268794a70e3fd..0026c0b8557303 100644 --- a/stdlib/InteractiveUtils/test/highlighting.jl +++ b/stdlib/InteractiveUtils/test/highlighting.jl @@ -9,7 +9,8 @@ myzeros(::Type{T}, ::Type{S}, ::Type{R}, dims::Tuple{Vararg{Integer, N}}, dims2: Tuple{Type{<:Integer}, Type{>:String}, Type{T} where Signed<:T<:Real, Tuple{Vararg{Int}}, NTuple{4,Int}}) seekstart(io) @test startswith(readline(io), "MethodInstance for ") - @test startswith(readline(io), " from myzeros(::Type{T}, ::") + @test occursin(r"^ from myzeros\(::.*Type.*{T}, ::", readline(io)) + readline(io) # skip location information from method printing - already tested in base @test occursin(r"^Static Parameters$", readline(io)) @test occursin(r"^ T <: .*Integer", readline(io)) @test occursin(r"^ .*Signed.* <: R <: .*Real", readline(io)) diff --git a/stdlib/InteractiveUtils/test/runtests.jl b/stdlib/InteractiveUtils/test/runtests.jl index d31e07bd1b55d4..50236e7c8cfc5a 100644 --- a/stdlib/InteractiveUtils/test/runtests.jl +++ b/stdlib/InteractiveUtils/test/runtests.jl @@ -579,7 +579,7 @@ file, ln = functionloc(versioninfo, Tuple{}) @test e isa MethodError m = @which versioninfo() s = sprint(showerror, e) - m = match(Regex("at (.*?):$(m.line)"), s) + m = match(Regex("@ .+ (.*?):$(m.line)"), s) @test isfile(expanduser(m.captures[1])) g() = x diff --git a/stdlib/Test/src/Test.jl b/stdlib/Test/src/Test.jl index 11ec4f29961f66..9a080812dbc45e 100644 --- a/stdlib/Test/src/Test.jl +++ b/stdlib/Test/src/Test.jl @@ -1611,7 +1611,8 @@ Int64 julia> @code_warntype f(2) MethodInstance for f(::Int64) - from f(a) in Main at none:1 + from f(a) + @ Main none:1 Arguments #self#::Core.Const(f) a::Int64 diff --git a/test/ambiguous.jl b/test/ambiguous.jl index 8d8c3efab53b9b..a06b92c5481b3a 100644 --- a/test/ambiguous.jl +++ b/test/ambiguous.jl @@ -39,15 +39,11 @@ let err = try end io = IOBuffer() Base.showerror(io, err) - lines = split(String(take!(io)), '\n') - ambig_checkline(str) = startswith(str, " ambig(x, y::Integer) in $curmod_str at") || - startswith(str, " ambig(x::Integer, y) in $curmod_str at") || - startswith(str, " ambig(x::Number, y) in $curmod_str at") - @test ambig_checkline(lines[2]) - @test ambig_checkline(lines[3]) - @test ambig_checkline(lines[4]) - @test lines[5] == "Possible fix, define" - @test lines[6] == " ambig(::Integer, ::Integer)" + errstr = String(take!(io)) + @test occursin(" ambig(x, y::Integer)\n @ $curmod_str", errstr) + @test occursin(" ambig(x::Integer, y)\n @ $curmod_str", errstr) + @test occursin(" ambig(x::Number, y)\n @ $curmod_str", errstr) + @test occursin("Possible fix, define\n ambig(::Integer, ::Integer)", errstr) end ambig_with_bounds(x, ::Int, ::T) where {T<:Integer,S} = 0 @@ -60,7 +56,7 @@ let err = try io = IOBuffer() Base.showerror(io, err) lines = split(String(take!(io)), '\n') - @test lines[end] == " ambig_with_bounds(::$Int, ::$Int, ::T) where T<:Integer" + @test lines[end-1] == " ambig_with_bounds(::$Int, ::$Int, ::T) where T<:Integer" end ## Other ways of accessing functions diff --git a/test/error.jl b/test/error.jl index 1dae62fb91e585..eaf77c5d53912b 100644 --- a/test/error.jl +++ b/test/error.jl @@ -1,5 +1,8 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license +# for curmod_str +include("testenv.jl") + @testset "ExponentialBackOff" begin @test length(ExponentialBackOff(n=10)) == 10 @test collect(ExponentialBackOff(n=10, first_delay=0.01))[1] == 0.01 @@ -93,6 +96,6 @@ end f44319(1) catch e s = sprint(showerror, e) - @test s == "MethodError: no method matching f44319(::Int$(Sys.WORD_SIZE))\nClosest candidates are:\n f44319() at none:0" + @test s == "MethodError: no method matching f44319(::Int$(Sys.WORD_SIZE))\n\nClosest candidates are:\n f44319()\n @ $curmod_str none:0\n" end end diff --git a/test/errorshow.jl b/test/errorshow.jl index 16518180a272b3..b578c5025e98e9 100644 --- a/test/errorshow.jl +++ b/test/errorshow.jl @@ -49,28 +49,34 @@ include("testenv.jl") end file = @__FILE__ +sep = Base.Filesystem.path_separator +modul = @__MODULE__ Base.stacktrace_contract_userdir() && (file = Base.contractuser(file)) -cfile = " at $file:" +fname = basename(file) +dname = dirname(file) +cmod = "\n @ $modul" +cfile = " $file:" c1line = @__LINE__() + 1 method_c1(x::Float64, s::AbstractString...) = true buf = IOBuffer() Base.show_method_candidates(buf, Base.MethodError(method_c1,(1, 1, ""))) -@test String(take!(buf)) == "\nClosest candidates are:\n method_c1(!Matched::Float64, !Matched::AbstractString...)$cfile$c1line" +@test occursin("\n\nClosest candidates are:\n method_c1(!Matched::Float64, !Matched::AbstractString...)$cmod$cfile$c1line\n", String(take!(buf))) @test length(methods(method_c1)) <= 3 # because of '...' in candidate printing Base.show_method_candidates(IOContext(buf, :color => true), Base.MethodError(method_c1,(1, 1, ""))) -@test String(take!(buf)) == "\n\e[0mClosest candidates are:\n\e[0m method_c1(\e[91m::Float64\e[39m, \e[91m::AbstractString...\e[39m)$cfile$c1line" +mod_col = Base.text_colors[Base.STACKTRACE_FIXEDCOLORS[modul]] +@test occursin("\n\n\e[0mClosest candidates are:\n\e[0m method_c1(\e[91m::Float64\e[39m, \e[91m::AbstractString...\e[39m)\n\e[0m\e[90m @\e[39m $mod_col$modul\e[39m \e[90m$dname$sep\e[39m\e[90m\e[4m$fname:$c1line\e[24m\e[39m\n", String(take!(buf))) Base.show_method_candidates(buf, Base.MethodError(method_c1,(1, "", ""))) -@test String(take!(buf)) == "\nClosest candidates are:\n method_c1(!Matched::Float64, ::AbstractString...)$cfile$c1line" +@test occursin("\n\nClosest candidates are:\n method_c1(!Matched::Float64, ::AbstractString...)$cmod$cfile$c1line\n", String(take!(buf))) # should match Base.show_method_candidates(buf, Base.MethodError(method_c1,(1., "", ""))) -@test String(take!(buf)) == "\nClosest candidates are:\n method_c1(::Float64, ::AbstractString...)$cfile$c1line" +@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 Base.show_method_candidates(buf, Base.MethodError(method_c1,(1, 1, 1))) -@test String(take!(buf)) == "" +@test isempty(String(take!(buf))) # matches the implicit constructor -> convert method Base.show_method_candidates(buf, Base.MethodError(Tuple{}, (1, 1, 1))) @@ -87,27 +93,27 @@ method_c2(x::Int32, y::Int32, z::Int32) = true method_c2(x::T, y::T, z::T) where {T<:Real} = true Base.show_method_candidates(buf, Base.MethodError(method_c2,(1., 1., 2))) -@test String(take!(buf)) == "\nClosest candidates are:\n method_c2(!Matched::Int32, ::Float64, ::Any...)$cfile$(c2line+2)\n method_c2(::T, ::T, !Matched::T) where T<:Real$cfile$(c2line+5)\n method_c2(!Matched::Int32, ::Any...)$cfile$(c2line+1)\n ..." +@test occursin( "\n\nClosest candidates are:\n method_c2(!Matched::Int32, ::Float64, ::Any...)$cmod$cfile$(c2line+2)\n method_c2(::T, ::T, !Matched::T) where T<:Real$cmod$cfile$(c2line+5)\n method_c2(!Matched::Int32, ::Any...)$cmod$cfile$(c2line+1)\n ...\n", String(take!(buf))) c3line = @__LINE__() + 1 method_c3(x::Float64, y::Float64) = true Base.show_method_candidates(buf, Base.MethodError(method_c3,(1.,))) -@test String(take!(buf)) == "\nClosest candidates are:\n method_c3(::Float64, !Matched::Float64)$cfile$c3line" +@test occursin( "\n\nClosest candidates are:\n method_c3(::Float64, !Matched::Float64)$cmod$cfile$c3line\n", String(take!(buf))) # Test for the method error in issue #8651 c4line = @__LINE__ method_c4() = true method_c4(x::AbstractString) = false Base.show_method_candidates(buf, MethodError(method_c4,("",))) -@test String(take!(buf)) == "\nClosest candidates are:\n method_c4(::AbstractString)$cfile$(c4line+2)\n method_c4()$cfile$(c4line+1)" +@test occursin("\n\nClosest candidates are:\n method_c4(::AbstractString)$cmod$cfile$(c4line+2)\n method_c4()$cmod$cfile$(c4line+1)\n", String(take!(buf))) c5line = @__LINE__() + 1 method_c5(::Type{Float64}) = true Base.show_method_candidates(buf, MethodError(method_c5,(Float64,))) -@test String(take!(buf)) == "\nClosest candidates are:\n method_c5(::Type{Float64})$cfile$c5line" +@test occursin("\nClosest candidates are:\n method_c5(::Type{Float64})$cmod$cfile$c5line", String(take!(buf))) Base.show_method_candidates(buf, MethodError(method_c5,(Int32,))) -@test String(take!(buf)) == "\nClosest candidates are:\n method_c5(!Matched::Type{Float64})$cfile$c5line" +@test occursin("\nClosest candidates are:\n method_c5(!Matched::Type{Float64})$cmod$cfile$c5line", String(take!(buf))) mutable struct Test_type end test_type = Test_type() @@ -125,13 +131,13 @@ PR16155line2 = @__LINE__() + 1 (::Type{T})(arg::Any) where {T<:PR16155} = "replace call-to-convert method from sysimg" Base.show_method_candidates(buf, MethodError(PR16155,(1.0, 2.0, Int64(3)))) -@test String(take!(buf)) == "\nClosest candidates are:\n $(curmod_prefix)PR16155(::Any, ::Any)$cfile$PR16155line\n $(curmod_prefix)PR16155(!Matched::Int64, ::Any)$cfile$PR16155line\n (::Type{T})(::Any) where T<:$(curmod_prefix)PR16155$cfile$PR16155line2" +@test occursin("\nClosest candidates are:\n $(curmod_prefix)PR16155(::Any, ::Any)$cmod$cfile$PR16155line\n $(curmod_prefix)PR16155(!Matched::Int64, ::Any)$cmod$cfile$PR16155line\n (::Type{T})(::Any) where T<:$(curmod_prefix)PR16155$cmod$cfile$PR16155line2", String(take!(buf))) Base.show_method_candidates(buf, MethodError(PR16155,(Int64(3), 2.0, Int64(3)))) -@test String(take!(buf)) == "\nClosest candidates are:\n $(curmod_prefix)PR16155(::Int64, ::Any)$cfile$PR16155line\n $(curmod_prefix)PR16155(::Any, ::Any)$cfile$PR16155line\n (::Type{T})(::Any) where T<:$(curmod_prefix)PR16155$cfile$PR16155line2" +@test occursin("\nClosest candidates are:\n $(curmod_prefix)PR16155(::Int64, ::Any)$cmod$cfile$PR16155line\n $(curmod_prefix)PR16155(::Any, ::Any)$cmod$cfile$PR16155line\n (::Type{T})(::Any) where T<:$(curmod_prefix)PR16155$cmod$cfile$PR16155line2", String(take!(buf))) Base.show_method_candidates(buf, MethodError(Complex{T} where T<:Integer, (1.2,))) -@test startswith(String(take!(buf)), "\nClosest candidates are:\n (::Type{T})(::T) where T<:Number") +@test startswith(String(take!(buf)), "\n\nClosest candidates are:\n (::Type{T})(::T) where T<:Number") c6line = @__LINE__ method_c6(; x=1) = x @@ -155,21 +161,21 @@ m_error = try TestKWError.method_c6_in_module(1, x=1) catch e; e; end showerror(buf, m_error) error_out3 = String(take!(buf)) -@test occursin("method_c6(; x)$cfile$(c6line + 1) got unsupported keyword argument \"y\"", error_out) -@test occursin("method_c6(!Matched::Any; y)$cfile$(c6line + 2)", error_out) -@test occursin("method_c6(::Any; y)$cfile$(c6line + 2) got unsupported keyword argument \"x\"", error_out1) -@test occursin("method_c6_in_module(; x)$cfile$(c6mline + 2) got unsupported keyword argument \"y\"", error_out2) -@test occursin("method_c6_in_module(!Matched::Any; y)$cfile$(c6mline + 3)", error_out2) -@test occursin("method_c6_in_module(::Any; y)$cfile$(c6mline + 3) got unsupported keyword argument \"x\"", error_out3) +@test occursin("method_c6(; x) got unsupported keyword argument \"y\"$cmod$cfile$(c6line + 1)", error_out) +@test occursin("method_c6(!Matched::Any; y)$cmod$cfile$(c6line + 2)", error_out) +@test occursin("method_c6(::Any; y) got unsupported keyword argument \"x\"$cmod$cfile$(c6line + 2)", error_out1) +@test occursin("method_c6_in_module(; x) got unsupported keyword argument \"y\"$cmod$cfile$(c6mline + 2)", error_out2) +@test occursin("method_c6_in_module(!Matched::Any; y)$cmod$cfile$(c6mline + 3)", error_out2) +@test occursin("method_c6_in_module(::Any; y) got unsupported keyword argument \"x\"$cmod$cfile$(c6mline + 3)", error_out3) c7line = @__LINE__() + 1 method_c7(a, b; kargs...) = a Base.show_method_candidates(buf, MethodError(method_c7, (1, 1)), pairs((x = 1, y = 2))) -@test String(take!(buf)) == "\nClosest candidates are:\n method_c7(::Any, ::Any; kargs...)$cfile$c7line" +@test occursin("\nClosest candidates are:\n method_c7(::Any, ::Any; kargs...)$cmod$cfile$c7line", String(take!(buf))) c8line = @__LINE__() + 1 method_c8(a, b; y=1, w=1) = a Base.show_method_candidates(buf, MethodError(method_c8, (1, 1)), pairs((x = 1, y = 2, z = 1, w = 1))) -@test String(take!(buf)) == "\nClosest candidates are:\n method_c8(::Any, ::Any; y, w)$cfile$c8line got unsupported keyword arguments \"x\", \"z\"" +@test occursin("\nClosest candidates are:\n method_c8(::Any, ::Any; y, w) got unsupported keyword arguments \"x\", \"z\"$cmod$cfile$c8line", String(take!(buf))) let no_kwsorter_match, e no_kwsorter_match() = 0 @@ -183,7 +189,7 @@ addConstraint_15639(c::Int32) = c addConstraint_15639(c::Int64; uncset=nothing) = addConstraint_15639(Int32(c), uncset=uncset) Base.show_method_candidates(buf, MethodError(addConstraint_15639, (Int32(1),)), pairs((uncset = nothing,))) -@test String(take!(buf)) == "\nClosest candidates are:\n addConstraint_15639(::Int32)$cfile$(ac15639line + 1) got unsupported keyword argument \"uncset\"\n addConstraint_15639(!Matched::Int64; uncset)$cfile$(ac15639line + 2)" +@test occursin("\nClosest candidates are:\n addConstraint_15639(::Int32) got unsupported keyword argument \"uncset\"$cmod$cfile$(ac15639line + 1)\n addConstraint_15639(!Matched::Int64; uncset)$cmod$cfile$(ac15639line + 2)", String(take!(buf))) # Busted Vararg method definitions bad_vararg_decl(x::Int, y::Vararg) = 1 # don't do this, instead use (x::Int, y...) @@ -422,27 +428,28 @@ let err_str, j = reinterpret(EightBitTypeT{Int32}, 0x54), sp = Base.source_path() sn = basename(sp) + Base.stacktrace_contract_userdir() && (sp = Base.contractuser(sp)) @test sprint(show, which(String, Tuple{})) == - "String() in $curmod_str at $sp:$(method_defs_lineno + 0)" + "String()\n @ $curmod_str $sp:$(method_defs_lineno + 0)" @test sprint(show, which("a", Tuple{})) == - "(::String)() in $curmod_str at $sp:$(method_defs_lineno + 1)" + "(::String)()\n @ $curmod_str $sp:$(method_defs_lineno + 1)" @test sprint(show, which(EightBitType, Tuple{})) == - "$(curmod_prefix)EightBitType() in $curmod_str at $sp:$(method_defs_lineno + 2)" + "$(curmod_prefix)EightBitType()\n @ $curmod_str $sp:$(method_defs_lineno + 2)" @test sprint(show, which(reinterpret(EightBitType, 0x54), Tuple{})) == - "(::$(curmod_prefix)EightBitType)() in $curmod_str at $sp:$(method_defs_lineno + 3)" + "(::$(curmod_prefix)EightBitType)()\n @ $curmod_str $sp:$(method_defs_lineno + 3)" @test sprint(show, which(EightBitTypeT, Tuple{})) == - "$(curmod_prefix)EightBitTypeT() in $curmod_str at $sp:$(method_defs_lineno + 4)" + "$(curmod_prefix)EightBitTypeT()\n @ $curmod_str $sp:$(method_defs_lineno + 4)" @test sprint(show, which(EightBitTypeT{Int32}, Tuple{})) == - "$(curmod_prefix)EightBitTypeT{T}() where T in $curmod_str at $sp:$(method_defs_lineno + 5)" + "$(curmod_prefix)EightBitTypeT{T}() where T\n @ $curmod_str $sp:$(method_defs_lineno + 5)" @test sprint(show, which(reinterpret(EightBitTypeT{Int32}, 0x54), Tuple{})) == - "(::$(curmod_prefix)EightBitTypeT)() in $curmod_str at $sp:$(method_defs_lineno + 6)" + "(::$(curmod_prefix)EightBitTypeT)()\n @ $curmod_str $sp:$(method_defs_lineno + 6)" @test startswith(sprint(show, which(Complex{Int}, Tuple{Int})), "Complex{T}(") @test startswith(sprint(show, which(getfield(Base, Symbol("@doc")), Tuple{LineNumberNode, Module, Vararg{Any}})), - "var\"@doc\"(__source__::LineNumberNode, __module__::Module, x...) in Core at boot.jl:") + "var\"@doc\"(__source__::LineNumberNode, __module__::Module, x...)\n @ Core boot.jl:") @test startswith(sprint(show, which(FunctionLike(), Tuple{})), - "(::$(curmod_prefix)FunctionLike)() in $curmod_str at $sp:$(method_defs_lineno + 7)") + "(::$(curmod_prefix)FunctionLike)()\n @ $curmod_str $sp:$(method_defs_lineno + 7)") @test startswith(sprint(show, which(StructWithUnionAllMethodDefs{<:Integer}, (Any,))), "($(curmod_prefix)StructWithUnionAllMethodDefs{T} where T<:Integer)(x)") @test repr("text/plain", FunctionLike()) == "(::$(curmod_prefix)FunctionLike) (generic function with 1 method)" diff --git a/test/reflection.jl b/test/reflection.jl index 10973f46793805..5fd1be83ce01e0 100644 --- a/test/reflection.jl +++ b/test/reflection.jl @@ -224,7 +224,7 @@ let ex = :(a + b) end foo13825(::Array{T, N}, ::Array, ::Vector) where {T, N} = nothing @test startswith(string(first(methods(foo13825))), - "foo13825(::Array{T, N}, ::Array, ::Vector) where {T, N} in") + "foo13825(::Array{T, N}, ::Array, ::Vector) where {T, N}\n") mutable struct TLayout x::Int8 @@ -425,7 +425,7 @@ let li = typeof(fieldtype).name.mt.cache.func::Core.MethodInstance, mmime = repr("text/plain", li.def) @test lrepr == lmime == "MethodInstance for fieldtype(...)" - @test mrepr == mmime == "fieldtype(...) in Core" + @test mrepr == mmime == "fieldtype(...)\n @ Core none:0" end diff --git a/test/show.jl b/test/show.jl index 4477aa9f779106..5aff7b1b3d6b0b 100644 --- a/test/show.jl +++ b/test/show.jl @@ -1891,16 +1891,16 @@ function _methodsstr(@nospecialize f) end @testset "show function methods" begin - @test occursin("methods for generic function \"sin\":\n", _methodsstr(sin)) + @test occursin("methods for generic function \"sin\" from Base:\n", _methodsstr(sin)) end @testset "show macro methods" begin - @test startswith(_methodsstr(getfield(Base,Symbol("@show"))), "# 1 method for macro \"@show\":\n") + @test startswith(_methodsstr(getfield(Base,Symbol("@show"))), "# 1 method for macro \"@show\" from Base:\n") end @testset "show constructor methods" begin @test occursin(" methods for type constructor:\n", _methodsstr(Vector)) end @testset "show builtin methods" begin - @test startswith(_methodsstr(typeof), "# 1 method for builtin function \"typeof\":\n") + @test startswith(_methodsstr(typeof), "# 1 method for builtin function \"typeof\" from Core:\n") end @testset "show callable object methods" begin @test occursin("methods for callable object:\n", _methodsstr(:)) diff --git a/test/worlds.jl b/test/worlds.jl index 015ff470a56dd9..93445e07699c0b 100644 --- a/test/worlds.jl +++ b/test/worlds.jl @@ -154,7 +154,7 @@ f265(::Int) = 1 h265() = true file = @__FILE__ Base.stacktrace_contract_userdir() && (file = Base.contractuser(file)) -loc_h265 = "$file:$(@__LINE__() - 3)" +loc_h265 = "@ $(@__MODULE__) $file:$(@__LINE__() - 3)" @test h265() @test_throws TaskFailedException(t265) put_n_take!(h265, ()) @test_throws TaskFailedException(t265) fetch(t265) @@ -170,7 +170,7 @@ let ex = t265.exception MethodError: no method matching h265() The applicable method may be too new: running in world age $wc265, while current world is $wc.""" @test startswith(str, cmps) - cmps = "\n h265() at $loc_h265 (method too new to be called from this world context.)" + cmps = "\n h265() (method too new to be called from this world context.)\n $loc_h265" @test occursin(cmps, str) end