Skip to content

Commit

Permalink
Method completion using inference to determine the input types to rem…
Browse files Browse the repository at this point in the history
…ove non matching methods. Add test for the new functionality, fix  #6338.

(cherry picked from commit f8196cc)
ref #11679
  • Loading branch information
dhoegh authored and tkelman committed Nov 29, 2015
1 parent f02e71d commit 931cfbe
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 8 deletions.
54 changes: 52 additions & 2 deletions base/REPLCompletions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -222,14 +222,64 @@ get_value(sym::Symbol, fn) = isdefined(fn, sym) ? (fn.(sym), true) : (nothing, f
get_value(sym::QuoteNode, fn) = isdefined(fn, sym.value) ? (fn.(sym.value), true) : (nothing, false)
get_value(sym, fn) = sym, true

# Return the value of a getfield call expression
function get_value_getfield(ex::Expr, fn)
# Example :((top(getfield))(Base,:max))
val, found = get_value_getfield(ex.args[2],fn) #Look up Base in Main and returns the module
found || return (nothing, false)
get_value_getfield(ex.args[3],val) #Look up max in Base and returns the function if found.
end
get_value_getfield(sym, fn) = get_value(sym, fn)
# Determines the return type with Base.return_types of a function call using the type information of the arguments.
function get_type_call(expr::Expr)
f_name = expr.args[1]
# The if statement should find the f function. How f is found depends on how f is referenced
if isa(f_name, TopNode)
f = Base.(f_name.name)
found = true
elseif isa(f_name, Expr) && f_name.args[1] === TopNode(:getfield)
f, found = get_value_getfield(f_name, Main)
else
f, found = get_value(f_name, Main)
end
found || return (Any, false) # If the function f is not found return Any.
args = Any[]
for ex in expr.args[2:end] # Find the type of the function arguments
typ, found = get_type(ex, Main)
found ? push!(args, typ) : push!(args, Any)
end
return_types = Base.return_types(f,Tuple{args...})
length(return_types) == 1 || return (Any, false)
return (return_types[1], true)
end
# Returns the return type. example: get_type(:(Base.strip("",' ')),Main) returns (ASCIIString,true)
function get_type(sym::Expr, fn)
sym=expand(sym)
val, found = get_value(sym, fn)
found && return Base.typesof(val).parameters[1], found
if sym.head === :call
# getfield call is special cased as the evaluation of getfield provides good type information,
# is inexpensive and it is also performed in the complete_symbol function.
if sym.args[1] === TopNode(:getfield)
val, found = get_value_getfield(sym, Main)
return found ? Base.typesof(val).parameters[1] : Any, found
end
return get_type_call(sym)
end
(Any, false)
end
function get_type(sym, fn)
val, found = get_value(sym, fn)
return found ? Base.typesof(val).parameters[1] : Any, found
end
# Method completion on function call expression that look like :(max(1))
function complete_methods(ex_org::Expr)
args_ex = DataType[]
func, found = get_value(ex_org.args[1], Main)
(!found || (found && !isgeneric(func))) && return UTF8String[]
for ex in ex_org.args[2:end]
val, found = get_value(ex, Main)
found ? push!(args_ex, Base.typesof(val).parameters[1]) : push!(args_ex, Any)
val, found = get_type(ex, Main)
push!(args_ex, val)
end
out = UTF8String[]
t_in = Tuple{args_ex...} # Input types
Expand Down
50 changes: 44 additions & 6 deletions test/replcompletions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,15 @@ module CompletionFoo
test3(x::AbstractArray{Int}, y::Int) = pass
test3(x::AbstractArray{Float64}, y::Float64) = pass

test4(x::AbstractString, y::AbstractString) = pass
test4(x::AbstractString, y::Regex) = pass

test5(x::Array{Bool,1}) = pass
test5(x::BitArray{1}) = pass
test5(x::Float64) = pass

array = [1, 1]
varfloat = 0.1
end

function temp_pkg_dir(fn::Function)
Expand Down Expand Up @@ -186,7 +194,7 @@ c, r, res = test_complete(s)
@test r == 1:3
@test s[r] == "max"

# Test completion of methods with input args
# Test completion of methods with input concrete args and args where typeinference determine their type
s = "CompletionFoo.test(1,1, "
c, r, res = test_complete(s)
@test !res
Expand Down Expand Up @@ -239,24 +247,54 @@ for (T, arg) in [(ASCIIString,"\")\""),(Char, "')'")]
@test s[r] == "CompletionFoo.test2"
end

# This cannot find the correct method due to the backticks expands to a macro in the parser.
# Then the function argument is an expression which is not handled by current method completion logic.
s = "(1, CompletionFoo.test2(`)`,"
c, r, res = test_complete(s)
@test length(c) == 3
@test c[1] == string(methods(CompletionFoo.test2, Tuple{Cmd})[1])
@test length(c) == 1

s = "CompletionFoo.test3([1.,2.],"
s = "CompletionFoo.test3([1, 2] + CompletionFoo.varfloat,"
c, r, res = test_complete(s)
@test !res
@test length(c) == 2
@test c[1] == string(methods(CompletionFoo.test3, Tuple{Array{Float64, 1}, Float64})[1])
@test length(c) == 1

s = "CompletionFoo.test3([1.,2.], 1.,"
c, r, res = test_complete(s)
@test !res
@test c[1] == string(methods(CompletionFoo.test3, Tuple{Array{Float64, 1}, Float64})[1])
@test r == 1:19
@test length(c) == 1
@test s[r] == "CompletionFoo.test3"

s = "CompletionFoo.test4(\"e\",r\" \","
c, r, res = test_complete(s)
@test !res
@test c[1] == string(methods(CompletionFoo.test4, Tuple{ASCIIString, Regex})[1])
@test r == 1:19
@test length(c) == 1
@test s[r] == "CompletionFoo.test4"

s = "CompletionFoo.test5(push!(Base.split(\"\",' '),\"\",\"\").==\"\","
c, r, res = test_complete(s)
@test !res
@test length(c) == 1
@test c[1] == string(methods(CompletionFoo.test5, Tuple{BitArray{1}})[1])

########## Test where the current inference logic fails ########
# Fails due to inferrence fails to determine a concrete type from the map
# But it returns AbstractArray{T,N} and hence is able to remove test5(x::Float64) from the suggestions
s = "CompletionFoo.test5(map(x-> x==\"\",push!(Base.split(\"\",' '),\"\",\"\")),"
c, r, res = test_complete(s)
@test !res
@test length(c) == 2

# equivalent to above but due to the time macro the completion fails to find the concrete type
s = "CompletionFoo.test3(@time([1, 2] + CompletionFoo.varfloat),"
c, r, res = test_complete(s)
@test !res
@test length(c) == 2
#################################################################

# Test completion in multi-line comments
s = "#=\n\\alpha"
c, r, res = test_complete(s)
Expand Down

0 comments on commit 931cfbe

Please sign in to comment.