• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JuliaLang / julia / #37636

30 Sep 2023 06:44AM UTC coverage: 86.537% (-0.9%) from 87.418%
#37636

push

local

web-flow
[REPLCompletions] suppress false positive field completions (#51502)

Field completions can be wrong when `getproperty` and `propertynames`
are overloaded for a target object type, since a custom `getproperty`
does not necessarily accept field names. This commit simply suppresses
field completions in such cases. Fixes the second part of #51499.

15 of 15 new or added lines in 1 file covered. (100.0%)

73123 of 84499 relevant lines covered (86.54%)

12113738.76 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.17
/stdlib/REPL/src/REPLCompletions.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
module REPLCompletions
4

5
export completions, shell_completions, bslash_completions, completion_text
6

7
using Core: CodeInfo, MethodInstance, CodeInstance, Const
8
const CC = Core.Compiler
9
using Base.Meta
10
using Base: propertynames, something
11

12
abstract type Completion end
13

14
struct TextCompletion <: Completion
15
    text::String
4✔
16
end
17

18
struct KeywordCompletion <: Completion
19
    keyword::String
158✔
20
end
21

22
struct KeyvalCompletion <: Completion
23
    keyval::String
15✔
24
end
25

26
struct PathCompletion <: Completion
27
    path::String
6,858✔
28
end
29

30
struct ModuleCompletion <: Completion
31
    parent::Module
379,873✔
32
    mod::String
33
end
34

35
struct PackageCompletion <: Completion
36
    package::String
195✔
37
end
38

39
struct PropertyCompletion <: Completion
40
    value
40✔
41
    property::Symbol
42
end
43

44
struct FieldCompletion <: Completion
45
    typ::DataType
24✔
46
    field::Symbol
47
end
48

49
struct MethodCompletion <: Completion
50
    tt # may be used by an external consumer to infer return type, etc.
51
    method::Method
52
    MethodCompletion(@nospecialize(tt), method::Method) = new(tt, method)
2,176✔
53
end
54

55
struct BslashCompletion <: Completion
56
    bslash::String
5,164✔
57
end
58

59
struct ShellCompletion <: Completion
60
    text::String
61
end
62

63
struct DictCompletion <: Completion
64
    dict::AbstractDict
124✔
65
    key::String
66
end
67

68
struct KeywordArgumentCompletion <: Completion
69
    kwarg::String
47✔
70
end
71

72
# interface definition
73
function Base.getproperty(c::Completion, name::Symbol)
2,970✔
74
    if name === :text
8,153,259✔
75
        return getfield(c, :text)::String
4✔
76
    elseif name === :keyword
8,151,548✔
77
        return getfield(c, :keyword)::String
1,707✔
78
    elseif name === :path
8,144,810✔
79
        return getfield(c, :path)::String
60,290✔
80
    elseif name === :parent
8,144,810✔
81
        return getfield(c, :parent)::Module
×
82
    elseif name === :mod
8,999✔
83
        return getfield(c, :mod)::String
8,135,811✔
84
    elseif name === :package
8,999✔
85
        return getfield(c, :package)::String
2,431✔
86
    elseif name === :property
6,568✔
87
        return getfield(c, :property)::Symbol
124✔
88
    elseif name === :field
6,444✔
89
        return getfield(c, :field)::Symbol
54✔
90
    elseif name === :method
5,593✔
91
        return getfield(c, :method)::Method
2,178✔
92
    elseif name === :bslash
429✔
93
        return getfield(c, :bslash)::String
5,164✔
94
    elseif name === :text
429✔
95
        return getfield(c, :text)::String
×
96
    elseif name === :key
429✔
97
        return getfield(c, :key)::String
124✔
98
    elseif name === :kwarg
305✔
99
        return getfield(c, :kwarg)::String
222✔
100
    end
101
    return getfield(c, name)
83✔
102
end
103

104
_completion_text(c::TextCompletion) = c.text
4✔
105
_completion_text(c::KeywordCompletion) = c.keyword
1,707✔
106
_completion_text(c::KeyvalCompletion) = c.keyval
83✔
107
_completion_text(c::PathCompletion) = c.path
6,717✔
108
_completion_text(c::ModuleCompletion) = c.mod
8,135,811✔
109
_completion_text(c::PackageCompletion) = c.package
2,431✔
110
_completion_text(c::PropertyCompletion) = sprint(Base.show_sym, c.property)
124✔
111
_completion_text(c::FieldCompletion) = sprint(Base.show_sym, c.field)
54✔
112
_completion_text(c::MethodCompletion) = repr(c.method)
786✔
113
_completion_text(c::BslashCompletion) = c.bslash
5,164✔
114
_completion_text(c::ShellCompletion) = c.text
×
115
_completion_text(c::DictCompletion) = c.key
124✔
116
_completion_text(c::KeywordArgumentCompletion) = c.kwarg*'='
222✔
117

118
completion_text(c) = _completion_text(c)::String
8,153,227✔
119

120
const Completions = Tuple{Vector{Completion}, UnitRange{Int}, Bool}
121

122
function completes_global(x, name)
1,870,598✔
123
    return startswith(x, name) && !('#' in x)
1,870,598✔
124
end
125

126
function appendmacro!(syms, macros, needle, endchar)
20,916✔
127
    for macsym in macros
39,594✔
128
        s = String(macsym)
51,454✔
129
        if endswith(s, needle)
51,454✔
130
            from = nextind(s, firstindex(s))
3,446✔
131
            to = prevind(s, sizeof(s)-sizeof(needle)+1)
3,446✔
132
            push!(syms, s[from:to]*endchar)
6,892✔
133
        end
134
    end
51,454✔
135
end
136

137
function filtered_mod_names(ffunc::Function, mod::Module, name::AbstractString, all::Bool = false, imported::Bool = false)
10,458✔
138
    ssyms = names(mod, all = all, imported = imported)
19,335✔
139
    all || filter!(Base.Fix1(Base.isexported, mod), ssyms)
19,335✔
140
    filter!(ffunc, ssyms)
10,458✔
141
    macros = filter(x -> startswith(String(x), "@" * name), ssyms)
1,881,056✔
142
    syms = String[sprint((io,s)->Base.show_sym(io, s; allow_macroname=true), s) for s in ssyms if completes_global(String(s), name)]
386,885✔
143
    appendmacro!(syms, macros, "_str", "\"")
10,458✔
144
    appendmacro!(syms, macros, "_cmd", "`")
10,458✔
145
    return [ModuleCompletion(mod, sym) for sym in syms]
10,458✔
146
end
147

148
# REPL Symbol Completions
149
function complete_symbol(@nospecialize(ex), name::String, @nospecialize(ffunc), context_module::Module=Main)
1,644✔
150
    mod = context_module
×
151

152
    lookup_module = true
×
153
    t = Union{}
×
154
    val = nothing
×
155
    if ex !== nothing
1,644✔
156
        res = repl_eval_ex(ex, context_module)
662✔
157
        res === nothing && return Completion[]
662✔
158
        if res isa Const
644✔
159
            val = res.val
627✔
160
            if isa(val, Module)
627✔
161
                mod = val
599✔
162
                lookup_module = true
599✔
163
            else
164
                lookup_module = false
×
165
                t = typeof(val)
655✔
166
            end
167
        else
168
            lookup_module = false
×
169
            t = CC.widenconst(res)
17✔
170
        end
171
    end
172

173
    suggestions = Completion[]
1,626✔
174
    if lookup_module
1,626✔
175
        # We will exclude the results that the user does not want, as well
176
        # as excluding Main.Main.Main, etc., because that's most likely not what
177
        # the user wants
178
        p = let mod=mod, modname=nameof(mod)
1,581✔
179
            (s::Symbol) -> !Base.isdeprecated(mod, s) && s != modname && ffunc(mod, s)::Bool
1,897,795✔
180
        end
181
        # Looking for a binding in a module
182
        if mod == context_module
1,581✔
183
            # Also look in modules we got through `using`
184
            mods = ccall(:jl_module_usings, Any, (Any,), context_module)::Vector
1,292✔
185
            for m in mods
1,292✔
186
                append!(suggestions, filtered_mod_names(p, m::Module, name))
11,955✔
187
            end
10,169✔
188
            append!(suggestions, filtered_mod_names(p, mod, name, true, true))
2,017✔
189
        else
190
            append!(suggestions, filtered_mod_names(p, mod, name, true, false))
1,870✔
191
        end
192
    elseif val !== nothing # looking for a property of an instance
45✔
193
        for property in propertynames(val, false)
27✔
194
            # TODO: support integer arguments (#36872)
195
            if property isa Symbol && startswith(string(property), name)
82✔
196
                push!(suggestions, PropertyCompletion(val, property))
40✔
197
            end
198
        end
42✔
199
    elseif field_completion_eligible(t)
18✔
200
        # Looking for a member of a type
201
        add_field_completions!(suggestions, name, t)
13✔
202
    end
203
    return suggestions
1,626✔
204
end
205

206
function add_field_completions!(suggestions::Vector{Completion}, name::String, @nospecialize(t))
15✔
207
    if isa(t, Union)
15✔
208
        add_field_completions!(suggestions, name, t.a)
2✔
209
        add_field_completions!(suggestions, name, t.b)
2✔
210
    else
211
        @assert isconcretetype(t)
15✔
212
        fields = fieldnames(t)
15✔
213
        for field in fields
15✔
214
            isa(field, Symbol) || continue # Tuple type has ::Int field name
28✔
215
            s = string(field)
28✔
216
            if startswith(s, name)
56✔
217
                push!(suggestions, FieldCompletion(t, field))
24✔
218
            end
219
        end
28✔
220
    end
221
end
222

223
const GENERIC_PROPERTYNAMES_METHOD = which(propertynames, (Any,))
224

225
function field_completion_eligible(@nospecialize t)
4✔
226
    if isa(t, Union)
22✔
227
        return field_completion_eligible(t.a) && field_completion_eligible(t.b)
2✔
228
    end
229
    isconcretetype(t) || return false
23✔
230
    # field completion is correct only when `getproperty` fallbacks to `getfield`
231
    match = Base._which(Tuple{typeof(propertynames),t}; raise=false)
17✔
232
    match === nothing && return false
17✔
233
    return match.method === GENERIC_PROPERTYNAMES_METHOD
17✔
234
end
235

236
function complete_from_list(T::Type, list::Vector{String}, s::Union{String,SubString{String}})
1,255✔
237
    r = searchsorted(list, s)
1,255✔
238
    i = first(r)
1,255✔
239
    n = length(list)
1,255✔
240
    while i <= n && startswith(list[i],s)
2,604✔
241
        r = first(r):i
173✔
242
        i += 1
173✔
243
    end
173✔
244
    Completion[T(kw) for kw in list[r]]
1,255✔
245
end
246

247
const sorted_keywords = [
248
    "abstract type", "baremodule", "begin", "break", "catch", "ccall",
249
    "const", "continue", "do", "else", "elseif", "end", "export",
250
    "finally", "for", "function", "global", "if", "import",
251
    "let", "local", "macro", "module", "mutable struct",
252
    "primitive type", "quote", "return", "struct",
253
    "try", "using", "while"]
254

255
complete_keyword(s::Union{String,SubString{String}}) = complete_from_list(KeywordCompletion, sorted_keywords, s)
543✔
256

257
const sorted_keyvals = ["false", "true"]
258

259
complete_keyval(s::Union{String,SubString{String}}) = complete_from_list(KeyvalCompletion, sorted_keyvals, s)
712✔
260

261
function complete_path(path::AbstractString, pos::Int;
1,136✔
262
                       use_envpath=false, shell_escape=false,
263
                       string_escape=false)
264
    @assert !(shell_escape && string_escape)
568✔
265
    if Base.Sys.isunix() && occursin(r"^~(?:/|$)", path)
568✔
266
        # if the path is just "~", don't consider the expanded username as a prefix
267
        if path == "~"
2✔
268
            dir, prefix = homedir(), ""
2✔
269
        else
270
            dir, prefix = splitdir(homedir() * path[2:end])
2✔
271
        end
272
    else
273
        dir, prefix = splitdir(path)
566✔
274
    end
275
    local files
×
276
    try
568✔
277
        if isempty(dir)
568✔
278
            files = readdir()
302✔
279
        elseif isdir(dir)
266✔
280
            files = readdir(dir)
107✔
281
        else
282
            return Completion[], 0:-1, false
568✔
283
        end
284
    catch
285
        return Completion[], 0:-1, false
×
286
    end
287

288
    matches = Set{String}()
409✔
289
    for file in files
414✔
290
        if startswith(file, prefix)
78,618✔
291
            p = joinpath(dir, file)
6,497✔
292
            is_dir = try isdir(p) catch; false end
19,493✔
293
            push!(matches, is_dir ? joinpath(file, "") : file)
6,497✔
294
        end
295
    end
43,450✔
296

297
    if use_envpath && length(dir) == 0
409✔
298
        # Look for files in PATH as well
299
        local pathdirs = split(ENV["PATH"], @static Sys.iswindows() ? ";" : ":")
18✔
300

301
        for pathdir in pathdirs
18✔
302
            local actualpath
×
303
            try
228✔
304
                actualpath = realpath(pathdir)
260✔
305
            catch
306
                # Bash doesn't expect every folder in PATH to exist, so neither shall we
307
                continue
32✔
308
            end
309

310
            if actualpath != pathdir && in(actualpath,pathdirs)
244✔
311
                # Remove paths which (after resolving links) are in the env path twice.
312
                # Many distros eg. point /bin to /usr/bin but have both in the env path.
313
                continue
48✔
314
            end
315

316
            local filesinpath
×
317
            try
148✔
318
                filesinpath = readdir(pathdir)
149✔
319
            catch e
320
                # Bash allows dirs in PATH that can't be read, so we should as well.
321
                if isa(e, Base.IOError) || isa(e, Base.ArgumentError)
1✔
322
                    continue
1✔
323
                else
324
                    # We only handle IOError and ArgumentError here
325
                    rethrow()
×
326
                end
327
            end
328

329
            for file in filesinpath
195✔
330
                # In a perfect world, we would filter on whether the file is executable
331
                # here, or even on whether the current user can execute the file in question.
332
                if startswith(file, prefix) && isfile(joinpath(pathdir, file))
62,385✔
333
                    push!(matches, file)
589✔
334
                end
335
            end
34,093✔
336
        end
246✔
337
    end
338

339
    function do_escape(s)
409✔
340
        return shell_escape ? replace(s, r"(\s|\\)" => s"\\\0") :
10,514✔
341
               string_escape ? escape_string(s, ('\"','$')) :
342
               s
343
    end
344

345
    matchList = Completion[PathCompletion(do_escape(s)) for s in matches]
7,124✔
346
    startpos = pos - lastindex(do_escape(prefix)) + 1
543✔
347
    # The pos - lastindex(prefix) + 1 is correct due to `lastindex(prefix)-lastindex(prefix)==0`,
348
    # hence we need to add one to get the first index. This is also correct when considering
349
    # pos, because pos is the `lastindex` a larger string which `endswith(path)==true`.
350
    return matchList, startpos:pos, !isempty(matchList)
409✔
351
end
352

353
function complete_expanduser(path::AbstractString, r)
138✔
354
    expanded =
138✔
355
        try expanduser(path)
139✔
356
        catch e
357
            e isa ArgumentError || rethrow()
1✔
358
            path
139✔
359
        end
360
    return Completion[PathCompletion(expanded)], r, path != expanded
138✔
361
end
362

363
# Returns a range that includes the method name in front of the first non
364
# closed start brace from the end of the string.
365
function find_start_brace(s::AbstractString; c_start='(', c_end=')')
5,294✔
366
    r = reverse(s)
2,647✔
367
    i = firstindex(r)
×
368
    braces = in_comment = 0
×
369
    in_single_quotes = in_double_quotes = in_back_ticks = false
×
370
    while i <= ncodeunits(r)
61,404✔
371
        c, i = iterate(r, i)
118,838✔
372
        if c == '#' && i <= ncodeunits(r) && iterate(r, i)[1] == '='
59,419✔
373
            c, i = iterate(r, i) # consume '='
8✔
374
            new_comments = 1
×
375
            # handle #=#=#=#, by counting =# pairs
376
            while i <= ncodeunits(r) && iterate(r, i)[1] == '#'
6✔
377
                c, i = iterate(r, i) # consume '#'
6✔
378
                iterate(r, i)[1] == '=' || break
3✔
379
                c, i = iterate(r, i) # consume '='
4✔
380
                new_comments += 1
2✔
381
            end
2✔
382
            if c == '='
4✔
383
                in_comment += new_comments
3✔
384
            else
385
                in_comment -= new_comments
5✔
386
            end
387
        elseif !in_single_quotes && !in_double_quotes && !in_back_ticks && in_comment == 0
59,415✔
388
            if c == c_start
57,680✔
389
                braces += 1
911✔
390
            elseif c == c_end
56,769✔
391
                braces -= 1
249✔
392
            elseif c == '\''
56,520✔
393
                in_single_quotes = true
15✔
394
            elseif c == '"'
56,505✔
395
                in_double_quotes = true
271✔
396
            elseif c == '`'
56,234✔
397
                in_back_ticks = true
57,680✔
398
            end
399
        else
400
            if in_single_quotes &&
1,735✔
401
                c == '\'' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
402
                in_single_quotes = false
15✔
403
            elseif in_double_quotes &&
1,720✔
404
                c == '"' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
405
                in_double_quotes = false
228✔
406
            elseif in_back_ticks &&
1,492✔
407
                c == '`' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
408
                in_back_ticks = false
7✔
409
            elseif in_comment > 0 &&
1,485✔
410
                c == '=' && i <= ncodeunits(r) && iterate(r, i)[1] == '#'
411
                # handle =#=#=#=, by counting #= pairs
412
                c, i = iterate(r, i) # consume '#'
6✔
413
                old_comments = 1
×
414
                while i <= ncodeunits(r) && iterate(r, i)[1] == '='
4✔
415
                    c, i = iterate(r, i) # consume '='
4✔
416
                    iterate(r, i)[1] == '#' || break
2✔
417
                    c, i = iterate(r, i) # consume '#'
2✔
418
                    old_comments += 1
1✔
419
                end
1✔
420
                if c == '#'
3✔
421
                    in_comment -= old_comments
2✔
422
                else
423
                    in_comment += old_comments
1✔
424
                end
425
            end
426
        end
427
        braces == 1 && break
59,419✔
428
    end
58,757✔
429
    braces != 1 && return 0:-1, -1
2,647✔
430
    method_name_end = reverseind(s, i)
662✔
431
    startind = nextind(s, something(findprev(in(non_identifier_chars), s, method_name_end), 0))::Int
1,025✔
432
    return (startind:lastindex(s), method_name_end)
662✔
433
end
434

435
struct REPLInterpreterCache
436
    dict::IdDict{MethodInstance,CodeInstance}
×
437
end
438
REPLInterpreterCache() = REPLInterpreterCache(IdDict{MethodInstance,CodeInstance}())
×
439
const REPL_INTERPRETER_CACHE = REPLInterpreterCache()
440

441
function get_code_cache()
×
442
    # XXX Avoid storing analysis results into the cache that persists across precompilation,
443
    #     as [sys|pkg]image currently doesn't support serializing externally created `CodeInstance`.
444
    #     Otherwise, `CodeInstance`s created by `REPLInterpreter`, that are much less optimized
445
    #     that those produced by `NativeInterpreter`, will leak into the native code cache,
446
    #     potentially causing runtime slowdown.
447
    #     (see https://github.com/JuliaLang/julia/issues/48453).
448
    if Base.generating_output()
481✔
449
        return REPLInterpreterCache()
×
450
    else
451
        return REPL_INTERPRETER_CACHE
481✔
452
    end
453
end
454

455
struct REPLInterpreter <: CC.AbstractInterpreter
456
    repl_frame::CC.InferenceResult
457
    world::UInt
458
    inf_params::CC.InferenceParams
459
    opt_params::CC.OptimizationParams
460
    inf_cache::Vector{CC.InferenceResult}
461
    code_cache::REPLInterpreterCache
462
    function REPLInterpreter(repl_frame::CC.InferenceResult;
962✔
463
                             world::UInt = Base.get_world_counter(),
464
                             inf_params::CC.InferenceParams = CC.InferenceParams(;
465
                                unoptimize_throw_blocks=false),
466
                             opt_params::CC.OptimizationParams = CC.OptimizationParams(),
467
                             inf_cache::Vector{CC.InferenceResult} = CC.InferenceResult[],
468
                             code_cache::REPLInterpreterCache = get_code_cache())
469
        return new(repl_frame, world, inf_params, opt_params, inf_cache, code_cache)
481✔
470
    end
471
end
472
CC.InferenceParams(interp::REPLInterpreter) = interp.inf_params
127,069✔
473
CC.OptimizationParams(interp::REPLInterpreter) = interp.opt_params
×
474
CC.get_world_counter(interp::REPLInterpreter) = interp.world
35,035✔
475
CC.get_inference_cache(interp::REPLInterpreter) = interp.inf_cache
11,776✔
476
CC.code_cache(interp::REPLInterpreter) = CC.WorldView(interp.code_cache, CC.WorldRange(interp.world))
31,297✔
477
CC.get(wvc::CC.WorldView{REPLInterpreterCache}, mi::MethodInstance, default) = get(wvc.cache.dict, mi, default)
43,179✔
478
CC.getindex(wvc::CC.WorldView{REPLInterpreterCache}, mi::MethodInstance) = getindex(wvc.cache.dict, mi)
×
479
CC.haskey(wvc::CC.WorldView{REPLInterpreterCache}, mi::MethodInstance) = haskey(wvc.cache.dict, mi)
3,860✔
480
CC.setindex!(wvc::CC.WorldView{REPLInterpreterCache}, ci::CodeInstance, mi::MethodInstance) = setindex!(wvc.cache.dict, ci, mi)
3,860✔
481

482
# REPLInterpreter is only used for type analysis, so it should disable optimization entirely
483
CC.may_optimize(::REPLInterpreter) = false
×
484

485
# REPLInterpreter analyzes a top-level frame, so better to not bail out from it
486
CC.bail_out_toplevel_call(::REPLInterpreter, ::CC.InferenceLoopState, ::CC.InferenceState) = false
×
487

488
# `REPLInterpreter` aggressively resolves global bindings to enable reasonable completions
489
# for lines like `Mod.a.|` (where `|` is the cursor position).
490
# Aggressive binding resolution poses challenges for the inference cache validation
491
# (until https://github.com/JuliaLang/julia/issues/40399 is implemented).
492
# To avoid the cache validation issues, `REPLInterpreter` only allows aggressive binding
493
# resolution for top-level frame representing REPL input code (`repl_frame`) and for child
494
# `getproperty` frames that are constant propagated from the `repl_frame`. This works, since
495
# a.) these frames are never cached, and
496
# b.) their results are only observed by the non-cached `repl_frame`.
497
#
498
# `REPLInterpreter` also aggressively concrete evaluate `:inconsistent` calls within
499
# `repl_frame` to provide reasonable completions for lines like `Ref(Some(42))[].|`.
500
# Aggressive concrete evaluation allows us to get accurate type information about complex
501
# expressions that otherwise can not be constant folded, in a safe way, i.e. it still
502
# doesn't evaluate effectful expressions like `pop!(xs)`.
503
# Similarly to the aggressive binding resolution, aggressive concrete evaluation doesn't
504
# present any cache validation issues because `repl_frame` is never cached.
505

506
is_repl_frame(interp::REPLInterpreter, sv::CC.InferenceState) = interp.repl_frame === sv.result
59,599✔
507

508
# aggressive global binding resolution within `repl_frame`
509
function CC.abstract_eval_globalref(interp::REPLInterpreter, g::GlobalRef,
×
510
                                    sv::CC.InferenceState)
511
    if is_repl_frame(interp, sv)
38,059✔
512
        if CC.isdefined_globalref(g)
640✔
513
            return Const(ccall(:jl_get_globalref_value, Any, (Any,), g))
635✔
514
        end
515
        return Union{}
5✔
516
    end
517
    return @invoke CC.abstract_eval_globalref(interp::CC.AbstractInterpreter, g::GlobalRef,
37,419✔
518
                                              sv::CC.InferenceState)
519
end
520

521
function is_repl_frame_getproperty(interp::REPLInterpreter, sv::CC.InferenceState)
67✔
522
    def = sv.linfo.def
67✔
523
    def isa Method || return false
67✔
524
    def.name === :getproperty || return false
67✔
525
    sv.cached && return false
67✔
526
    return is_repl_frame(interp, sv.parent)
66✔
527
end
528

529
# aggressive global binding resolution for `getproperty(::Module, ::Symbol)` calls within `repl_frame`
530
function CC.builtin_tfunction(interp::REPLInterpreter, @nospecialize(f),
11,806✔
531
                              argtypes::Vector{Any}, sv::CC.InferenceState)
532
    if f === Core.getglobal && is_repl_frame_getproperty(interp, sv)
11,806✔
533
        if length(argtypes) == 2
43✔
534
            a1, a2 = argtypes
43✔
535
            if isa(a1, Const) && isa(a2, Const)
43✔
536
                a1val, a2val = a1.val, a2.val
43✔
537
                if isa(a1val, Module) && isa(a2val, Symbol)
43✔
538
                    g = GlobalRef(a1val, a2val)
43✔
539
                    if CC.isdefined_globalref(g)
43✔
540
                        return Const(ccall(:jl_get_globalref_value, Any, (Any,), g))
43✔
541
                    end
542
                    return Union{}
×
543
                end
544
            end
545
        end
546
    end
547
    return @invoke CC.builtin_tfunction(interp::CC.AbstractInterpreter, f::Any,
11,763✔
548
                                        argtypes::Vector{Any}, sv::CC.InferenceState)
549
end
550

551
# aggressive concrete evaluation for `:inconsistent` frames within `repl_frame`
552
function CC.concrete_eval_eligible(interp::REPLInterpreter, @nospecialize(f),
×
553
                                   result::CC.MethodCallResult, arginfo::CC.ArgInfo,
554
                                   sv::CC.InferenceState)
555
    if is_repl_frame(interp, sv)
21,474✔
556
        neweffects = CC.Effects(result.effects; consistent=CC.ALWAYS_TRUE)
162✔
557
        result = CC.MethodCallResult(result.rt, result.edgecycle, result.edgelimited,
324✔
558
                                     result.edge, neweffects)
559
    end
560
    return @invoke CC.concrete_eval_eligible(interp::CC.AbstractInterpreter, f::Any,
21,474✔
561
                                             result::CC.MethodCallResult, arginfo::CC.ArgInfo,
562
                                             sv::CC.InferenceState)
563
end
564

565
function resolve_toplevel_symbols!(src::Core.CodeInfo, mod::Module)
×
566
    @ccall jl_resolve_globals_in_ir(
481✔
567
        #=jl_array_t *stmts=# src.code::Any,
568
        #=jl_module_t *m=# mod::Any,
569
        #=jl_svec_t *sparam_vals=# Core.svec()::Any,
570
        #=int binding_effects=# 0::Int)::Cvoid
571
    return src
×
572
end
573

574
# lower `ex` and run type inference on the resulting top-level expression
575
function repl_eval_ex(@nospecialize(ex), context_module::Module)
1,286✔
576
    if isexpr(ex, :toplevel) || isexpr(ex, :tuple)
2,571✔
577
        # get the inference result for the last expression
578
        ex = ex.args[end]
3✔
579
    end
580
    lwr = try
1,286✔
581
        Meta.lower(context_module, ex)
1,299✔
582
    catch # macro expansion failed, etc.
583
        return nothing
13✔
584
    end
585
    if lwr isa Symbol
1,273✔
586
        return isdefined(context_module, lwr) ? Const(getfield(context_module, lwr)) : nothing
670✔
587
    end
588
    lwr isa Expr || return Const(lwr) # `ex` is literal
695✔
589
    isexpr(lwr, :thunk) || return nothing # lowered to `Expr(:error, ...)` or similar
541✔
590
    src = lwr.args[1]::Core.CodeInfo
481✔
591

592
    # construct top-level `MethodInstance`
593
    mi = ccall(:jl_new_method_instance_uninit, Ref{Core.MethodInstance}, ());
481✔
594
    mi.specTypes = Tuple{}
481✔
595

596
    mi.def = context_module
481✔
597
    resolve_toplevel_symbols!(src, context_module)
481✔
598
    @atomic mi.uninferred = src
481✔
599

600
    result = CC.InferenceResult(mi)
481✔
601
    interp = REPLInterpreter(result)
962✔
602
    frame = CC.InferenceState(result, src, #=cache=#:no, interp)::CC.InferenceState
481✔
603

604
    # NOTE Use the fixed world here to make `REPLInterpreter` robust against
605
    #      potential invalidations of `Core.Compiler` methods.
606
    Base.invoke_in_world(COMPLETION_WORLD[], CC.typeinf, interp, frame)
481✔
607

608
    result = frame.result.result
481✔
609
    result === Union{} && return nothing # for whatever reason, callers expect this as the Bottom and/or Top type instead
481✔
610
    return result
472✔
611
end
612

613
# `COMPLETION_WORLD[]` will be initialized within `__init__`
614
# (to allow us to potentially remove REPL from the sysimage in the future).
615
# Note that inference from the `code_typed` call below will use the current world age
616
# rather than `typemax(UInt)`, since `Base.invoke_in_world` uses the current world age
617
# when the given world age is higher than the current one.
618
const COMPLETION_WORLD = Ref{UInt}(typemax(UInt))
619

620
# Generate code cache for `REPLInterpreter` now:
621
# This code cache will be available at the world of `COMPLETION_WORLD`,
622
# assuming no invalidation will happen before initializing REPL.
623
# Once REPL is loaded, `REPLInterpreter` will be resilient against future invalidations.
624
code_typed(CC.typeinf, (REPLInterpreter, CC.InferenceState))
625

626
# Method completion on function call expression that look like :(max(1))
627
MAX_METHOD_COMPLETIONS::Int = 40
628
function _complete_methods(ex_org::Expr, context_module::Module, shift::Bool)
309✔
629
    funct = repl_eval_ex(ex_org.args[1], context_module)
309✔
630
    funct === nothing && return 2, nothing, [], Set{Symbol}()
309✔
631
    funct = CC.widenconst(funct)
305✔
632
    args_ex, kwargs_ex, kwargs_flag = complete_methods_args(ex_org, context_module, true, true)
604✔
633
    return kwargs_flag, funct, args_ex, kwargs_ex
305✔
634
end
635

636
function complete_methods(ex_org::Expr, context_module::Module=Main, shift::Bool=false)
×
637
    kwargs_flag, funct, args_ex, kwargs_ex = _complete_methods(ex_org, context_module, shift)::Tuple{Int, Any, Vector{Any}, Set{Symbol}}
138✔
638
    out = Completion[]
138✔
639
    kwargs_flag == 2 && return out # one of the kwargs is invalid
138✔
640
    kwargs_flag == 0 && push!(args_ex, Vararg{Any}) # allow more arguments if there is no semicolon
126✔
641
    complete_methods!(out, funct, args_ex, kwargs_ex, shift ? -2 : MAX_METHOD_COMPLETIONS, kwargs_flag == 1)
183✔
642
    return out
126✔
643
end
644

645
MAX_ANY_METHOD_COMPLETIONS::Int = 10
646
function complete_any_methods(ex_org::Expr, callee_module::Module, context_module::Module, moreargs::Bool, shift::Bool)
13✔
647
    out = Completion[]
13✔
648
    args_ex, kwargs_ex, kwargs_flag = try
13✔
649
        # this may throw, since we set default_any to false
650
        complete_methods_args(ex_org, context_module, false, false)
13✔
651
    catch ex
652
        ex isa ArgumentError || rethrow()
×
653
        return out
13✔
654
    end
655
    kwargs_flag == 2 && return out # one of the kwargs is invalid
13✔
656

657
    # moreargs determines whether to accept more args, independently of the presence of a
658
    # semicolon for the ".?(" syntax
659
    moreargs && push!(args_ex, Vararg{Any})
13✔
660

661
    seen = Base.IdSet()
13✔
662
    for name in names(callee_module; all=true)
13✔
663
        if !Base.isdeprecated(callee_module, name) && isdefined(callee_module, name) && !startswith(string(name), '#')
1,313✔
664
            func = getfield(callee_module, name)
637✔
665
            if !isa(func, Module)
637✔
666
                funct = Core.Typeof(func)
1,157✔
667
                if !in(funct, seen)
624✔
668
                    push!(seen, funct)
598✔
669
                    complete_methods!(out, funct, args_ex, kwargs_ex, MAX_ANY_METHOD_COMPLETIONS, false)
598✔
670
                end
671
            elseif callee_module === Main && isa(func, Module)
26✔
672
                callee_module2 = func
×
673
                for name in names(callee_module2)
×
674
                    if !Base.isdeprecated(callee_module2, name) && isdefined(callee_module2, name) && !startswith(string(name), '#')
×
675
                        func = getfield(callee_module, name)
×
676
                        if !isa(func, Module)
×
677
                            funct = Core.Typeof(func)
×
678
                            if !in(funct, seen)
×
679
                                push!(seen, funct)
×
680
                                complete_methods!(out, funct, args_ex, kwargs_ex, MAX_ANY_METHOD_COMPLETIONS, false)
×
681
                            end
682
                        end
683
                    end
684
                end
×
685
            end
686
        end
687
    end
1,326✔
688

689
    if !shift
13✔
690
        # Filter out methods where all arguments are `Any`
691
        filter!(out) do c
2✔
692
            isa(c, TextCompletion) && return false
11✔
693
            isa(c, MethodCompletion) || return true
11✔
694
            sig = Base.unwrap_unionall(c.method.sig)::DataType
11✔
695
            return !all(T -> T === Any || T === Vararg{Any}, sig.parameters[2:end])
19✔
696
        end
697
    end
698

699
    return out
13✔
700
end
701

702
function detect_invalid_kwarg!(kwargs_ex::Vector{Symbol}, @nospecialize(x), kwargs_flag::Int, possible_splat::Bool)
×
703
    n = isexpr(x, :kw) ? x.args[1] : x
57✔
704
    if n isa Symbol
55✔
705
        push!(kwargs_ex, n)
41✔
706
        return kwargs_flag
41✔
707
    end
708
    possible_splat && isexpr(x, :...) && return kwargs_flag
14✔
709
    return 2 # The kwarg is invalid
10✔
710
end
711

712
function detect_args_kwargs(funargs::Vector{Any}, context_module::Module, default_any::Bool, broadcasting::Bool)
318✔
713
    args_ex = Any[]
318✔
714
    kwargs_ex = Symbol[]
318✔
715
    kwargs_flag = 0
×
716
    # kwargs_flag is:
717
    # * 0 if there is no semicolon and no invalid kwarg
718
    # * 1 if there is a semicolon and no invalid kwarg
719
    # * 2 if there are two semicolons or more, or if some kwarg is invalid, which
720
    #        means that it is not of the form "bar=foo", "bar" or "bar..."
721
    for i in (1+!broadcasting):length(funargs)
482✔
722
        ex = funargs[i]
295✔
723
        if isexpr(ex, :parameters)
460✔
724
            kwargs_flag = ifelse(kwargs_flag == 0, 1, 2) # there should be at most one :parameters
59✔
725
            for x in ex.args
89✔
726
                kwargs_flag = detect_invalid_kwarg!(kwargs_ex, x, kwargs_flag, true)
46✔
727
            end
62✔
728
        elseif isexpr(ex, :kw)
401✔
729
            kwargs_flag = detect_invalid_kwarg!(kwargs_ex, ex, kwargs_flag, false)
22✔
730
        else
731
            if broadcasting
214✔
732
                # handle broadcasting, but only handle number of arguments instead of
733
                # argument types
734
                push!(args_ex, Any)
5✔
735
            else
736
                argt = repl_eval_ex(ex, context_module)
209✔
737
                if argt !== nothing
209✔
738
                    push!(args_ex, CC.widenconst(argt))
183✔
739
                elseif default_any
26✔
740
                    push!(args_ex, Any)
26✔
741
                else
742
                    throw(ArgumentError("argument not found"))
×
743
                end
744
            end
745
        end
746
    end
426✔
747
    return args_ex, Set{Symbol}(kwargs_ex), kwargs_flag
318✔
748
end
749

750
is_broadcasting_expr(ex::Expr) = ex.head === :. && isexpr(ex.args[2], :tuple)
1,781✔
751

752
function complete_methods_args(ex::Expr, context_module::Module, default_any::Bool, allow_broadcasting::Bool)
×
753
    if allow_broadcasting && is_broadcasting_expr(ex)
305✔
754
        return detect_args_kwargs((ex.args[2]::Expr).args, context_module, default_any, true)
6✔
755
    end
756
    return detect_args_kwargs(ex.args, context_module, default_any, false)
312✔
757
end
758

759
function complete_methods!(out::Vector{Completion}, @nospecialize(funct), args_ex::Vector{Any}, kwargs_ex::Set{Symbol}, max_method_completions::Int, exact_nargs::Bool)
893✔
760
    # Input types and number of arguments
761
    t_in = Tuple{funct, args_ex...}
893✔
762
    m = Base._methods_by_ftype(t_in, nothing, max_method_completions, Base.get_world_counter(),
893✔
763
        #=ambig=# true, Ref(typemin(UInt)), Ref(typemax(UInt)), Ptr{Int32}(C_NULL))
764
    if !isa(m, Vector)
893✔
765
        push!(out, TextCompletion(sprint(Base.show_signature_function, funct) * "( too many methods, use SHIFT-TAB to show )"))
4✔
766
        return
4✔
767
    end
768
    for match in m
1,353✔
769
        # TODO: if kwargs_ex, filter out methods without kwargs?
770
        push!(out, MethodCompletion(match.spec_types, match.method))
2,176✔
771
    end
2,176✔
772
    # TODO: filter out methods with wrong number of arguments if `exact_nargs` is set
773
end
774

775
include("latex_symbols.jl")
776
include("emoji_symbols.jl")
777

778
const non_identifier_chars = [" \t\n\r\"\\'`\$><=:;|&{}()[],+-*/?%^~"...]
779
const whitespace_chars = [" \t\n\r"...]
780
# "\"'`"... is added to whitespace_chars as non of the bslash_completions
781
# characters contain any of these characters. It prohibits the
782
# bslash_completions function to try and complete on escaped characters in strings
783
const bslash_separators = [whitespace_chars..., "\"'`"...]
784

785
const subscripts = Dict(k[3]=>v[1] for (k,v) in latex_symbols if startswith(k, "\\_") && length(k)==3)
786
const subscript_regex = Regex("^\\\\_[" * join(isdigit(k) || isletter(k) ? "$k" : "\\$k" for k in keys(subscripts)) * "]+\\z")
787
const superscripts = Dict(k[3]=>v[1] for (k,v) in latex_symbols if startswith(k, "\\^") && length(k)==3)
788
const superscript_regex = Regex("^\\\\\\^[" * join(isdigit(k) || isletter(k) ? "$k" : "\\$k" for k in keys(superscripts)) * "]+\\z")
789

790
# Aux function to detect whether we're right after a
791
# using or import keyword
792
function afterusing(string::String, startpos::Int)
1,472✔
793
    (isempty(string) || startpos == 0) && return false
1,472✔
794
    str = string[1:prevind(string,startpos)]
2,627✔
795
    isempty(str) && return false
1,469✔
796
    rstr = reverse(str)
1,158✔
797
    r = findfirst(r"\s(gnisu|tropmi)\b", rstr)
1,158✔
798
    r === nothing && return false
1,158✔
799
    fr = reverseind(str, last(r))
30✔
800
    return occursin(r"^\b(using|import)\s*((\w+[.])*\w+\s*,\s*)*$", str[fr:end])
30✔
801
end
802

803
function close_path_completion(str, startpos, r, paths, pos)
135✔
804
    length(paths) == 1 || return false  # Only close if there's a single choice...
254✔
805
    _path = str[startpos:prevind(str, first(r))] * (paths[1]::PathCompletion).path
25✔
806
    path = expanduser(unescape_string(replace(_path, "\\\$"=>"\$", "\\\""=>"\"")))
16✔
807
    # ...except if it's a directory...
808
    try
16✔
809
        isdir(path)
17✔
810
    catch e
811
        e isa Base.IOError || rethrow() # `path` cannot be determined to be a file
17✔
812
    end && return false
813
    # ...and except if there's already a " at the cursor.
814
    return lastindex(str) <= pos || str[nextind(str, pos)] != '"'
6✔
815
end
816

817
function bslash_completions(string::String, pos::Int)
1,887✔
818
    slashpos = something(findprev(isequal('\\'), string, pos), 0)
1,937✔
819
    if (something(findprev(in(bslash_separators), string, pos), 0) < slashpos &&
1,915✔
820
        !(1 < slashpos && (string[prevind(string, slashpos)]=='\\')))
821
        # latex / emoji symbol substitution
822
        s = string[slashpos:pos]
96✔
823
        latex = get(latex_symbols, s, "")
69✔
824
        if !isempty(latex) # complete an exact match
48✔
825
            return (true, (Completion[BslashCompletion(latex)], slashpos:pos, true))
21✔
826
        elseif occursin(subscript_regex, s)
27✔
827
            sub = map(c -> subscripts[c], s[3:end])
8✔
828
            return (true, (Completion[BslashCompletion(sub)], slashpos:pos, true))
1✔
829
        elseif occursin(superscript_regex, s)
26✔
830
            sup = map(c -> superscripts[c], s[3:end])
8✔
831
            return (true, (Completion[BslashCompletion(sup)], slashpos:pos, true))
1✔
832
        end
833
        emoji = get(emoji_symbols, s, "")
27✔
834
        if !isempty(emoji)
25✔
835
            return (true, (Completion[BslashCompletion(emoji)], slashpos:pos, true))
2✔
836
        end
837
        # return possible matches; these cannot be mixed with regular
838
        # Julian completions as only latex / emoji symbols contain the leading \
839
        if startswith(s, "\\:") # emoji
44✔
840
            namelist = Iterators.filter(k -> startswith(k, s), keys(emoji_symbols))
2,371✔
841
        else # latex
842
            namelist = Iterators.filter(k -> startswith(k, s), keys(latex_symbols))
104,984✔
843
        end
844
        return (true, (Completion[BslashCompletion(name) for name in sort!(collect(namelist))], slashpos:pos, true))
23✔
845
    end
846
    return (false, (Completion[], 0:-1, false))
1,839✔
847
end
848

849
function dict_identifier_key(str::String, tag::Symbol, context_module::Module=Main)
2,021✔
850
    if tag === :string
2,021✔
851
        str_close = str*"\""
158✔
852
    elseif tag === :cmd
1,862✔
853
        str_close = str*"`"
5✔
854
    else
855
        str_close = str
×
856
    end
857
    frange, end_of_identifier = find_start_brace(str_close, c_start='[', c_end=']')
2,020✔
858
    isempty(frange) && return (nothing, nothing, nothing)
2,020✔
859
    objstr = str[1:end_of_identifier]
212✔
860
    objex = Meta.parse(objstr, raise=false, depwarn=false)
106✔
861
    objt = repl_eval_ex(objex, context_module)
106✔
862
    isa(objt, Core.Const) || return (nothing, nothing, nothing)
130✔
863
    obj = objt.val
82✔
864
    isa(obj, AbstractDict) || return (nothing, nothing, nothing)
83✔
865
    length(obj)::Int < 1_000_000 || return (nothing, nothing, nothing)
81✔
866
    begin_of_key = something(findnext(!isspace, str, nextind(str, end_of_identifier) + 1), # +1 for [
156✔
867
                             lastindex(str)+1)
868
    return (obj, str[begin_of_key:end], begin_of_key)
81✔
869
end
870

871
# This needs to be a separate non-inlined function, see #19441
872
@noinline function find_dict_matches(identifier::AbstractDict, partial_key)
80✔
873
    matches = String[]
80✔
874
    for key in keys(identifier)
122✔
875
        rkey = repr(key)
933✔
876
        startswith(rkey,partial_key) && push!(matches,rkey)
1,584✔
877
    end
1,448✔
878
    return matches
80✔
879
end
880

881
# Identify an argument being completed in a method call. If the argument is empty, method
882
# suggestions will be provided instead of argument completions.
883
function identify_possible_method_completion(partial, last_idx)
2,604✔
884
    fail = 0:-1, Expr(:nothing), 0:-1, 0
2,604✔
885

886
    # First, check that the last punctuation is either ',', ';' or '('
887
    idx_last_punct = something(findprev(x -> ispunct(x) && x != '_' && x != '!', partial, last_idx), 0)::Int
18,514✔
888
    idx_last_punct == 0 && return fail
2,604✔
889
    last_punct = partial[idx_last_punct]
4,224✔
890
    last_punct == ',' || last_punct == ';' || last_punct == '(' || return fail
4,014✔
891

892
    # Then, check that `last_punct` is only followed by an identifier or nothing
893
    before_last_word_start = something(findprev(in(non_identifier_chars), partial, last_idx), 0)
1,414✔
894
    before_last_word_start == 0 && return fail
707✔
895
    all(isspace, @view partial[nextind(partial, idx_last_punct):before_last_word_start]) || return fail
787✔
896

897
    # Check that `last_punct` is either the last '(' or placed after a previous '('
898
    frange, method_name_end = find_start_brace(@view partial[1:idx_last_punct])
627✔
899
    method_name_end ∈ frange || return fail
772✔
900

901
    # Strip the preceding ! operators, if any, and close the expression with a ')'
902
    s = replace(partial[frange], r"\G\!+([^=\(]+)" => s"\1"; count=1) * ')'
964✔
903
    ex = Meta.parse(s, raise=false, depwarn=false)
482✔
904
    isa(ex, Expr) || return fail
482✔
905

906
    # `wordrange` is the position of the last argument to complete
907
    wordrange = nextind(partial, before_last_word_start):last_idx
620✔
908
    return frange, ex, wordrange, method_name_end
482✔
909
end
910

911
# Provide completion for keyword arguments in function calls
912
function complete_keyword_argument(partial, last_idx, context_module)
1,641✔
913
    frange, ex, wordrange, = identify_possible_method_completion(partial, last_idx)
1,641✔
914
    fail = Completion[], 0:-1, frange
1,641✔
915
    ex.head === :call || is_broadcasting_expr(ex) || return fail
3,114✔
916

917
    kwargs_flag, funct, args_ex, kwargs_ex = _complete_methods(ex, context_module, true)::Tuple{Int, Any, Vector{Any}, Set{Symbol}}
171✔
918
    kwargs_flag == 2 && return fail # one of the previous kwargs is invalid
171✔
919

920
    methods = Completion[]
169✔
921
    complete_methods!(methods, funct, Any[Vararg{Any}], kwargs_ex, -1, kwargs_flag == 1)
169✔
922
    # TODO: use args_ex instead of Any[Vararg{Any}] and only provide kwarg completion for
923
    # method calls compatible with the current arguments.
924

925
    # For each method corresponding to the function call, provide completion suggestions
926
    # for each keyword that starts like the last word and that is not already used
927
    # previously in the expression. The corresponding suggestion is "kwname=".
928
    # If the keyword corresponds to an existing name, also include "kwname" as a suggestion
929
    # since the syntax "foo(; kwname)" is equivalent to "foo(; kwname=kwname)".
930
    last_word = partial[wordrange] # the word to complete
338✔
931
    kwargs = Set{String}()
169✔
932
    for m in methods
169✔
933
        m::MethodCompletion
1,381✔
934
        possible_kwargs = Base.kwarg_decl(m.method)
1,381✔
935
        current_kwarg_candidates = String[]
1,381✔
936
        for _kw in possible_kwargs
2,504✔
937
            kw = String(_kw)
472✔
938
            if !endswith(kw, "...") && startswith(kw, last_word) && _kw ∉ kwargs_ex
797✔
939
                push!(current_kwarg_candidates, kw)
67✔
940
            end
941
        end
730✔
942
        union!(kwargs, current_kwarg_candidates)
1,381✔
943
    end
1,550✔
944

945
    suggestions = Completion[KeywordArgumentCompletion(kwarg) for kwarg in kwargs]
216✔
946
    append!(suggestions, complete_symbol(nothing, last_word, Returns(true), context_module))
169✔
947
    append!(suggestions, complete_keyval(last_word))
169✔
948

949
    return sort!(suggestions, by=completion_text), wordrange
169✔
950
end
951

952
function project_deps_get_completion_candidates(pkgstarts::String, project_file::String)
1✔
953
    loading_candidates = String[]
1✔
954
    d = Base.parsed_toml(project_file)
1✔
955
    pkg = get(d, "name", nothing)::Union{String, Nothing}
2✔
956
    if pkg !== nothing && startswith(pkg, pkgstarts)
2✔
957
        push!(loading_candidates, pkg)
1✔
958
    end
959
    deps = get(d, "deps", nothing)::Union{Dict{String, Any}, Nothing}
2✔
960
    if deps !== nothing
1✔
961
        for (pkg, _) in deps
2✔
962
            startswith(pkg, pkgstarts) && push!(loading_candidates, pkg)
2✔
963
        end
1✔
964
    end
965
    return Completion[PackageCompletion(name) for name in loading_candidates]
1✔
966
end
967

968
function complete_identifiers!(suggestions::Vector{Completion}, @nospecialize(ffunc::Function), context_module::Module, string::String, name::String, pos::Int, dotpos::Int, startpos::Int, comp_keywords=false)
1,475✔
969
    ex = nothing
3✔
970
    if comp_keywords
1,475✔
971
        append!(suggestions, complete_keyword(name))
543✔
972
        append!(suggestions, complete_keyval(name))
543✔
973
    end
974
    if dotpos > 1 && string[dotpos] == '.'
2,587✔
975
        s = string[1:dotpos-1]
1,324✔
976
        # First see if the whole string up to `pos` is a valid expression. If so, use it.
977
        ex = Meta.parse(s, raise=false, depwarn=false)
662✔
978
        if isexpr(ex, :incomplete)
707✔
979
            s = string[startpos:pos]
1,090✔
980
            # Heuristic to find the start of the expression. TODO: This would be better
981
            # done with a proper error-recovering parser.
982
            if 0 < startpos <= lastindex(string) && string[startpos] == '.'
1,090✔
983
                i = prevind(string, startpos)
1✔
984
                while 0 < i
1✔
985
                    c = string[i]
2✔
986
                    if c in (')', ']')
4✔
987
                        if c == ')'
×
988
                            c_start = '('
×
989
                            c_end = ')'
×
990
                        elseif c == ']'
×
991
                            c_start = '['
×
992
                            c_end = ']'
×
993
                        end
994
                        frange, end_of_identifier = find_start_brace(string[1:prevind(string, i)], c_start=c_start, c_end=c_end)
×
995
                        isempty(frange) && break # unbalanced parens
×
996
                        startpos = first(frange)
×
997
                        i = prevind(string, startpos)
×
998
                    elseif c in ('\'', '\"', '\`')
6✔
999
                        s = "$c$c"*string[startpos:pos]
×
1000
                        break
×
1001
                    else
1002
                        break
×
1003
                    end
1004
                    s = string[startpos:pos]
×
1005
                end
×
1006
            end
1007
            if something(findlast(in(non_identifier_chars), s), 0) < something(findlast(isequal('.'), s), 0)
1,090✔
1008
                lookup_name, name = rsplit(s, ".", limit=2)
545✔
1009
                name = String(name)
545✔
1010

1011
                ex = Meta.parse(lookup_name, raise=false, depwarn=false)
545✔
1012
            end
1013
            isexpr(ex, :incomplete) && (ex = nothing)
873✔
1014
        end
1015
    end
1016
    append!(suggestions, complete_symbol(ex, name, ffunc, context_module))
1,475✔
1017
    return sort!(unique(suggestions), by=completion_text), (dotpos+1):pos, true
1,475✔
1018
end
1019

1020
function completions(string::String, pos::Int, context_module::Module=Main, shift::Bool=true)
2,378✔
1021
    # First parse everything up to the current position
1022
    partial = string[1:pos]
4,409✔
1023
    inc_tag = Base.incomplete_tag(Meta.parse(partial, raise=false, depwarn=false))
2,032✔
1024

1025
    # ?(x, y)TAB lists methods you can call with these objects
1026
    # ?(x, y TAB lists methods that take these objects as the first two arguments
1027
    # MyModule.?(x, y)TAB restricts the search to names in MyModule
1028
    rexm = match(r"(\w+\.|)\?\((.*)$", partial)
2,032✔
1029
    if rexm !== nothing
2,032✔
1030
        # Get the module scope
1031
        if isempty(rexm.captures[1])
26✔
1032
            callee_module = context_module
×
1033
        else
1034
            modname = Symbol(rexm.captures[1][1:end-1])
13✔
1035
            if isdefined(context_module, modname)
13✔
1036
                callee_module = getfield(context_module, modname)
13✔
1037
                if !isa(callee_module, Module)
13✔
1038
                    callee_module = context_module
13✔
1039
                end
1040
            else
1041
                callee_module = context_module
×
1042
            end
1043
        end
1044
        moreargs = !endswith(rexm.captures[2], ')')
14✔
1045
        callstr = "_(" * rexm.captures[2]
13✔
1046
        if moreargs
13✔
1047
            callstr *= ')'
7✔
1048
        end
1049
        ex_org = Meta.parse(callstr, raise=false, depwarn=false)
13✔
1050
        if isa(ex_org, Expr)
13✔
1051
            return complete_any_methods(ex_org, callee_module::Module, context_module, moreargs, shift), (0:length(rexm.captures[1])+1) .+ rexm.offset, false
13✔
1052
        end
1053
    end
1054

1055
    # if completing a key in a Dict
1056
    identifier, partial_key, loc = dict_identifier_key(partial, inc_tag, context_module)
2,099✔
1057
    if identifier !== nothing
2,019✔
1058
        matches = find_dict_matches(identifier, partial_key)
80✔
1059
        length(matches)==1 && (lastindex(string) <= pos || string[nextind(string,pos)] != ']') && (matches[1]*=']')
84✔
1060
        length(matches)>0 && return Completion[DictCompletion(identifier, match) for match in sort!(matches)], loc::Int:pos, true
80✔
1061
    end
1062

1063
    ffunc = Returns(true)
×
1064
    suggestions = Completion[]
1,955✔
1065

1066
    # Check if this is a var"" string macro that should be completed like
1067
    # an identifier rather than a string.
1068
    # TODO: It would be nice for the parser to give us more information here
1069
    # so that we can lookup the macro by identity rather than pattern matching
1070
    # its invocation.
1071
    varrange = findprev("var\"", string, pos)
1,955✔
1072

1073
    if varrange !== nothing
1,955✔
1074
        ok, ret = bslash_completions(string, pos)
3✔
1075
        ok && return ret
3✔
1076
        startpos = first(varrange) + 4
3✔
1077
        dotpos = something(findprev(isequal('.'), string, first(varrange)-1), 0)
5✔
1078
        return complete_identifiers!(Completion[], ffunc, context_module, string,
3✔
1079
            string[startpos:pos], pos, dotpos, startpos)
1080
    elseif inc_tag === :cmd
1,952✔
1081
        m = match(r"[\t\n\r\"`><=*?|]| (?!\\)", reverse(partial))
1✔
1082
        startpos = nextind(partial, reverseind(partial, m.offset))
2✔
1083
        r = startpos:pos
1✔
1084

1085
        # This expansion with "\\ "=>' ' replacement and shell_escape=true
1086
        # assumes the path isn't further quoted within the cmd backticks.
1087
        expanded = complete_expanduser(replace(string[r], r"\\ " => " "), r)
2✔
1088
        expanded[3] && return expanded  # If user expansion available, return it
1✔
1089

1090
        paths, r, success = complete_path(replace(string[r], r"\\ " => " "), pos,
2✔
1091
                                          shell_escape=true)
1092

1093
        return sort!(paths, by=p->p.path), r, success
1✔
1094
    elseif inc_tag === :string
1,951✔
1095
        # Find first non-escaped quote
1096
        m = match(r"\"(?!\\)", reverse(partial))
137✔
1097
        startpos = nextind(partial, reverseind(partial, m.offset))
274✔
1098
        r = startpos:pos
159✔
1099

1100
        expanded = complete_expanduser(string[r], r)
252✔
1101
        expanded[3] && return expanded  # If user expansion available, return it
137✔
1102

1103
        path_prefix = try
135✔
1104
            unescape_string(replace(string[r], "\\\$"=>"\$", "\\\""=>"\""))
248✔
1105
        catch
1106
            nothing
135✔
1107
        end
1108
        if !isnothing(path_prefix)
270✔
1109
            paths, r, success = complete_path(path_prefix, pos, string_escape=true)
135✔
1110

1111
            if close_path_completion(string, startpos, r, paths, pos)
135✔
1112
                paths[1] = PathCompletion((paths[1]::PathCompletion).path * "\"")
5✔
1113
            end
1114

1115
            # Fallthrough allowed so that Latex symbols can be completed in strings
1116
            success && return sort!(paths, by=p->p.path), r, success
53,687✔
1117
        end
1118
    end
1119

1120
    ok, ret = bslash_completions(string, pos)
1,879✔
1121
    ok && return ret
1,879✔
1122

1123
    # Make sure that only bslash_completions is working on strings
1124
    inc_tag === :string && return Completion[], 0:-1, false
1,836✔
1125
    if inc_tag === :other
1,780✔
1126
        frange, ex, wordrange, method_name_end = identify_possible_method_completion(partial, pos)
963✔
1127
        if last(frange) != -1 && all(isspace, @view partial[wordrange]) # no last argument to complete
963✔
1128
            if ex.head === :call
138✔
1129
                return complete_methods(ex, context_module, shift), first(frange):method_name_end, false
135✔
1130
            elseif is_broadcasting_expr(ex)
3✔
1131
                return complete_methods(ex, context_module, shift), first(frange):(method_name_end - 1), false
3✔
1132
            end
1133
        end
1134
    elseif inc_tag === :comment
817✔
1135
        return Completion[], 0:-1, false
1✔
1136
    end
1137

1138
    # Check whether we can complete a keyword argument in a function call
1139
    kwarg_completion, wordrange = complete_keyword_argument(partial, pos, context_module)
3,113✔
1140
    isempty(wordrange) || return kwarg_completion, wordrange, !isempty(kwarg_completion)
1,810✔
1141

1142
    dotpos = something(findprev(isequal('.'), string, pos), 0)
2,257✔
1143
    startpos = nextind(string, something(findprev(in(non_identifier_chars), string, pos), 0))
2,628✔
1144
    # strip preceding ! operator
1145
    if (m = match(r"\G\!+", partial, startpos)) isa RegexMatch
1,472✔
1146
        startpos += length(m.match)
2✔
1147
    end
1148

1149
    name = string[max(startpos, dotpos+1):pos]
2,572✔
1150
    comp_keywords = !isempty(name) && startpos > dotpos
1,472✔
1151
    if afterusing(string, startpos)
1,472✔
1152
        # We're right after using or import. Let's look only for packages
1153
        # and modules we can reach from here
1154

1155
        # If there's no dot, we're in toplevel, so we should
1156
        # also search for packages
1157
        s = string[startpos:pos]
33✔
1158
        if dotpos <= startpos
18✔
1159
            for dir in Base.load_path()
17✔
1160
                if basename(dir) in Base.project_names && isfile(dir)
93✔
1161
                    append!(suggestions, project_deps_get_completion_candidates(s, dir))
1✔
1162
                end
1163
                isdir(dir) || continue
37✔
1164
                for pname in readdir(dir)
18✔
1165
                    if pname[1] != '.' && pname != "METADATA" &&
1,974✔
1166
                        pname != "REQUIRE" && startswith(pname, s)
1167
                        # Valid file paths are
1168
                        #   <Mod>.jl
1169
                        #   <Mod>/src/<Mod>.jl
1170
                        #   <Mod>.jl/src/<Mod>.jl
1171
                        if isfile(joinpath(dir, pname))
194✔
1172
                            endswith(pname, ".jl") && push!(suggestions,
×
1173
                                                            PackageCompletion(pname[1:prevind(pname, end-2)]))
1174
                        else
1175
                            mod_name = if endswith(pname, ".jl")
194✔
1176
                                pname[1:prevind(pname, end-2)]
×
1177
                            else
1178
                                pname
194✔
1179
                            end
1180
                            if isfile(joinpath(dir, pname, "src",
194✔
1181
                                               "$mod_name.jl"))
1182
                                push!(suggestions, PackageCompletion(mod_name))
193✔
1183
                            end
1184
                        end
1185
                    end
1186
                end
1,023✔
1187
            end
54✔
1188
        end
1189
        ffunc = (mod,x)->(Base.isbindingresolved(mod, x) && isdefined(mod, x) && isa(getfield(mod, x), Module))
20,291✔
1190
        comp_keywords = false
×
1191
    end
1192

1193
    startpos == 0 && (pos = -1)
1,472✔
1194
    dotpos < startpos && (dotpos = startpos - 1)
1,472✔
1195
    return complete_identifiers!(suggestions, ffunc, context_module, string,
1,472✔
1196
        name, pos, dotpos, startpos, comp_keywords)
1197
end
1198

1199
function shell_completions(string, pos)
452✔
1200
    # First parse everything up to the current position
1201
    scs = string[1:pos]
904✔
1202
    local args, last_parse
×
1203
    try
452✔
1204
        args, last_parse = Base.shell_parse(scs, true)::Tuple{Expr,UnitRange{Int}}
469✔
1205
    catch
1206
        return Completion[], 0:-1, false
17✔
1207
    end
1208
    ex = args.args[end]::Expr
435✔
1209
    # Now look at the last thing we parsed
1210
    isempty(ex.args) && return Completion[], 0:-1, false
435✔
1211
    arg = ex.args[end]
435✔
1212
    if all(s -> isa(s, AbstractString), ex.args)
1,307✔
1213
        arg = arg::AbstractString
432✔
1214
        # Treat this as a path
1215

1216
        # As Base.shell_parse throws away trailing spaces (unless they are escaped),
1217
        # we need to special case here.
1218
        # If the last char was a space, but shell_parse ignored it search on "".
1219
        ignore_last_word = arg != " " && scs[end] == ' '
863✔
1220
        prefix = ignore_last_word ? "" : join(ex.args)
847✔
1221

1222
        # Also try looking into the env path if the user wants to complete the first argument
1223
        use_envpath = !ignore_last_word && length(args.args) < 2
432✔
1224

1225
        return complete_path(prefix, pos, use_envpath=use_envpath, shell_escape=true)
432✔
1226
    elseif isexpr(arg, :incomplete) || isexpr(arg, :error)
5✔
1227
        partial = scs[last_parse]
4✔
1228
        ret, range = completions(partial, lastindex(partial))
2✔
1229
        range = range .+ (first(last_parse) - 1)
2✔
1230
        return ret, range, true
2✔
1231
    end
1232
    return Completion[], 0:-1, false
1✔
1233
end
1234

1235
function UndefVarError_hint(io::IO, ex::UndefVarError)
3✔
1236
    var = ex.var
3✔
1237
    if var === :or
3✔
1238
        print(io, "\nsuggestion: Use `||` for short-circuiting boolean OR.")
×
1239
    elseif var === :and
3✔
1240
        print(io, "\nsuggestion: Use `&&` for short-circuiting boolean AND.")
×
1241
    elseif var === :help
3✔
1242
        println(io)
×
1243
        # Show friendly help message when user types help or help() and help is undefined
1244
        show(io, MIME("text/plain"), Base.Docs.parsedoc(Base.Docs.keywords[:help]))
×
1245
    elseif var === :quit
3✔
1246
        print(io, "\nsuggestion: To exit Julia, use Ctrl-D, or type exit() and press enter.")
×
1247
    end
1248
end
1249

1250
function __init__()
11✔
1251
    Base.Experimental.register_error_hint(UndefVarError_hint, UndefVarError)
11✔
1252
    COMPLETION_WORLD[] = Base.get_world_counter()
11✔
1253
    nothing
11✔
1254
end
1255

1256
end # module
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc