From 8f112ba4e9a53bff2dc429bf500960e1d3db8a71 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Tue, 11 May 2021 00:33:56 +0900 Subject: [PATCH] REPL: improve `getfield` type completion (#40624) fix #40247 --- stdlib/REPL/src/REPLCompletions.jl | 32 ++++++---- stdlib/REPL/test/replcompletions.jl | 90 +++++++++++++++++++++-------- 2 files changed, 86 insertions(+), 36 deletions(-) diff --git a/stdlib/REPL/src/REPLCompletions.jl b/stdlib/REPL/src/REPLCompletions.jl index 5412f6f157ada5..5d857dbe104075 100644 --- a/stdlib/REPL/src/REPLCompletions.jl +++ b/stdlib/REPL/src/REPLCompletions.jl @@ -387,14 +387,24 @@ get_value(sym::QuoteNode, fn) = isdefined(fn, sym.value) ? (getfield(fn, sym.val get_value(sym::GlobalRef, fn) = get_value(sym.name, sym.mod) 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. +# 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(obj, fn) + objt isa DataType || return Any, false + found || return Any, false + if x isa QuoteNode + fld = x.value + elseif isexpr(x, :quote) || isexpr(x, :inert) + fld = x.args[1] + else + fld = nothing # we don't know how to get the value of variable `x` here + end + fld isa Symbol || return Any, false + hasfield(objt, fld) || return Any, false + return fieldtype(objt, fld), true 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) @@ -424,7 +434,7 @@ 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 @@ -432,10 +442,8 @@ function try_get_type(sym::Expr, fn::Module) # 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 diff --git a/stdlib/REPL/test/replcompletions.jl b/stdlib/REPL/test/replcompletions.jl index 97eee615791423..545e81a27968d3 100644 --- a/stdlib/REPL/test/replcompletions.jl +++ b/stdlib/REPL/test/replcompletions.jl @@ -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)) @@ -297,7 +298,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 @@ -987,13 +988,13 @@ 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" @@ -1001,7 +1002,7 @@ 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" @@ -1009,21 +1010,21 @@ 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" @@ -1031,7 +1032,7 @@ let s = "f" 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" @@ -1039,48 +1040,48 @@ let s = "@f" 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 @@ -1089,7 +1090,7 @@ 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 @@ -1097,7 +1098,7 @@ let s = "test.(1,1, " 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" @@ -1105,12 +1106,12 @@ 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 @@ -1120,3 +1121,44 @@ let (test_complete("Main.@noexist."); @test true) (test_complete("@Main.noexist."); @test true) 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