Skip to content

Commit 37eb474

Browse files
aviateskjohanmon
authored andcommitted
REPL: improve getfield type completion (JuliaLang#40624)
fix JuliaLang#40247
1 parent 805fe26 commit 37eb474

File tree

2 files changed

+86
-36
lines changed

2 files changed

+86
-36
lines changed

stdlib/REPL/src/REPLCompletions.jl

+20-12
Original file line numberDiff line numberDiff line change
@@ -387,14 +387,24 @@ get_value(sym::QuoteNode, fn) = isdefined(fn, sym.value) ? (getfield(fn, sym.val
387387
get_value(sym::GlobalRef, fn) = get_value(sym.name, sym.mod)
388388
get_value(sym, fn) = (sym, true)
389389

390-
# Return the value of a getfield call expression
391-
function get_value_getfield(ex::Expr, fn)
392-
# Example :((top(getfield))(Base,:max))
393-
val, found = get_value_getfield(ex.args[2],fn) #Look up Base in Main and returns the module
394-
(found && length(ex.args) >= 3) || return (nothing, false)
395-
return get_value_getfield(ex.args[3], val) #Look up max in Base and returns the function if found.
390+
# Return the type of a getfield call expression
391+
function get_type_getfield(ex::Expr, fn::Module)
392+
length(ex.args) == 3 || return Any, false # should never happen, but just for safety
393+
obj, x = ex.args[2:3]
394+
objt, found = get_type(obj, fn)
395+
objt isa DataType || return Any, false
396+
found || return Any, false
397+
if x isa QuoteNode
398+
fld = x.value
399+
elseif isexpr(x, :quote) || isexpr(x, :inert)
400+
fld = x.args[1]
401+
else
402+
fld = nothing # we don't know how to get the value of variable `x` here
403+
end
404+
fld isa Symbol || return Any, false
405+
hasfield(objt, fld) || return Any, false
406+
return fieldtype(objt, fld), true
396407
end
397-
get_value_getfield(sym, fn) = get_value(sym, fn)
398408

399409
# Determines the return type with Base.return_types of a function call using the type information of the arguments.
400410
function get_type_call(expr::Expr)
@@ -424,18 +434,16 @@ function get_type_call(expr::Expr)
424434
return (return_type, true)
425435
end
426436

427-
# Returns the return type. example: get_type(:(Base.strip("", ' ')), Main) returns (String, true)
437+
# Returns the return type. example: get_type(:(Base.strip("", ' ')), Main) returns (SubString{String}, true)
428438
function try_get_type(sym::Expr, fn::Module)
429439
val, found = get_value(sym, fn)
430440
found && return Core.Typeof(val), found
431441
if sym.head === :call
432442
# getfield call is special cased as the evaluation of getfield provides good type information,
433443
# is inexpensive and it is also performed in the complete_symbol function.
434444
a1 = sym.args[1]
435-
if isa(a1,GlobalRef) && isconst(a1.mod,a1.name) && isdefined(a1.mod,a1.name) &&
436-
eval(a1) === Core.getfield
437-
val, found = get_value_getfield(sym, Main)
438-
return found ? Core.Typeof(val) : Any, found
445+
if a1 === :getfield || a1 === GlobalRef(Core, :getfield)
446+
return get_type_getfield(sym, fn)
439447
end
440448
return get_type_call(sym)
441449
elseif sym.head === :thunk

stdlib/REPL/test/replcompletions.jl

+66-24
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,11 @@ function map_completion_text(completions)
9797
return map(completion_text, c), r, res
9898
end
9999

100-
test_complete(s) = map_completion_text(@inferred(completions(s,lastindex(s))))
101-
test_scomplete(s) = map_completion_text(@inferred(shell_completions(s,lastindex(s))))
102-
test_bslashcomplete(s) = map_completion_text(@inferred(bslash_completions(s,lastindex(s)))[2])
103-
test_complete_context(s) = map_completion_text(@inferred(completions(s,lastindex(s),Main.CompletionFoo)))
100+
test_complete(s) = map_completion_text(@inferred(completions(s, lastindex(s))))
101+
test_scomplete(s) = map_completion_text(@inferred(shell_completions(s, lastindex(s))))
102+
test_bslashcomplete(s) = map_completion_text(@inferred(bslash_completions(s, lastindex(s)))[2])
103+
test_complete_context(s, m) = map_completion_text(@inferred(completions(s,lastindex(s), m)))
104+
test_complete_foo(s) = test_complete_context(s, Main.CompletionFoo)
104105

105106
module M32377 end
106107
test_complete_32377(s) = map_completion_text(completions(s,lastindex(s), M32377))
@@ -297,7 +298,7 @@ end
297298

298299
# test latex symbol completion in getindex expressions (#24705)
299300
let s = "tuple[\\alpha"
300-
c, r, res = test_complete_context(s)
301+
c, r, res = test_complete_foo(s)
301302
@test c[1] == "α"
302303
@test r == 7:12
303304
@test length(c) == 1
@@ -987,100 +988,100 @@ end
987988

988989
# No CompletionFoo.CompletionFoo
989990
let s = ""
990-
c, r = test_complete_context(s)
991+
c, r = test_complete_foo(s)
991992
@test !("CompletionFoo" in c)
992993
end
993994

994995
# Can see `rand()` after `using Random`
995996
let s = "r"
996-
c, r = test_complete_context(s)
997+
c, r = test_complete_foo(s)
997998
@test "rand" in c
998999
@test r == 1:1
9991000
@test s[r] == "r"
10001001
end
10011002

10021003
# Can see `Test.AbstractTestSet` after `import Test`
10031004
let s = "Test.A"
1004-
c, r = test_complete_context(s)
1005+
c, r = test_complete_foo(s)
10051006
@test "AbstractTestSet" in c
10061007
@test r == 6:6
10071008
@test s[r] == "A"
10081009
end
10091010

10101011
# Can complete relative import
10111012
let s = "import ..M"
1012-
c, r = test_complete_context(s)
1013+
c, r = test_complete_foo(s)
10131014
@test_broken "Main" in c
10141015
@test r == 10:10
10151016
@test s[r] == "M"
10161017
end
10171018

10181019
let s = ""
1019-
c, r = test_complete_context(s)
1020+
c, r = test_complete_foo(s)
10201021
@test "bar" in c
10211022
@test r === 1:0
10221023
@test s[r] == ""
10231024
end
10241025

10251026
let s = "f"
1026-
c, r = test_complete_context(s)
1027+
c, r = test_complete_foo(s)
10271028
@test "foo" in c
10281029
@test r == 1:1
10291030
@test s[r] == "f"
10301031
@test !("foobar" in c)
10311032
end
10321033

10331034
let s = "@f"
1034-
c, r = test_complete_context(s)
1035+
c, r = test_complete_foo(s)
10351036
@test "@foobar" in c
10361037
@test r == 1:2
10371038
@test s[r] == "@f"
10381039
@test !("foo" in c)
10391040
end
10401041

10411042
let s = "type_test.x"
1042-
c, r = test_complete_context(s)
1043+
c, r = test_complete_foo(s)
10431044
@test "xx" in c
10441045
@test r == 11:11
10451046
@test s[r] == "x"
10461047
end
10471048

10481049
let s = "bar.no_val_available"
1049-
c, r = test_complete_context(s)
1050+
c, r = test_complete_foo(s)
10501051
@test length(c)==0
10511052
end
10521053

10531054
let s = "type_test.xx.y"
1054-
c, r = test_complete_context(s)
1055+
c, r = test_complete_foo(s)
10551056
@test "yy" in c
10561057
@test r == 14:14
10571058
@test s[r] == "y"
10581059
end
10591060

10601061
let s = ":(function foo(::Int) end).args[1].args[2]."
1061-
c, r = test_complete_context(s)
1062+
c, r = test_complete_foo(s)
10621063
@test c == Any[]
10631064
end
10641065

10651066
let s = "log(log.(x),"
1066-
c, r = test_complete_context(s)
1067+
c, r = test_complete_foo(s)
10671068
@test !isempty(c)
10681069
end
10691070

10701071
let s = "Base.return_types(getin"
1071-
c, r = test_complete_context(s)
1072+
c, r = test_complete_foo(s)
10721073
@test "getindex" in c
10731074
@test r == 19:23
10741075
@test s[r] == "getin"
10751076
end
10761077

10771078
let s = "using Test, Random"
1078-
c, r = test_complete_context(s)
1079+
c, r = test_complete_foo(s)
10791080
@test !("RandomDevice" in c)
10801081
end
10811082

10821083
let s = "test(1,1, "
1083-
c, r, res = test_complete_context(s)
1084+
c, r, res = test_complete_foo(s)
10841085
@test !res
10851086
@test c[1] == string(first(methods(Main.CompletionFoo.test, Tuple{Int, Int})))
10861087
@test length(c) == 3
@@ -1089,28 +1090,28 @@ let s = "test(1,1, "
10891090
end
10901091

10911092
let s = "test.(1,1, "
1092-
c, r, res = test_complete_context(s)
1093+
c, r, res = test_complete_foo(s)
10931094
@test !res
10941095
@test length(c) == 4
10951096
@test r == 1:4
10961097
@test s[r] == "test"
10971098
end
10981099

10991100
let s = "prevind(\"θ\",1,"
1100-
c, r, res = test_complete_context(s)
1101+
c, r, res = test_complete_foo(s)
11011102
@test c[1] == string(first(methods(prevind, Tuple{String, Int})))
11021103
@test r == 1:7
11031104
@test s[r] == "prevind"
11041105
end
11051106

11061107
# Issue #32840
11071108
let s = "typeof(+)."
1108-
c, r = test_complete_context(s)
1109+
c, r = test_complete_foo(s)
11091110
@test length(c) == length(fieldnames(DataType))
11101111
end
11111112

11121113
let s = "test_dict[\"ab"
1113-
c, r = test_complete_context(s)
1114+
c, r = test_complete_foo(s)
11141115
@test c == Any["\"abc\"", "\"abcd\""]
11151116
end
11161117

@@ -1120,3 +1121,44 @@ let
11201121
(test_complete("Main.@noexist."); @test true)
11211122
(test_complete("@Main.noexist."); @test true)
11221123
end
1124+
1125+
@testset "https://github.com/JuliaLang/julia/issues/40247" begin
1126+
# getfield type completion can work for complicated expression
1127+
1128+
let
1129+
m = Module()
1130+
@eval m begin
1131+
struct Rs
1132+
rs::Vector{Regex}
1133+
end
1134+
var = nothing
1135+
function foo()
1136+
global var = 1
1137+
return Rs([r"foo"])
1138+
end
1139+
end
1140+
1141+
c, r = test_complete_context("foo().rs[1].", m)
1142+
@test m.var 1 # getfield type completion should never execute `foo()`
1143+
@test length(c) == fieldcount(Regex)
1144+
end
1145+
1146+
let
1147+
m = Module()
1148+
@eval m begin
1149+
struct R
1150+
r::Regex
1151+
end
1152+
var = nothing
1153+
function foo()
1154+
global var = 1
1155+
return R(r"foo")
1156+
end
1157+
end
1158+
1159+
c, r = test_complete_context("foo().r.", m)
1160+
# the current implementation of `REPL.REPLCompletions.completions(::String, ::Int, ::Module)`
1161+
# cuts off "foo().r." to `.r.`, and the getfield type completion doesn't work for this simpler case
1162+
@test_broken length(c) == fieldcount(Regex)
1163+
end
1164+
end

0 commit comments

Comments
 (0)