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

JuliaLang / julia / #37498

pending completion
#37498

push

local

web-flow
Merge pull request #49261 from N5N3/some-simple-fast

Some simple intersection/subtyping performance tuning.

72020 of 83141 relevant lines covered (86.62%)

31651695.99 hits per line

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

88.02
/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
1✔
16
end
17

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

22
struct PathCompletion <: Completion
23
    path::String
491✔
24
end
25

26
struct ModuleCompletion <: Completion
27
    parent::Module
6,379✔
28
    mod::String
29
end
30

31
struct PackageCompletion <: Completion
32
    package::String
67✔
33
end
34

35
struct PropertyCompletion <: Completion
36
    value
27✔
37
    property::Symbol
38
end
39

40
struct FieldCompletion <: Completion
41
    typ::DataType
19✔
42
    field::Symbol
43
end
44

45
struct MethodCompletion <: Completion
46
    tt # may be used by an external consumer to infer return type, etc.
47
    method::Method
48
    MethodCompletion(@nospecialize(tt), method::Method) = new(tt, method)
619✔
49
end
50

51
struct BslashCompletion <: Completion
52
    bslash::String
68✔
53
end
54

55
struct ShellCompletion <: Completion
56
    text::String
57
end
58

59
struct DictCompletion <: Completion
60
    dict::AbstractDict
118✔
61
    key::String
62
end
63

64
struct KeywordArgumentCompletion <: Completion
65
    kwarg::String
39✔
66
end
67

68
# interface definition
69
function Base.getproperty(c::Completion, name::Symbol)
147,845✔
70
    if name === :text
148,388✔
71
        return getfield(c, :text)::String
1✔
72
    elseif name === :keyword
148,387✔
73
        return getfield(c, :keyword)::String
3,807✔
74
    elseif name === :path
144,105✔
75
        return getfield(c, :path)::String
475✔
76
    elseif name === :parent
144,105✔
77
        return getfield(c, :parent)::Module
×
78
    elseif name === :mod
144,105✔
79
        return getfield(c, :mod)::String
142,278✔
80
    elseif name === :package
1,827✔
81
        return getfield(c, :package)::String
860✔
82
    elseif name === :property
967✔
83
        return getfield(c, :property)::Symbol
95✔
84
    elseif name === :field
872✔
85
        return getfield(c, :field)::Symbol
43✔
86
    elseif name === :method
294✔
87
        return getfield(c, :method)::Method
621✔
88
    elseif name === :bslash
226✔
89
        return getfield(c, :bslash)::String
68✔
90
    elseif name === :text
226✔
91
        return getfield(c, :text)::String
×
92
    elseif name === :key
226✔
93
        return getfield(c, :key)::String
118✔
94
    elseif name === :kwarg
108✔
95
        return getfield(c, :kwarg)::String
108✔
96
    end
97
    return getfield(c, name)
×
98
end
99

100
_completion_text(c::TextCompletion) = c.text
1✔
101
_completion_text(c::KeywordCompletion) = c.keyword
3,807✔
102
_completion_text(c::PathCompletion) = c.path
460✔
103
_completion_text(c::ModuleCompletion) = c.mod
142,278✔
104
_completion_text(c::PackageCompletion) = c.package
860✔
105
_completion_text(c::PropertyCompletion) = sprint(Base.show_sym, c.property)
95✔
106
_completion_text(c::FieldCompletion) = sprint(Base.show_sym, c.field)
43✔
107
_completion_text(c::MethodCompletion) = repr(c.method)
525✔
108
_completion_text(c::BslashCompletion) = c.bslash
68✔
109
_completion_text(c::ShellCompletion) = c.text
×
110
_completion_text(c::DictCompletion) = c.key
118✔
111
_completion_text(c::KeywordArgumentCompletion) = c.kwarg*'='
108✔
112

113
completion_text(c) = _completion_text(c)::String
148,363✔
114

115
const Completions = Tuple{Vector{Completion}, UnitRange{Int}, Bool}
116

117
function completes_global(x, name)
76,564✔
118
    return startswith(x, name) && !('#' in x)
76,564✔
119
end
120

121
function appendmacro!(syms, macros, needle, endchar)
946✔
122
    for s in macros
1,834✔
123
        if endswith(s, needle)
920✔
124
            from = nextind(s, firstindex(s))
80✔
125
            to = prevind(s, sizeof(s)-sizeof(needle)+1)
80✔
126
            push!(syms, s[from:to]*endchar)
160✔
127
        end
128
    end
920✔
129
end
130

131
function filtered_mod_names(ffunc::Function, mod::Module, name::AbstractString, all::Bool = false, imported::Bool = false)
473✔
132
    ssyms = names(mod, all = all, imported = imported)
871✔
133
    filter!(ffunc, ssyms)
473✔
134
    syms = String[string(s) for s in ssyms]
933✔
135
    macros =  filter(x -> startswith(x, "@" * name), syms)
76,957✔
136
    appendmacro!(syms, macros, "_str", "\"")
473✔
137
    appendmacro!(syms, macros, "_cmd", "`")
473✔
138
    filter!(x->completes_global(x, name), syms)
77,037✔
139
    return [ModuleCompletion(mod, sym) for sym in syms]
473✔
140
end
141

142
# REPL Symbol Completions
143
function complete_symbol(sym::String, @nospecialize(ffunc), context_module::Module=Main)
126✔
144
    mod = context_module
×
145
    name = sym
×
146

147
    lookup_module = true
×
148
    t = Union{}
×
149
    val = nothing
×
150
    if something(findlast(in(non_identifier_chars), sym), 0) < something(findlast(isequal('.'), sym), 0)
188✔
151
        # Find module
152
        lookup_name, name = rsplit(sym, ".", limit=2)
62✔
153

154
        ex = Meta.parse(lookup_name, raise=false, depwarn=false)
62✔
155

156
        res = repl_eval_ex(ex, context_module)
62✔
157
        res === nothing && return Completion[]
62✔
158
        if res isa Const
48✔
159
            val = res.val
33✔
160
            if isa(val, Module)
33✔
161
                mod = val
11✔
162
                lookup_module = true
11✔
163
            else
164
                lookup_module = false
×
165
                t = typeof(val)
55✔
166
            end
167
        else
168
            lookup_module = false
×
169
            t = CC.widenconst(res)
15✔
170
        end
171
    end
172

173
    suggestions = Completion[]
112✔
174
    if lookup_module
112✔
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)
75✔
179
            s->(!Base.isdeprecated(mod, s) && s != modname && ffunc(mod, s)::Bool)
84,839✔
180
        end
181
        # Looking for a binding in a module
182
        if mod == context_module
75✔
183
            # Also look in modules we got through `using`
184
            mods = ccall(:jl_module_usings, Any, (Any,), context_module)::Vector
65✔
185
            for m in mods
65✔
186
                append!(suggestions, filtered_mod_names(p, m::Module, name))
472✔
187
            end
463✔
188
            append!(suggestions, filtered_mod_names(p, mod, name, true, true))
85✔
189
        else
190
            append!(suggestions, filtered_mod_names(p, mod, name, true, false))
85✔
191
        end
192
    elseif val !== nothing # looking for a property of an instance
37✔
193
        for property in propertynames(val, false)
20✔
194
            # TODO: support integer arguments (#36872)
195
            if property isa Symbol && startswith(string(property), name)
58✔
196
                push!(suggestions, PropertyCompletion(val, property))
27✔
197
            end
198
        end
31✔
199
    else
200
        # Looking for a member of a type
201
        if t isa DataType && t != Any
17✔
202
            # Check for cases like Type{typeof(+)}
203
            if Base.isType(t)
12✔
204
                t = typeof(t.parameters[1])
×
205
            end
206
            # Only look for fields if this is a concrete type
207
            if isconcretetype(t)
12✔
208
                fields = fieldnames(t)
12✔
209
                for field in fields
12✔
210
                    isa(field, Symbol) || continue # Tuple type has ::Int field name
19✔
211
                    s = string(field)
19✔
212
                    if startswith(s, name)
38✔
213
                        push!(suggestions, FieldCompletion(t, field))
19✔
214
                    end
215
                end
19✔
216
            end
217
        end
218
    end
219
    suggestions
112✔
220
end
221

222
const sorted_keywords = [
223
    "abstract type", "baremodule", "begin", "break", "catch", "ccall",
224
    "const", "continue", "do", "else", "elseif", "end", "export", "false",
225
    "finally", "for", "function", "global", "if", "import",
226
    "let", "local", "macro", "module", "mutable struct",
227
    "primitive type", "quote", "return", "struct",
228
    "true", "try", "using", "while"]
229

230
function complete_keyword(s::Union{String,SubString{String}})
85✔
231
    r = searchsorted(sorted_keywords, s)
85✔
232
    i = first(r)
85✔
233
    n = length(sorted_keywords)
85✔
234
    while i <= n && startswith(sorted_keywords[i],s)
495✔
235
        r = first(r):i
178✔
236
        i += 1
178✔
237
    end
178✔
238
    Completion[KeywordCompletion(kw) for kw in sorted_keywords[r]]
85✔
239
end
240

241
function complete_path(path::AbstractString, pos::Int; use_envpath=false, shell_escape=false)
82✔
242
    if Base.Sys.isunix() && occursin(r"^~(?:/|$)", path)
41✔
243
        # if the path is just "~", don't consider the expanded username as a prefix
244
        if path == "~"
×
245
            dir, prefix = homedir(), ""
×
246
        else
247
            dir, prefix = splitdir(homedir() * path[2:end])
×
248
        end
249
    else
250
        dir, prefix = splitdir(path)
41✔
251
    end
252
    local files
×
253
    try
41✔
254
        if isempty(dir)
41✔
255
            files = readdir()
29✔
256
        elseif isdir(dir)
12✔
257
            files = readdir(dir)
11✔
258
        else
259
            return Completion[], 0:-1, false
41✔
260
        end
261
    catch
262
        return Completion[], 0:-1, false
×
263
    end
264

265
    matches = Set{String}()
40✔
266
    for file in files
40✔
267
        if startswith(file, prefix)
5,948✔
268
            id = try isdir(joinpath(dir, file)) catch; false end
1,370✔
269
            # joinpath is not used because windows needs to complete with double-backslash
270
            push!(matches, id ? file * (@static Sys.iswindows() ? "\\\\" : "/") : file)
456✔
271
        end
272
    end
3,186✔
273

274
    if use_envpath && length(dir) == 0
40✔
275
        # Look for files in PATH as well
276
        local pathdirs = split(ENV["PATH"], @static Sys.iswindows() ? ";" : ":")
6✔
277

278
        for pathdir in pathdirs
6✔
279
            local actualpath
×
280
            try
60✔
281
                actualpath = realpath(pathdir)
68✔
282
            catch
283
                # Bash doesn't expect every folder in PATH to exist, so neither shall we
284
                continue
8✔
285
            end
286

287
            if actualpath != pathdir && in(actualpath,pathdirs)
64✔
288
                # Remove paths which (after resolving links) are in the env path twice.
289
                # Many distros eg. point /bin to /usr/bin but have both in the env path.
290
                continue
12✔
291
            end
292

293
            local filesinpath
×
294
            try
40✔
295
                filesinpath = readdir(pathdir)
41✔
296
            catch e
297
                # Bash allows dirs in PATH that can't be read, so we should as well.
298
                if isa(e, Base.IOError) || isa(e, Base.ArgumentError)
1✔
299
                    continue
1✔
300
                else
301
                    # We only handle IOError and ArgumentError here
302
                    rethrow()
×
303
                end
304
            end
305

306
            for file in filesinpath
51✔
307
                # In a perfect world, we would filter on whether the file is executable
308
                # here, or even on whether the current user can execute the file in question.
309
                if startswith(file, prefix) && isfile(joinpath(pathdir, file))
11,612✔
310
                    push!(matches, file)
3✔
311
                end
312
            end
8,493✔
313
        end
66✔
314
    end
315

316
    matchList = Completion[PathCompletion(shell_escape ? replace(s, r"\s" => s"\\\0") : s) for s in matches]
498✔
317
    startpos = pos - lastindex(prefix) + 1 - count(isequal(' '), prefix)
40✔
318
    # The pos - lastindex(prefix) + 1 is correct due to `lastindex(prefix)-lastindex(prefix)==0`,
319
    # hence we need to add one to get the first index. This is also correct when considering
320
    # pos, because pos is the `lastindex` a larger string which `endswith(path)==true`.
321
    return matchList, startpos:pos, !isempty(matchList)
40✔
322
end
323

324
function complete_expanduser(path::AbstractString, r)
31✔
325
    expanded =
31✔
326
        try expanduser(path)
32✔
327
        catch e
328
            e isa ArgumentError || rethrow()
1✔
329
            path
32✔
330
        end
331
    return Completion[PathCompletion(expanded)], r, path != expanded
31✔
332
end
333

334
# Returns a range that includes the method name in front of the first non
335
# closed start brace from the end of the string.
336
function find_start_brace(s::AbstractString; c_start='(', c_end=')')
1,026✔
337
    braces = 0
×
338
    r = reverse(s)
513✔
339
    i = firstindex(r)
×
340
    in_single_quotes = false
×
341
    in_double_quotes = false
×
342
    in_back_ticks = false
×
343
    in_comment = 0
×
344
    while i <= ncodeunits(r)
8,339✔
345
        c, i = iterate(r, i)
16,192✔
346
        if c == '#' && i <= ncodeunits(r) && iterate(r, i)[1] == '='
8,096✔
347
            c, i = iterate(r, i) # consume '='
16✔
348
            new_comments = 1
×
349
            # handle #=#=#=#, by counting =# pairs
350
            while i <= ncodeunits(r) && iterate(r, i)[1] == '#'
12✔
351
                c, i = iterate(r, i) # consume '#'
12✔
352
                iterate(r, i)[1] == '=' || break
6✔
353
                c, i = iterate(r, i) # consume '='
8✔
354
                new_comments += 1
4✔
355
            end
4✔
356
            if c == '='
8✔
357
                in_comment += new_comments
6✔
358
            else
359
                in_comment -= new_comments
10✔
360
            end
361
        elseif !in_single_quotes && !in_double_quotes && !in_back_ticks && in_comment == 0
8,088✔
362
            if c == c_start
7,566✔
363
                braces += 1
321✔
364
            elseif c == c_end
7,245✔
365
                braces -= 1
51✔
366
            elseif c == '\''
7,194✔
367
                in_single_quotes = true
16✔
368
            elseif c == '"'
7,178✔
369
                in_double_quotes = true
73✔
370
            elseif c == '`'
7,105✔
371
                in_back_ticks = true
7,566✔
372
            end
373
        else
374
            if in_single_quotes &&
522✔
375
                c == '\'' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
376
                in_single_quotes = false
16✔
377
            elseif in_double_quotes &&
506✔
378
                c == '"' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
379
                in_double_quotes = false
60✔
380
            elseif in_back_ticks &&
446✔
381
                c == '`' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
382
                in_back_ticks = false
7✔
383
            elseif in_comment > 0 &&
439✔
384
                c == '=' && i <= ncodeunits(r) && iterate(r, i)[1] == '#'
385
                # handle =#=#=#=, by counting #= pairs
386
                c, i = iterate(r, i) # consume '#'
12✔
387
                old_comments = 1
×
388
                while i <= ncodeunits(r) && iterate(r, i)[1] == '='
8✔
389
                    c, i = iterate(r, i) # consume '='
8✔
390
                    iterate(r, i)[1] == '#' || break
4✔
391
                    c, i = iterate(r, i) # consume '#'
4✔
392
                    old_comments += 1
2✔
393
                end
2✔
394
                if c == '#'
6✔
395
                    in_comment -= old_comments
4✔
396
                else
397
                    in_comment += old_comments
2✔
398
                end
399
            end
400
        end
401
        braces == 1 && break
8,096✔
402
    end
7,826✔
403
    braces != 1 && return 0:-1, -1
513✔
404
    method_name_end = reverseind(s, i)
270✔
405
    startind = nextind(s, something(findprev(in(non_identifier_chars), s, method_name_end), 0))::Int
288✔
406
    return (startind:lastindex(s), method_name_end)
270✔
407
end
408

409
struct REPLInterpreterCache
410
    dict::IdDict{MethodInstance,CodeInstance}
×
411
end
412
REPLInterpreterCache() = REPLInterpreterCache(IdDict{MethodInstance,CodeInstance}())
×
413
const REPL_INTERPRETER_CACHE = REPLInterpreterCache()
414

415
function get_code_cache()
×
416
    # XXX Avoid storing analysis results into the cache that persists across precompilation,
417
    #     as [sys|pkg]image currently doesn't support serializing externally created `CodeInstance`.
418
    #     Otherwise, `CodeInstance`s created by `REPLInterpreter``, that are much less optimized
419
    #     that those produced by `NativeInterpreter`, will leak into the native code cache,
420
    #     potentially causing runtime slowdown.
421
    #     (see https://github.com/JuliaLang/julia/issues/48453).
422
    if (@ccall jl_generating_output()::Cint) == 1
147✔
423
        return REPLInterpreterCache()
×
424
    else
425
        return REPL_INTERPRETER_CACHE
147✔
426
    end
427
end
428

429
struct REPLInterpreter <: CC.AbstractInterpreter
430
    repl_frame::CC.InferenceResult
431
    world::UInt
432
    inf_params::CC.InferenceParams
433
    opt_params::CC.OptimizationParams
434
    inf_cache::Vector{CC.InferenceResult}
435
    code_cache::REPLInterpreterCache
436
    function REPLInterpreter(repl_frame::CC.InferenceResult;
294✔
437
                             world::UInt = Base.get_world_counter(),
438
                             inf_params::CC.InferenceParams = CC.InferenceParams(),
439
                             opt_params::CC.OptimizationParams = CC.OptimizationParams(),
440
                             inf_cache::Vector{CC.InferenceResult} = CC.InferenceResult[],
441
                             code_cache::REPLInterpreterCache = get_code_cache())
442
        return new(repl_frame, world, inf_params, opt_params, inf_cache, code_cache)
147✔
443
    end
444
end
445
CC.InferenceParams(interp::REPLInterpreter) = interp.inf_params
58,004✔
446
CC.OptimizationParams(interp::REPLInterpreter) = interp.opt_params
×
447
CC.get_world_counter(interp::REPLInterpreter) = interp.world
17,720✔
448
CC.get_inference_cache(interp::REPLInterpreter) = interp.inf_cache
6,944✔
449
CC.code_cache(interp::REPLInterpreter) = CC.WorldView(interp.code_cache, CC.WorldRange(interp.world))
14,832✔
450
CC.get(wvc::CC.WorldView{REPLInterpreterCache}, mi::MethodInstance, default) = get(wvc.cache.dict, mi, default)
18,982✔
451
CC.getindex(wvc::CC.WorldView{REPLInterpreterCache}, mi::MethodInstance) = getindex(wvc.cache.dict, mi)
×
452
CC.haskey(wvc::CC.WorldView{REPLInterpreterCache}, mi::MethodInstance) = haskey(wvc.cache.dict, mi)
2,123✔
453
CC.setindex!(wvc::CC.WorldView{REPLInterpreterCache}, ci::CodeInstance, mi::MethodInstance) = setindex!(wvc.cache.dict, ci, mi)
2,123✔
454

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

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

461
# `REPLInterpreter` aggressively resolves global bindings to enable reasonable completions
462
# for lines like `Mod.a.|` (where `|` is the cursor position).
463
# Aggressive binding resolution poses challenges for the inference cache validation
464
# (until https://github.com/JuliaLang/julia/issues/40399 is implemented).
465
# To avoid the cache validation issues, `REPLInterpreter` only allows aggressive binding
466
# resolution for top-level frame representing REPL input code (`repl_frame`) and for child
467
# `getproperty` frames that are constant propagated from the `repl_frame`. This works, since
468
# a.) these frames are never cached, and
469
# b.) their results are only observed by the non-cached `repl_frame`.
470
#
471
# `REPLInterpreter` also aggressively concrete evaluate `:inconsistent` calls within
472
# `repl_frame` to provide reasonable completions for lines like `Ref(Some(42))[].|`.
473
# Aggressive concrete evaluation allows us to get accurate type information about complex
474
# expressions that otherwise can not be constant folded, in a safe way, i.e. it still
475
# doesn't evaluate effectful expressions like `pop!(xs)`.
476
# Similarly to the aggressive binding resolution, aggressive concrete evaluation doesn't
477
# present any cache validation issues because `repl_frame` is never cached.
478

479
is_repl_frame(interp::REPLInterpreter, sv::CC.InferenceState) = interp.repl_frame === sv.result
26,723✔
480

481
# aggressive global binding resolution within `repl_frame`
482
function CC.abstract_eval_globalref(interp::REPLInterpreter, g::GlobalRef,
×
483
                                    sv::CC.InferenceState)
484
    if is_repl_frame(interp, sv)
16,930✔
485
        if CC.isdefined_globalref(g)
270✔
486
            return Const(ccall(:jl_get_globalref_value, Any, (Any,), g))
266✔
487
        end
488
        return Union{}
4✔
489
    end
490
    return @invoke CC.abstract_eval_globalref(interp::CC.AbstractInterpreter, g::GlobalRef,
16,660✔
491
                                              sv::CC.InferenceState)
492
end
493

494
function is_repl_frame_getproperty(interp::REPLInterpreter, sv::CC.InferenceState)
22✔
495
    def = sv.linfo.def
22✔
496
    def isa Method || return false
22✔
497
    def.name === :getproperty || return false
22✔
498
    sv.cached && return false
22✔
499
    return is_repl_frame(interp, sv.parent)
21✔
500
end
501

502
# aggressive global binding resolution for `getproperty(::Module, ::Symbol)` calls within `repl_frame`
503
function CC.builtin_tfunction(interp::REPLInterpreter, @nospecialize(f),
4,854✔
504
                              argtypes::Vector{Any}, sv::CC.InferenceState)
505
    if f === Core.getglobal && is_repl_frame_getproperty(interp, sv)
4,854✔
506
        if length(argtypes) == 2
3✔
507
            a1, a2 = argtypes
3✔
508
            if isa(a1, Const) && isa(a2, Const)
3✔
509
                a1val, a2val = a1.val, a2.val
3✔
510
                if isa(a1val, Module) && isa(a2val, Symbol)
3✔
511
                    g = GlobalRef(a1val, a2val)
3✔
512
                    if CC.isdefined_globalref(g)
3✔
513
                        return Const(ccall(:jl_get_globalref_value, Any, (Any,), g))
3✔
514
                    end
515
                    return Union{}
×
516
                end
517
            end
518
        end
519
    end
520
    return @invoke CC.builtin_tfunction(interp::CC.AbstractInterpreter, f::Any,
4,851✔
521
                                        argtypes::Vector{Any}, sv::CC.InferenceState)
522
end
523

524
# aggressive concrete evaluation for `:inconsistent` frames within `repl_frame`
525
function CC.concrete_eval_eligible(interp::REPLInterpreter, @nospecialize(f),
×
526
                                   result::CC.MethodCallResult, arginfo::CC.ArgInfo,
527
                                   sv::CC.InferenceState)
528
    if is_repl_frame(interp, sv)
9,772✔
529
        neweffects = CC.Effects(result.effects; consistent=CC.ALWAYS_TRUE)
104✔
530
        result = CC.MethodCallResult(result.rt, result.edgecycle, result.edgelimited,
208✔
531
                                     result.edge, neweffects)
532
    end
533
return @invoke CC.concrete_eval_eligible(interp::CC.AbstractInterpreter, f::Any,
19,544✔
534
                                         result::CC.MethodCallResult, arginfo::CC.ArgInfo,
535
                                         sv::CC.InferenceState)
536
end
537

538
function resolve_toplevel_symbols!(mod::Module, src::Core.CodeInfo)
×
539
    newsrc = copy(src)
147✔
540
    @ccall jl_resolve_globals_in_ir(
147✔
541
        #=jl_array_t *stmts=# newsrc.code::Any,
542
        #=jl_module_t *m=# mod::Any,
543
        #=jl_svec_t *sparam_vals=# Core.svec()::Any,
544
        #=int binding_effects=# 0::Int)::Cvoid
545
    return newsrc
×
546
end
547

548
# lower `ex` and run type inference on the resulting top-level expression
549
function repl_eval_ex(@nospecialize(ex), context_module::Module)
314✔
550
    lwr = try
314✔
551
        Meta.lower(context_module, ex)
327✔
552
    catch # macro expansion failed, etc.
553
        return nothing
13✔
554
    end
555
    if lwr isa Symbol
301✔
556
        return isdefined(context_module, lwr) ? Const(getfield(context_module, lwr)) : nothing
59✔
557
    end
558
    lwr isa Expr || return Const(lwr) # `ex` is literal
328✔
559
    isexpr(lwr, :thunk) || return nothing # lowered to `Expr(:error, ...)` or similar
165✔
560
    src = lwr.args[1]::Core.CodeInfo
147✔
561

562
    # construct top-level `MethodInstance`
563
    mi = ccall(:jl_new_method_instance_uninit, Ref{Core.MethodInstance}, ());
147✔
564
    mi.specTypes = Tuple{}
147✔
565

566
    mi.def = context_module
147✔
567
    src = resolve_toplevel_symbols!(context_module, src)
147✔
568
    @atomic mi.uninferred = src
147✔
569

570
    result = CC.InferenceResult(mi)
147✔
571
    interp = REPLInterpreter(result)
294✔
572
    frame = CC.InferenceState(result, src, #=cache=#:no, interp)::CC.InferenceState
147✔
573

574
    CC.typeinf(interp, frame)
147✔
575

576
    return frame.result.result
147✔
577
end
578

579
# Method completion on function call expression that look like :(max(1))
580
MAX_METHOD_COMPLETIONS::Int = 40
581
function _complete_methods(ex_org::Expr, context_module::Module, shift::Bool)
114✔
582
    funct = repl_eval_ex(ex_org.args[1], context_module)
114✔
583
    funct === nothing && return 2, nothing, [], Set{Symbol}()
114✔
584
    funct = CC.widenconst(funct)
114✔
585
    args_ex, kwargs_ex, kwargs_flag = complete_methods_args(ex_org, context_module, true, true)
222✔
586
    return kwargs_flag, funct, args_ex, kwargs_ex
114✔
587
end
588

589
function complete_methods(ex_org::Expr, context_module::Module=Main, shift::Bool=false)
×
590
    kwargs_flag, funct, args_ex, kwargs_ex = _complete_methods(ex_org, context_module, shift)::Tuple{Int, Any, Vector{Any}, Set{Symbol}}
81✔
591
    out = Completion[]
81✔
592
    kwargs_flag == 2 && return out # one of the kwargs is invalid
81✔
593
    kwargs_flag == 0 && push!(args_ex, Vararg{Any}) # allow more arguments if there is no semicolon
71✔
594
    complete_methods!(out, funct, args_ex, kwargs_ex, shift ? -2 : MAX_METHOD_COMPLETIONS, kwargs_flag == 1)
72✔
595
    return out
71✔
596
end
597

598
MAX_ANY_METHOD_COMPLETIONS::Int = 10
599
function complete_any_methods(ex_org::Expr, callee_module::Module, context_module::Module, moreargs::Bool, shift::Bool)
13✔
600
    out = Completion[]
13✔
601
    args_ex, kwargs_ex, kwargs_flag = try
13✔
602
        # this may throw, since we set default_any to false
603
        complete_methods_args(ex_org, context_module, false, false)
13✔
604
    catch ex
605
        ex isa ArgumentError || rethrow()
×
606
        return out
13✔
607
    end
608
    kwargs_flag == 2 && return out # one of the kwargs is invalid
13✔
609

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

614
    seen = Base.IdSet()
13✔
615
    for name in names(callee_module; all=true)
13✔
616
        if !Base.isdeprecated(callee_module, name) && isdefined(callee_module, name) && !startswith(string(name), '#')
1,287✔
617
            func = getfield(callee_module, name)
611✔
618
            if !isa(func, Module)
611✔
619
                funct = Core.Typeof(func)
1,118✔
620
                if !in(funct, seen)
585✔
621
                    push!(seen, funct)
585✔
622
                    complete_methods!(out, funct, args_ex, kwargs_ex, MAX_ANY_METHOD_COMPLETIONS, false)
585✔
623
                end
624
            elseif callee_module === Main && isa(func, Module)
26✔
625
                callee_module2 = func
×
626
                for name in names(callee_module2)
×
627
                    if !Base.isdeprecated(callee_module2, name) && isdefined(callee_module2, name) && !startswith(string(name), '#')
×
628
                        func = getfield(callee_module, name)
×
629
                        if !isa(func, Module)
×
630
                            funct = Core.Typeof(func)
×
631
                            if !in(funct, seen)
×
632
                                push!(seen, funct)
×
633
                                complete_methods!(out, funct, args_ex, kwargs_ex, MAX_ANY_METHOD_COMPLETIONS, false)
×
634
                            end
635
                        end
636
                    end
637
                end
×
638
            end
639
        end
640
    end
1,300✔
641

642
    if !shift
13✔
643
        # Filter out methods where all arguments are `Any`
644
        filter!(out) do c
2✔
645
            isa(c, TextCompletion) && return false
10✔
646
            isa(c, MethodCompletion) || return true
10✔
647
            sig = Base.unwrap_unionall(c.method.sig)::DataType
10✔
648
            return !all(T -> T === Any || T === Vararg{Any}, sig.parameters[2:end])
18✔
649
        end
650
    end
651

652
    return out
13✔
653
end
654

655
function detect_invalid_kwarg!(kwargs_ex::Vector{Symbol}, @nospecialize(x), kwargs_flag::Int, possible_splat::Bool)
×
656
    n = isexpr(x, :kw) ? x.args[1] : x
57✔
657
    if n isa Symbol
55✔
658
        push!(kwargs_ex, n)
41✔
659
        return kwargs_flag
41✔
660
    end
661
    possible_splat && isexpr(x, :...) && return kwargs_flag
14✔
662
    return 2 # The kwarg is invalid
10✔
663
end
664

665
function detect_args_kwargs(funargs::Vector{Any}, context_module::Module, default_any::Bool, broadcasting::Bool)
127✔
666
    args_ex = Any[]
127✔
667
    kwargs_ex = Symbol[]
127✔
668
    kwargs_flag = 0
×
669
    # kwargs_flag is:
670
    # * 0 if there is no semicolon and no invalid kwarg
671
    # * 1 if there is a semicolon and no invalid kwarg
672
    # * 2 if there are two semicolons or more, or if some kwarg is invalid, which
673
    #        means that it is not of the form "bar=foo", "bar" or "bar..."
674
    for i in (1+!broadcasting):length(funargs)
238✔
675
        ex = funargs[i]
219✔
676
        if isexpr(ex, :parameters)
333✔
677
            kwargs_flag = ifelse(kwargs_flag == 0, 1, 2) # there should be at most one :parameters
54✔
678
            for x in ex.args
79✔
679
                kwargs_flag = detect_invalid_kwarg!(kwargs_ex, x, kwargs_flag, true)
46✔
680
            end
62✔
681
        elseif isexpr(ex, :kw)
279✔
682
            kwargs_flag = detect_invalid_kwarg!(kwargs_ex, ex, kwargs_flag, false)
22✔
683
        else
684
            if broadcasting
143✔
685
                # handle broadcasting, but only handle number of arguments instead of
686
                # argument types
687
                push!(args_ex, Any)
5✔
688
            else
689
                argt = repl_eval_ex(ex, context_module)
138✔
690
                if argt !== nothing
138✔
691
                    push!(args_ex, CC.widenconst(argt))
105✔
692
                elseif default_any
33✔
693
                    push!(args_ex, Any)
33✔
694
                else
695
                    throw(ArgumentError("argument not found"))
×
696
                end
697
            end
698
        end
699
    end
327✔
700
    return args_ex, Set{Symbol}(kwargs_ex), kwargs_flag
127✔
701
end
702

703
is_broadcasting_expr(ex::Expr) = ex.head === :. && isexpr(ex.args[2], :tuple)
213✔
704

705
function complete_methods_args(ex::Expr, context_module::Module, default_any::Bool, allow_broadcasting::Bool)
×
706
    if allow_broadcasting && is_broadcasting_expr(ex)
114✔
707
        return detect_args_kwargs((ex.args[2]::Expr).args, context_module, default_any, true)
6✔
708
    end
709
    return detect_args_kwargs(ex.args, context_module, default_any, false)
121✔
710
end
711

712
function complete_methods!(out::Vector{Completion}, @nospecialize(funct), args_ex::Vector{Any}, kwargs_ex::Set{Symbol}, max_method_completions::Int, exact_nargs::Bool)
689✔
713
    # Input types and number of arguments
714
    t_in = Tuple{funct, args_ex...}
689✔
715
    m = Base._methods_by_ftype(t_in, nothing, max_method_completions, Base.get_world_counter(),
689✔
716
        #=ambig=# true, Ref(typemin(UInt)), Ref(typemax(UInt)), Ptr{Int32}(C_NULL))
717
    if !isa(m, Vector)
689✔
718
        push!(out, TextCompletion(sprint(Base.show_signature_function, funct) * "( too many methods, use SHIFT-TAB to show )"))
1✔
719
        return
1✔
720
    end
721
    for match in m
1,144✔
722
        # TODO: if kwargs_ex, filter out methods without kwargs?
723
        push!(out, MethodCompletion(match.spec_types, match.method))
619✔
724
    end
619✔
725
    # TODO: filter out methods with wrong number of arguments if `exact_nargs` is set
726
end
727

728
include("latex_symbols.jl")
729
include("emoji_symbols.jl")
730

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

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

743
# Aux function to detect whether we're right after a
744
# using or import keyword
745
function afterusing(string::String, startpos::Int)
93✔
746
    (isempty(string) || startpos == 0) && return false
93✔
747
    str = string[1:prevind(string,startpos)]
140✔
748
    isempty(str) && return false
90✔
749
    rstr = reverse(str)
50✔
750
    r = findfirst(r"\s(gnisu|tropmi)\b", rstr)
50✔
751
    r === nothing && return false
50✔
752
    fr = reverseind(str, last(r))
9✔
753
    return occursin(r"^\b(using|import)\s*((\w+[.])*\w+\s*,\s*)*$", str[fr:end])
9✔
754
end
755

756
function close_path_completion(str, startpos, r, paths, pos)
28✔
757
    length(paths) == 1 || return false  # Only close if there's a single choice...
43✔
758
    _path = str[startpos:prevind(str, first(r))] * (paths[1]::PathCompletion).path
19✔
759
    path = expanduser(replace(_path, r"\\ " => " "))
13✔
760
    # ...except if it's a directory...
761
    try
13✔
762
        isdir(path)
14✔
763
    catch e
764
        e isa Base.IOError || rethrow() # `path` cannot be determined to be a file
14✔
765
    end && return false
766
    # ...and except if there's already a " at the cursor.
767
    return lastindex(str) <= pos || str[nextind(str, pos)] != '"'
3✔
768
end
769

770
function bslash_completions(string::String, pos::Int)
249✔
771
    slashpos = something(findprev(isequal('\\'), string, pos), 0)
287✔
772
    if (something(findprev(in(bslash_separators), string, pos), 0) < slashpos &&
271✔
773
        !(1 < slashpos && (string[prevind(string, slashpos)]=='\\')))
774
        # latex / emoji symbol substitution
775
        s = string[slashpos:pos]
72✔
776
        latex = get(latex_symbols, s, "")
55✔
777
        if !isempty(latex) # complete an exact match
36✔
778
            return (true, (Completion[BslashCompletion(latex)], slashpos:pos, true))
19✔
779
        elseif occursin(subscript_regex, s)
17✔
780
            sub = map(c -> subscripts[c], s[3:end])
8✔
781
            return (true, (Completion[BslashCompletion(sub)], slashpos:pos, true))
1✔
782
        elseif occursin(superscript_regex, s)
16✔
783
            sup = map(c -> superscripts[c], s[3:end])
8✔
784
            return (true, (Completion[BslashCompletion(sup)], slashpos:pos, true))
1✔
785
        end
786
        emoji = get(emoji_symbols, s, "")
17✔
787
        if !isempty(emoji)
15✔
788
            return (true, (Completion[BslashCompletion(emoji)], slashpos:pos, true))
2✔
789
        end
790
        # return possible matches; these cannot be mixed with regular
791
        # Julian completions as only latex / emoji symbols contain the leading \
792
        if startswith(s, "\\:") # emoji
26✔
793
            namelist = Iterators.filter(k -> startswith(k, s), keys(emoji_symbols))
2,371✔
794
        else # latex
795
            namelist = Iterators.filter(k -> startswith(k, s), keys(latex_symbols))
55,882✔
796
        end
797
        return (true, (Completion[BslashCompletion(name) for name in sort!(collect(namelist))], slashpos:pos, true))
13✔
798
    end
799
    return (false, (Completion[], 0:-1, false))
213✔
800
end
801

802
function dict_identifier_key(str::String, tag::Symbol, context_module::Module=Main)
323✔
803
    if tag === :string
323✔
804
        str_close = str*"\""
51✔
805
    elseif tag === :cmd
271✔
806
        str_close = str*"`"
5✔
807
    else
808
        str_close = str
×
809
    end
810

811
    frange, end_of_identifier = find_start_brace(str_close, c_start='[', c_end=']')
322✔
812
    isempty(frange) && return (nothing, nothing, nothing)
322✔
813
    obj = context_module
×
814
    for name in split(str[frange[1]:end_of_identifier], '.')
81✔
815
        Base.isidentifier(name) || return (nothing, nothing, nothing)
121✔
816
        sym = Symbol(name)
117✔
817
        isdefined(obj, sym) || return (nothing, nothing, nothing)
117✔
818
        obj = getfield(obj, sym)
117✔
819
    end
196✔
820
    (isa(obj, AbstractDict) && length(obj)::Int < 1_000_000) || return (nothing, nothing, nothing)
80✔
821
    begin_of_key = something(findnext(!isspace, str, nextind(str, end_of_identifier) + 1), # +1 for [
152✔
822
                             lastindex(str)+1)
823
    return (obj::AbstractDict, str[begin_of_key:end], begin_of_key)
78✔
824
end
825

826
# This needs to be a separate non-inlined function, see #19441
827
@noinline function find_dict_matches(identifier::AbstractDict, partial_key)
77✔
828
    matches = String[]
77✔
829
    for key in keys(identifier)
116✔
830
        rkey = repr(key)
924✔
831
        startswith(rkey,partial_key) && push!(matches,rkey)
924✔
832
    end
1,430✔
833
    return matches
77✔
834
end
835

836
# Identify an argument being completed in a method call. If the argument is empty, method
837
# suggestions will be provided instead of argument completions.
838
function identify_possible_method_completion(partial, last_idx)
291✔
839
    fail = 0:-1, Expr(:nothing), 0:-1, 0
291✔
840

841
    # First, check that the last punctuation is either ',', ';' or '('
842
    idx_last_punct = something(findprev(x -> ispunct(x) && x != '_' && x != '!', partial, last_idx), 0)::Int
1,453✔
843
    idx_last_punct == 0 && return fail
291✔
844
    last_punct = partial[idx_last_punct]
544✔
845
    last_punct == ',' || last_punct == ';' || last_punct == '(' || return fail
449✔
846

847
    # Then, check that `last_punct` is only followed by an identifier or nothing
848
    before_last_word_start = something(findprev(in(non_identifier_chars), partial, last_idx), 0)
330✔
849
    before_last_word_start == 0 && return fail
165✔
850
    all(isspace, @view partial[nextind(partial, idx_last_punct):before_last_word_start]) || return fail
171✔
851

852
    # Check that `last_punct` is either the last '(' or placed after a previous '('
853
    frange, method_name_end = find_start_brace(@view partial[1:idx_last_punct])
159✔
854
    method_name_end ∈ frange || return fail
169✔
855

856
    # Strip the preceding ! operators, if any, and close the expression with a ')'
857
    s = replace(partial[frange], r"\G\!+([^=\(]+)" => s"\1"; count=1) * ')'
298✔
858
    ex = Meta.parse(s, raise=false, depwarn=false)
149✔
859
    isa(ex, Expr) || return fail
149✔
860

861
    # `wordrange` is the position of the last argument to complete
862
    wordrange = nextind(partial, before_last_word_start):last_idx
230✔
863
    return frange, ex, wordrange, method_name_end
149✔
864
end
865

866
# Provide completion for keyword arguments in function calls
867
function complete_keyword_argument(partial, last_idx, context_module)
126✔
868
    frange, ex, wordrange, = identify_possible_method_completion(partial, last_idx)
126✔
869
    fail = Completion[], 0:-1, frange
126✔
870
    ex.head === :call || is_broadcasting_expr(ex) || return fail
222✔
871

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

875
    methods = Completion[]
33✔
876
    complete_methods!(methods, funct, Any[Vararg{Any}], kwargs_ex, -1, kwargs_flag == 1)
33✔
877
    # TODO: use args_ex instead of Any[Vararg{Any}] and only provide kwarg completion for
878
    # method calls compatible with the current arguments.
879

880
    # For each method corresponding to the function call, provide completion suggestions
881
    # for each keyword that starts like the last word and that is not already used
882
    # previously in the expression. The corresponding suggestion is "kwname=".
883
    # If the keyword corresponds to an existing name, also include "kwname" as a suggestion
884
    # since the syntax "foo(; kwname)" is equivalent to "foo(; kwname=kwname)".
885
    last_word = partial[wordrange] # the word to complete
66✔
886
    kwargs = Set{String}()
33✔
887
    for m in methods
33✔
888
        m::MethodCompletion
86✔
889
        possible_kwargs = Base.kwarg_decl(m.method)
86✔
890
        current_kwarg_candidates = String[]
86✔
891
        for _kw in possible_kwargs
88✔
892
            kw = String(_kw)
198✔
893
            if !endswith(kw, "...") && startswith(kw, last_word) && _kw ∉ kwargs_ex
231✔
894
                push!(current_kwarg_candidates, kw)
51✔
895
            end
896
        end
282✔
897
        union!(kwargs, current_kwarg_candidates)
86✔
898
    end
119✔
899

900
    suggestions = Completion[KeywordArgumentCompletion(kwarg) for kwarg in kwargs]
72✔
901
    append!(suggestions, complete_symbol(last_word, Returns(true), context_module))
33✔
902

903
    return sort!(suggestions, by=completion_text), wordrange
33✔
904
end
905

906
function project_deps_get_completion_candidates(pkgstarts::String, project_file::String)
8✔
907
    loading_candidates = String[]
8✔
908
    d = Base.parsed_toml(project_file)
8✔
909
    pkg = get(d, "name", nothing)::Union{String, Nothing}
9✔
910
    if pkg !== nothing && startswith(pkg, pkgstarts)
9✔
911
        push!(loading_candidates, pkg)
1✔
912
    end
913
    deps = get(d, "deps", nothing)::Union{Dict{String, Any}, Nothing}
16✔
914
    if deps !== nothing
8✔
915
        for (pkg, _) in deps
16✔
916
            startswith(pkg, pkgstarts) && push!(loading_candidates, pkg)
13✔
917
        end
8✔
918
    end
919
    return Completion[PackageCompletion(name) for name in loading_candidates]
8✔
920
end
921

922
function completions(string::String, pos::Int, context_module::Module=Main, shift::Bool=true)
662✔
923
    # First parse everything up to the current position
924
    partial = string[1:pos]
993✔
925
    inc_tag = Base.incomplete_tag(Meta.parse(partial, raise=false, depwarn=false))
334✔
926

927
    # ?(x, y)TAB lists methods you can call with these objects
928
    # ?(x, y TAB lists methods that take these objects as the first two arguments
929
    # MyModule.?(x, y)TAB restricts the search to names in MyModule
930
    rexm = match(r"(\w+\.|)\?\((.*)$", partial)
334✔
931
    if rexm !== nothing
334✔
932
        # Get the module scope
933
        if isempty(rexm.captures[1])
26✔
934
            callee_module = context_module
×
935
        else
936
            modname = Symbol(rexm.captures[1][1:end-1])
13✔
937
            if isdefined(context_module, modname)
13✔
938
                callee_module = getfield(context_module, modname)
13✔
939
                if !isa(callee_module, Module)
13✔
940
                    callee_module = context_module
13✔
941
                end
942
            else
943
                callee_module = context_module
×
944
            end
945
        end
946
        moreargs = !endswith(rexm.captures[2], ')')
14✔
947
        callstr = "_(" * rexm.captures[2]
13✔
948
        if moreargs
13✔
949
            callstr *= ')'
7✔
950
        end
951
        ex_org = Meta.parse(callstr, raise=false, depwarn=false)
13✔
952
        if isa(ex_org, Expr)
13✔
953
            return complete_any_methods(ex_org, callee_module::Module, context_module, moreargs, shift), (0:length(rexm.captures[1])+1) .+ rexm.offset, false
13✔
954
        end
955
    end
956

957
    # if completing a key in a Dict
958
    identifier, partial_key, loc = dict_identifier_key(partial, inc_tag, context_module)
398✔
959
    if identifier !== nothing
321✔
960
        matches = find_dict_matches(identifier, partial_key)
77✔
961
        length(matches)==1 && (lastindex(string) <= pos || string[nextind(string,pos)] != ']') && (matches[1]*=']')
81✔
962
        length(matches)>0 && return Completion[DictCompletion(identifier, match) for match in sort!(matches)], loc::Int:pos, true
77✔
963
    end
964

965
    # otherwise...
966
    if inc_tag in [:cmd, :string]
489✔
967
        m = match(r"[\t\n\r\"`><=*?|]| (?!\\)", reverse(partial))
31✔
968
        startpos = nextind(partial, reverseind(partial, m.offset))
62✔
969
        r = startpos:pos
31✔
970

971
        expanded = complete_expanduser(replace(string[r], r"\\ " => " "), r)
62✔
972
        expanded[3] && return expanded  # If user expansion available, return it
31✔
973

974
        paths, r, success = complete_path(replace(string[r], r"\\ " => " "), pos)
58✔
975

976
        if inc_tag === :string && close_path_completion(string, startpos, r, paths, pos)
29✔
977
            paths[1] = PathCompletion((paths[1]::PathCompletion).path * "\"")
2✔
978
        end
979

980
        #Latex symbols can be completed for strings
981
        (success || inc_tag === :cmd) && return sort!(paths, by=p->p.path), r, success
29✔
982
    end
983

984
    ok, ret = bslash_completions(string, pos)
244✔
985
    ok && return ret
244✔
986

987
    # Make sure that only bslash_completions is working on strings
988
    inc_tag === :string && return Completion[], 0:-1, false
213✔
989
    if inc_tag === :other
208✔
990
        frange, ex, wordrange, method_name_end = identify_possible_method_completion(partial, pos)
165✔
991
        if last(frange) != -1 && all(isspace, @view partial[wordrange]) # no last argument to complete
165✔
992
            if ex.head === :call
81✔
993
                return complete_methods(ex, context_module, shift), first(frange):method_name_end, false
78✔
994
            elseif is_broadcasting_expr(ex)
3✔
995
                return complete_methods(ex, context_module, shift), first(frange):(method_name_end - 1), false
3✔
996
            end
997
        end
998
    elseif inc_tag === :comment
43✔
999
        return Completion[], 0:-1, false
1✔
1000
    end
1001

1002
    # Check whether we can complete a keyword argument in a function call
1003
    kwarg_completion, wordrange = complete_keyword_argument(partial, pos, context_module)
219✔
1004
    isempty(wordrange) || return kwarg_completion, wordrange, !isempty(kwarg_completion)
159✔
1005

1006
    dotpos = something(findprev(isequal('.'), string, pos), 0)
162✔
1007
    startpos = nextind(string, something(findprev(in(non_identifier_chars), string, pos), 0))
141✔
1008
    # strip preceding ! operator
1009
    if (m = match(r"\G\!+", partial, startpos)) isa RegexMatch
93✔
1010
        startpos += length(m.match)
2✔
1011
    end
1012

1013
    ffunc = Returns(true)
93✔
1014
    suggestions = Completion[]
93✔
1015
    comp_keywords = true
×
1016
    if afterusing(string, startpos)
93✔
1017
        # We're right after using or import. Let's look only for packages
1018
        # and modules we can reach from here
1019

1020
        # If there's no dot, we're in toplevel, so we should
1021
        # also search for packages
1022
        s = string[startpos:pos]
15✔
1023
        if dotpos <= startpos
8✔
1024
            for dir in Base.load_path()
7✔
1025
                if basename(dir) in Base.project_names && isfile(dir)
43✔
1026
                    append!(suggestions, project_deps_get_completion_candidates(s, dir))
8✔
1027
                end
1028
                isdir(dir) || continue
17✔
1029
                for pname in readdir(dir)
8✔
1030
                    if pname[1] != '.' && pname != "METADATA" &&
800✔
1031
                        pname != "REQUIRE" && startswith(pname, s)
1032
                        # Valid file paths are
1033
                        #   <Mod>.jl
1034
                        #   <Mod>/src/<Mod>.jl
1035
                        #   <Mod>.jl/src/<Mod>.jl
1036
                        if isfile(joinpath(dir, pname))
65✔
1037
                            endswith(pname, ".jl") && push!(suggestions,
×
1038
                                                            PackageCompletion(pname[1:prevind(pname, end-2)]))
1039
                        else
1040
                            mod_name = if endswith(pname, ".jl")
65✔
1041
                                pname[1:prevind(pname, end-2)]
×
1042
                            else
1043
                                pname
65✔
1044
                            end
1045
                            if isfile(joinpath(dir, pname, "src",
65✔
1046
                                               "$mod_name.jl"))
1047
                                push!(suggestions, PackageCompletion(mod_name))
64✔
1048
                            end
1049
                        end
1050
                    end
1051
                end
423✔
1052
            end
24✔
1053
        end
1054
        ffunc = (mod,x)->(Base.isbindingresolved(mod, x) && isdefined(mod, x) && isa(getfield(mod, x), Module))
8,146✔
1055
        comp_keywords = false
×
1056
    end
1057
    startpos == 0 && (pos = -1)
93✔
1058
    dotpos < startpos && (dotpos = startpos - 1)
93✔
1059
    s = string[startpos:pos]
180✔
1060
    comp_keywords && append!(suggestions, complete_keyword(s))
93✔
1061
    # if the start of the string is a `.`, try to consume more input to get back to the beginning of the last expression
1062
    if 0 < startpos <= lastindex(string) && string[startpos] == '.'
180✔
1063
        i = prevind(string, startpos)
28✔
1064
        while 0 < i
60✔
1065
            c = string[i]
70✔
1066
            if c in (')', ']')
47✔
1067
                if c == ')'
32✔
1068
                    c_start = '('
×
1069
                    c_end = ')'
26✔
1070
                elseif c == ']'
6✔
1071
                    c_start = '['
×
1072
                    c_end = ']'
×
1073
                end
1074
                frange, end_of_identifier = find_start_brace(string[1:prevind(string, i)], c_start=c_start, c_end=c_end)
64✔
1075
                isempty(frange) && break # unbalanced parens
32✔
1076
                startpos = first(frange)
32✔
1077
                i = prevind(string, startpos)
32✔
1078
            elseif c in ('\'', '\"', '\`')
12✔
1079
                s = "$c$c"*string[startpos:pos]
×
1080
                break
×
1081
            else
1082
                break
×
1083
            end
1084
            s = string[startpos:pos]
64✔
1085
        end
32✔
1086
    end
1087
    append!(suggestions, complete_symbol(s, ffunc, context_module))
178✔
1088
    return sort!(unique(suggestions), by=completion_text), (dotpos+1):pos, true
93✔
1089
end
1090

1091
function shell_completions(string, pos)
15✔
1092
    # First parse everything up to the current position
1093
    scs = string[1:pos]
30✔
1094
    local args, last_parse
×
1095
    try
15✔
1096
        args, last_parse = Base.shell_parse(scs, true)::Tuple{Expr,UnitRange{Int}}
15✔
1097
    catch
1098
        return Completion[], 0:-1, false
×
1099
    end
1100
    ex = args.args[end]::Expr
15✔
1101
    # Now look at the last thing we parsed
1102
    isempty(ex.args) && return Completion[], 0:-1, false
15✔
1103
    arg = ex.args[end]
15✔
1104
    if all(s -> isa(s, AbstractString), ex.args)
44✔
1105
        arg = arg::AbstractString
12✔
1106
        # Treat this as a path
1107

1108
        # As Base.shell_parse throws away trailing spaces (unless they are escaped),
1109
        # we need to special case here.
1110
        # If the last char was a space, but shell_parse ignored it search on "".
1111
        ignore_last_word = arg != " " && scs[end] == ' '
23✔
1112
        prefix = ignore_last_word ? "" : join(ex.args)
23✔
1113

1114
        # Also try looking into the env path if the user wants to complete the first argument
1115
        use_envpath = !ignore_last_word && length(args.args) < 2
12✔
1116

1117
        return complete_path(prefix, pos, use_envpath=use_envpath, shell_escape=true)
12✔
1118
    elseif isexpr(arg, :incomplete) || isexpr(arg, :error)
5✔
1119
        partial = scs[last_parse]
4✔
1120
        ret, range = completions(partial, lastindex(partial))
2✔
1121
        range = range .+ (first(last_parse) - 1)
2✔
1122
        return ret, range, true
2✔
1123
    end
1124
    return Completion[], 0:-1, false
1✔
1125
end
1126

1127
function UndefVarError_hint(io::IO, ex::UndefVarError)
6✔
1128
    var = ex.var
6✔
1129
    if var === :or
6✔
1130
        print(io, "\nsuggestion: Use `||` for short-circuiting boolean OR.")
×
1131
    elseif var === :and
6✔
1132
        print(io, "\nsuggestion: Use `&&` for short-circuiting boolean AND.")
×
1133
    elseif var === :help
6✔
1134
        println(io)
×
1135
        # Show friendly help message when user types help or help() and help is undefined
1136
        show(io, MIME("text/plain"), Base.Docs.parsedoc(Base.Docs.keywords[:help]))
×
1137
    elseif var === :quit
6✔
1138
        print(io, "\nsuggestion: To exit Julia, use Ctrl-D, or type exit() and press enter.")
×
1139
    end
1140
end
1141

1142
function __init__()
460✔
1143
    Base.Experimental.register_error_hint(UndefVarError_hint, UndefVarError)
460✔
1144
    nothing
460✔
1145
end
1146

1147
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