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

JuliaLang / julia / #37919

29 Sep 2024 09:41AM UTC coverage: 86.232% (-0.3%) from 86.484%
#37919

push

local

web-flow
fix rawbigints OOB issues (#55917)

Fixes issues introduced in #50691 and found in #55906:
* use `@inbounds` and `@boundscheck` macros in rawbigints, for catching
OOB with `--check-bounds=yes`
* fix OOB in `truncate`

12 of 13 new or added lines in 1 file covered. (92.31%)

1287 existing lines in 41 files now uncovered.

77245 of 89578 relevant lines covered (86.23%)

15686161.83 hits per line

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

87.89
/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: Const
8
const CC = Core.Compiler
9
using Base.Meta
10
using Base: propertynames, something, IdSet
11
using Base.Filesystem: _readdirx
12

13
abstract type Completion end
14

15
struct TextCompletion <: Completion
16
    text::String
193✔
17
end
18

19
struct KeywordCompletion <: Completion
20
    keyword::String
181✔
21
end
22

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

27
struct PathCompletion <: Completion
28
    path::String
7,844✔
29
end
30

31
struct ModuleCompletion <: Completion
32
    parent::Module
402,095✔
33
    mod::String
34
end
35

36
struct PackageCompletion <: Completion
37
    package::String
262✔
38
end
39

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

45
struct FieldCompletion <: Completion
46
    typ::DataType
18✔
47
    field::Symbol
48
end
49

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

56
struct BslashCompletion <: Completion
57
    bslash::String
7,826✔
58
end
59

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

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

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

73
# interface definition
74
function Base.getproperty(c::Completion, name::Symbol)
75
    if name === :text
8,981,304✔
76
        return getfield(c, :text)::String
193✔
77
    elseif name === :keyword
8,979,016✔
78
        return getfield(c, :keyword)::String
2,095✔
79
    elseif name === :path
8,971,698✔
80
        return getfield(c, :path)::String
36,080✔
81
    elseif name === :parent
8,971,698✔
82
        return getfield(c, :parent)::Module
×
83
    elseif name === :mod
16,127✔
84
        return getfield(c, :mod)::String
8,955,571✔
85
    elseif name === :package
12,073✔
86
        return getfield(c, :package)::String
4,054✔
87
    elseif name === :property
12,073✔
88
        return getfield(c, :property)::Symbol
271✔
89
    elseif name === :field
11,802✔
90
        return getfield(c, :field)::Symbol
35✔
91
    elseif name === :method
8,140✔
92
        return getfield(c, :method)::Method
5,060✔
93
    elseif name === :bslash
314✔
94
        return getfield(c, :bslash)::String
7,826✔
95
    elseif name === :text
314✔
96
        return getfield(c, :text)::String
×
97
    elseif name === :key
314✔
98
        return getfield(c, :key)::String
124✔
99
    elseif name === :kwarg
190✔
100
        return getfield(c, :kwarg)::String
89✔
101
    end
102
    return getfield(c, name)
101✔
103
end
104

105
_completion_text(c::TextCompletion) = c.text
193✔
106
_completion_text(c::KeywordCompletion) = c.keyword
1,968✔
107
_completion_text(c::KeyvalCompletion) = c.keyval
101✔
108
_completion_text(c::PathCompletion) = c.path
7,288✔
109
_completion_text(c::ModuleCompletion) = c.mod
4,878,208✔
110
_completion_text(c::PackageCompletion) = c.package
3,144✔
111
_completion_text(c::PropertyCompletion) = sprint(Base.show_sym, c.property)
271✔
112
_completion_text(c::FieldCompletion) = sprint(Base.show_sym, c.field)
35✔
113
_completion_text(c::MethodCompletion) = repr(c.method)
3,615✔
114
_completion_text(c::BslashCompletion) = c.bslash
7,826✔
115
_completion_text(c::ShellCompletion) = c.text
×
116
_completion_text(c::DictCompletion) = c.key
124✔
117
_completion_text(c::KeywordArgumentCompletion) = c.kwarg*'='
89✔
118

119
completion_text(c) = _completion_text(c)::String
4,902,779✔
120

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

123
function completes_global(x, name)
124
    return startswith(x, name) && !('#' in x)
3,062,906✔
125
end
126

127
function appendmacro!(syms, macros, needle, endchar)
3,310✔
128
    for macsym in macros
3,310✔
129
        s = String(macsym)
51,578✔
130
        if endswith(s, needle)
51,578✔
131
            from = nextind(s, firstindex(s))
6,656✔
132
            to = prevind(s, sizeof(s)-sizeof(needle)+1)
3,328✔
133
            push!(syms, s[from:to]*endchar)
6,656✔
134
        end
135
    end
51,578✔
136
end
137

138
function append_filtered_mod_names!(ffunc::Function, suggestions::Vector{Completion},
1,655✔
139
                                    mod::Module, name::String, complete_internal_only::Bool)
140
    imported = usings = !complete_internal_only
1,655✔
141
    ssyms = names(mod; all=true, imported, usings)
1,655✔
142
    filter!(ffunc, ssyms)
1,655✔
143
    macros = filter(x -> startswith(String(x), "@" * name), ssyms)
1,782,900✔
144
    syms = String[sprint((io,s)->Base.show_sym(io, s; allow_macroname=true), s) for s in ssyms if completes_global(String(s), name)]
400,422✔
145
    appendmacro!(syms, macros, "_str", "\"")
1,655✔
146
    appendmacro!(syms, macros, "_cmd", "`")
1,655✔
147
    for sym in syms
1,655✔
148
        push!(suggestions, ModuleCompletion(mod, sym))
402,095✔
149
    end
402,095✔
150
    return suggestions
1,655✔
151
end
152

153
# REPL Symbol Completions
154
function complete_symbol!(suggestions::Vector{Completion},
3,464✔
155
                          @nospecialize(prefix), name::String, context_module::Module;
156
                          complete_modules_only::Bool=false,
157
                          shift::Bool=false)
158
    local mod, t, val
1,732✔
159
    complete_internal_only = false
×
160
    if prefix !== nothing
1,732✔
161
        res = repl_eval_ex(prefix, context_module)
739✔
162
        res === nothing && return Completion[]
739✔
163
        if res isa Const
730✔
164
            val = res.val
708✔
165
            if isa(val, Module)
708✔
166
                mod = val
662✔
167
                if !shift
662✔
168
                    # when module is explicitly accessed, show internal bindings that are
169
                    # defined by the module, unless shift key is pressed
170
                    complete_internal_only = true
×
171
                end
172
            else
173
                t = typeof(val)
46✔
174
            end
175
        else
176
            t = CC.widenconst(res)
22✔
177
        end
178
    else
179
        mod = context_module
993✔
180
    end
181

182
    if @isdefined(mod) # lookup names available within the module
1,723✔
183
        let modname = nameof(mod),
1,655✔
184
            is_main = mod===Main
185
            append_filtered_mod_names!(suggestions, mod, name, complete_internal_only) do s::Symbol
1,655✔
186
                if Base.isdeprecated(mod, s)
1,836,248✔
187
                    return false
×
188
                elseif s === modname
1,836,248✔
189
                    return false # exclude `Main.Main.Main`, etc.
2,609✔
190
                elseif complete_modules_only && !completes_module(mod, s)
1,833,639✔
191
                    return false
51,447✔
192
                elseif is_main && s === :MainInclude
1,782,192✔
193
                    return false
947✔
194
                end
195
                return true
1,781,245✔
196
            end
197
        end
198
    elseif @isdefined(val) # looking for a property of an instance
68✔
199
        try
46✔
200
            for property in propertynames(val, false)
46✔
201
                # TODO: support integer arguments (#36872)
202
                if property isa Symbol && startswith(string(property), name)
83✔
203
                    push!(suggestions, PropertyCompletion(val, property))
77✔
204
                end
205
            end
83✔
206
        catch
1✔
207
        end
208
    elseif @isdefined(t) && field_completion_eligible(t)
22✔
209
        # Looking for a member of a type
210
        add_field_completions!(suggestions, name, t)
13✔
211
    end
212
    return suggestions
1,723✔
213
end
214

215
completes_module(mod::Module, x::Symbol) =
52,035✔
216
    Base.isbindingresolved(mod, x) && isdefined(mod, x) && isa(getglobal(mod, x), Module)
217

218
function add_field_completions!(suggestions::Vector{Completion}, name::String, @nospecialize(t))
15✔
219
    if isa(t, Union)
15✔
220
        add_field_completions!(suggestions, name, t.a)
2✔
221
        add_field_completions!(suggestions, name, t.b)
2✔
222
    else
223
        @assert isconcretetype(t)
15✔
224
        fields = fieldnames(t)
15✔
225
        for field in fields
15✔
226
            isa(field, Symbol) || continue # Tuple type has ::Int field name
18✔
227
            s = string(field)
18✔
228
            if startswith(s, name)
18✔
229
                push!(suggestions, FieldCompletion(t, field))
18✔
230
            end
231
        end
18✔
232
    end
233
end
234

235
const GENERIC_PROPERTYNAMES_METHOD = which(propertynames, (Any,))
236

237
function field_completion_eligible(@nospecialize t)
26✔
238
    if isa(t, Union)
26✔
239
        return field_completion_eligible(t.a) && field_completion_eligible(t.b)
2✔
240
    end
241
    isconcretetype(t) || return false
31✔
242
    # field completion is correct only when `getproperty` fallbacks to `getfield`
243
    match = Base._which(Tuple{typeof(propertynames),t}; raise=false)
34✔
244
    match === nothing && return false
17✔
245
    return match.method === GENERIC_PROPERTYNAMES_METHOD
17✔
246
end
247

248
function complete_from_list!(suggestions::Vector{Completion}, T::Type, list::Vector{String}, s::String)
1,346✔
249
    r = searchsorted(list, s)
1,346✔
250
    i = first(r)
1,346✔
251
    n = length(list)
1,346✔
252
    while i <= n && startswith(list[i],s)
1,542✔
253
        r = first(r):i
196✔
254
        i += 1
196✔
255
    end
196✔
256
    for kw in list[r]
1,346✔
257
        push!(suggestions, T(kw))
196✔
258
    end
196✔
259
    return suggestions
1,346✔
260
end
261

262
const sorted_keywords = [
263
    "abstract type", "baremodule", "begin", "break", "catch", "ccall",
264
    "const", "continue", "do", "else", "elseif", "end", "export",
265
    "finally", "for", "function", "global", "if", "import",
266
    "let", "local", "macro", "module", "mutable struct",
267
    "primitive type", "quote", "return", "struct",
268
    "try", "using", "while"]
269

270
complete_keyword!(suggestions::Vector{Completion}, s::String) =
604✔
271
    complete_from_list!(suggestions, KeywordCompletion, sorted_keywords, s)
272

273
const sorted_keyvals = ["false", "true"]
274

275
complete_keyval!(suggestions::Vector{Completion}, s::String) =
742✔
276
    complete_from_list!(suggestions, KeyvalCompletion, sorted_keyvals, s)
277

278
function do_raw_escape(s)
279
    # escape_raw_string with delim='`' and ignoring the rule for the ending \
280
    return replace(s, r"(\\+)`" => s"\1\\`")
×
281
end
282
function do_shell_escape(s)
283
    return Base.shell_escape_posixly(s)
3,712✔
284
end
285
function do_string_escape(s)
286
    return escape_string(s, ('\"','$'))
7,382✔
287
end
288

289
const PATH_cache_lock = Base.ReentrantLock()
290
const PATH_cache = Set{String}()
291
PATH_cache_task::Union{Task,Nothing} = nothing # used for sync in tests
292
next_cache_update::Float64 = 0.0
293
function maybe_spawn_cache_PATH()
21✔
294
    global PATH_cache_task, next_cache_update
×
295
    @lock PATH_cache_lock begin
21✔
296
        PATH_cache_task isa Task && !istaskdone(PATH_cache_task) && return
21✔
297
        time() < next_cache_update && return
21✔
298
        PATH_cache_task = Threads.@spawn begin
6✔
299
            REPLCompletions.cache_PATH()
3✔
300
            @lock PATH_cache_lock PATH_cache_task = nothing # release memory when done
3✔
301
        end
302
        Base.errormonitor(PATH_cache_task)
3✔
303
    end
304
end
305

306
# caches all reachable files in PATH dirs
307
function cache_PATH()
3✔
308
    path = get(ENV, "PATH", nothing)
3✔
309
    path isa String || return
3✔
310

311
    global next_cache_update
×
312

313
    # Calling empty! on PATH_cache would be annoying for async typing hints as completions would temporarily disappear.
314
    # So keep track of what's added this time and at the end remove any that didn't appear this time from the global cache.
315
    this_PATH_cache = Set{String}()
3✔
316

317
    @debug "caching PATH files" PATH=path
3✔
318
    pathdirs = split(path, @static Sys.iswindows() ? ";" : ":")
3✔
319

320
    next_yield_time = time() + 0.01
3✔
321

322
    t = @elapsed for pathdir in pathdirs
3✔
323
        actualpath = try
18✔
324
            realpath(pathdir)
20✔
325
        catch ex
326
            ex isa Base.IOError || rethrow()
2✔
327
            # Bash doesn't expect every folder in PATH to exist, so neither shall we
328
            continue
2✔
329
        end
330

331
        if actualpath != pathdir && in(actualpath, pathdirs)
27✔
332
            # Remove paths which (after resolving links) are in the env path twice.
333
            # Many distros eg. point /bin to /usr/bin but have both in the env path.
334
            continue
3✔
335
        end
336

337
        path_entries = try
13✔
338
            _readdirx(pathdir)
14✔
339
        catch e
340
            # Bash allows dirs in PATH that can't be read, so we should as well.
341
            if isa(e, Base.IOError) || isa(e, Base.ArgumentError)
1✔
342
                continue
1✔
343
            else
344
                # We only handle IOError and ArgumentError here
345
                rethrow()
×
346
            end
347
        end
348
        for entry in path_entries
12✔
349
            # In a perfect world, we would filter on whether the file is executable
350
            # here, or even on whether the current user can execute the file in question.
351
            try
2,463✔
352
                if isfile(entry)
2,463✔
353
                    @lock PATH_cache_lock push!(PATH_cache, entry.name)
2,394✔
354
                    push!(this_PATH_cache, entry.name)
2,395✔
355
                end
356
            catch e
357
                # `isfile()` can throw in rare cases such as when probing a
358
                # symlink that points to a file within a directory we do not
359
                # have read access to.
360
                if isa(e, Base.IOError)
1✔
361
                    continue
1✔
362
                else
363
                    rethrow()
×
364
                end
365
            end
366
            if time() >= next_yield_time
2,462✔
UNCOV
367
                yield() # to avoid blocking typing when -t1
×
UNCOV
368
                next_yield_time = time() + 0.01
×
369
            end
370
        end
2,463✔
371
    end
372

373
    @lock PATH_cache_lock begin
3✔
374
        intersect!(PATH_cache, this_PATH_cache) # remove entries from PATH_cache that weren't found this time
3✔
375
        next_cache_update = time() + 10 # earliest next update can run is 10s after
3✔
376
    end
377

378
    @debug "caching PATH files took $t seconds" length(pathdirs) length(PATH_cache)
3✔
379
    return PATH_cache
3✔
380
end
381

382
function complete_path(path::AbstractString;
1,128✔
383
                       use_envpath=false,
384
                       shell_escape=false,
385
                       raw_escape=false,
386
                       string_escape=false,
387
                       contract_user=false)
388
    @assert !(shell_escape && string_escape)
564✔
389
    if Base.Sys.isunix() && occursin(r"^~(?:/|$)", path)
564✔
390
        # if the path is just "~", don't consider the expanded username as a prefix
391
        if path == "~"
×
392
            dir, prefix = homedir(), ""
×
393
        else
394
            dir, prefix = splitdir(homedir() * path[2:end])
×
395
        end
396
    else
397
        dir, prefix = splitdir(path)
564✔
398
    end
399
    entries = try
564✔
400
        if isempty(dir)
564✔
401
            _readdirx()
297✔
402
        elseif isdir(dir)
267✔
403
            _readdirx(dir)
108✔
404
        else
405
            return Completion[], dir, false
564✔
406
        end
407
    catch ex
408
        ex isa Base.IOError || rethrow()
×
409
        return Completion[], dir, false
×
410
    end
411

412
    matches = Set{String}()
405✔
413
    for entry in entries
405✔
414
        if startswith(entry.name, prefix)
45,687✔
415
            is_dir = try isdir(entry) catch ex; ex isa Base.IOError ? false : rethrow() end
7,069✔
416
            push!(matches, is_dir ? entry.name * "/" : entry.name)
13,122✔
417
        end
418
    end
45,687✔
419

420
    if use_envpath && isempty(dir)
405✔
421
        # Look for files in PATH as well. These are cached in `cache_PATH` in an async task to not block typing.
422
        # If we cannot get lock because its still caching just pass over this so that typing isn't laggy.
423
        maybe_spawn_cache_PATH() # only spawns if enough time has passed and the previous caching task has completed
21✔
424
        @lock PATH_cache_lock begin
21✔
425
            for file in PATH_cache
42✔
426
                startswith(file, prefix) && push!(matches, file)
11,247✔
427
            end
22,494✔
428
        end
429
    end
430

431
    matches = ((shell_escape ? do_shell_escape(s) : string_escape ? do_string_escape(s) : s) for s in matches)
405✔
432
    matches = ((raw_escape ? do_raw_escape(s) : s) for s in matches)
×
433
    matches = Completion[PathCompletion(contract_user ? contractuser(s) : s) for s in matches]
7,490✔
434
    return matches, dir, !isempty(matches)
405✔
435
end
436

437
function complete_path(path::AbstractString,
×
438
                       pos::Int;
439
                       use_envpath=false,
440
                       shell_escape=false,
441
                       string_escape=false,
442
                       contract_user=false)
443
    ## TODO: enable this depwarn once Pkg is fixed
444
    #Base.depwarn("complete_path with pos argument is deprecated because the return value [2] is incorrect to use", :complete_path)
445
    paths, dir, success = complete_path(path; use_envpath, shell_escape, string_escape)
×
446
    if Base.Sys.isunix() && occursin(r"^~(?:/|$)", path)
×
447
        # if the path is just "~", don't consider the expanded username as a prefix
448
        if path == "~"
×
449
            dir, prefix = homedir(), ""
×
450
        else
451
            dir, prefix = splitdir(homedir() * path[2:end])
×
452
        end
453
    else
454
        dir, prefix = splitdir(path)
×
455
    end
456
    startpos = pos - lastindex(prefix) + 1
×
457
    Sys.iswindows() && map!(paths, paths) do c::PathCompletion
×
458
        # emulation for unnecessarily complicated return value, since / is a
459
        # perfectly acceptable path character which does not require quoting
460
        # but is required by Pkg's awkward parser handling
461
        return endswith(c.path, "/") ? PathCompletion(chop(c.path) * "\\\\") : c
×
462
    end
463
    return paths, startpos:pos, success
×
464
end
465

466
function complete_expanduser(path::AbstractString, r)
548✔
467
    expanded =
548✔
468
        try expanduser(path)
549✔
469
        catch e
470
            e isa ArgumentError || rethrow()
1✔
471
            path
549✔
472
        end
473
    return Completion[PathCompletion(expanded)], r, path != expanded
548✔
474
end
475

476
# Returns a range that includes the method name in front of the first non
477
# closed start brace from the end of the string.
478
function find_start_brace(s::AbstractString; c_start='(', c_end=')')
5,634✔
479
    r = reverse(s)
2,817✔
480
    i = firstindex(r)
×
481
    braces = in_comment = 0
×
482
    in_single_quotes = in_double_quotes = in_back_ticks = false
×
483
    while i <= ncodeunits(r)
64,369✔
484
        c, i = iterate(r, i)
124,480✔
485
        if c == '#' && i <= ncodeunits(r) && iterate(r, i)[1] == '='
62,240✔
486
            c, i = iterate(r, i) # consume '='
8✔
487
            new_comments = 1
×
488
            # handle #=#=#=#, by counting =# pairs
489
            while i <= ncodeunits(r) && iterate(r, i)[1] == '#'
6✔
490
                c, i = iterate(r, i) # consume '#'
6✔
491
                iterate(r, i)[1] == '=' || break
3✔
492
                c, i = iterate(r, i) # consume '='
4✔
493
                new_comments += 1
2✔
494
            end
2✔
495
            if c == '='
4✔
496
                in_comment += new_comments
3✔
497
            else
498
                in_comment -= new_comments
1✔
499
            end
500
        elseif !in_single_quotes && !in_double_quotes && !in_back_ticks && in_comment == 0
62,236✔
501
            if c == c_start
60,471✔
502
                braces += 1
958✔
503
            elseif c == c_end
59,513✔
504
                braces -= 1
270✔
505
            elseif c == '\''
59,243✔
506
                in_single_quotes = true
×
507
            elseif c == '"'
59,227✔
508
                in_double_quotes = true
×
509
            elseif c == '`'
58,949✔
510
                in_back_ticks = true
×
511
            end
512
        else
513
            if in_single_quotes &&
1,765✔
514
                c == '\'' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
515
                in_single_quotes = false
×
516
            elseif in_double_quotes &&
1,750✔
517
                c == '"' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
518
                in_double_quotes = false
×
519
            elseif in_back_ticks &&
1,522✔
520
                c == '`' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
521
                in_back_ticks = false
×
522
            elseif in_comment > 0 &&
1,515✔
523
                c == '=' && i <= ncodeunits(r) && iterate(r, i)[1] == '#'
524
                # handle =#=#=#=, by counting #= pairs
525
                c, i = iterate(r, i) # consume '#'
6✔
526
                old_comments = 1
×
527
                while i <= ncodeunits(r) && iterate(r, i)[1] == '='
4✔
528
                    c, i = iterate(r, i) # consume '='
4✔
529
                    iterate(r, i)[1] == '#' || break
2✔
530
                    c, i = iterate(r, i) # consume '#'
2✔
531
                    old_comments += 1
1✔
532
                end
1✔
533
                if c == '#'
3✔
534
                    in_comment -= old_comments
2✔
535
                else
536
                    in_comment += old_comments
1✔
537
                end
538
            end
539
        end
540
        braces == 1 && break
62,240✔
541
    end
61,552✔
542
    braces != 1 && return 0:-1, -1
2,817✔
543
    method_name_end = reverseind(s, i)
1,368✔
544
    startind = nextind(s, something(findprev(in(non_identifier_chars), s, method_name_end), 0))::Int
1,066✔
545
    return (startind:lastindex(s), method_name_end)
688✔
546
end
547

548
struct REPLCacheToken end
549

550
struct REPLInterpreter <: CC.AbstractInterpreter
551
    limit_aggressive_inference::Bool
552
    world::UInt
553
    inf_params::CC.InferenceParams
554
    opt_params::CC.OptimizationParams
555
    inf_cache::Vector{CC.InferenceResult}
556
    function REPLInterpreter(limit_aggressive_inference::Bool=false;
522✔
557
                             world::UInt = Base.get_world_counter(),
558
                             inf_params::CC.InferenceParams = CC.InferenceParams(;
559
                                 aggressive_constant_propagation=true),
560
                             opt_params::CC.OptimizationParams = CC.OptimizationParams(),
561
                             inf_cache::Vector{CC.InferenceResult} = CC.InferenceResult[])
562
        return new(limit_aggressive_inference, world, inf_params, opt_params, inf_cache)
516✔
563
    end
564
end
565
CC.InferenceParams(interp::REPLInterpreter) = interp.inf_params
163,705✔
566
CC.OptimizationParams(interp::REPLInterpreter) = interp.opt_params
104✔
567
CC.get_inference_world(interp::REPLInterpreter) = interp.world
99,553✔
568
CC.get_inference_cache(interp::REPLInterpreter) = interp.inf_cache
43,618✔
569
CC.cache_owner(::REPLInterpreter) = REPLCacheToken()
×
570

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

574
# REPLInterpreter doesn't need any sources to be cached, so discard them aggressively
575
CC.transform_result_for_cache(::REPLInterpreter, ::Core.MethodInstance, ::CC.WorldRange, ::CC.InferenceResult) = nothing
×
576

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

580
# `REPLInterpreter` aggressively resolves global bindings to enable reasonable completions
581
# for lines like `Mod.a.|` (where `|` is the cursor position).
582
# Aggressive binding resolution poses challenges for the inference cache validation
583
# (until https://github.com/JuliaLang/julia/issues/40399 is implemented).
584
# To avoid the cache validation issues, `REPLInterpreter` only allows aggressive binding
585
# resolution for top-level frame representing REPL input code and for child uncached frames
586
# that are constant propagated from the top-level frame ("repl-frame"s). This works, even if
587
# those global bindings are not constant and may be mutated in the future, since:
588
# a.) "repl-frame"s are never cached, and
589
# b.) mutable values are never observed by any cached frames.
590
#
591
# `REPLInterpreter` also aggressively concrete evaluate `:inconsistent` calls within
592
# "repl-frame" to provide reasonable completions for lines like `Ref(Some(42))[].|`.
593
# Aggressive concrete evaluation allows us to get accurate type information about complex
594
# expressions that otherwise can not be constant folded, in a safe way, i.e. it still
595
# doesn't evaluate effectful expressions like `pop!(xs)`.
596
# Similarly to the aggressive binding resolution, aggressive concrete evaluation doesn't
597
# present any cache validation issues because "repl-frame" is never cached.
598

599
# `REPLInterpreter` is specifically used by `repl_eval_ex`, where all top-level frames are
600
# `repl_frame` always. However, this assumption wouldn't stand if `REPLInterpreter` were to
601
# be employed, for instance, by `typeinf_ext_toplevel`.
602
is_repl_frame(sv::CC.InferenceState) = sv.linfo.def isa Module && sv.cache_mode === CC.CACHE_MODE_NULL
196✔
603

604
function is_call_graph_uncached(sv::CC.InferenceState)
243,237✔
605
    CC.is_cached(sv) && return false
340,768✔
606
    parent = CC.frame_parent(sv)
494,795✔
607
    parent === nothing && return true
251,558✔
608
    return is_call_graph_uncached(parent::CC.InferenceState)
243,237✔
609
end
610

611
# aggressive global binding resolution within `repl_frame`
612
function CC.abstract_eval_globalref(interp::REPLInterpreter, g::GlobalRef,
613
                                    sv::CC.InferenceState)
614
    if (interp.limit_aggressive_inference ? is_repl_frame(sv) : is_call_graph_uncached(sv))
122,728✔
615
        if CC.isdefined_globalref(g)
5,614✔
616
            return CC.RTEffects(Const(ccall(:jl_get_globalref_value, Any, (Any,), g)), Union{}, CC.EFFECTS_TOTAL)
5,608✔
617
        end
618
        return CC.RTEffects(Union{}, UndefVarError, CC.EFFECTS_THROWS)
6✔
619
    end
620
    return @invoke CC.abstract_eval_globalref(interp::CC.AbstractInterpreter, g::GlobalRef,
55,818✔
621
                                              sv::CC.InferenceState)
622
end
623

624
function is_repl_frame_getproperty(sv::CC.InferenceState)
625
    def = sv.linfo.def
1✔
626
    def isa Method || return false
1✔
627
    def.name === :getproperty || return false
1✔
628
    CC.is_cached(sv) && return false
1✔
629
    return is_repl_frame(CC.frame_parent(sv))
1✔
630
end
631

632
# aggressive global binding resolution for `getproperty(::Module, ::Symbol)` calls within `repl_frame`
633
function CC.builtin_tfunction(interp::REPLInterpreter, @nospecialize(f),
18,362✔
634
                              argtypes::Vector{Any}, sv::CC.InferenceState)
635
    if f === Core.getglobal && (interp.limit_aggressive_inference ? is_repl_frame_getproperty(sv) : is_call_graph_uncached(sv))
18,363✔
636
        if length(argtypes) == 2
57✔
637
            a1, a2 = argtypes
57✔
638
            if isa(a1, Const) && isa(a2, Const)
57✔
639
                a1val, a2val = a1.val, a2.val
57✔
640
                if isa(a1val, Module) && isa(a2val, Symbol)
57✔
641
                    g = GlobalRef(a1val, a2val)
57✔
642
                    if CC.isdefined_globalref(g)
57✔
643
                        return Const(ccall(:jl_get_globalref_value, Any, (Any,), g))
57✔
644
                    end
645
                    return Union{}
×
646
                end
647
            end
648
        end
649
    end
650
    return @invoke CC.builtin_tfunction(interp::CC.AbstractInterpreter, f::Any,
18,305✔
651
                                        argtypes::Vector{Any}, sv::CC.InferenceState)
652
end
653

654
# aggressive concrete evaluation for `:inconsistent` frames within `repl_frame`
655
function CC.concrete_eval_eligible(interp::REPLInterpreter, @nospecialize(f),
36,216✔
656
                                   result::CC.MethodCallResult, arginfo::CC.ArgInfo,
657
                                   sv::CC.InferenceState)
658
    if (interp.limit_aggressive_inference ? is_repl_frame(sv) : is_call_graph_uncached(sv))
72,373✔
659
        neweffects = CC.Effects(result.effects; consistent=CC.ALWAYS_TRUE)
2,660✔
660
        result = CC.MethodCallResult(result.rt, result.exct, result.edgecycle, result.edgelimited,
5,318✔
661
                                     result.edge, neweffects)
662
    end
663
    ret = @invoke CC.concrete_eval_eligible(interp::CC.AbstractInterpreter, f::Any,
72,432✔
664
                                            result::CC.MethodCallResult, arginfo::CC.ArgInfo,
665
                                            sv::CC.InferenceState)
666
    if ret === :semi_concrete_eval
36,216✔
667
        # while the base eligibility check probably won't permit semi-concrete evaluation
668
        # for `REPLInterpreter` (given it completely turns off optimization),
669
        # this ensures we don't inadvertently enter irinterp
670
        ret = :none
×
671
    end
672
    return ret
36,216✔
673
end
674

675
# allow constant propagation for mutable constants
676
function CC.const_prop_argument_heuristic(interp::REPLInterpreter, arginfo::CC.ArgInfo, sv::CC.InferenceState)
677
    if !interp.limit_aggressive_inference
35,693✔
678
        any(@nospecialize(a)->isa(a, Const), arginfo.argtypes) && return true # even if mutable
71,345✔
679
    end
680
    return @invoke CC.const_prop_argument_heuristic(interp::CC.AbstractInterpreter, arginfo::CC.ArgInfo, sv::CC.InferenceState)
95✔
681
end
682

683
function resolve_toplevel_symbols!(src::Core.CodeInfo, mod::Module)
684
    @ccall jl_resolve_globals_in_ir(
510✔
685
        #=jl_array_t *stmts=# src.code::Any,
686
        #=jl_module_t *m=# mod::Any,
687
        #=jl_svec_t *sparam_vals=# Core.svec()::Any,
688
        #=int binding_effects=# 0::Int)::Cvoid
689
    return src
×
690
end
691

692
# lower `ex` and run type inference on the resulting top-level expression
693
function repl_eval_ex(@nospecialize(ex), context_module::Module; limit_aggressive_inference::Bool=false)
2,742✔
694
    if (isexpr(ex, :toplevel) || isexpr(ex, :tuple)) && !isempty(ex.args)
2,741✔
695
        # get the inference result for the last expression
696
        ex = ex.args[end]
3✔
697
    end
698
    lwr = try
1,371✔
699
        Meta.lower(context_module, ex)
1,371✔
700
    catch # macro expansion failed, etc.
701
        return nothing
5✔
702
    end
703
    if lwr isa Symbol
1,366✔
704
        return isdefined(context_module, lwr) ? Const(getfield(context_module, lwr)) : nothing
724✔
705
    end
706
    lwr isa Expr || return Const(lwr) # `ex` is literal
744✔
707
    isexpr(lwr, :thunk) || return nothing # lowered to `Expr(:error, ...)` or similar
570✔
708
    src = lwr.args[1]::Core.CodeInfo
510✔
709

710
    resolve_toplevel_symbols!(src, context_module)
510✔
711
    # construct top-level `MethodInstance`
712
    mi = ccall(:jl_method_instance_for_thunk, Ref{Core.MethodInstance}, (Any, Any), src, context_module)
510✔
713

714
    interp = REPLInterpreter(limit_aggressive_inference)
510✔
715
    result = CC.InferenceResult(mi)
510✔
716
    frame = CC.InferenceState(result, src, #=cache=#:no, interp)
510✔
717

718
    # NOTE Use the fixed world here to make `REPLInterpreter` robust against
719
    #      potential invalidations of `Core.Compiler` methods.
720
    Base.invoke_in_world(COMPLETION_WORLD[], CC.typeinf, interp, frame)
510✔
721

722
    result = frame.result.result
510✔
723
    result === Union{} && return nothing # for whatever reason, callers expect this as the Bottom and/or Top type instead
510✔
724
    return result
501✔
725
end
726

727
# `COMPLETION_WORLD[]` will be initialized within `__init__`
728
# (to allow us to potentially remove REPL from the sysimage in the future).
729
# Note that inference from the `code_typed` call below will use the current world age
730
# rather than `typemax(UInt)`, since `Base.invoke_in_world` uses the current world age
731
# when the given world age is higher than the current one.
732
const COMPLETION_WORLD = Ref{UInt}(typemax(UInt))
733

734
# Generate code cache for `REPLInterpreter` now:
735
# This code cache will be available at the world of `COMPLETION_WORLD`,
736
# assuming no invalidation will happen before initializing REPL.
737
# Once REPL is loaded, `REPLInterpreter` will be resilient against future invalidations.
738
code_typed(CC.typeinf, (REPLInterpreter, CC.InferenceState))
739

740
# Method completion on function call expression that look like :(max(1))
741
MAX_METHOD_COMPLETIONS::Int = 40
742
function _complete_methods(ex_org::Expr, context_module::Module, shift::Bool)
310✔
743
    funct = repl_eval_ex(ex_org.args[1], context_module)
310✔
744
    funct === nothing && return 2, nothing, [], Set{Symbol}()
310✔
745
    funct = CC.widenconst(funct)
305✔
746
    args_ex, kwargs_ex, kwargs_flag = complete_methods_args(ex_org, context_module, true, true)
305✔
747
    return kwargs_flag, funct, args_ex, kwargs_ex
305✔
748
end
749

750
function complete_methods(ex_org::Expr, context_module::Module=Main, shift::Bool=false)
751
    kwargs_flag, funct, args_ex, kwargs_ex = _complete_methods(ex_org, context_module, shift)::Tuple{Int, Any, Vector{Any}, Set{Symbol}}
139✔
752
    out = Completion[]
139✔
753
    kwargs_flag == 2 && return out # one of the kwargs is invalid
139✔
754
    kwargs_flag == 0 && push!(args_ex, Vararg{Any}) # allow more arguments if there is no semicolon
126✔
755
    complete_methods!(out, funct, args_ex, kwargs_ex, shift ? -2 : MAX_METHOD_COMPLETIONS, kwargs_flag == 1)
183✔
756
    return out
126✔
757
end
758

759
MAX_ANY_METHOD_COMPLETIONS::Int = 10
760
function recursive_explore_names!(seen::IdSet, callee_module::Module, initial_module::Module, exploredmodules::IdSet{Module}=IdSet{Module}())
72✔
761
    push!(exploredmodules, callee_module)
86✔
762
    for name in names(callee_module; all=true, imported=true)
72✔
763
        if !Base.isdeprecated(callee_module, name) && !startswith(string(name), '#') && isdefined(initial_module, name)
28,457✔
764
            func = getfield(callee_module, name)
2,246✔
765
            if !isa(func, Module)
2,246✔
766
                funct = Core.Typeof(func)
3,922✔
767
                push!(seen, funct)
2,113✔
768
            elseif isa(func, Module) && func ∉ exploredmodules
133✔
769
                recursive_explore_names!(seen, func, initial_module, exploredmodules)
58✔
770
            end
771
        end
772
    end
14,246✔
773
end
774
function recursive_explore_names(callee_module::Module, initial_module::Module)
775
    seen = IdSet{Any}()
14✔
776
    recursive_explore_names!(seen, callee_module, initial_module)
14✔
777
    seen
×
778
end
779

780
function complete_any_methods(ex_org::Expr, callee_module::Module, context_module::Module, moreargs::Bool, shift::Bool)
14✔
781
    out = Completion[]
14✔
782
    args_ex, kwargs_ex, kwargs_flag = try
14✔
783
        # this may throw, since we set default_any to false
784
        complete_methods_args(ex_org, context_module, false, false)
14✔
785
    catch ex
786
        ex isa ArgumentError || rethrow()
×
787
        return out
14✔
788
    end
789
    kwargs_flag == 2 && return out # one of the kwargs is invalid
14✔
790

791
    # moreargs determines whether to accept more args, independently of the presence of a
792
    # semicolon for the ".?(" syntax
793
    moreargs && push!(args_ex, Vararg{Any})
14✔
794

795
    for seen_name in recursive_explore_names(callee_module, callee_module)
28✔
796
        complete_methods!(out, seen_name, args_ex, kwargs_ex, MAX_ANY_METHOD_COMPLETIONS, false)
1,919✔
797
    end
3,824✔
798

799
    if !shift
14✔
800
        # Filter out methods where all arguments are `Any`
801
        filter!(out) do c
2✔
802
            isa(c, TextCompletion) && return false
12✔
803
            isa(c, MethodCompletion) || return true
12✔
804
            sig = Base.unwrap_unionall(c.method.sig)::DataType
12✔
805
            return !all(@nospecialize(T) -> T === Any || T === Vararg{Any}, sig.parameters[2:end])
20✔
806
        end
807
    end
808

809
    return out
14✔
810
end
811

812
function detect_invalid_kwarg!(kwargs_ex::Vector{Symbol}, @nospecialize(x), kwargs_flag::Int, possible_splat::Bool)
813
    n = isexpr(x, :kw) ? x.args[1] : x
55✔
814
    if n isa Symbol
55✔
815
        push!(kwargs_ex, n)
41✔
816
        return kwargs_flag
41✔
817
    end
818
    possible_splat && isexpr(x, :...) && return kwargs_flag
13✔
819
    return 2 # The kwarg is invalid
10✔
820
end
821

822
function detect_args_kwargs(funargs::Vector{Any}, context_module::Module, default_any::Bool, broadcasting::Bool)
319✔
823
    args_ex = Any[]
319✔
824
    kwargs_ex = Symbol[]
319✔
825
    kwargs_flag = 0
×
826
    # kwargs_flag is:
827
    # * 0 if there is no semicolon and no invalid kwarg
828
    # * 1 if there is a semicolon and no invalid kwarg
829
    # * 2 if there are two semicolons or more, or if some kwarg is invalid, which
830
    #        means that it is not of the form "bar=foo", "bar" or "bar..."
831
    for i in (1+!broadcasting):length(funargs)
474✔
832
        ex = funargs[i]
295✔
833
        if isexpr(ex, :parameters)
295✔
834
            kwargs_flag = ifelse(kwargs_flag == 0, 1, 2) # there should be at most one :parameters
59✔
835
            for x in ex.args
59✔
836
                kwargs_flag = detect_invalid_kwarg!(kwargs_ex, x, kwargs_flag, true)
46✔
837
            end
33✔
838
        elseif isexpr(ex, :kw)
236✔
839
            kwargs_flag = detect_invalid_kwarg!(kwargs_ex, ex, kwargs_flag, false)
22✔
840
        else
841
            if broadcasting
214✔
842
                # handle broadcasting, but only handle number of arguments instead of
843
                # argument types
844
                push!(args_ex, Any)
5✔
845
            else
846
                argt = repl_eval_ex(ex, context_module)
209✔
847
                if argt !== nothing
209✔
848
                    push!(args_ex, CC.widenconst(argt))
183✔
849
                elseif default_any
26✔
850
                    push!(args_ex, Any)
26✔
851
                else
852
                    throw(ArgumentError("argument not found"))
×
853
                end
854
            end
855
        end
856
    end
426✔
857
    return args_ex, Set{Symbol}(kwargs_ex), kwargs_flag
319✔
858
end
859

860
is_broadcasting_expr(ex::Expr) = ex.head === :. && isexpr(ex.args[2], :tuple)
1,900✔
861

862
function complete_methods_args(ex::Expr, context_module::Module, default_any::Bool, allow_broadcasting::Bool)
863
    if allow_broadcasting && is_broadcasting_expr(ex)
305✔
864
        return detect_args_kwargs((ex.args[2]::Expr).args, context_module, default_any, true)
6✔
865
    end
866
    return detect_args_kwargs(ex.args, context_module, default_any, false)
313✔
867
end
868

869
function complete_methods!(out::Vector{Completion}, @nospecialize(funct), args_ex::Vector{Any}, kwargs_ex::Set{Symbol}, max_method_completions::Int, exact_nargs::Bool)
2,214✔
870
    # Input types and number of arguments
871
    t_in = Tuple{funct, args_ex...}
2,214✔
872
    m = Base._methods_by_ftype(t_in, nothing, max_method_completions, Base.get_world_counter(),
2,214✔
873
        #=ambig=# true, Ref(typemin(UInt)), Ref(typemax(UInt)), Ptr{Int32}(C_NULL))
874
    if !isa(m, Vector)
2,214✔
875
        push!(out, TextCompletion(sprint(Base.show_signature_function, funct) * "( too many methods, use SHIFT-TAB to show )"))
193✔
876
        return
193✔
877
    end
878
    for match in m
2,021✔
879
        # TODO: if kwargs_ex, filter out methods without kwargs?
880
        push!(out, MethodCompletion(match.spec_types, match.method))
5,058✔
881
    end
5,058✔
882
    # TODO: filter out methods with wrong number of arguments if `exact_nargs` is set
883
end
884

885
include("latex_symbols.jl")
886
include("emoji_symbols.jl")
887

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

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

900
# Aux function to detect whether we're right after a using or import keyword
901
function get_import_mode(s::String)
1,591✔
902
    # allow all of these to start with leading whitespace and macros like @eval and @eval(
903
    # ^\s*(?:@\w+\s*(?:\(\s*)?)?
904

905
    # match simple cases like `using |` and `import  |`
906
    mod_import_match_simple = match(r"^\s*(?:@\w+\s*(?:\(\s*)?)?\b(using|import)\s*$", s)
1,591✔
907
    if mod_import_match_simple !== nothing
1,591✔
908
        if mod_import_match_simple[1] == "using"
12✔
909
            return :using_module
4✔
910
        else
911
            return :import_module
2✔
912
        end
913
    end
914
    # match module import statements like `using Foo|`, `import Foo, Bar|` and `using Foo.Bar, Baz, |`
915
    mod_import_match = match(r"^\s*(?:@\w+\s*(?:\(\s*)?)?\b(using|import)\s+([\w\.]+(?:\s*,\s*[\w\.]+)*),?\s*$", s)
1,585✔
916
    if mod_import_match !== nothing
1,585✔
917
        if mod_import_match.captures[1] == "using"
62✔
918
            return :using_module
24✔
919
        else
920
            return :import_module
7✔
921
        end
922
    end
923
    # now match explicit name import statements like `using Foo: |` and `import Foo: bar, baz|`
924
    name_import_match = match(r"^\s*(?:@\w+\s*(?:\(\s*)?)?\b(using|import)\s+([\w\.]+)\s*:\s*([\w@!\s,]+)$", s)
1,554✔
925
    if name_import_match !== nothing
1,554✔
926
        if name_import_match[1] == "using"
10✔
927
            return :using_name
5✔
928
        else
929
            return :import_name
×
930
        end
931
    end
932
    return nothing
1,549✔
933
end
934

935
function close_path_completion(dir, paths, str, pos)
142✔
936
    length(paths) == 1 || return false  # Only close if there's a single choice...
268✔
937
    path = (paths[1]::PathCompletion).path
16✔
938
    path = unescape_string(replace(path, "\\\$"=>"\$"))
32✔
939
    path = joinpath(dir, path)
16✔
940
    # ...except if it's a directory...
941
    try
16✔
942
        isdir(path)
17✔
943
    catch e
944
        e isa Base.IOError || rethrow() # `path` cannot be determined to be a file
17✔
945
    end && return false
946
    # ...and except if there's already a " at the cursor.
947
    return lastindex(str) <= pos || str[nextind(str, pos)] != '"'
6✔
948
end
949

950
function bslash_completions(string::String, pos::Int, hint::Bool=false)
2,020✔
951
    slashpos = something(findprev(isequal('\\'), string, pos), 0)
4,098✔
952
    if (something(findprev(in(bslash_separators), string, pos), 0) < slashpos &&
2,048✔
953
        !(1 < slashpos && (string[prevind(string, slashpos)]=='\\')))
954
        # latex / emoji symbol substitution
955
        s = string[slashpos:pos]
112✔
956
        latex = get(latex_symbols, s, "")
78✔
957
        if !isempty(latex) # complete an exact match
56✔
958
            return (true, (Completion[BslashCompletion(latex)], slashpos:pos, true))
22✔
959
        elseif occursin(subscript_regex, s)
34✔
960
            sub = map(c -> subscripts[c], s[3:end])
62✔
961
            return (true, (Completion[BslashCompletion(sub)], slashpos:pos, true))
6✔
962
        elseif occursin(superscript_regex, s)
28✔
963
            sup = map(c -> superscripts[c], s[3:end])
14✔
964
            return (true, (Completion[BslashCompletion(sup)], slashpos:pos, true))
1✔
965
        end
966
        emoji = get(emoji_symbols, s, "")
29✔
967
        if !isempty(emoji)
27✔
968
            return (true, (Completion[BslashCompletion(emoji)], slashpos:pos, true))
2✔
969
        end
970
        # return possible matches; these cannot be mixed with regular
971
        # Julian completions as only latex / emoji symbols contain the leading \
972
        if startswith(s, "\\:") # emoji
25✔
973
            namelist = Iterators.filter(k -> startswith(k, s), keys(emoji_symbols))
1,243✔
974
        else # latex
975
            namelist = Iterators.filter(k -> startswith(k, s), keys(latex_symbols))
61,176✔
976
        end
977
        return (true, (Completion[BslashCompletion(name) for name in sort!(collect(namelist))], slashpos:pos, true))
25✔
978
    end
979
    return (false, (Completion[], 0:-1, false))
1,964✔
980
end
981

982
function dict_identifier_key(str::String, tag::Symbol, context_module::Module=Main)
2,155✔
983
    if tag === :string
2,155✔
984
        str_close = str*"\""
164✔
985
    elseif tag === :cmd
1,990✔
986
        str_close = str*"`"
5✔
987
    else
988
        str_close = str
×
989
    end
990
    frange, end_of_identifier = find_start_brace(str_close, c_start='[', c_end=']')
2,154✔
991
    isempty(frange) && return (nothing, nothing, nothing)
2,154✔
992
    objstr = str[1:end_of_identifier]
212✔
993
    objex = Meta.parse(objstr, raise=false, depwarn=false)
106✔
994
    objt = repl_eval_ex(objex, context_module)
106✔
995
    isa(objt, Core.Const) || return (nothing, nothing, nothing)
130✔
996
    obj = objt.val
82✔
997
    isa(obj, AbstractDict) || return (nothing, nothing, nothing)
83✔
998
    length(obj)::Int < 1_000_000 || return (nothing, nothing, nothing)
81✔
999
    begin_of_key = something(findnext(!isspace, str, nextind(str, end_of_identifier) + 1), # +1 for [
156✔
1000
                             lastindex(str)+1)
1001
    return (obj, str[begin_of_key:end], begin_of_key)
81✔
1002
end
1003

1004
# This needs to be a separate non-inlined function, see #19441
1005
@noinline function find_dict_matches(identifier::AbstractDict, partial_key)
80✔
1006
    matches = String[]
80✔
1007
    for key in keys(identifier)
122✔
1008
        rkey = repr(key)
933✔
1009
        startswith(rkey,partial_key) && push!(matches,rkey)
933✔
1010
    end
1,410✔
1011
    return matches
80✔
1012
end
1013

1014
# Identify an argument being completed in a method call. If the argument is empty, method
1015
# suggestions will be provided instead of argument completions.
1016
function identify_possible_method_completion(partial, last_idx)
2,754✔
1017
    fail = 0:-1, Expr(:nothing), 0:-1, 0
2,754✔
1018

1019
    # First, check that the last punctuation is either ',', ';' or '('
1020
    idx_last_punct = something(findprev(x -> ispunct(x) && x != '_' && x != '!', partial, last_idx), 0)::Int
19,429✔
1021
    idx_last_punct == 0 && return fail
2,754✔
1022
    last_punct = partial[idx_last_punct]
4,450✔
1023
    last_punct == ',' || last_punct == ';' || last_punct == '(' || return fail
4,239✔
1024

1025
    # Then, check that `last_punct` is only followed by an identifier or nothing
1026
    before_last_word_start = something(findprev(in(non_identifier_chars), partial, last_idx), 0)
1,506✔
1027
    before_last_word_start == 0 && return fail
753✔
1028
    all(isspace, @view partial[nextind(partial, idx_last_punct):before_last_word_start]) || return fail
857✔
1029

1030
    # Check that `last_punct` is either the last '(' or placed after a previous '('
1031
    frange, method_name_end = find_start_brace(@view partial[1:idx_last_punct])
649✔
1032
    method_name_end ∈ frange || return fail
815✔
1033

1034
    # Strip the preceding ! operators, if any, and close the expression with a ')'
1035
    s = replace(partial[frange], r"\G\!+([^=\(]+)" => s"\1"; count=1) * ')'
966✔
1036
    ex = Meta.parse(s, raise=false, depwarn=false)
483✔
1037
    isa(ex, Expr) || return fail
483✔
1038

1039
    # `wordrange` is the position of the last argument to complete
1040
    wordrange = nextind(partial, before_last_word_start):last_idx
622✔
1041
    return frange, ex, wordrange, method_name_end
483✔
1042
end
1043

1044
# Provide completion for keyword arguments in function calls
1045
function complete_keyword_argument(partial::String, last_idx::Int, context_module::Module;
3,520✔
1046
                                   shift::Bool=false)
1047
    frange, ex, wordrange, = identify_possible_method_completion(partial, last_idx)
1,760✔
1048
    fail = Completion[], 0:-1, frange
1,760✔
1049
    ex.head === :call || is_broadcasting_expr(ex) || return fail
3,352✔
1050

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

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

1059
    # For each method corresponding to the function call, provide completion suggestions
1060
    # for each keyword that starts like the last word and that is not already used
1061
    # previously in the expression. The corresponding suggestion is "kwname=".
1062
    # If the keyword corresponds to an existing name, also include "kwname" as a suggestion
1063
    # since the syntax "foo(; kwname)" is equivalent to "foo(; kwname=kwname)".
1064
    last_word = partial[wordrange] # the word to complete
338✔
1065
    kwargs = Set{String}()
169✔
1066
    for m in methods
169✔
1067
        m::MethodCompletion
1,433✔
1068
        possible_kwargs = Base.kwarg_decl(m.method)
1,433✔
1069
        current_kwarg_candidates = String[]
1,433✔
1070
        for _kw in possible_kwargs
1,433✔
1071
            kw = String(_kw)
478✔
1072
            if !endswith(kw, "...") && startswith(kw, last_word) && _kw ∉ kwargs_ex
511✔
1073
                push!(current_kwarg_candidates, kw)
67✔
1074
            end
1075
        end
478✔
1076
        union!(kwargs, current_kwarg_candidates)
1,433✔
1077
    end
1,433✔
1078

1079
    suggestions = Completion[KeywordArgumentCompletion(kwarg) for kwarg in kwargs]
216✔
1080

1081
    # Only add these if not in kwarg space. i.e. not in `foo(; `
1082
    if kwargs_flag == 0
169✔
1083
        complete_symbol!(suggestions, #=prefix=#nothing, last_word, context_module; shift)
138✔
1084
        complete_keyval!(suggestions, last_word)
138✔
1085
    end
1086

1087
    return sort!(suggestions, by=completion_text), wordrange
169✔
1088
end
1089

1090
function get_loading_candidates(pkgstarts::String, project_file::String)
1✔
1091
    loading_candidates = String[]
1✔
1092
    d = Base.parsed_toml(project_file)
1✔
1093
    pkg = get(d, "name", nothing)::Union{String, Nothing}
2✔
1094
    if pkg !== nothing && startswith(pkg, pkgstarts)
1✔
1095
        push!(loading_candidates, pkg)
1✔
1096
    end
1097
    deps = get(d, "deps", nothing)::Union{Dict{String, Any}, Nothing}
2✔
1098
    if deps !== nothing
1✔
1099
        for (pkg, _) in deps
2✔
1100
            startswith(pkg, pkgstarts) && push!(loading_candidates, pkg)
1✔
1101
        end
1✔
1102
    end
1103
    return loading_candidates
1✔
1104
end
1105

1106
function complete_loading_candidates!(suggestions::Vector{Completion}, pkgstarts::String, project_file::String)
1✔
1107
    for name in get_loading_candidates(pkgstarts, project_file)
1✔
1108
        push!(suggestions, PackageCompletion(name))
2✔
1109
    end
2✔
1110
    return suggestions
1✔
1111
end
1112

1113
function complete_identifiers!(suggestions::Vector{Completion},
3,188✔
1114
                               context_module::Module, string::String, name::String,
1115
                               pos::Int, separatorpos::Int, startpos::Int;
1116
                               comp_keywords::Bool=false,
1117
                               complete_modules_only::Bool=false,
1118
                               shift::Bool=false)
1119
    if comp_keywords
1,594✔
1120
        complete_keyword!(suggestions, name)
604✔
1121
        complete_keyval!(suggestions, name)
604✔
1122
    end
1123
    if separatorpos > 1 && (string[separatorpos] == '.' || string[separatorpos] == ':')
1,599✔
1124
        s = string[1:prevind(string, separatorpos)]
1,666✔
1125
        # First see if the whole string up to `pos` is a valid expression. If so, use it.
1126
        prefix = Meta.parse(s, raise=false, depwarn=false)
833✔
1127
        if isexpr(prefix, :incomplete)
833✔
1128
            s = string[startpos:pos]
1,205✔
1129
            # Heuristic to find the start of the expression. TODO: This would be better
1130
            # done with a proper error-recovering parser.
1131
            if 0 < startpos <= lastindex(string) && string[startpos] == '.'
1,205✔
1132
                i = prevind(string, startpos)
6✔
1133
                while 0 < i
6✔
1134
                    c = string[i]
12✔
1135
                    if c in (')', ']')
12✔
1136
                        if c == ')'
×
1137
                            c_start = '('
×
1138
                            c_end = ')'
×
1139
                        elseif c == ']'
×
1140
                            c_start = '['
×
1141
                            c_end = ']'
×
1142
                        end
1143
                        frange, end_of_identifier = find_start_brace(string[1:prevind(string, i)], c_start=c_start, c_end=c_end)
×
1144
                        isempty(frange) && break # unbalanced parens
×
1145
                        startpos = first(frange)
×
1146
                        i = prevind(string, startpos)
×
1147
                    elseif c in ('\'', '\"', '\`')
12✔
1148
                        s = "$c$c"*string[startpos:pos]
×
1149
                        break
×
1150
                    else
1151
                        break
×
1152
                    end
1153
                    s = string[startpos:pos]
×
1154
                end
×
1155
            end
1156
            if something(findlast(in(non_identifier_chars), s), 0) < something(findlast(isequal('.'), s), 0)
1,189✔
1157
                lookup_name, name = rsplit(s, ".", limit=2)
550✔
1158
                name = String(name)
550✔
1159
                prefix = Meta.parse(lookup_name, raise=false, depwarn=false)
550✔
1160
            end
1161
            isexpr(prefix, :incomplete) && (prefix = nothing)
639✔
1162
        elseif isexpr(prefix, (:using, :import))
194✔
1163
            arglast = prefix.args[end] # focus on completion to the last argument
16✔
1164
            if isexpr(arglast, :.)
16✔
1165
                # We come here for cases like:
1166
                # - `string`: "using Mod1.Mod2.M"
1167
                # - `ex`: :(using Mod1.Mod2)
1168
                # - `name`: "M"
1169
                # Now we transform `ex` to `:(Mod1.Mod2)` to allow `complete_symbol!` to
1170
                # complete for inner modules whose name starts with `M`.
1171
                # Note that `complete_modules_only=true` is set within `completions`
1172
                prefix = nothing
16✔
1173
                firstdot = true
×
1174
                for arg = arglast.args
16✔
1175
                    if arg === :.
32✔
1176
                        # override `context_module` if multiple `.` accessors are used
1177
                        if firstdot
15✔
1178
                            firstdot = false
×
1179
                        else
1180
                            context_module = parentmodule(context_module)
3✔
1181
                        end
1182
                    elseif arg isa Symbol
17✔
1183
                        if prefix === nothing
17✔
1184
                            prefix = arg
16✔
1185
                        else
1186
                            prefix = Expr(:., prefix, QuoteNode(arg))
1✔
1187
                        end
1188
                    else # invalid expression
1189
                        prefix = nothing
×
1190
                        break
×
1191
                    end
1192
                end
32✔
1193
            end
1194
        elseif isexpr(prefix, :call) && length(prefix.args) > 1
178✔
1195
            isinfix = s[end] != ')'
37✔
1196
            # A complete call expression that does not finish with ')' is an infix call.
1197
            if !isinfix
19✔
1198
                # Handle infix call argument completion of the form bar + foo(qux).
1199
                frange, end_of_identifier = find_start_brace(@view s[1:prevind(s, end)])
28✔
1200
                isinfix = Meta.parse(@view(s[frange[1]:end]), raise=false, depwarn=false) == prefix.args[end]
14✔
1201
            end
1202
            if isinfix
19✔
1203
                prefix = prefix.args[end]
8✔
1204
            end
1205
        elseif isexpr(prefix, :macrocall) && length(prefix.args) > 1
159✔
1206
            # allow symbol completions within potentially incomplete macrocalls
1207
            if s[end] ≠ '`' && s[end] ≠ ')'
43✔
1208
                prefix = prefix.args[end]
12✔
1209
            end
1210
        end
1211
    else
1212
        prefix = nothing
×
1213
    end
1214
    complete_symbol!(suggestions, prefix, name, context_module; complete_modules_only, shift)
1,594✔
1215
    return suggestions
1,594✔
1216
end
1217

1218
function completions(string::String, pos::Int, context_module::Module=Main, shift::Bool=true, hint::Bool=false)
2,168✔
1219
    # First parse everything up to the current position
1220
    partial = string[1:pos]
4,734✔
1221
    inc_tag = Base.incomplete_tag(Meta.parse(partial, raise=false, depwarn=false))
2,167✔
1222

1223
    # ?(x, y)TAB lists methods you can call with these objects
1224
    # ?(x, y TAB lists methods that take these objects as the first two arguments
1225
    # MyModule.?(x, y)TAB restricts the search to names in MyModule
1226
    rexm = match(r"(\w+\.|)\?\((.*)$", partial)
2,167✔
1227
    if rexm !== nothing
2,167✔
1228
        # Get the module scope
1229
        if isempty(rexm.captures[1])
28✔
1230
            callee_module = context_module
×
1231
        else
1232
            modname = Symbol(rexm.captures[1][1:end-1])
13✔
1233
            if isdefined(context_module, modname)
13✔
1234
                callee_module = getfield(context_module, modname)
13✔
1235
                if !isa(callee_module, Module)
13✔
1236
                    callee_module = context_module
×
1237
                end
1238
            else
1239
                callee_module = context_module
×
1240
            end
1241
        end
1242
        moreargs = !endswith(rexm.captures[2], ')')
14✔
1243
        callstr = "_(" * rexm.captures[2]
14✔
1244
        if moreargs
14✔
1245
            callstr *= ')'
8✔
1246
        end
1247
        ex_org = Meta.parse(callstr, raise=false, depwarn=false)
14✔
1248
        if isa(ex_org, Expr)
14✔
1249
            return complete_any_methods(ex_org, callee_module::Module, context_module, moreargs, shift), (0:length(rexm.captures[1])+1) .+ rexm.offset, false
14✔
1250
        end
1251
    end
1252

1253
    # if completing a key in a Dict
1254
    identifier, partial_key, loc = dict_identifier_key(partial, inc_tag, context_module)
2,233✔
1255
    if identifier !== nothing
2,153✔
1256
        matches = find_dict_matches(identifier, partial_key)
80✔
1257
        length(matches)==1 && (lastindex(string) <= pos || string[nextind(string,pos)] != ']') && (matches[1]*=']')
80✔
1258
        length(matches)>0 && return Completion[DictCompletion(identifier, match) for match in sort!(matches)], loc::Int:pos, true
80✔
1259
    end
1260

1261
    suggestions = Completion[]
2,089✔
1262

1263
    # Check if this is a var"" string macro that should be completed like
1264
    # an identifier rather than a string.
1265
    # TODO: It would be nice for the parser to give us more information here
1266
    # so that we can lookup the macro by identity rather than pattern matching
1267
    # its invocation.
1268
    varrange = findprev("var\"", string, pos)
2,089✔
1269

1270
    expanded = nothing
×
1271
    was_expanded = false
2,089✔
1272

1273
    if varrange !== nothing
2,089✔
1274
        ok, ret = bslash_completions(string, pos)
3✔
1275
        ok && return ret
3✔
1276
        startpos = first(varrange) + 4
3✔
1277
        separatorpos = something(findprev(isequal('.'), string, first(varrange)-1), 0)
5✔
1278
        name = string[startpos:pos]
5✔
1279
        complete_identifiers!(suggestions, context_module, string, name,
3✔
1280
                              pos, separatorpos, startpos;
1281
                              shift)
1282
        return sort!(unique!(completion_text, suggestions), by=completion_text), (separatorpos+1):pos, true
3✔
1283
    elseif inc_tag === :cmd
2,086✔
1284
        # TODO: should this call shell_completions instead of partially reimplementing it?
1285
        let m = match(r"[\t\n\r\"`><=*?|]| (?!\\)", reverse(partial)) # fuzzy shell_parse in reverse
1✔
1286
            startpos = nextind(partial, reverseind(partial, m.offset))
2✔
1287
            r = startpos:pos
1✔
1288
            scs::String = string[r]
2✔
1289

1290
            expanded = complete_expanduser(scs, r)
1✔
1291
            was_expanded = expanded[3]
1✔
1292
            if was_expanded
1✔
1293
                scs = (only(expanded[1])::PathCompletion).path
×
1294
                # If tab press, ispath and user expansion available, return it now
1295
                # otherwise see if we can complete the path further before returning with expanded ~
1296
                !hint && ispath(scs) && return expanded::Completions
×
1297
            end
1298

1299
            path::String = replace(scs, r"(\\+)\g1(\\?)`" => "\1\2`") # fuzzy unescape_raw_string: match an even number of \ before ` and replace with half as many
1✔
1300
            # This expansion with "\\ "=>' ' replacement and shell_escape=true
1301
            # assumes the path isn't further quoted within the cmd backticks.
1302
            path = replace(path, r"\\ " => " ", r"\$" => "\$") # fuzzy shell_parse (reversed by shell_escape_posixly)
1✔
1303
            paths, dir, success = complete_path(path, shell_escape=true, raw_escape=true)
1✔
1304

1305
            if success && !isempty(dir)
1✔
1306
                let dir = do_raw_escape(do_shell_escape(dir))
×
1307
                    # if escaping of dir matches scs prefix, remove that from the completions
1308
                    # otherwise make it the whole completion
1309
                    if endswith(dir, "/") && startswith(scs, dir)
×
1310
                        r = (startpos + sizeof(dir)):pos
×
1311
                    elseif startswith(scs, dir * "/")
×
1312
                        r = nextind(string, startpos + sizeof(dir)):pos
×
1313
                    else
1314
                        map!(paths, paths) do c::PathCompletion
×
1315
                            p = dir * "/" * c.path
×
1316
                            was_expanded && (p = contractuser(p))
×
1317
                            return PathCompletion(p)
×
1318
                        end
1319
                    end
1320
                end
1321
            end
1322
            if isempty(paths) && !hint && was_expanded
1✔
1323
                # if not able to provide completions, not hinting, and ~ expansion was possible, return ~ expansion
1324
                return expanded::Completions
×
1325
            else
1326
                return sort!(paths, by=p->p.path), r::UnitRange{Int}, success
1✔
1327
            end
1328
        end
1329
    elseif inc_tag === :string
2,085✔
1330
        # Find first non-escaped quote
1331
        let m = match(r"\"(?!\\)", reverse(partial))
143✔
1332
            startpos = nextind(partial, reverseind(partial, m.offset))
286✔
1333
            r = startpos:pos
166✔
1334
            scs::String = string[r]
263✔
1335

1336
            expanded = complete_expanduser(scs, r)
143✔
1337
            was_expanded = expanded[3]
143✔
1338
            if was_expanded
143✔
1339
                scs = (only(expanded[1])::PathCompletion).path
2✔
1340
                # If tab press, ispath and user expansion available, return it now
1341
                # otherwise see if we can complete the path further before returning with expanded ~
1342
                !hint && ispath(scs) && return expanded::Completions
2✔
1343
            end
1344

1345
            path = try
142✔
1346
                unescape_string(replace(scs, "\\\$"=>"\$"))
261✔
1347
            catch ex
1348
                ex isa ArgumentError || rethrow()
×
1349
                nothing
142✔
1350
            end
1351
            if !isnothing(path)
284✔
1352
                paths, dir, success = complete_path(path::String, string_escape=true)
142✔
1353

1354
                if close_path_completion(dir, paths, path, pos)
142✔
1355
                    p = (paths[1]::PathCompletion).path * "\""
6✔
1356
                    hint && was_expanded && (p = contractuser(p))
6✔
1357
                    paths[1] = PathCompletion(p)
6✔
1358
                end
1359

1360
                if success && !isempty(dir)
142✔
1361
                    let dir = do_string_escape(dir)
18✔
1362
                        # if escaping of dir matches scs prefix, remove that from the completions
1363
                        # otherwise make it the whole completion
1364
                        if endswith(dir, "/") && startswith(scs, dir)
9✔
1365
                            r = (startpos + sizeof(dir)):pos
1✔
1366
                        elseif startswith(scs, dir * "/") && dir != dirname(homedir())
8✔
1367
                            was_expanded && (dir = contractuser(dir))
8✔
1368
                            r = nextind(string, startpos + sizeof(dir)):pos
8✔
1369
                        else
1370
                            map!(paths, paths) do c::PathCompletion
×
1371
                                p = dir * "/" * c.path
×
1372
                                hint && was_expanded && (p = contractuser(p))
×
1373
                                return PathCompletion(p)
×
1374
                            end
1375
                        end
1376
                    end
1377
                end
1378

1379
                # Fallthrough allowed so that Latex symbols can be completed in strings
1380
                if success
142✔
1381
                    return sort!(paths, by=p->p.path), r::UnitRange{Int}, success
28,833✔
1382
                elseif !hint && was_expanded
71✔
1383
                    # if not able to provide completions, not hinting, and ~ expansion was possible, return ~ expansion
1384
                    return expanded::Completions
1✔
1385
                end
1386
            end
1387
        end
1388
    end
1389
    # if path has ~ and we didn't find any paths to complete just return the expanded path
1390
    was_expanded && return expanded::Completions
2,012✔
1391

1392
    ok, ret = bslash_completions(string, pos)
2,012✔
1393
    ok && return ret
2,012✔
1394

1395
    # Make sure that only bslash_completions is working on strings
1396
    inc_tag === :string && return Completion[], 0:-1, false
1,961✔
1397
    if inc_tag === :other
1,900✔
1398
        frange, ex, wordrange, method_name_end = identify_possible_method_completion(partial, pos)
994✔
1399
        if last(frange) != -1 && all(isspace, @view partial[wordrange]) # no last argument to complete
994✔
1400
            if ex.head === :call
139✔
1401
                return complete_methods(ex, context_module, shift), first(frange):method_name_end, false
136✔
1402
            elseif is_broadcasting_expr(ex)
3✔
1403
                return complete_methods(ex, context_module, shift), first(frange):(method_name_end - 1), false
3✔
1404
            end
1405
        end
1406
    elseif inc_tag === :comment
906✔
1407
        return Completion[], 0:-1, false
1✔
1408
    end
1409

1410
    # Check whether we can complete a keyword argument in a function call
1411
    kwarg_completion, wordrange = complete_keyword_argument(partial, pos, context_module; shift)
3,351✔
1412
    isempty(wordrange) || return kwarg_completion, wordrange, !isempty(kwarg_completion)
1,929✔
1413

1414
    startpos = nextind(string, something(findprev(in(non_identifier_chars), string, pos), 0))
2,821✔
1415
    # strip preceding ! operator
1416
    if (m = match(r"\G\!+", partial, startpos)) isa RegexMatch
1,591✔
1417
        startpos += length(m.match)
2✔
1418
    end
1419

1420
    separatorpos = something(findprev(isequal('.'), string, pos), 0)
2,421✔
1421
    namepos = max(startpos, separatorpos+1)
1,591✔
1422
    name = string[namepos:pos]
2,784✔
1423
    import_mode = get_import_mode(string)
1,591✔
1424
    if import_mode === :using_module || import_mode === :import_module
3,154✔
1425
        # Given input lines like `using Foo|`, `import Foo, Bar|` and `using Foo.Bar, Baz, |`:
1426
        # Let's look only for packages and modules we can reach from here
1427

1428
        # If there's no dot, we're in toplevel, so we should
1429
        # also search for packages
1430
        s = string[startpos:pos]
70✔
1431
        if separatorpos <= startpos
37✔
1432
            for dir in Base.load_path()
25✔
1433
                if basename(dir) in Base.project_names && isfile(dir)
56✔
1434
                    complete_loading_candidates!(suggestions, s, dir)
1✔
1435
                end
1436
                isdir(dir) || continue
28✔
1437
                for entry in _readdirx(dir)
26✔
1438
                    pname = entry.name
1,527✔
1439
                    if pname[1] != '.' && pname != "METADATA" &&
3,054✔
1440
                        pname != "REQUIRE" && startswith(pname, s)
1441
                        # Valid file paths are
1442
                        #   <Mod>.jl
1443
                        #   <Mod>/src/<Mod>.jl
1444
                        #   <Mod>.jl/src/<Mod>.jl
1445
                        if isfile(entry)
261✔
1446
                            endswith(pname, ".jl") && push!(suggestions,
×
1447
                                                            PackageCompletion(pname[1:prevind(pname, end-2)]))
1448
                        else
1449
                            mod_name = if endswith(pname, ".jl")
261✔
1450
                                pname[1:prevind(pname, end-2)]
×
1451
                            else
1452
                                pname
261✔
1453
                            end
1454
                            if isfile(joinpath(entry, "src",
261✔
1455
                                               "$mod_name.jl"))
1456
                                push!(suggestions, PackageCompletion(mod_name))
260✔
1457
                            end
1458
                        end
1459
                    end
1460
                end
1,527✔
1461
            end
28✔
1462
        end
1463
        comp_keywords = false
37✔
1464
        complete_modules_only = import_mode === :using_module # allow completion for `import Mod.name` (where `name` is not a module)
37✔
1465
    elseif import_mode === :using_name || import_mode === :import_name
3,103✔
1466
        # `using Foo: |` and `import Foo: bar, baz|`
1467
        separatorpos = findprev(isequal(':'), string, pos)::Int
5✔
1468
        comp_keywords = false
×
1469
        complete_modules_only = false
×
1470
    else
1471
        comp_keywords = !isempty(name) && startpos > separatorpos
1,549✔
1472
        complete_modules_only = false
×
1473
    end
1474

1475
    complete_identifiers!(suggestions, context_module, string, name,
1,591✔
1476
                          pos, separatorpos, startpos;
1477
                          comp_keywords, complete_modules_only, shift)
1478
    return sort!(unique!(completion_text, suggestions), by=completion_text), namepos:pos, true
1,591✔
1479
end
1480

1481
function shell_completions(string, pos, hint::Bool=false)
442✔
1482
    # First parse everything up to the current position
1483
    scs = string[1:pos]
904✔
1484
    args, last_arg_start = try
442✔
1485
        Base.shell_parse(scs, true)::Tuple{Expr,Int}
460✔
1486
    catch ex
1487
        ex isa ArgumentError || ex isa ErrorException || rethrow()
36✔
1488
        return Completion[], 0:-1, false
442✔
1489
    end
1490
    ex = args.args[end]::Expr
424✔
1491
    # Now look at the last thing we parsed
1492
    isempty(ex.args) && return Completion[], 0:-1, false
424✔
1493
    lastarg = ex.args[end]
424✔
1494
    # As Base.shell_parse throws away trailing spaces (unless they are escaped),
1495
    # we need to special case here.
1496
    # If the last char was a space, but shell_parse ignored it search on "".
1497
    if isexpr(lastarg, :incomplete) || isexpr(lastarg, :error)
846✔
1498
        partial = string[last_arg_start:pos]
4✔
1499
        ret, range = completions(partial, lastindex(partial), Main, true, hint)
4✔
1500
        range = range .+ (last_arg_start - 1)
2✔
1501
        return ret, range, true
2✔
1502
    elseif endswith(scs, ' ') && !endswith(scs, "\\ ")
422✔
1503
        r = pos+1:pos
34✔
1504
        paths, dir, success = complete_path("", use_envpath=false, shell_escape=true)
17✔
1505
        return paths, r, success
17✔
1506
    elseif all(@nospecialize(arg) -> arg isa AbstractString, ex.args)
815✔
1507
        # Join these and treat this as a path
1508
        path::String = join(ex.args)
404✔
1509
        r = last_arg_start:pos
404✔
1510

1511
        # Also try looking into the env path if the user wants to complete the first argument
1512
        use_envpath = length(args.args) < 2
404✔
1513

1514
        expanded = complete_expanduser(path, r)
404✔
1515
        was_expanded = expanded[3]
404✔
1516
        if was_expanded
404✔
1517
            path = (only(expanded[1])::PathCompletion).path
2✔
1518
            # If tab press, ispath and user expansion available, return it now
1519
            # otherwise see if we can complete the path further before returning with expanded ~
1520
            !hint && ispath(path) && return expanded::Completions
2✔
1521
        end
1522

1523
        paths, dir, success = complete_path(path, use_envpath=use_envpath, shell_escape=true, contract_user=was_expanded)
404✔
1524

1525
        if success && !isempty(dir)
404✔
1526
            let dir = do_shell_escape(dir)
98✔
1527
                # if escaping of dir matches scs prefix, remove that from the completions
1528
                # otherwise make it the whole completion
1529
                partial = string[last_arg_start:pos]
196✔
1530
                if endswith(dir, "/") && startswith(partial, dir)
98✔
1531
                    r = (last_arg_start + sizeof(dir)):pos
14✔
1532
                elseif startswith(partial, dir * "/")
86✔
1533
                    r = nextind(string, last_arg_start + sizeof(dir)):pos
82✔
1534
                else
1535
                    map!(paths, paths) do c::PathCompletion
4✔
1536
                        return PathCompletion(dir * "/" * c.path)
4✔
1537
                    end
1538
                end
1539
            end
1540
        end
1541
        # if ~ was expanded earlier and the incomplete string isn't a path
1542
        # return the path with contracted user to match what the hint shows. Otherwise expand ~
1543
        # i.e. require two tab presses to expand user
1544
        if was_expanded && !ispath(path)
404✔
1545
            map!(paths, paths) do c::PathCompletion
×
1546
                PathCompletion(contractuser(c.path))
×
1547
            end
1548
        end
1549
        return paths, r, success
404✔
1550
    end
1551
    return Completion[], 0:-1, false
1✔
1552
end
1553

1554
function __init__()
4✔
1555
    COMPLETION_WORLD[] = Base.get_world_counter()
4✔
1556
    return nothing
4✔
1557
end
1558

1559
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