Skip to content

Commit

Permalink
REPL: fix JuliaLang#40247, improve getfield type completion
Browse files Browse the repository at this point in the history
  • Loading branch information
aviatesk committed Apr 27, 2021
1 parent 75c4f55 commit 73d0b3f
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 51 deletions.
27 changes: 14 additions & 13 deletions stdlib/REPL/src/REPLCompletions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -386,14 +386,17 @@ get_value(sym::Symbol, fn) = isdefined(fn, sym) ? (getfield(fn, sym), true) : (n
get_value(sym::QuoteNode, fn) = isdefined(fn, sym.value) ? (getfield(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 && length(ex.args) >= 3) || return (nothing, false)
return 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)
# Return the type of a getfield call expression
function get_type_getfield(ex::Expr, fn::Module)
length(ex.args) == 3 || return Any, false # should never happen, but just for safety
obj, x = ex.args[2:3]
objt, found = get_type_getfield(obj, fn)
found || return Any, false
fld = isa(x, QuoteNode) ? x.value : x
isdefined(objt, fld) || return Any, false
return fieldtype(objt, fld), true
end
get_type_getfield(@nospecialize(sym), fn) = get_type(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)
Expand Down Expand Up @@ -423,18 +426,16 @@ function get_type_call(expr::Expr)
return (return_type, true)
end

# Returns the return type. example: get_type(:(Base.strip("", ' ')), Main) returns (String, true)
# Returns the return type. example: get_type(:(Base.strip("", ' ')), Main) returns (SubString{String}, true)
function try_get_type(sym::Expr, fn::Module)
val, found = get_value(sym, fn)
found && return Core.Typeof(val), 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.
a1 = sym.args[1]
if isa(a1,GlobalRef) && isconst(a1.mod,a1.name) && isdefined(a1.mod,a1.name) &&
eval(a1) === Core.getfield
val, found = get_value_getfield(sym, Main)
return found ? Core.Typeof(val) : Any, found
if a1 === :getfield || a1 === GlobalRef(Core, :getfield)
return get_type_getfield(sym, fn)
end
return get_type_call(sym)
elseif sym.head === :thunk
Expand Down
118 changes: 80 additions & 38 deletions stdlib/REPL/test/replcompletions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,11 @@ function map_completion_text(completions)
return map(completion_text, c), r, res
end

test_complete(s) = map_completion_text(@inferred(completions(s,lastindex(s))))
test_scomplete(s) = map_completion_text(@inferred(shell_completions(s,lastindex(s))))
test_bslashcomplete(s) = map_completion_text(@inferred(bslash_completions(s,lastindex(s)))[2])
test_complete_context(s) = map_completion_text(@inferred(completions(s,lastindex(s),Main.CompletionFoo)))
test_complete(s) = map_completion_text(@inferred(completions(s, lastindex(s))))
test_scomplete(s) = map_completion_text(@inferred(shell_completions(s, lastindex(s))))
test_bslashcomplete(s) = map_completion_text(@inferred(bslash_completions(s, lastindex(s)))[2])
test_complete_context(s, m) = map_completion_text(@inferred(completions(s,lastindex(s), m)))
test_complete_foo(s) = test_complete_context(s, Main.CompletionFoo)

module M32377 end
test_complete_32377(s) = map_completion_text(completions(s,lastindex(s), M32377))
Expand Down Expand Up @@ -148,20 +149,20 @@ let s = "Main.CompletionFoo.f"
@test !("foobar" in c)
end

# test method completions when `!` operator precedes
let
s = "!is"
c, r = test_complete(s)
@test "isa" in c
@test s[r] == "is"
@test !("!" in c)

s = "!!is"
c, r = test_complete(s)
@test "isa" in c
@test s[r] == "is"
@test !("!" in c)
end
# # test method completions when `!` operator precedes
# let
# s = "!is"
# c, r = test_complete(s)
# @test "isa" in c
# @test s[r] == "is"
# @test !("!" in c)
#
# s = "!!is"
# c, r = test_complete(s)
# @test "isa" in c
# @test s[r] == "is"
# @test !("!" in c)
# end

# issue #6424
let s = "Main.CompletionFoo.@f"
Expand Down Expand Up @@ -293,7 +294,7 @@ end

# test latex symbol completion in getindex expressions (#24705)
let s = "tuple[\\alpha"
c, r, res = test_complete_context(s)
c, r, res = test_complete_foo(s)
@test c[1] == "α"
@test r == 7:12
@test length(c) == 1
Expand Down Expand Up @@ -983,100 +984,100 @@ end

# No CompletionFoo.CompletionFoo
let s = ""
c, r = test_complete_context(s)
c, r = test_complete_foo(s)
@test !("CompletionFoo" in c)
end

# Can see `rand()` after `using Random`
let s = "r"
c, r = test_complete_context(s)
c, r = test_complete_foo(s)
@test "rand" in c
@test r == 1:1
@test s[r] == "r"
end

# Can see `Test.AbstractTestSet` after `import Test`
let s = "Test.A"
c, r = test_complete_context(s)
c, r = test_complete_foo(s)
@test "AbstractTestSet" in c
@test r == 6:6
@test s[r] == "A"
end

# Can complete relative import
let s = "import ..M"
c, r = test_complete_context(s)
c, r = test_complete_foo(s)
@test_broken "Main" in c
@test r == 10:10
@test s[r] == "M"
end

let s = ""
c, r = test_complete_context(s)
c, r = test_complete_foo(s)
@test "bar" in c
@test r === 1:0
@test s[r] == ""
end

let s = "f"
c, r = test_complete_context(s)
c, r = test_complete_foo(s)
@test "foo" in c
@test r == 1:1
@test s[r] == "f"
@test !("foobar" in c)
end

let s = "@f"
c, r = test_complete_context(s)
c, r = test_complete_foo(s)
@test "@foobar" in c
@test r == 1:2
@test s[r] == "@f"
@test !("foo" in c)
end

let s = "type_test.x"
c, r = test_complete_context(s)
c, r = test_complete_foo(s)
@test "xx" in c
@test r == 11:11
@test s[r] == "x"
end

let s = "bar.no_val_available"
c, r = test_complete_context(s)
c, r = test_complete_foo(s)
@test length(c)==0
end

let s = "type_test.xx.y"
c, r = test_complete_context(s)
c, r = test_complete_foo(s)
@test "yy" in c
@test r == 14:14
@test s[r] == "y"
end

let s = ":(function foo(::Int) end).args[1].args[2]."
c, r = test_complete_context(s)
c, r = test_complete_foo(s)
@test c == Any[]
end

let s = "log(log.(x),"
c, r = test_complete_context(s)
c, r = test_complete_foo(s)
@test !isempty(c)
end

let s = "Base.return_types(getin"
c, r = test_complete_context(s)
c, r = test_complete_foo(s)
@test "getindex" in c
@test r == 19:23
@test s[r] == "getin"
end

let s = "using Test, Random"
c, r = test_complete_context(s)
c, r = test_complete_foo(s)
@test !("RandomDevice" in c)
end

let s = "test(1,1, "
c, r, res = test_complete_context(s)
c, r, res = test_complete_foo(s)
@test !res
@test c[1] == string(first(methods(Main.CompletionFoo.test, Tuple{Int, Int})))
@test length(c) == 3
Expand All @@ -1085,27 +1086,68 @@ let s = "test(1,1, "
end

let s = "test.(1,1, "
c, r, res = test_complete_context(s)
c, r, res = test_complete_foo(s)
@test !res
@test length(c) == 4
@test r == 1:4
@test s[r] == "test"
end

let s = "prevind(\"θ\",1,"
c, r, res = test_complete_context(s)
c, r, res = test_complete_foo(s)
@test c[1] == string(first(methods(prevind, Tuple{String, Int})))
@test r == 1:7
@test s[r] == "prevind"
end

# Issue #32840
let s = "typeof(+)."
c, r = test_complete_context(s)
c, r = test_complete_foo(s)
@test length(c) == length(fieldnames(DataType))
end

let s = "test_dict[\"ab"
c, r = test_complete_context(s)
c, r = test_complete_foo(s)
@test c == Any["\"abc\"", "\"abcd\""]
end

@testset "https://github.com/JuliaLang/julia/issues/40247" begin
# getfield type completion can work for complicated expression

let
m = Module()
@eval m begin
struct Rs
rs::Vector{Regex}
end
var = nothing
function foo()
global var = 1
return Rs([r"foo"])
end
end

c, r = test_complete_context("foo().rs[1].", m)
@test m.var 1 # getfield type completion should never execute `foo()`
@test length(c) == fieldcount(Regex)
end

let
m = Module()
@eval m begin
struct R
r::Regex
end
var = nothing
function foo()
global var = 1
return R(r"foo")
end
end

c, r = test_complete_context("foo().r.", m)
# the current implementation of `REPL.REPLCompletions.completions(::String, ::Int, ::Module)`
# cuts off "foo().r." to `.r.`, and the getfield type completion doesn't work for this simpler case
@test_broken length(c) == fieldcount(Regex)
end
end

0 comments on commit 73d0b3f

Please sign in to comment.