Skip to content

Commit

Permalink
REPL: enable MethodCompletions after keyword arguments (JuliaLang#3…
Browse files Browse the repository at this point in the history
…8106)

* enable `MethodCompletion`s after keyword arguments

* improve type stability
  • Loading branch information
aviatesk committed Nov 17, 2020
1 parent 9b0232e commit 382a665
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 10 deletions.
45 changes: 35 additions & 10 deletions stdlib/REPL/src/REPLCompletions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ struct MethodCompletion <: Completion
func
input_types::Type
method::Method
orig_method::Union{Nothing,Method} # if `method` is a keyword method, keep the original method for sensible printing
end

struct BslashCompletion <: Completion
Expand Down Expand Up @@ -89,7 +90,7 @@ _completion_text(c::ModuleCompletion) = c.mod
_completion_text(c::PackageCompletion) = c.package
_completion_text(c::PropertyCompletion) = string(c.property)
_completion_text(c::FieldCompletion) = string(c.field)
_completion_text(c::MethodCompletion) = sprint(io -> show(io, c.method))
_completion_text(c::MethodCompletion) = sprint(io -> show(io, isnothing(c.orig_method) ? c.method : c.orig_method::Method))
_completion_text(c::BslashCompletion) = c.bslash
_completion_text(c::ShellCompletion) = c.text
_completion_text(c::DictCompletion) = c.key
Expand Down Expand Up @@ -314,7 +315,7 @@ end
function should_method_complete(s::AbstractString)
method_complete = false
for c in reverse(s)
if c in [',', '(']
if c in [',', '(', ';']
method_complete = true
break
elseif !(c in whitespace_chars)
Expand Down Expand Up @@ -465,34 +466,58 @@ end

# Method completion on function call expression that look like :(max(1))
function complete_methods(ex_org::Expr, context_module::Module=Main)
args_ex = Any[]
func, found = get_value(ex_org.args[1], context_module)::Tuple{Any,Bool}
!found && return Completion[]

funargs = ex_org.args[2:end]
# handle broadcasting, but only handle number of arguments instead of
# argument types
args_ex = Any[]
kwargs_ex = Pair{Symbol,Any}[]
if ex_org.head === :. && ex_org.args[2] isa Expr
for _ in (ex_org.args[2]::Expr).args
push!(args_ex, Any)
end
else
for ex in funargs
val, found = get_type(ex, context_module)
push!(args_ex, val)
if isexpr(ex, :parameters)
for x in ex.args
n, v = isexpr(x, :kw) ? (x.args...,) : (x, x)
push!(kwargs_ex, n => first(get_type(v, context_module)))
end
elseif isexpr(ex, :kw)
n, v = (ex.args...,)
push!(kwargs_ex, n => first(get_type(v, context_module)))
else
push!(args_ex, first(get_type(ex, context_module)))
end
end
end

out = Completion[]
t_in = Tuple{Core.Typeof(func), args_ex...} # Input types
na = length(args_ex)+1
ml = methods(func)
for method in ml
# Input types and number of arguments
if isempty(kwargs_ex)
t_in = Tuple{Core.Typeof(func), args_ex...}
na = length(t_in.parameters)::Int
orig_ml = fill(nothing, length(ml))
else
isdefined(ml.mt, :kwsorter) || return out
kwfunc = ml.mt.kwsorter
kwargt = NamedTuple{(first.(kwargs_ex)...,), Tuple{last.(kwargs_ex)...}}
t_in = Tuple{Core.Typeof(kwfunc), kwargt, Core.Typeof(func), args_ex...}
na = length(t_in.parameters)::Int
orig_ml = ml # this method is supposed to be used for printing
ml = methods(kwfunc)
func = kwfunc
end

for (method::Method, orig_method) in zip(ml, orig_ml)
ms = method.sig

# Check if the method's type signature intersects the input types
if typeintersect(Base.rewrap_unionall(Tuple{(Base.unwrap_unionall(ms)::DataType).parameters[1 : min(na, end)]...}, ms), t_in) !== Union{}
push!(out, MethodCompletion(func, t_in, method))
if typeintersect(Base.rewrap_unionall(Tuple{(Base.unwrap_unionall(ms)::DataType).parameters[1 : min(na, end)]...}, ms), t_in) != Union{}
push!(out, MethodCompletion(func, t_in, method, orig_method))
end
end
return out
Expand Down
21 changes: 21 additions & 0 deletions stdlib/REPL/test/replcompletions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ let ex = quote
test7() = rand(Bool) ? 1 : 1.0
test8() = Any[1][1]
kwtest(; x=1, y=2, w...) = pass
kwtest2(a; x=1, y=2, w...) = pass

array = [1, 1]
varfloat = 0.1
Expand Down Expand Up @@ -483,13 +484,33 @@ let s = "CompletionFoo.test3(@time([1, 2] + CompletionFoo.varfloat),"
end
#################################################################

# method completions with kwargs
let s = "CompletionFoo.kwtest( "
c, r, res = test_complete(s)
@test !res
@test length(c) == 1
@test occursin("x, y, w...", c[1])
end

for s in ("CompletionFoo.kwtest(;",
"CompletionFoo.kwtest(; x=1, ",
"CompletionFoo.kwtest(; kw=1, ",
)
c, r, res = test_complete(s)
@test !res
@test length(c) == 1
@test occursin("x, y, w...", c[1])
end

for s in ("CompletionFoo.kwtest2(1; x=1,",
"CompletionFoo.kwtest2(1; kw=1, ",
)
c, r, res = test_complete(s)
@test !res
@test length(c) == 1
@test occursin("a; x, y, w...", c[1])
end

# Test of inference based getfield completion
let s = "(1+2im)."
c,r = test_complete(s)
Expand Down

0 comments on commit 382a665

Please sign in to comment.