Skip to content

Commit

Permalink
Factor MethodList representation by optional arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
Liozou committed Jul 3, 2022
1 parent f4801ff commit 7d7545b
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 93 deletions.
4 changes: 3 additions & 1 deletion base/errorshow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -739,7 +739,7 @@ function print_stackframe(io, i, frame::StackFrame, n::Int, digit_align_width, m
printstyled(io, inlined ? " [inlined]" : "", color = :light_black)
end

function print_module_path_file(io, modul, file, line, modulecolor = :light_black, digit_align_width = 0)
function print_module_path_file(io, modul, file, line, modulecolor, digit_align_width, htmlurl="")
printstyled(io, " " ^ (digit_align_width + 2) * "@", color = :light_black)

# module
Expand All @@ -756,7 +756,9 @@ function print_module_path_file(io, modul, file, line, modulecolor = :light_blac
!isempty(dir) && printstyled(io, dir, Filesystem.path_separator, color = :light_black)

# filename, separator, line
isempty(htmlurl) || print(io, """<a href="$htmlurl" target="_blank">""")
printstyled(io, basename(file), ":", line; color = :light_black, underline = true)
isempty(htmlurl) || print(io, "</a>")
end

function show_backtrace(io::IO, t::Vector)
Expand Down
210 changes: 129 additions & 81 deletions base/methodshow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,7 @@ end

# NOTE: second argument is deprecated and is no longer used
function kwarg_decl(m::Method, kwtype = nothing)
if m.sig === Tuple # OpaqueClosure
return Symbol[]
end
m.sig === Tuple && return Symbol[] # for OpaqueClosure / builtins
mt = get_methodtable(m)
if isdefined(mt, :kwsorter)
kwtype = typeof(mt.kwsorter)
Expand Down Expand Up @@ -205,8 +203,64 @@ function sym_to_string(sym)
end
end

function show(io::IO, m::Method; modulecolor = :light_black, digit_align_width = -1)
tv, decls, file, line = arg_decl_parts(m)

# type for pretty-printing methods corresponding to the same definition site with different
# optional arguments
struct FactoredMethod
m::Vector{Method} # the different methods, by order of increasing nargs
kwargs::Vector{Symbol}
end
FactoredMethod(m::Method) = FactoredMethod([m], kwarg_decl(m))

struct FactoredMethodList
list::Vector{FactoredMethod}
positions::Vector{Vector{Int}}
ms::MethodList
end

function FactoredMethodList(ms::MethodList)
isempty(ms) && return FactoredMethodList(FactoredMethod[], Vector{Int}[], ms)
I = sortperm(ms.ms; by=m->(m.line, m.file, m.nargs))
list = FactoredMethod[FactoredMethod(ms[I[1]])]
positions = Vector{Int}[[I[1]]]
for _i in 2:length(ms)
i = I[_i]
last_m = last(last(list).m)
m = ms[i]
newmethod = true
# Check whether m and last_m could refer to the same method except for the
# addition of a new optional argument
if !last_m.isva && # the vararg variant is always last
isempty(last(list).kwargs) && # the kwargs variant is always last
m.file == last_m.file && m.line == last_m.line && # same location
startswith(m.slot_syms, last_m.slot_syms) && # same argument names so far
(m.nargs == last_m.nargs + 1 || (m.isva && m.nargs == last_m.nargs + 2))
# number of arguments consistent with one new optional argument (+ slurp)
unwrapped = Base.unwrap_unionall(m.sig).types
truncated = Tuple{unwrapped[1:last_m.nargs]...}
rewrapped = Base.rewrap_unionall(truncated, m.sig)
if rewrapped == last_m.sig # same argument types so far
# At this point, we have determined that method m has a signature identical
# to that of last_m, except for one additional argument (+ slurp)
newmethod = false
push!(last(list).m, m)
append!(last(list).kwargs, kwarg_decl(m)) # we ensure that only one of the methods has kwargs, if any
push!(last(positions), i)
end
end
if newmethod
push!(list, FactoredMethod(m))
push!(positions, [i])
end
end
J = sortperm(positions)
return FactoredMethodList(list[J], positions[J], ms)
end


function show_method(io::IO, f::Union{Method,FactoredMethod}, html::Bool, modulecolor=:light_black, digit_align_width=-1)
m = f isa FactoredMethod ? last(f.m) : f
tv, decls, file, line = arg_decl_parts(m, html)
sig = unwrap_unionall(m.sig)
if sig === Tuple
# Builtin
Expand All @@ -215,36 +269,44 @@ function show(io::IO, m::Method; modulecolor = :light_black, digit_align_width =
line = 0
else
print(io, decls[1][2], "(")

# arguments
for (i,d) in enumerate(decls[2:end])
supmandatory = 1 + (f isa FactoredMethod ? first(f.m).nargs : m.nargs)
last_optional = m.nargs >= supmandatory ? m.nargs - m.isva : 0
for i in 2:m.nargs
i == supmandatory && print(io, '[')
d = decls[i]
printstyled(io, d[1], color=:light_black)
if !isempty(d[2])
print(io, "::")
html && print(io, "<b>")
print_type_bicolor(io, d[2], color=:bold, inner_color=:normal)
html && print(io, "</b>")
end
i < length(decls)-1 && print(io, ", ")
i == last_optional && print(io, ']')
i == m.nargs || print(io, ", ")
end

kwargs = kwarg_decl(m)
kwargs = f isa FactoredMethod ? f.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
print(io, html ? "; <i>" : "; ")
join(io, map(sym_to_string, kwargs), ", ", ", ")
html && print(io, "</i>")
end
print(io, ")")
show_method_params(io, tv)
if !isempty(tv)
html && print(io,"<i>")
show_method_params(io, tv)
html && print(io,"</i>")
end
end

# 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)
print_module_path_file(io, m.module, string(file), line, modulecolor, digit_align_width+4, html ? url(m) : "")
end

function show(io::IO, m::Union{Method,FactoredMethod}; modulecolor=:light_black, digit_align_width=-1)
show_method(io, m, false, modulecolor, digit_align_width)
end
show(io::IO, ::MIME"text/html", m::Union{Method,FactoredMethod}) = show_method(io, m, true)

function show_method_list_header(io::IO, ms::MethodList, namefmt::Function)
mt = ms.mt
Expand Down Expand Up @@ -278,16 +340,34 @@ function show_method_list_header(io::IO, ms::MethodList, namefmt::Function)
!iszero(n) && print(io, ":")
end

function show_method_table(io::IO, ms::MethodList, max::Int=-1, header::Bool=true)
# cluster into consecutive numbers, e.g. [4, 11, 7, 9, 12, 2, 1, 8] into [1:2, 4, 7:9, 11:12]
function _cluster_consecutive(list)
l = sort(list)
result = Union{Int,UnitRange{Int}}[]
ref = last = popfirst!(l)
for x in l
if x == last + 1
last = x
else
ref == last ? push!(result, ref) : push!(result, ref:last)
ref = last = x
end
end
ref == last ? push!(result, ref) : push!(result, ref:last)
return result
end

function show_method_table(io::IO, ml::Union{MethodList,FactoredMethodList}, max::Int=-1, header::Bool=true)
ms = ml isa FactoredMethodList ? ml.ms : ml
mt = ms.mt
name = mt.name
hasname = isdefined(mt.module, name) &&
typeof(getfield(mt.module, name)) <: Function
if header
show_method_list_header(io, ms, str -> "\""*str*"\"")
end
n = rest = 0
local last
rest = 0
local last_meth

last_shown_line_infos = get(io, :last_shown_line_infos, nothing)
last_shown_line_infos === nothing || empty!(last_shown_line_infos)
Expand All @@ -298,36 +378,43 @@ function show_method_table(io::IO, ms::MethodList, max::Int=-1, header::Bool=tru
mt.module
end

digit_align_width = length(string(max > 0 ? max : length(ms)))
if ml isa FactoredMethodList
positions = Vector{Union{Int,UnitRange{Int}}}[_cluster_consecutive(x) for x in ml.positions]
digit_align_width = 2 + maximum(x -> length(x) - 1 + sum(y -> y isa Int ? ndigits(y) : ndigits(first(y)) + ndigits(last(y)), x), positions; init=0)
methods = ml.list
else
positions = eachindex(ml.ms)
digit_align_width = isempty(ms) ? 0 : (2 + ndigits(length(ms)))
methods = ml.ms
end

for meth in ms
if max == -1 || n < max
n += 1
for (n, meth) in zip(positions, methods)
if max == -1 || first(n) < max
println(io)
print(io, ' ', lpad('['*join(n, ',')*']', digit_align_width), ' ')

print(io, " ", lpad("[$n]", digit_align_width + 2), " ")

modulecolor = if meth.module == modul
meth_module = ml isa FactoredMethodList ? first(meth.m).module : meth.module
modulecolor = if meth_module == modul
nothing
else
m = parentmodule_before_main(meth.module)
m = parentmodule_before_main(meth_module)
get!(() -> popfirst!(STACKTRACE_MODULECOLORS), STACKTRACE_FIXEDCOLORS, m)
end
show(io, meth; modulecolor)

file, line = updated_methodloc(meth)
file, line = updated_methodloc(ml isa FactoredMethodList ? last(meth.m) : meth)
if last_shown_line_infos !== nothing
push!(last_shown_line_infos, (string(file), line))
end
else
rest += 1
last = meth
last_meth = meth
end
end
if rest > 0
println(io)
if rest == 1
show(io, last)
show(io, last_meth)
else
print(io, "... $rest methods not shown")
if hasname
Expand All @@ -338,8 +425,10 @@ function show_method_table(io::IO, ms::MethodList, max::Int=-1, header::Bool=tru
nothing
end

show(io::IO, ms::MethodList) = show_method_table(io, ms)
show(io::IO, ::MIME"text/plain", ms::MethodList) = show_method_table(io, ms)
show(io::IO, ml::FactoredMethodList) = show_method_table(io, ml)
show(io::IO, ms::MethodList) = show_method_table(io, FactoredMethodList(ms))
show(io::IO, ::MIME"text/plain", ml::FactoredMethodList) = show_method_table(io, ml)
show(io::IO, ::MIME"text/plain", ml::MethodList) = show_method_table(io, FactoredMethodList(ml))
show(io::IO, mt::Core.MethodTable) = show_method_table(io, MethodList(mt))

function inbase(m::Module)
Expand Down Expand Up @@ -392,53 +481,12 @@ function url(m::Method)
end
end

function show(io::IO, ::MIME"text/html", m::Method)
tv, decls, file, line = arg_decl_parts(m, true)
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]) ? string(d[1]) : string(d[1], "::<b>", d[2] , "</b>") for d in decls[2:end]
],
", ",
", ",
)
kwargs = kwarg_decl(m)
if !isempty(kwargs)
print(io, "; <i>")
join(io, map(sym_to_string, kwargs), ", ", ", ")
print(io, "</i>")
end
print(io, ")")
if !isempty(tv)
print(io,"<i>")
show_method_params(io, tv)
print(io,"</i>")
end
print(io, " in ", m.module)
if line > 0
file, line = updated_methodloc(m)
u = url(m)
if isempty(u)
print(io, " at ", file, ":", line)
else
print(io, """ at <a href="$u" target="_blank">""",
file, ":", line, "</a>")
end
end
end

function show(io::IO, mime::MIME"text/html", ms::MethodList)
function show(io::IO, mime::MIME"text/html", ml::Union{MethodList,FactoredMethodList})
ms = ml isa FactoredMethodList ? ml.ms : ml
mt = ms.mt
show_method_list_header(io, ms, str -> "<b>"*str*"</b>")
print(io, "<ul>")
for meth in ms
for meth in (ml isa FactoredMethodList ? ml.list : ms)
print(io, "<li> ")
show(io, mime, meth)
print(io, "</li> ")
Expand Down
28 changes: 18 additions & 10 deletions stdlib/REPL/src/REPLCompletions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,8 @@ struct FieldCompletion <: Completion
end

struct MethodCompletion <: Completion
tt # may be used by an external consumer to infer return type, etc.
method::Method
MethodCompletion(@nospecialize(tt), method::Method) = new(tt, method)
tt::Vector{Any} # may be used by an external consumer to infer return type, etc.
method::Base.FactoredMethod
end

struct BslashCompletion <: Completion
Expand Down Expand Up @@ -78,7 +77,7 @@ function Base.getproperty(c::Completion, name::Symbol)
elseif name === :field
return getfield(c, :field)::Symbol
elseif name === :method
return getfield(c, :method)::Method
return getfield(c, :method)::Base.FactoredMethod
elseif name === :bslash
return getfield(c, :bslash)::String
elseif name === :text
Expand Down Expand Up @@ -587,7 +586,7 @@ function complete_any_methods(ex_org::Expr, callee_module::Module, context_modul
filter!(out) do c
isa(c, TextCompletion) && return false
isa(c, MethodCompletion) || return true
sig = Base.unwrap_unionall(c.method.sig)::DataType
sig = Base.unwrap_unionall(last(c.method.m).sig)::DataType
return !all(T -> T === Any || T === Vararg{Any}, sig.parameters[2:end])
end
end
Expand Down Expand Up @@ -623,15 +622,24 @@ end
function complete_methods!(out::Vector{Completion}, @nospecialize(funct), args_ex::Vector{Any}, kwargs_ex::Bool, max_method_completions::Int)
# Input types and number of arguments
t_in = Tuple{funct, args_ex...}
m = Base._methods_by_ftype(t_in, nothing, max_method_completions, Base.get_world_counter(),
ml = Base._methods_by_ftype(t_in, nothing, max_method_completions, Base.get_world_counter(),
#=ambig=# true, Ref(typemin(UInt)), Ref(typemax(UInt)), Ptr{Int32}(C_NULL))
if m === false
if ml === false
push!(out, TextCompletion(sprint(Base.show_signature_function, funct) * "( too many methods, use SHIFT-TAB to show )"))
end
m isa Vector || return
for match in m
ml isa Vector || return
isempty(ml) && return
mm = [m::Core.MethodMatch for m in ml]
first_m = mm[1].method
if first_m.sig === Tuple # Builtin
push!(out, MethodCompletion(Any[t_in], Base.FactoredMethod(first_m)))
return
end
mt = Base.get_methodtable(first_m)
fml = Base.FactoredMethodList(Base.MethodList([m.method for m in mm], mt))
for (fm, pos) in zip(fml.list, fml.positions)
# TODO: if kwargs_ex, filter out methods without kwargs?
push!(out, MethodCompletion(match.spec_types, match.method))
push!(out, MethodCompletion(Any[mm[p].spec_types for p in pos], fm))
end
end

Expand Down
7 changes: 7 additions & 0 deletions stdlib/REPL/test/replcompletions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,13 @@ let s = "CompletionFoo.test4(\"e\",r\" \","
@test s[r] == "CompletionFoo.test4"
end

# Test builtins method completion
let s = "typeof("
c, r = test_complete(s)
@test length(c) == 1
@test c[1] == "typeof(...)\n @ Core none:0"
end

# (As discussed in #19829, the Base.REPLCompletions.get_type function isn't
# powerful enough to analyze anonymous functions.)
let s = "CompletionFoo.test5(broadcast((x,y)->x==y, push!(Base.split(\"\",' '),\"\",\"\"), \"\"),"
Expand Down
Loading

0 comments on commit 7d7545b

Please sign in to comment.