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

JuliaLang / julia / #37996

25 Jan 2025 02:50AM UTC coverage: 85.981% (-0.02%) from 86.001%
#37996

push

local

web-flow
bpart: Give a warning when accessing a backdated const binding (#57133)

This implements the strategy proposed in
https://github.com/JuliaLang/julia/pull/57102#issuecomment-2605511266.
Example:
```
julia> function foo(i)
           eval(:(const x = $i))
           x
       end
foo (generic function with 1 method)

julia> foo(1)
WARNING: Detected access to binding Main.x in a world prior to its definition world.
  Julia 1.12 has introduced more strict world age semantics for global bindings.
  !!! This code may malfunction under Revise.
  !!! This code will error in future versions of Julia.
Hint: Add an appropriate `invokelatest` around the access to this binding.
1
```

The warning is triggered once per binding to avoid spamming for repeated
access.

17 of 24 new or added lines in 6 files covered. (70.83%)

459 existing lines in 18 files now uncovered.

52029 of 60512 relevant lines covered (85.98%)

12098050.55 hits per line

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

89.87
/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, named_completion
6

7
using Core: Const
8
# We want to insulate the REPLCompletion module from any changes the user may
9
# make to the compiler, since it runs by default and the system becomes unusable
10
# if it breaks.
11
const CC = Base.Compiler
12
using Base.Meta
13
using Base: propertynames, something, IdSet
14
using Base.Filesystem: _readdirx
15

16
using ..REPL.LineEdit: NamedCompletion
17

18
abstract type Completion end
19

20
struct TextCompletion <: Completion
21
    text::String
187✔
22
end
23

24
struct KeywordCompletion <: Completion
25
    keyword::String
181✔
26
end
27

28
struct KeyvalCompletion <: Completion
29
    keyval::String
15✔
30
end
31

32
struct PathCompletion <: Completion
33
    path::String
8,512✔
34
end
35

36
struct ModuleCompletion <: Completion
37
    parent::Module
461,955✔
38
    mod::String
39
end
40

41
struct PackageCompletion <: Completion
42
    package::String
262✔
43
end
44

45
struct PropertyCompletion <: Completion
46
    value
77✔
47
    property::Symbol
48
end
49

50
struct FieldCompletion <: Completion
51
    typ::DataType
18✔
52
    field::Symbol
53
end
54

55
struct MethodCompletion <: Completion
56
    tt # may be used by an external consumer to infer return type, etc.
57
    method::Method
58
    MethodCompletion(@nospecialize(tt), method::Method) = new(tt, method)
4,368✔
59
end
60

61
struct BslashCompletion <: Completion
62
    completion::String # what is actually completed, for example "\trianglecdot"
7,826✔
63
    name::String # what is displayed, for example "â—¬ \trianglecdot"
64
end
65
BslashCompletion(completion::String) = BslashCompletion(completion, completion)
31✔
66

67
struct ShellCompletion <: Completion
68
    text::String
69
end
70

71
struct DictCompletion <: Completion
72
    dict::AbstractDict
124✔
73
    key::String
74
end
75

76
struct KeywordArgumentCompletion <: Completion
77
    kwarg::String
47✔
78
end
79

80
# interface definition
81
function Base.getproperty(c::Completion, name::Symbol)
82
    if name === :text
9,922,588✔
83
        return getfield(c, :text)::String
185✔
84
    elseif name === :keyword
9,920,304✔
85
        return getfield(c, :keyword)::String
2,099✔
86
    elseif name === :path
9,912,328✔
87
        return getfield(c, :path)::String
36,536✔
88
    elseif name === :parent
9,912,328✔
89
        return getfield(c, :parent)::Module
×
90
    elseif name === :mod
24,551✔
91
        return getfield(c, :mod)::String
9,887,777✔
92
    elseif name === :package
20,655✔
93
        return getfield(c, :package)::String
3,896✔
94
    elseif name === :property
20,655✔
95
        return getfield(c, :property)::Symbol
271✔
96
    elseif name === :field
20,384✔
97
        return getfield(c, :field)::Symbol
35✔
98
    elseif name === :method
15,979✔
99
        return getfield(c, :method)::Method
4,370✔
100
    elseif name === :bslash
15,979✔
101
        return getfield(c, :bslash)::String
×
102
    elseif name === :text
15,979✔
103
        return getfield(c, :text)::String
×
104
    elseif name === :key
15,979✔
105
        return getfield(c, :key)::String
124✔
106
    elseif name === :kwarg
15,855✔
107
        return getfield(c, :kwarg)::String
89✔
108
    end
109
    return getfield(c, name)
15,766✔
110
end
111

112
_completion_text(c::TextCompletion) = c.text
185✔
113
_completion_text(c::KeywordCompletion) = c.keyword
2,099✔
114
_completion_text(c::KeyvalCompletion) = c.keyval
114✔
115
_completion_text(c::PathCompletion) = c.path
7,946✔
116
_completion_text(c::ModuleCompletion) = c.mod
9,887,777✔
117
_completion_text(c::PackageCompletion) = c.package
3,896✔
118
_completion_text(c::PropertyCompletion) = sprint(Base.show_sym, c.property)
271✔
119
_completion_text(c::FieldCompletion) = sprint(Base.show_sym, c.field)
35✔
120
_completion_text(c::MethodCompletion) = repr(c.method)
3,468✔
121
_completion_text(c::ShellCompletion) = c.text
×
122
_completion_text(c::DictCompletion) = c.key
124✔
123
_completion_text(c::KeywordArgumentCompletion) = c.kwarg*'='
89✔
124

125
completion_text(c) = _completion_text(c)::String
9,906,004✔
126

127
named_completion(c::BslashCompletion) = NamedCompletion(c.completion, c.name)
7,826✔
128

129
function named_completion(c)
912,606✔
130
    text = completion_text(c)::String
9,906,003✔
131
    return NamedCompletion(text, text)
9,906,003✔
132
end
133

134
named_completion_completion(c) = named_completion(c).completion::String
4,491,416✔
135

136
const Completions = Tuple{Vector{Completion}, UnitRange{Int}, Bool}
137

138
function completes_global(x, name)
139
    return startswith(x, name) && !('#' in x)
3,150,549✔
140
end
141

142
function appendmacro!(syms, macros, needle, endchar)
3,410✔
143
    for macsym in macros
3,410✔
144
        s = String(macsym)
60,218✔
145
        if endswith(s, needle)
60,218✔
146
            from = nextind(s, firstindex(s))
7,760✔
147
            to = prevind(s, sizeof(s)-sizeof(needle)+1)
3,880✔
148
            push!(syms, s[from:to]*endchar)
7,760✔
149
        end
150
    end
60,218✔
151
end
152

153
function append_filtered_mod_names!(ffunc::Function, suggestions::Vector{Completion},
1,705✔
154
                                    mod::Module, name::String, complete_internal_only::Bool)
155
    imported = usings = !complete_internal_only
1,705✔
156
    ssyms = names(mod; all=true, imported, usings)
1,705✔
157
    filter!(ffunc, ssyms)
1,705✔
158
    macros = filter(x -> startswith(String(x), "@" * name), ssyms)
1,857,578✔
159

160
    # don't complete string and command macros when the input matches the internal name like `r_` to `r"`
161
    if !startswith(name, "@")
1,705✔
162
        filter!(macros) do m
1,682✔
163
            s = String(m)
30,113✔
164
            if endswith(s, "_str") || endswith(s, "_cmd")
56,356✔
165
                occursin(name, first(s, length(s)-4))
3,884✔
166
            else
167
                true
168
            end
169
        end
170
    end
171

172
    syms = String[sprint((io,s)->Base.show_sym(io, s; allow_macroname=true), s) for s in ssyms if completes_global(String(s), name)]
459,780✔
173
    appendmacro!(syms, macros, "_str", "\"")
1,705✔
174
    appendmacro!(syms, macros, "_cmd", "`")
1,705✔
175
    for sym in syms
1,705✔
176
        push!(suggestions, ModuleCompletion(mod, sym))
461,955✔
177
    end
461,955✔
178
    return suggestions
1,705✔
179
end
180

181
# REPL Symbol Completions
182
function complete_symbol!(suggestions::Vector{Completion},
3,566✔
183
                          @nospecialize(prefix), name::String, context_module::Module;
184
                          complete_modules_only::Bool=false,
185
                          shift::Bool=false)
186
    local mod, t, val
1,783✔
187
    complete_internal_only = false
1,783✔
188
    if prefix !== nothing
1,783✔
189
        res = repl_eval_ex(prefix, context_module)
744✔
190
        res === nothing && return Completion[]
744✔
191
        if res isa Const
735✔
192
            val = res.val
713✔
193
            if isa(val, Module)
713✔
194
                mod = val
666✔
195
                if !shift
666✔
196
                    # when module is explicitly accessed, show internal bindings that are
197
                    # defined by the module, unless shift key is pressed
198
                    complete_internal_only = true
622✔
199
                end
200
            else
201
                t = typeof(val)
47✔
202
            end
203
        else
204
            t = CC.widenconst(res)
22✔
205
        end
206
    else
207
        mod = context_module
1,039✔
208
    end
209

210
    if @isdefined(mod) # lookup names available within the module
1,774✔
211
        let modname = nameof(mod),
1,705✔
212
            is_main = mod===Main
213
            append_filtered_mod_names!(suggestions, mod, name, complete_internal_only) do s::Symbol
1,705✔
214
                if Base.isdeprecated(mod, s)
1,911,679✔
215
                    return false
×
216
                elseif s === modname
1,911,679✔
217
                    return false # exclude `Main.Main.Main`, etc.
2,705✔
218
                elseif complete_modules_only && !completes_module(mod, s)
1,908,974✔
219
                    return false
52,108✔
220
                elseif is_main && s === :MainInclude
1,856,866✔
221
                    return false
993✔
222
                end
223
                return true
1,855,873✔
224
            end
225
        end
226
    elseif @isdefined(val) # looking for a property of an instance
69✔
227
        try
47✔
228
            for property in propertynames(val, false)
47✔
229
                # TODO: support integer arguments (#36872)
230
                if property isa Symbol && startswith(string(property), name)
83✔
231
                    push!(suggestions, PropertyCompletion(val, property))
77✔
232
                end
233
            end
83✔
234
        catch
1✔
235
        end
236
    elseif @isdefined(t) && field_completion_eligible(t)
22✔
237
        # Looking for a member of a type
238
        add_field_completions!(suggestions, name, t)
13✔
239
    end
240
    return suggestions
1,774✔
241
end
242

243
completes_module(mod::Module, x::Symbol) =
52,672✔
244
    Base.isbindingresolved(mod, x) && isdefined(mod, x) && isa(getglobal(mod, x), Module)
245

246
function add_field_completions!(suggestions::Vector{Completion}, name::String, @nospecialize(t))
15✔
247
    if isa(t, Union)
15✔
248
        add_field_completions!(suggestions, name, t.a)
2✔
249
        add_field_completions!(suggestions, name, t.b)
2✔
250
    else
251
        @assert isconcretetype(t)
15✔
252
        fields = fieldnames(t)
15✔
253
        for field in fields
15✔
254
            isa(field, Symbol) || continue # Tuple type has ::Int field name
18✔
255
            s = string(field)
18✔
256
            if startswith(s, name)
18✔
257
                push!(suggestions, FieldCompletion(t, field))
18✔
258
            end
259
        end
18✔
260
    end
261
end
262

263
const GENERIC_PROPERTYNAMES_METHOD = which(propertynames, (Any,))
264

265
function field_completion_eligible(@nospecialize t)
26✔
266
    if isa(t, Union)
26✔
267
        return field_completion_eligible(t.a) && field_completion_eligible(t.b)
2✔
268
    end
269
    isconcretetype(t) || return false
31✔
270
    # field completion is correct only when `getproperty` fallbacks to `getfield`
271
    match = Base._which(Tuple{typeof(propertynames),t}; raise=false)
17✔
272
    match === nothing && return false
17✔
273
    return match.method === GENERIC_PROPERTYNAMES_METHOD
17✔
274
end
275

276
function complete_from_list!(suggestions::Vector{Completion}, T::Type, list::Vector{String}, s::String)
1,346✔
277
    r = searchsorted(list, s)
1,346✔
278
    i = first(r)
1,346✔
279
    n = length(list)
1,346✔
280
    while i <= n && startswith(list[i],s)
1,542✔
281
        r = first(r):i
196✔
282
        i += 1
196✔
283
    end
196✔
284
    for kw in list[r]
1,346✔
285
        push!(suggestions, T(kw))
196✔
286
    end
196✔
287
    return suggestions
1,346✔
288
end
289

290
const sorted_keywords = [
291
    "abstract type", "baremodule", "begin", "break", "catch", "ccall",
292
    "const", "continue", "do", "else", "elseif", "end", "export",
293
    "finally", "for", "function", "global", "if", "import",
294
    "let", "local", "macro", "module", "mutable struct",
295
    "primitive type", "quote", "return", "struct",
296
    "try", "using", "while"]
297

298
complete_keyword!(suggestions::Vector{Completion}, s::String) =
604✔
299
    complete_from_list!(suggestions, KeywordCompletion, sorted_keywords, s)
300

301
const sorted_keyvals = ["false", "true"]
302

303
complete_keyval!(suggestions::Vector{Completion}, s::String) =
742✔
304
    complete_from_list!(suggestions, KeyvalCompletion, sorted_keyvals, s)
305

306
function do_raw_escape(s)
307
    # escape_raw_string with delim='`' and ignoring the rule for the ending \
308
    return replace(s, r"(\\+)`" => s"\1\\`")
×
309
end
310
function do_shell_escape(s)
311
    return Base.shell_escape_posixly(s)
4,385✔
312
end
313
function do_string_escape(s)
314
    return escape_string(s, ('\"','$'))
7,356✔
315
end
316

317
const PATH_cache_lock = Base.ReentrantLock()
318
const PATH_cache = Set{String}()
319
PATH_cache_task::Union{Task,Nothing} = nothing # used for sync in tests
320
next_cache_update::Float64 = 0.0
321
function maybe_spawn_cache_PATH()
21✔
322
    global PATH_cache_task, next_cache_update
21✔
323
    @lock PATH_cache_lock begin
21✔
324
        PATH_cache_task isa Task && !istaskdone(PATH_cache_task) && return
21✔
325
        time() < next_cache_update && return
20✔
326
        PATH_cache_task = Threads.@spawn begin
6✔
327
            REPLCompletions.cache_PATH()
3✔
328
            @lock PATH_cache_lock PATH_cache_task = nothing # release memory when done
3✔
329
        end
330
        Base.errormonitor(PATH_cache_task)
3✔
331
    end
332
end
333

334
# caches all reachable files in PATH dirs
335
function cache_PATH()
3✔
336
    path = get(ENV, "PATH", nothing)
3✔
337
    path isa String || return
3✔
338

339
    global next_cache_update
3✔
340

341
    # Calling empty! on PATH_cache would be annoying for async typing hints as completions would temporarily disappear.
342
    # 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.
343
    this_PATH_cache = Set{String}()
3✔
344

345
    @debug "caching PATH files" PATH=path
3✔
346
    pathdirs = split(path, @static Sys.iswindows() ? ";" : ":")
3✔
347

348
    next_yield_time = time() + 0.01
3✔
349

350
    t = @elapsed for pathdir in pathdirs
3✔
351
        actualpath = try
19✔
352
            realpath(pathdir)
22✔
353
        catch ex
354
            ex isa Base.IOError || rethrow()
3✔
355
            # Bash doesn't expect every folder in PATH to exist, so neither shall we
356
            continue
3✔
357
        end
358

359
        if actualpath != pathdir && in(actualpath, pathdirs)
27✔
360
            # Remove paths which (after resolving links) are in the env path twice.
361
            # Many distros eg. point /bin to /usr/bin but have both in the env path.
362
            continue
3✔
363
        end
364

365
        path_entries = try
13✔
366
            _readdirx(pathdir)
14✔
367
        catch e
368
            # Bash allows dirs in PATH that can't be read, so we should as well.
369
            if isa(e, Base.IOError) || isa(e, Base.ArgumentError)
1✔
370
                continue
1✔
371
            else
372
                # We only handle IOError and ArgumentError here
373
                rethrow()
×
374
            end
375
        end
376
        for entry in path_entries
12✔
377
            # In a perfect world, we would filter on whether the file is executable
378
            # here, or even on whether the current user can execute the file in question.
379
            try
2,477✔
380
                if isfile(entry)
2,477✔
381
                    @lock PATH_cache_lock push!(PATH_cache, entry.name)
2,405✔
382
                    push!(this_PATH_cache, entry.name)
2,406✔
383
                end
384
            catch e
385
                # `isfile()` can throw in rare cases such as when probing a
386
                # symlink that points to a file within a directory we do not
387
                # have read access to.
388
                if isa(e, Base.IOError)
1✔
389
                    continue
1✔
390
                else
391
                    rethrow()
×
392
                end
393
            end
394
            if time() >= next_yield_time
2,476✔
395
                yield() # to avoid blocking typing when -t1
1✔
396
                next_yield_time = time() + 0.01
1✔
397
            end
398
        end
2,477✔
399
    end
400

401
    @lock PATH_cache_lock begin
3✔
402
        intersect!(PATH_cache, this_PATH_cache) # remove entries from PATH_cache that weren't found this time
3✔
403
        next_cache_update = time() + 10 # earliest next update can run is 10s after
3✔
404
    end
405

406
    @debug "caching PATH files took $t seconds" length(pathdirs) length(PATH_cache)
3✔
407
    return PATH_cache
3✔
408
end
409

410
function complete_path(path::AbstractString;
1,144✔
411
                       use_envpath=false,
412
                       shell_escape=false,
413
                       raw_escape=false,
414
                       string_escape=false,
415
                       contract_user=false)
416
    @assert !(shell_escape && string_escape)
572✔
417
    if Base.Sys.isunix() && occursin(r"^~(?:/|$)", path)
572✔
418
        # if the path is just "~", don't consider the expanded username as a prefix
419
        if path == "~"
×
420
            dir, prefix = homedir(), ""
×
421
        else
422
            dir, prefix = splitdir(homedir() * path[2:end])
×
423
        end
424
    else
425
        dir, prefix = splitdir(path)
572✔
426
    end
427
    entries = try
572✔
428
        if isempty(dir)
572✔
429
            _readdirx()
297✔
430
        elseif isdir(dir)
550✔
431
            _readdirx(dir)
112✔
432
        else
433
            return Completion[], dir, false
572✔
434
        end
435
    catch ex
436
        ex isa Base.IOError || rethrow()
×
437
        return Completion[], dir, false
×
438
    end
439

440
    matches = Set{String}()
409✔
441
    for entry in entries
409✔
442
        if startswith(entry.name, prefix)
46,817✔
443
            is_dir = try isdir(entry) catch ex; ex isa Base.IOError ? false : rethrow() end
7,725✔
444
            push!(matches, is_dir ? entry.name * "/" : entry.name)
14,458✔
445
        end
446
    end
46,817✔
447

448
    if use_envpath && isempty(dir)
409✔
449
        # Look for files in PATH as well. These are cached in `cache_PATH` in an async task to not block typing.
450
        # If we cannot get lock because its still caching just pass over this so that typing isn't laggy.
451
        maybe_spawn_cache_PATH() # only spawns if enough time has passed and the previous caching task has completed
21✔
452
        @lock PATH_cache_lock begin
21✔
453
            for file in PATH_cache
42✔
454
                startswith(file, prefix) && push!(matches, file)
11,302✔
455
            end
22,604✔
456
        end
457
    end
458

459
    matches = ((shell_escape ? do_shell_escape(s) : string_escape ? do_string_escape(s) : s) for s in matches)
409✔
460
    matches = ((raw_escape ? do_raw_escape(s) : s) for s in matches)
409✔
461
    matches = Completion[PathCompletion(contract_user ? contractuser(s) : s) for s in matches]
8,146✔
462
    return matches, dir, !isempty(matches)
409✔
463
end
464

465
function complete_path(path::AbstractString,
×
466
                       pos::Int;
467
                       use_envpath=false,
468
                       shell_escape=false,
469
                       string_escape=false,
470
                       contract_user=false)
471
    ## TODO: enable this depwarn once Pkg is fixed
472
    #Base.depwarn("complete_path with pos argument is deprecated because the return value [2] is incorrect to use", :complete_path)
473
    paths, dir, success = complete_path(path; use_envpath, shell_escape, string_escape)
×
474
    if Base.Sys.isunix() && occursin(r"^~(?:/|$)", path)
×
475
        # if the path is just "~", don't consider the expanded username as a prefix
476
        if path == "~"
×
477
            dir, prefix = homedir(), ""
×
478
        else
479
            dir, prefix = splitdir(homedir() * path[2:end])
×
480
        end
481
    else
482
        dir, prefix = splitdir(path)
×
483
    end
484
    startpos = pos - lastindex(prefix) + 1
×
485
    Sys.iswindows() && map!(paths, paths) do c::PathCompletion
×
486
        # emulation for unnecessarily complicated return value, since / is a
487
        # perfectly acceptable path character which does not require quoting
488
        # but is required by Pkg's awkward parser handling
489
        return endswith(c.path, "/") ? PathCompletion(chop(c.path) * "\\\\") : c
×
490
    end
491
    return paths, startpos:pos, success
×
492
end
493

494
function complete_expanduser(path::AbstractString, r)
558✔
495
    expanded =
558✔
496
        try expanduser(path)
559✔
497
        catch e
498
            e isa ArgumentError || rethrow()
1✔
499
            path
559✔
500
        end
501
    return Completion[PathCompletion(expanded)], r, path != expanded
558✔
502
end
503

504
# Returns a range that includes the method name in front of the first non
505
# closed start brace from the end of the string.
506
function find_start_brace(s::AbstractString; c_start='(', c_end=')')
5,842✔
507
    r = reverse(s)
2,921✔
508
    i = firstindex(r)
2,921✔
509
    braces = in_comment = 0
2,921✔
510
    in_single_quotes = in_double_quotes = in_back_ticks = false
2,921✔
511
    num_single_quotes_in_string = count('\'', s)
2,921✔
512
    while i <= ncodeunits(r)
68,118✔
513
        c, i = iterate(r, i)
131,776✔
514
        if c == '#' && i <= ncodeunits(r) && iterate(r, i)[1] == '='
65,888✔
515
            c, i = iterate(r, i) # consume '='
8✔
516
            new_comments = 1
4✔
517
            # handle #=#=#=#, by counting =# pairs
518
            while i <= ncodeunits(r) && iterate(r, i)[1] == '#'
6✔
519
                c, i = iterate(r, i) # consume '#'
6✔
520
                iterate(r, i)[1] == '=' || break
3✔
521
                c, i = iterate(r, i) # consume '='
4✔
522
                new_comments += 1
2✔
523
            end
2✔
524
            if c == '='
4✔
525
                in_comment += new_comments
3✔
526
            else
527
                in_comment -= new_comments
1✔
528
            end
529
        elseif !in_single_quotes && !in_double_quotes && !in_back_ticks && in_comment == 0
65,884✔
530
            if c == c_start
64,012✔
531
                braces += 1
1,016✔
532
            elseif c == c_end
62,996✔
533
                braces -= 1
325✔
534
            elseif c == '\'' && num_single_quotes_in_string % 2 == 0
62,671✔
535
                # ' can be a transpose too, so check if there are even number of 's in the string
536
                # TODO: This probably needs to be more robust
537
                in_single_quotes = true
15✔
538
            elseif c == '"'
62,656✔
539
                in_double_quotes = true
286✔
540
            elseif c == '`'
62,370✔
541
                in_back_ticks = true
8✔
542
            end
543
        else
544
            if in_single_quotes &&
1,872✔
545
                c == '\'' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
546
                in_single_quotes = false
15✔
547
            elseif in_double_quotes &&
1,857✔
548
                c == '"' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
549
                in_double_quotes = false
232✔
550
            elseif in_back_ticks &&
1,625✔
551
                c == '`' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
552
                in_back_ticks = false
7✔
553
            elseif in_comment > 0 &&
1,618✔
554
                c == '=' && i <= ncodeunits(r) && iterate(r, i)[1] == '#'
555
                # handle =#=#=#=, by counting #= pairs
556
                c, i = iterate(r, i) # consume '#'
6✔
557
                old_comments = 1
3✔
558
                while i <= ncodeunits(r) && iterate(r, i)[1] == '='
4✔
559
                    c, i = iterate(r, i) # consume '='
4✔
560
                    iterate(r, i)[1] == '#' || break
2✔
561
                    c, i = iterate(r, i) # consume '#'
2✔
562
                    old_comments += 1
1✔
563
                end
1✔
564
                if c == '#'
3✔
565
                    in_comment -= old_comments
2✔
566
                else
567
                    in_comment += old_comments
1✔
568
                end
569
            end
570
        end
571
        braces == 1 && break
65,888✔
572
    end
65,197✔
573
    braces != 1 && return 0:-1, -1
2,921✔
574
    method_name_end = reverseind(s, i)
1,374✔
575
    startind = nextind(s, something(findprev(in(non_identifier_chars), s, method_name_end), 0))::Int
1,070✔
576
    return (startind:lastindex(s), method_name_end)
691✔
577
end
578

579
struct REPLCacheToken end
580

581
struct REPLInterpreter <: CC.AbstractInterpreter
582
    limit_aggressive_inference::Bool
583
    world::UInt
584
    inf_params::CC.InferenceParams
585
    opt_params::CC.OptimizationParams
586
    inf_cache::Vector{CC.InferenceResult}
587
    function REPLInterpreter(limit_aggressive_inference::Bool=false;
524✔
588
                             world::UInt = Base.get_world_counter(),
589
                             inf_params::CC.InferenceParams = CC.InferenceParams(;
590
                                 aggressive_constant_propagation=true),
591
                             opt_params::CC.OptimizationParams = CC.OptimizationParams(),
592
                             inf_cache::Vector{CC.InferenceResult} = CC.InferenceResult[])
593
        return new(limit_aggressive_inference, world, inf_params, opt_params, inf_cache)
518✔
594
    end
595
end
596
CC.InferenceParams(interp::REPLInterpreter) = interp.inf_params
169,797✔
597
CC.OptimizationParams(interp::REPLInterpreter) = interp.opt_params
106✔
598
CC.get_inference_world(interp::REPLInterpreter) = interp.world
386,051✔
599
CC.get_inference_cache(interp::REPLInterpreter) = interp.inf_cache
44,876✔
600
CC.cache_owner(::REPLInterpreter) = REPLCacheToken()
×
601

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

605
# REPLInterpreter doesn't need any sources to be cached, so discard them aggressively
606
CC.transform_result_for_cache(::REPLInterpreter, ::CC.InferenceResult) = nothing
×
607

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

611
# `REPLInterpreter` aggressively resolves global bindings to enable reasonable completions
612
# for lines like `Mod.a.|` (where `|` is the cursor position).
613
# Aggressive binding resolution poses challenges for the inference cache validation
614
# (until https://github.com/JuliaLang/julia/issues/40399 is implemented).
615
# To avoid the cache validation issues, `REPLInterpreter` only allows aggressive binding
616
# resolution for top-level frame representing REPL input code and for child uncached frames
617
# that are constant propagated from the top-level frame ("repl-frame"s). This works, even if
618
# those global bindings are not constant and may be mutated in the future, since:
619
# a.) "repl-frame"s are never cached, and
620
# b.) mutable values are never observed by any cached frames.
621
#
622
# `REPLInterpreter` also aggressively concrete evaluate `:inconsistent` calls within
623
# "repl-frame" to provide reasonable completions for lines like `Ref(Some(42))[].|`.
624
# Aggressive concrete evaluation allows us to get accurate type information about complex
625
# expressions that otherwise can not be constant folded, in a safe way, i.e. it still
626
# doesn't evaluate effectful expressions like `pop!(xs)`.
627
# Similarly to the aggressive binding resolution, aggressive concrete evaluation doesn't
628
# present any cache validation issues because "repl-frame" is never cached.
629

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

635
function is_call_graph_uncached(sv::CC.InferenceState)
244,435✔
636
    CC.is_cached(sv) && return false
344,874✔
637
    parent = CC.frame_parent(sv)
498,105✔
638
    parent === nothing && return true
253,670✔
639
    return is_call_graph_uncached(parent::CC.InferenceState)
244,435✔
640
end
641

642
# aggressive global binding resolution within `repl_frame`
643
function CC.abstract_eval_globalref(interp::REPLInterpreter, g::GlobalRef, bailed::Bool,
63,452✔
644
                                    sv::CC.InferenceState)
645
    # Ignore saw_latestworld
646
    partition = CC.abstract_eval_binding_partition!(interp, g, sv)
63,452✔
647
    if (interp.limit_aggressive_inference ? is_repl_frame(sv) : is_call_graph_uncached(sv))
126,767✔
648
        if CC.is_defined_const_binding(CC.binding_kind(partition))
6,501✔
649
            return Pair{CC.RTEffects, Union{Nothing, Core.BindingPartition}}(
6,351✔
650
                CC.RTEffects(Const(CC.partition_restriction(partition)), Union{}, CC.EFFECTS_TOTAL), partition)
651
        else
652
            b = convert(Core.Binding, g)
75✔
653
            if CC.binding_kind(partition) == CC.BINDING_KIND_GLOBAL && isdefined(b, :value)
75✔
654
                return Pair{CC.RTEffects, Union{Nothing, Core.BindingPartition}}(
67✔
655
                    CC.RTEffects(Const(b.value), Union{}, CC.EFFECTS_TOTAL), partition)
656
            end
657
        end
658
        return Pair{CC.RTEffects, Union{Nothing, Core.BindingPartition}}(
8✔
659
            CC.RTEffects(Union{}, UndefVarError, CC.EFFECTS_THROWS), partition)
660
    end
661
    return @invoke CC.abstract_eval_globalref(interp::CC.AbstractInterpreter, g::GlobalRef, bailed::Bool,
57,026✔
662
                                              sv::CC.InferenceState)
663
end
664

665
function is_repl_frame_getproperty(sv::CC.InferenceState)
UNCOV
666
    def = sv.linfo.def
×
UNCOV
667
    def isa Method || return false
×
UNCOV
668
    def.name === :getproperty || return false
×
UNCOV
669
    CC.is_cached(sv) && return false
×
UNCOV
670
    return is_repl_frame(CC.frame_parent(sv))
×
671
end
672

673
# aggressive global binding resolution for `getproperty(::Module, ::Symbol)` calls within `repl_frame`
674
function CC.builtin_tfunction(interp::REPLInterpreter, @nospecialize(f),
18,085✔
675
                              argtypes::Vector{Any}, sv::CC.InferenceState)
676
    if f === Core.getglobal && (interp.limit_aggressive_inference ? is_repl_frame_getproperty(sv) : is_call_graph_uncached(sv))
18,085✔
677
        if length(argtypes) == 2
×
678
            a1, a2 = argtypes
×
UNCOV
679
            if isa(a1, Const) && isa(a2, Const)
×
680
                a1val, a2val = a1.val, a2.val
×
UNCOV
681
                if isa(a1val, Module) && isa(a2val, Symbol)
×
UNCOV
682
                    g = GlobalRef(a1val, a2val)
×
UNCOV
683
                    if isdefined_globalref(g)
×
UNCOV
684
                        return Const(ccall(:jl_get_globalref_value, Any, (Any,), g))
×
685
                    end
UNCOV
686
                    return Union{}
×
687
                end
688
            end
689
        end
690
    end
691
    return @invoke CC.builtin_tfunction(interp::CC.AbstractInterpreter, f::Any,
18,085✔
692
                                        argtypes::Vector{Any}, sv::CC.InferenceState)
693
end
694

695
# aggressive concrete evaluation for `:inconsistent` frames within `repl_frame`
696
function CC.concrete_eval_eligible(interp::REPLInterpreter, @nospecialize(f),
37,183✔
697
                                   result::CC.MethodCallResult, arginfo::CC.ArgInfo,
698
                                   sv::CC.InferenceState)
699
    if (interp.limit_aggressive_inference ? is_repl_frame(sv) : is_call_graph_uncached(sv))
74,307✔
700
        neweffects = CC.Effects(result.effects; consistent=CC.ALWAYS_TRUE)
2,819✔
701
        result = CC.MethodCallResult(result.rt, result.exct, neweffects, result.edge,
5,636✔
702
                                     result.edgecycle, result.edgelimited, result.volatile_inf_result)
703
    end
704
    ret = @invoke CC.concrete_eval_eligible(interp::CC.AbstractInterpreter, f::Any,
74,366✔
705
                                            result::CC.MethodCallResult, arginfo::CC.ArgInfo,
706
                                            sv::CC.InferenceState)
707
    if ret === :semi_concrete_eval
37,183✔
708
        # while the base eligibility check probably won't permit semi-concrete evaluation
709
        # for `REPLInterpreter` (given it completely turns off optimization),
710
        # this ensures we don't inadvertently enter irinterp
UNCOV
711
        ret = :none
×
712
    end
713
    return ret
37,183✔
714
end
715

716
# allow constant propagation for mutable constants
717
function CC.const_prop_argument_heuristic(interp::REPLInterpreter, arginfo::CC.ArgInfo, sv::CC.InferenceState)
718
    if !interp.limit_aggressive_inference
36,722✔
719
        any(@nospecialize(a)->isa(a, Const), arginfo.argtypes) && return true # even if mutable
73,403✔
720
    end
721
    return @invoke CC.const_prop_argument_heuristic(interp::CC.AbstractInterpreter, arginfo::CC.ArgInfo, sv::CC.InferenceState)
95✔
722
end
723

724
function resolve_toplevel_symbols!(src::Core.CodeInfo, mod::Module)
725
    @ccall jl_resolve_definition_effects_in_ir(
512✔
726
        #=jl_array_t *stmts=# src.code::Any,
727
        #=jl_module_t *m=# mod::Any,
728
        #=jl_svec_t *sparam_vals=# Core.svec()::Any,
729
        #=int binding_effects=# 0::Int)::Cvoid
UNCOV
730
    return src
×
731
end
732

733
# lower `ex` and run type inference on the resulting top-level expression
734
function repl_eval_ex(@nospecialize(ex), context_module::Module; limit_aggressive_inference::Bool=false)
2,760✔
735
    if (isexpr(ex, :toplevel) || isexpr(ex, :tuple)) && !isempty(ex.args)
2,759✔
736
        # get the inference result for the last expression
737
        ex = ex.args[end]
3✔
738
    end
739
    lwr = try
1,380✔
740
        Meta.lower(context_module, ex)
1,380✔
741
    catch # macro expansion failed, etc.
742
        return nothing
5✔
743
    end
744
    if lwr isa Symbol
1,375✔
745
        return isdefined(context_module, lwr) ? Const(getfield(context_module, lwr)) : nothing
730✔
746
    end
747
    lwr isa Expr || return Const(lwr) # `ex` is literal
748✔
748
    isexpr(lwr, :thunk) || return nothing # lowered to `Expr(:error, ...)` or similar
572✔
749
    src = lwr.args[1]::Core.CodeInfo
512✔
750

751
    resolve_toplevel_symbols!(src, context_module)
512✔
752
    # construct top-level `MethodInstance`
753
    mi = ccall(:jl_method_instance_for_thunk, Ref{Core.MethodInstance}, (Any, Any), src, context_module)
512✔
754

755
    interp = REPLInterpreter(limit_aggressive_inference)
512✔
756
    result = CC.InferenceResult(mi)
512✔
757
    frame = CC.InferenceState(result, src, #=cache=#:no, interp)
512✔
758

759
    # NOTE Use the fixed world here to make `REPLInterpreter` robust against
760
    #      potential invalidations of `Core.Compiler` methods.
761
    Base.invoke_in_world(COMPLETION_WORLD[], CC.typeinf, interp, frame)
512✔
762

763
    result = frame.result.result
512✔
764
    result === Union{} && return nothing # for whatever reason, callers expect this as the Bottom and/or Top type instead
512✔
765
    return result
503✔
766
end
767

768
# `COMPLETION_WORLD[]` will be initialized within `__init__`
769
# (to allow us to potentially remove REPL from the sysimage in the future).
770
# Note that inference from the `code_typed` call below will use the current world age
771
# rather than `typemax(UInt)`, since `Base.invoke_in_world` uses the current world age
772
# when the given world age is higher than the current one.
773
const COMPLETION_WORLD = Ref{UInt}(typemax(UInt))
774

775
# Generate code cache for `REPLInterpreter` now:
776
# This code cache will be available at the world of `COMPLETION_WORLD`,
777
# assuming no invalidation will happen before initializing REPL.
778
# Once REPL is loaded, `REPLInterpreter` will be resilient against future invalidations.
779
code_typed(CC.typeinf, (REPLInterpreter, CC.InferenceState))
780

781
# Method completion on function call expression that look like :(max(1))
782
MAX_METHOD_COMPLETIONS::Int = 40
783
function _complete_methods(ex_org::Expr, context_module::Module, shift::Bool)
312✔
784
    funct = repl_eval_ex(ex_org.args[1], context_module)
312✔
785
    funct === nothing && return 2, nothing, [], Set{Symbol}()
312✔
786
    funct = CC.widenconst(funct)
307✔
787
    args_ex, kwargs_ex, kwargs_flag = complete_methods_args(ex_org, context_module, true, true)
307✔
788
    return kwargs_flag, funct, args_ex, kwargs_ex
307✔
789
end
790

791
function complete_methods(ex_org::Expr, context_module::Module=Main, shift::Bool=false)
1✔
792
    kwargs_flag, funct, args_ex, kwargs_ex = _complete_methods(ex_org, context_module, shift)::Tuple{Int, Any, Vector{Any}, Set{Symbol}}
142✔
793
    out = Completion[]
141✔
794
    kwargs_flag == 2 && return out # one of the kwargs is invalid
141✔
795
    kwargs_flag == 0 && push!(args_ex, Vararg{Any}) # allow more arguments if there is no semicolon
128✔
796
    complete_methods!(out, funct, args_ex, kwargs_ex, shift ? -2 : MAX_METHOD_COMPLETIONS, kwargs_flag == 1)
186✔
797
    return out
128✔
798
end
799

800
MAX_ANY_METHOD_COMPLETIONS::Int = 10
801
function recursive_explore_names!(seen::IdSet, callee_module::Module, initial_module::Module, exploredmodules::IdSet{Module}=IdSet{Module}())
69✔
802
    push!(exploredmodules, callee_module)
83✔
803
    for name in names(callee_module; all=true, imported=true)
69✔
804
        if !Base.isdeprecated(callee_module, name) && !startswith(string(name), '#') && isdefined(initial_module, name)
29,669✔
805
            func = getfield(callee_module, name)
2,255✔
806
            if !isa(func, Module)
2,255✔
807
                funct = Core.Typeof(func)
3,935✔
808
                push!(seen, funct)
2,128✔
809
            elseif isa(func, Module) && func ∉ exploredmodules
127✔
810
                recursive_explore_names!(seen, func, initial_module, exploredmodules)
55✔
811
            end
812
        end
813
    end
14,852✔
814
end
815
function recursive_explore_names(callee_module::Module, initial_module::Module)
816
    seen = IdSet{Any}()
14✔
817
    recursive_explore_names!(seen, callee_module, initial_module)
14✔
UNCOV
818
    seen
×
819
end
820

821
function complete_any_methods(ex_org::Expr, callee_module::Module, context_module::Module, moreargs::Bool, shift::Bool)
14✔
822
    out = Completion[]
14✔
823
    args_ex, kwargs_ex, kwargs_flag = try
14✔
824
        # this may throw, since we set default_any to false
825
        complete_methods_args(ex_org, context_module, false, false)
14✔
826
    catch ex
UNCOV
827
        ex isa ArgumentError || rethrow()
×
828
        return out
14✔
829
    end
830
    kwargs_flag == 2 && return out # one of the kwargs is invalid
14✔
831

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

836
    for seen_name in recursive_explore_names(callee_module, callee_module)
28✔
837
        complete_methods!(out, seen_name, args_ex, kwargs_ex, MAX_ANY_METHOD_COMPLETIONS, false)
1,832✔
838
    end
3,650✔
839

840
    if !shift
14✔
841
        # Filter out methods where all arguments are `Any`
842
        filter!(out) do c
2✔
843
            isa(c, TextCompletion) && return false
12✔
844
            isa(c, MethodCompletion) || return true
12✔
845
            sig = Base.unwrap_unionall(c.method.sig)::DataType
12✔
846
            return !all(@nospecialize(T) -> T === Any || T === Vararg{Any}, sig.parameters[2:end])
20✔
847
        end
848
    end
849

850
    return out
14✔
851
end
852

853
function detect_invalid_kwarg!(kwargs_ex::Vector{Symbol}, @nospecialize(x), kwargs_flag::Int, possible_splat::Bool)
854
    n = isexpr(x, :kw) ? x.args[1] : x
55✔
855
    if n isa Symbol
55✔
856
        push!(kwargs_ex, n)
41✔
857
        return kwargs_flag
41✔
858
    end
859
    possible_splat && isexpr(x, :...) && return kwargs_flag
14✔
860
    return 2 # The kwarg is invalid
10✔
861
end
862

863
function detect_args_kwargs(funargs::Vector{Any}, context_module::Module, default_any::Bool, broadcasting::Bool)
321✔
864
    args_ex = Any[]
321✔
865
    kwargs_ex = Symbol[]
321✔
866
    kwargs_flag = 0
321✔
867
    # kwargs_flag is:
868
    # * 0 if there is no semicolon and no invalid kwarg
869
    # * 1 if there is a semicolon and no invalid kwarg
870
    # * 2 if there are two semicolons or more, or if some kwarg is invalid, which
871
    #        means that it is not of the form "bar=foo", "bar" or "bar..."
872
    for i in (1+!broadcasting):length(funargs)
477✔
873
        ex = funargs[i]
297✔
874
        if isexpr(ex, :parameters)
297✔
875
            kwargs_flag = ifelse(kwargs_flag == 0, 1, 2) # there should be at most one :parameters
60✔
876
            for x in ex.args
60✔
877
                kwargs_flag = detect_invalid_kwarg!(kwargs_ex, x, kwargs_flag, true)
46✔
878
            end
33✔
879
        elseif isexpr(ex, :kw)
237✔
880
            kwargs_flag = detect_invalid_kwarg!(kwargs_ex, ex, kwargs_flag, false)
22✔
881
        else
882
            if broadcasting
215✔
883
                # handle broadcasting, but only handle number of arguments instead of
884
                # argument types
885
                push!(args_ex, Any)
5✔
886
            else
887
                argt = repl_eval_ex(ex, context_module)
210✔
888
                if argt !== nothing
210✔
889
                    push!(args_ex, CC.widenconst(argt))
184✔
890
                elseif default_any
26✔
891
                    push!(args_ex, Any)
26✔
892
                else
UNCOV
893
                    throw(ArgumentError("argument not found"))
×
894
                end
895
            end
896
        end
897
    end
429✔
898
    return args_ex, Set{Symbol}(kwargs_ex), kwargs_flag
321✔
899
end
900

901
is_broadcasting_expr(ex::Expr) = ex.head === :. && isexpr(ex.args[2], :tuple)
1,953✔
902

903
function complete_methods_args(ex::Expr, context_module::Module, default_any::Bool, allow_broadcasting::Bool)
904
    if allow_broadcasting && is_broadcasting_expr(ex)
321✔
905
        return detect_args_kwargs((ex.args[2]::Expr).args, context_module, default_any, true)
6✔
906
    end
907
    return detect_args_kwargs(ex.args, context_module, default_any, false)
315✔
908
end
909

910
function complete_methods!(out::Vector{Completion}, @nospecialize(funct), args_ex::Vector{Any}, kwargs_ex::Set{Symbol}, max_method_completions::Int, exact_nargs::Bool)
2,129✔
911
    # Input types and number of arguments
912
    t_in = Tuple{funct, args_ex...}
2,129✔
913
    m = Base._methods_by_ftype(t_in, nothing, max_method_completions, Base.get_world_counter(),
2,129✔
914
        #=ambig=# true, Ref(typemin(UInt)), Ref(typemax(UInt)), Ptr{Int32}(C_NULL))
915
    if !isa(m, Vector)
2,129✔
916
        push!(out, TextCompletion(sprint(Base.show_signature_function, funct) * "( too many methods, use SHIFT-TAB to show )"))
187✔
917
        return
187✔
918
    end
919
    for match in m
1,942✔
920
        # TODO: if kwargs_ex, filter out methods without kwargs?
921
        push!(out, MethodCompletion(match.spec_types, match.method))
4,368✔
922
    end
4,368✔
923
    # TODO: filter out methods with wrong number of arguments if `exact_nargs` is set
924
end
925

926
include("latex_symbols.jl")
927
include("emoji_symbols.jl")
928

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

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

941
# Aux function to detect whether we're right after a using or import keyword
942
function get_import_mode(s::String)
1,642✔
943
    # allow all of these to start with leading whitespace and macros like @eval and @eval(
944
    # ^\s*(?:@\w+\s*(?:\(\s*)?)?
945

946
    # match simple cases like `using |` and `import  |`
947
    mod_import_match_simple = match(r"^\s*(?:@\w+\s*(?:\(\s*)?)?\b(using|import)\s*$", s)
1,642✔
948
    if mod_import_match_simple !== nothing
1,642✔
949
        if mod_import_match_simple[1] == "using"
12✔
950
            return :using_module
4✔
951
        else
952
            return :import_module
2✔
953
        end
954
    end
955
    # match module import statements like `using Foo|`, `import Foo, Bar|` and `using Foo.Bar, Baz, |`
956
    mod_import_match = match(r"^\s*(?:@\w+\s*(?:\(\s*)?)?\b(using|import)\s+([\w\.]+(?:\s*,\s*[\w\.]+)*),?\s*$", s)
1,636✔
957
    if mod_import_match !== nothing
1,636✔
958
        if mod_import_match.captures[1] == "using"
62✔
959
            return :using_module
24✔
960
        else
961
            return :import_module
7✔
962
        end
963
    end
964
    # now match explicit name import statements like `using Foo: |` and `import Foo: bar, baz|`
965
    name_import_match = match(r"^\s*(?:@\w+\s*(?:\(\s*)?)?\b(using|import)\s+([\w\.]+)\s*:\s*([\w@!\s,]+)$", s)
1,605✔
966
    if name_import_match !== nothing
1,605✔
967
        if name_import_match[1] == "using"
10✔
968
            return :using_name
5✔
969
        else
UNCOV
970
            return :import_name
×
971
        end
972
    end
973
    return nothing
1,600✔
974
end
975

976
function close_path_completion(dir, path, str, pos)
18✔
977
    path = unescape_string(replace(path, "\\\$"=>"\$"))
36✔
978
    path = joinpath(dir, path)
18✔
979
    # ...except if it's a directory...
980
    Base.isaccessibledir(path) && return false
18✔
981
    # ...and except if there's already a " at the cursor.
982
    return lastindex(str) <= pos || str[nextind(str, pos)] != '"'
8✔
983
end
984

985
function bslash_completions(string::String, pos::Int, hint::Bool=false)
2,072✔
986
    slashpos = something(findprev(isequal('\\'), string, pos), 0)
4,202✔
987
    if (something(findprev(in(bslash_separators), string, pos), 0) < slashpos &&
2,100✔
988
        !(1 < slashpos && (string[prevind(string, slashpos)]=='\\')))
989
        # latex / emoji symbol substitution
990
        s = string[slashpos:pos]
112✔
991
        latex = get(latex_symbols, s, "")
56✔
992
        if !isempty(latex) # complete an exact match
56✔
993
            return (true, (Completion[BslashCompletion(latex)], slashpos:pos, true))
22✔
994
        elseif occursin(subscript_regex, s)
34✔
995
            sub = map(c -> subscripts[c], s[3:end])
62✔
996
            return (true, (Completion[BslashCompletion(sub)], slashpos:pos, true))
6✔
997
        elseif occursin(superscript_regex, s)
28✔
998
            sup = map(c -> superscripts[c], s[3:end])
14✔
999
            return (true, (Completion[BslashCompletion(sup)], slashpos:pos, true))
1✔
1000
        end
1001
        emoji = get(emoji_symbols, s, "")
27✔
1002
        if !isempty(emoji)
27✔
1003
            return (true, (Completion[BslashCompletion(emoji)], slashpos:pos, true))
2✔
1004
        end
1005
        # return possible matches; these cannot be mixed with regular
1006
        # Julian completions as only latex / emoji symbols contain the leading \
1007
        symbol_dict = startswith(s, "\\:") ? emoji_symbols : latex_symbols
25✔
1008
        namelist = Iterators.filter(k -> startswith(k, s), keys(symbol_dict))
62,419✔
1009
        completions = Completion[BslashCompletion(name, "$(symbol_dict[name]) $name") for name in sort!(collect(namelist))]
25✔
1010
        return (true, (completions, slashpos:pos, true))
25✔
1011
    end
1012
    return (false, (Completion[], 0:-1, false))
2,016✔
1013
end
1014

1015
function dict_identifier_key(str::String, tag::Symbol, context_module::Module=Main)
2,213✔
1016
    if tag === :string
2,213✔
1017
        str_close = str*"\""
168✔
1018
    elseif tag === :cmd
2,043✔
1019
        str_close = str*"`"
5✔
1020
    else
1021
        str_close = str
2,038✔
1022
    end
1023
    frange, end_of_identifier = find_start_brace(str_close, c_start='[', c_end=']')
2,211✔
1024
    isempty(frange) && return (nothing, nothing, nothing)
2,211✔
1025
    objstr = str[1:end_of_identifier]
214✔
1026
    objex = Meta.parse(objstr, raise=false, depwarn=false)
107✔
1027
    objt = repl_eval_ex(objex, context_module)
107✔
1028
    isa(objt, Core.Const) || return (nothing, nothing, nothing)
131✔
1029
    obj = objt.val
83✔
1030
    isa(obj, AbstractDict) || return (nothing, nothing, nothing)
84✔
1031
    (Base.haslength(obj) && length(obj)::Int < 1_000_000) || return (nothing, nothing, nothing)
83✔
1032
    begin_of_key = something(findnext(!isspace, str, nextind(str, end_of_identifier) + 1), # +1 for [
156✔
1033
                             lastindex(str)+1)
1034
    return (obj, str[begin_of_key:end], begin_of_key)
81✔
1035
end
1036

1037
# This needs to be a separate non-inlined function, see #19441
1038
@noinline function find_dict_matches(identifier::AbstractDict, partial_key)
80✔
1039
    matches = String[]
80✔
1040
    for key in keys(identifier)
122✔
1041
        rkey = repr(key)
933✔
1042
        startswith(rkey,partial_key) && push!(matches,rkey)
933✔
1043
    end
1,410✔
1044
    return matches
80✔
1045
end
1046

1047
# Identify an argument being completed in a method call. If the argument is empty, method
1048
# suggestions will be provided instead of argument completions.
1049
function identify_possible_method_completion(partial, last_idx)
2,809✔
1050
    fail = 0:-1, Expr(:nothing), 0:-1, 0
2,809✔
1051

1052
    # First, check that the last punctuation is either ',', ';' or '('
1053
    idx_last_punct = something(findprev(x -> ispunct(x) && x != '_' && x != '!', partial, last_idx), 0)::Int
19,635✔
1054
    idx_last_punct == 0 && return fail
2,809✔
1055
    last_punct = partial[idx_last_punct]
4,560✔
1056
    last_punct == ',' || last_punct == ';' || last_punct == '(' || return fail
4,349✔
1057

1058
    # Then, check that `last_punct` is only followed by an identifier or nothing
1059
    before_last_word_start = something(findprev(in(non_identifier_chars), partial, last_idx), 0)
1,598✔
1060
    before_last_word_start == 0 && return fail
799✔
1061
    all(isspace, @view partial[nextind(partial, idx_last_punct):before_last_word_start]) || return fail
903✔
1062

1063
    # Check that `last_punct` is either the last '(' or placed after a previous '('
1064
    frange, method_name_end = find_start_brace(@view partial[1:idx_last_punct])
695✔
1065
    method_name_end ∈ frange || return fail
906✔
1066

1067
    # Strip the preceding ! operators, if any, and close the expression with a ')'
1068
    s = replace(partial[frange], r"\G\!+([^=\(]+)" => s"\1"; count=1) * ')'
968✔
1069
    ex = Meta.parse(s, raise=false, depwarn=false)
484✔
1070
    isa(ex, Expr) || return fail
484✔
1071

1072
    # `wordrange` is the position of the last argument to complete
1073
    wordrange = nextind(partial, before_last_word_start):last_idx
624✔
1074
    return frange, ex, wordrange, method_name_end
484✔
1075
end
1076

1077
# Provide completion for keyword arguments in function calls
1078
function complete_keyword_argument(partial::String, last_idx::Int, context_module::Module;
3,622✔
1079
                                   shift::Bool=false)
1080
    frange, ex, wordrange, = identify_possible_method_completion(partial, last_idx)
1,811✔
1081
    fail = Completion[], 0:-1, frange
1,811✔
1082
    ex.head === :call || is_broadcasting_expr(ex) || return fail
3,454✔
1083

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

1087
    methods = Completion[]
169✔
1088
    complete_methods!(methods, funct, Any[Vararg{Any}], kwargs_ex, shift ? -1 : MAX_METHOD_COMPLETIONS, kwargs_flag == 1)
304✔
1089
    # TODO: use args_ex instead of Any[Vararg{Any}] and only provide kwarg completion for
1090
    # method calls compatible with the current arguments.
1091

1092
    # For each method corresponding to the function call, provide completion suggestions
1093
    # for each keyword that starts like the last word and that is not already used
1094
    # previously in the expression. The corresponding suggestion is "kwname=".
1095
    # If the keyword corresponds to an existing name, also include "kwname" as a suggestion
1096
    # since the syntax "foo(; kwname)" is equivalent to "foo(; kwname=kwname)".
1097
    last_word = partial[wordrange] # the word to complete
338✔
1098
    kwargs = Set{String}()
169✔
1099
    for m in methods
169✔
1100
        # if MAX_METHOD_COMPLETIONS is hit a single TextCompletion is return by complete_methods! with an explanation
1101
        # which can be ignored here
1102
        m isa TextCompletion && continue
892✔
1103
        m::MethodCompletion
890✔
1104
        possible_kwargs = Base.kwarg_decl(m.method)
890✔
1105
        current_kwarg_candidates = String[]
890✔
1106
        for _kw in possible_kwargs
890✔
1107
            kw = String(_kw)
468✔
1108
            if !endswith(kw, "...") && startswith(kw, last_word) && _kw ∉ kwargs_ex
501✔
1109
                push!(current_kwarg_candidates, kw)
67✔
1110
            end
1111
        end
468✔
1112
        union!(kwargs, current_kwarg_candidates)
890✔
1113
    end
892✔
1114

1115
    suggestions = Completion[KeywordArgumentCompletion(kwarg) for kwarg in kwargs]
216✔
1116

1117
    # Only add these if not in kwarg space. i.e. not in `foo(; `
1118
    if kwargs_flag == 0
169✔
1119
        complete_symbol!(suggestions, #=prefix=#nothing, last_word, context_module; shift)
138✔
1120
        complete_keyval!(suggestions, last_word)
138✔
1121
    end
1122

1123
    return sort!(suggestions, by=named_completion_completion), wordrange
169✔
1124
end
1125

1126
function get_loading_candidates(pkgstarts::String, project_file::String)
1✔
1127
    loading_candidates = String[]
1✔
1128
    d = Base.parsed_toml(project_file)
1✔
1129
    pkg = get(d, "name", nothing)::Union{String, Nothing}
1✔
1130
    if pkg !== nothing && startswith(pkg, pkgstarts)
1✔
1131
        push!(loading_candidates, pkg)
1✔
1132
    end
1133
    deps = get(d, "deps", nothing)::Union{Dict{String, Any}, Nothing}
1✔
1134
    if deps !== nothing
1✔
1135
        for (pkg, _) in deps
2✔
1136
            startswith(pkg, pkgstarts) && push!(loading_candidates, pkg)
1✔
1137
        end
1✔
1138
    end
1139
    return loading_candidates
1✔
1140
end
1141

1142
function complete_loading_candidates!(suggestions::Vector{Completion}, pkgstarts::String, project_file::String)
1✔
1143
    for name in get_loading_candidates(pkgstarts, project_file)
1✔
1144
        push!(suggestions, PackageCompletion(name))
2✔
1145
    end
2✔
1146
    return suggestions
1✔
1147
end
1148

1149
function complete_identifiers!(suggestions::Vector{Completion},
3,290✔
1150
                               context_module::Module, string::String, name::String,
1151
                               pos::Int, separatorpos::Int, startpos::Int;
1152
                               comp_keywords::Bool=false,
1153
                               complete_modules_only::Bool=false,
1154
                               shift::Bool=false)
1155
    if comp_keywords
1,645✔
1156
        complete_keyword!(suggestions, name)
604✔
1157
        complete_keyval!(suggestions, name)
604✔
1158
    end
1159
    if separatorpos > 1 && (string[separatorpos] == '.' || string[separatorpos] == ':')
1,650✔
1160
        s = string[1:prevind(string, separatorpos)]
1,714✔
1161
        # First see if the whole string up to `pos` is a valid expression. If so, use it.
1162
        prefix = Meta.parse(s, raise=false, depwarn=false)
857✔
1163
        if isexpr(prefix, :incomplete)
857✔
1164
            s = string[startpos:pos]
1,224✔
1165
            # Heuristic to find the start of the expression. TODO: This would be better
1166
            # done with a proper error-recovering parser.
1167
            if 0 < startpos <= lastindex(string) && string[startpos] == '.'
1,224✔
1168
                i = prevind(string, startpos)
6✔
1169
                while 0 < i
6✔
1170
                    c = string[i]
12✔
1171
                    if c in (')', ']')
12✔
1172
                        if c == ')'
×
1173
                            c_start = '('
×
UNCOV
1174
                            c_end = ')'
×
1175
                        elseif c == ']'
×
1176
                            c_start = '['
×
UNCOV
1177
                            c_end = ']'
×
1178
                        end
UNCOV
1179
                        frange, end_of_identifier = find_start_brace(string[1:prevind(string, i)], c_start=c_start, c_end=c_end)
×
1180
                        isempty(frange) && break # unbalanced parens
×
1181
                        startpos = first(frange)
×
UNCOV
1182
                        i = prevind(string, startpos)
×
1183
                    elseif c in ('\'', '\"', '\`')
12✔
UNCOV
1184
                        s = "$c$c"*string[startpos:pos]
×
UNCOV
1185
                        break
×
1186
                    else
1187
                        break
6✔
1188
                    end
UNCOV
1189
                    s = string[startpos:pos]
×
UNCOV
1190
                end
×
1191
            end
1192
            if something(findlast(in(non_identifier_chars), s), 0) < something(findlast(isequal('.'), s), 0)
1,208✔
1193
                lookup_name, name = rsplit(s, ".", limit=2)
550✔
1194
                name = String(name)
550✔
1195
                prefix = Meta.parse(lookup_name, raise=false, depwarn=false)
550✔
1196
            end
1197
            isexpr(prefix, :incomplete) && (prefix = nothing)
658✔
1198
        elseif isexpr(prefix, (:using, :import))
199✔
1199
            arglast = prefix.args[end] # focus on completion to the last argument
16✔
1200
            if isexpr(arglast, :.)
16✔
1201
                # We come here for cases like:
1202
                # - `string`: "using Mod1.Mod2.M"
1203
                # - `ex`: :(using Mod1.Mod2)
1204
                # - `name`: "M"
1205
                # Now we transform `ex` to `:(Mod1.Mod2)` to allow `complete_symbol!` to
1206
                # complete for inner modules whose name starts with `M`.
1207
                # Note that `complete_modules_only=true` is set within `completions`
1208
                prefix = nothing
16✔
1209
                firstdot = true
16✔
1210
                for arg = arglast.args
16✔
1211
                    if arg === :.
32✔
1212
                        # override `context_module` if multiple `.` accessors are used
1213
                        if firstdot
15✔
1214
                            firstdot = false
12✔
1215
                        else
1216
                            context_module = parentmodule(context_module)
3✔
1217
                        end
1218
                    elseif arg isa Symbol
17✔
1219
                        if prefix === nothing
17✔
1220
                            prefix = arg
16✔
1221
                        else
1222
                            prefix = Expr(:., prefix, QuoteNode(arg))
1✔
1223
                        end
1224
                    else # invalid expression
UNCOV
1225
                        prefix = nothing
×
UNCOV
1226
                        break
×
1227
                    end
1228
                end
32✔
1229
            end
1230
        elseif isexpr(prefix, :call) && length(prefix.args) > 1
183✔
1231
            isinfix = s[end] != ')'
39✔
1232
            # A complete call expression that does not finish with ')' is an infix call.
1233
            if !isinfix
20✔
1234
                # Handle infix call argument completion of the form bar + foo(qux).
1235
                frange, end_of_identifier = find_start_brace(@view s[1:prevind(s, end)])
30✔
1236
                if !isempty(frange) # if find_start_brace fails to find the brace just continue
15✔
1237
                    isinfix = Meta.parse(@view(s[frange[1]:end]), raise=false, depwarn=false) == prefix.args[end]
15✔
1238
                end
1239
            end
1240
            if isinfix
20✔
1241
                prefix = prefix.args[end]
8✔
1242
            end
1243
        elseif isexpr(prefix, :macrocall) && length(prefix.args) > 1
163✔
1244
            # allow symbol completions within potentially incomplete macrocalls
1245
            if s[end] ≠ '`' && s[end] ≠ ')'
43✔
1246
                prefix = prefix.args[end]
12✔
1247
            end
1248
        end
1249
    else
1250
        prefix = nothing
788✔
1251
    end
1252
    complete_symbol!(suggestions, prefix, name, context_module; complete_modules_only, shift)
1,645✔
1253
    return suggestions
1,645✔
1254
end
1255

1256
function completions(string::String, pos::Int, context_module::Module=Main, shift::Bool=true, hint::Bool=false)
2,224✔
1257
    # First parse everything up to the current position
1258
    partial = string[1:pos]
4,855✔
1259
    inc_tag = Base.incomplete_tag(Meta.parse(partial, raise=false, depwarn=false))
2,223✔
1260

1261
    if !hint # require a tab press for completion of these
2,223✔
1262
        # ?(x, y)TAB lists methods you can call with these objects
1263
        # ?(x, y TAB lists methods that take these objects as the first two arguments
1264
        # MyModule.?(x, y)TAB restricts the search to names in MyModule
1265
        rexm = match(r"(\w+\.|)\?\((.*)$", partial)
419✔
1266
        if rexm !== nothing
419✔
1267
            # Get the module scope
1268
            if isempty(rexm.captures[1])
28✔
1269
                callee_module = context_module
1✔
1270
            else
1271
                modname = Symbol(rexm.captures[1][1:end-1])
13✔
1272
                if isdefined(context_module, modname)
13✔
1273
                    callee_module = getfield(context_module, modname)
13✔
1274
                    if !isa(callee_module, Module)
13✔
UNCOV
1275
                        callee_module = context_module
×
1276
                    end
1277
                else
UNCOV
1278
                    callee_module = context_module
×
1279
                end
1280
            end
1281
            moreargs = !endswith(rexm.captures[2], ')')
14✔
1282
            callstr = "_(" * rexm.captures[2]
14✔
1283
            if moreargs
14✔
1284
                callstr *= ')'
8✔
1285
            end
1286
            ex_org = Meta.parse(callstr, raise=false, depwarn=false)
14✔
1287
            if isa(ex_org, Expr)
14✔
1288
                return complete_any_methods(ex_org, callee_module::Module, context_module, moreargs, shift), (0:length(rexm.captures[1])+1) .+ rexm.offset, false
14✔
1289
            end
1290
        end
1291
    end
1292

1293
    # if completing a key in a Dict
1294
    identifier, partial_key, loc = dict_identifier_key(partial, inc_tag, context_module)
2,289✔
1295
    if identifier !== nothing
2,209✔
1296
        matches = find_dict_matches(identifier, partial_key)
80✔
1297
        length(matches)==1 && (lastindex(string) <= pos || string[nextind(string,pos)] != ']') && (matches[1]*=']')
80✔
1298
        length(matches)>0 && return Completion[DictCompletion(identifier, match) for match in sort!(matches)], loc::Int:pos, true
80✔
1299
    end
1300

1301
    suggestions = Completion[]
2,145✔
1302

1303
    # Check if this is a var"" string macro that should be completed like
1304
    # an identifier rather than a string.
1305
    # TODO: It would be nice for the parser to give us more information here
1306
    # so that we can lookup the macro by identity rather than pattern matching
1307
    # its invocation.
1308
    varrange = findprev("var\"", string, pos)
2,145✔
1309

1310
    expanded = nothing
2,145✔
1311
    was_expanded = false
2,145✔
1312

1313
    if varrange !== nothing
2,145✔
1314
        ok, ret = bslash_completions(string, pos)
3✔
1315
        ok && return ret
3✔
1316
        startpos = first(varrange) + 4
3✔
1317
        separatorpos = something(findprev(isequal('.'), string, first(varrange)-1), 0)
5✔
1318
        name = string[startpos:pos]
5✔
1319
        complete_identifiers!(suggestions, context_module, string, name,
3✔
1320
                              pos, separatorpos, startpos;
1321
                              shift)
1322
        return sort!(unique!(named_completion, suggestions), by=named_completion_completion), (separatorpos+1):pos, true
3✔
1323
    elseif inc_tag === :cmd
2,142✔
1324
        # TODO: should this call shell_completions instead of partially reimplementing it?
1325
        let m = match(r"[\t\n\r\"`><=*?|]| (?!\\)", reverse(partial)) # fuzzy shell_parse in reverse
1✔
1326
            startpos = nextind(partial, reverseind(partial, m.offset))
2✔
1327
            r = startpos:pos
1✔
1328
            scs::String = string[r]
2✔
1329

1330
            expanded = complete_expanduser(scs, r)
1✔
1331
            was_expanded = expanded[3]
1✔
1332
            if was_expanded
1✔
UNCOV
1333
                scs = (only(expanded[1])::PathCompletion).path
×
1334
                # If tab press, ispath and user expansion available, return it now
1335
                # otherwise see if we can complete the path further before returning with expanded ~
UNCOV
1336
                !hint && ispath(scs) && return expanded::Completions
×
1337
            end
1338

1339
            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✔
1340
            # This expansion with "\\ "=>' ' replacement and shell_escape=true
1341
            # assumes the path isn't further quoted within the cmd backticks.
1342
            path = replace(path, r"\\ " => " ", r"\$" => "\$") # fuzzy shell_parse (reversed by shell_escape_posixly)
1✔
1343
            paths, dir, success = complete_path(path, shell_escape=true, raw_escape=true)
1✔
1344

1345
            if success && !isempty(dir)
1✔
1346
                let dir = do_raw_escape(do_shell_escape(dir))
×
1347
                    # if escaping of dir matches scs prefix, remove that from the completions
1348
                    # otherwise make it the whole completion
UNCOV
1349
                    if endswith(dir, "/") && startswith(scs, dir)
×
UNCOV
1350
                        r = (startpos + sizeof(dir)):pos
×
UNCOV
1351
                    elseif startswith(scs, dir * "/")
×
UNCOV
1352
                        r = nextind(string, startpos + sizeof(dir)):pos
×
1353
                    else
UNCOV
1354
                        map!(paths, paths) do c::PathCompletion
×
1355
                            p = dir * "/" * c.path
×
UNCOV
1356
                            was_expanded && (p = contractuser(p))
×
UNCOV
1357
                            return PathCompletion(p)
×
1358
                        end
1359
                    end
1360
                end
1361
            end
1362
            if isempty(paths) && !hint && was_expanded
1✔
1363
                # if not able to provide completions, not hinting, and ~ expansion was possible, return ~ expansion
UNCOV
1364
                return expanded::Completions
×
1365
            else
1366
                return sort!(paths, by=p->p.path), r::UnitRange{Int}, success
1✔
1367
            end
1368
        end
1369
    elseif inc_tag === :string
2,141✔
1370
        # Find first non-escaped quote
1371
        let m = match(r"\"(?!\\)", reverse(partial))
147✔
1372
            startpos = nextind(partial, reverseind(partial, m.offset))
294✔
1373
            r = startpos:pos
170✔
1374
            scs::String = string[r]
271✔
1375

1376
            expanded = complete_expanduser(scs, r)
147✔
1377
            was_expanded = expanded[3]
147✔
1378
            if was_expanded
147✔
1379
                scs = (only(expanded[1])::PathCompletion).path
6✔
1380
                # If tab press, ispath and user expansion available, return it now
1381
                # otherwise see if we can complete the path further before returning with expanded ~
1382
                !hint && ispath(scs) && return expanded::Completions
6✔
1383
            end
1384

1385
            path = try
144✔
1386
                unescape_string(replace(scs, "\\\$"=>"\$"))
265✔
1387
            catch ex
UNCOV
1388
                ex isa ArgumentError || rethrow()
×
1389
                nothing
144✔
1390
            end
1391
            if !isnothing(path)
288✔
1392
                paths, dir, success = complete_path(path::String, string_escape=true)
144✔
1393

1394
                if length(paths) == 1
144✔
1395
                    p = (paths[1]::PathCompletion).path
18✔
1396
                    hint && was_expanded && (p = contractuser(p))
18✔
1397
                    if close_path_completion(dir, p, path, pos)
18✔
1398
                        paths[1] = PathCompletion(p * "\"")
8✔
1399
                    end
1400
                end
1401

1402
                if success && !isempty(dir)
144✔
1403
                    let dir = do_string_escape(dir)
22✔
1404
                        # if escaping of dir matches scs prefix, remove that from the completions
1405
                        # otherwise make it the whole completion
1406
                        if endswith(dir, "/") && startswith(scs, dir)
11✔
1407
                            r = (startpos + sizeof(dir)):pos
1✔
1408
                        elseif startswith(scs, dir * "/") && dir != dirname(homedir())
10✔
1409
                            was_expanded && (dir = contractuser(dir))
10✔
1410
                            r = nextind(string, startpos + sizeof(dir)):pos
10✔
1411
                        else
UNCOV
1412
                            map!(paths, paths) do c::PathCompletion
×
UNCOV
1413
                                p = dir * "/" * c.path
×
UNCOV
1414
                                hint && was_expanded && (p = contractuser(p))
×
UNCOV
1415
                                return PathCompletion(p)
×
1416
                            end
1417
                        end
1418
                    end
1419
                end
1420

1421
                # Fallthrough allowed so that Latex symbols can be completed in strings
1422
                if success
144✔
1423
                    return sort!(paths, by=p->p.path), r::UnitRange{Int}, success
28,633✔
1424
                elseif !hint && was_expanded
71✔
1425
                    # if not able to provide completions, not hinting, and ~ expansion was possible, return ~ expansion
1426
                    return expanded::Completions
1✔
1427
                end
1428
            end
1429
        end
1430
    end
1431
    # if path has ~ and we didn't find any paths to complete just return the expanded path
1432
    was_expanded && return expanded::Completions
2,064✔
1433

1434
    ok, ret = bslash_completions(string, pos)
2,064✔
1435
    ok && return ret
2,064✔
1436

1437
    # Make sure that only bslash_completions is working on strings
1438
    inc_tag === :string && return Completion[], 0:-1, false
2,013✔
1439
    if inc_tag === :other
1,952✔
1440
        frange, ex, wordrange, method_name_end = identify_possible_method_completion(partial, pos)
998✔
1441
        if last(frange) != -1 && all(isspace, @view partial[wordrange]) # no last argument to complete
998✔
1442
            if ex.head === :call
140✔
1443
                return complete_methods(ex, context_module, shift), first(frange):method_name_end, false
137✔
1444
            elseif is_broadcasting_expr(ex)
3✔
1445
                return complete_methods(ex, context_module, shift), first(frange):(method_name_end - 1), false
3✔
1446
            end
1447
        end
1448
    elseif inc_tag === :comment
954✔
1449
        return Completion[], 0:-1, false
1✔
1450
    end
1451

1452
    # Check whether we can complete a keyword argument in a function call
1453
    kwarg_completion, wordrange = complete_keyword_argument(partial, pos, context_module; shift)
3,453✔
1454
    isempty(wordrange) || return kwarg_completion, wordrange, !isempty(kwarg_completion)
1,980✔
1455

1456
    startpos = nextind(string, something(findprev(in(non_identifier_chars), string, pos), 0))
2,919✔
1457
    # strip preceding ! operator
1458
    if (m = match(r"\G\!+", partial, startpos)) isa RegexMatch
1,642✔
1459
        startpos += length(m.match)
2✔
1460
    end
1461

1462
    separatorpos = something(findprev(isequal('.'), string, pos), 0)
2,496✔
1463
    namepos = max(startpos, separatorpos+1)
1,642✔
1464
    name = string[namepos:pos]
2,839✔
1465
    import_mode = get_import_mode(string)
1,642✔
1466
    if import_mode === :using_module || import_mode === :import_module
3,256✔
1467
        # Given input lines like `using Foo|`, `import Foo, Bar|` and `using Foo.Bar, Baz, |`:
1468
        # Let's look only for packages and modules we can reach from here
1469

1470
        # If there's no dot, we're in toplevel, so we should
1471
        # also search for packages
1472
        s = string[startpos:pos]
70✔
1473
        if separatorpos <= startpos
37✔
1474
            for dir in Base.load_path()
25✔
1475
                if basename(dir) in Base.project_names && isfile(dir)
29✔
1476
                    complete_loading_candidates!(suggestions, s, dir)
1✔
1477
                end
1478
                isdir(dir) || continue
56✔
1479
                for entry in _readdirx(dir)
26✔
1480
                    pname = entry.name
1,527✔
1481
                    if pname[1] != '.' && pname != "METADATA" &&
3,054✔
1482
                        pname != "REQUIRE" && startswith(pname, s)
1483
                        # Valid file paths are
1484
                        #   <Mod>.jl
1485
                        #   <Mod>/src/<Mod>.jl
1486
                        #   <Mod>.jl/src/<Mod>.jl
1487
                        if isfile(entry)
261✔
UNCOV
1488
                            endswith(pname, ".jl") && push!(suggestions,
×
1489
                                                            PackageCompletion(pname[1:prevind(pname, end-2)]))
1490
                        else
1491
                            mod_name = if endswith(pname, ".jl")
261✔
UNCOV
1492
                                pname[1:prevind(pname, end-2)]
×
1493
                            else
1494
                                pname
522✔
1495
                            end
1496
                            if isfile(joinpath(entry, "src",
522✔
1497
                                               "$mod_name.jl"))
1498
                                push!(suggestions, PackageCompletion(mod_name))
260✔
1499
                            end
1500
                        end
1501
                    end
1502
                end
1,527✔
1503
            end
28✔
1504
        end
1505
        comp_keywords = false
37✔
1506
        complete_modules_only = import_mode === :using_module # allow completion for `import Mod.name` (where `name` is not a module)
37✔
1507
    elseif import_mode === :using_name || import_mode === :import_name
3,205✔
1508
        # `using Foo: |` and `import Foo: bar, baz|`
1509
        separatorpos = findprev(isequal(':'), string, pos)::Int
5✔
1510
        comp_keywords = false
5✔
1511
        complete_modules_only = false
5✔
1512
    else
1513
        comp_keywords = !isempty(name) && startpos > separatorpos
1,600✔
1514
        complete_modules_only = false
1,600✔
1515
    end
1516

1517
    complete_identifiers!(suggestions, context_module, string, name,
1,642✔
1518
                          pos, separatorpos, startpos;
1519
                          comp_keywords, complete_modules_only, shift)
1520
    return sort!(unique!(named_completion, suggestions), by=named_completion_completion), namepos:pos, true
1,642✔
1521
end
1522

1523
function shell_completions(string, pos, hint::Bool=false)
448✔
1524
    # First parse everything up to the current position
1525
    scs = string[1:pos]
916✔
1526
    args, last_arg_start = try
448✔
1527
        Base.shell_parse(scs, true)::Tuple{Expr,Int}
466✔
1528
    catch ex
1529
        ex isa ArgumentError || ex isa ErrorException || rethrow()
36✔
1530
        return Completion[], 0:-1, false
448✔
1531
    end
1532
    ex = args.args[end]::Expr
430✔
1533
    # Now look at the last thing we parsed
1534
    isempty(ex.args) && return Completion[], 0:-1, false
430✔
1535
    lastarg = ex.args[end]
430✔
1536
    # As Base.shell_parse throws away trailing spaces (unless they are escaped),
1537
    # we need to special case here.
1538
    # If the last char was a space, but shell_parse ignored it search on "".
1539
    if isexpr(lastarg, :incomplete) || isexpr(lastarg, :error)
858✔
1540
        partial = string[last_arg_start:pos]
4✔
1541
        ret, range = completions(partial, lastindex(partial), Main, true, hint)
4✔
1542
        range = range .+ (last_arg_start - 1)
2✔
1543
        return ret, range, true
2✔
1544
    elseif endswith(scs, ' ') && !endswith(scs, "\\ ")
428✔
1545
        r = pos+1:pos
34✔
1546
        paths, dir, success = complete_path("", use_envpath=false, shell_escape=true)
17✔
1547
        return paths, r, success
17✔
1548
    elseif all(@nospecialize(arg) -> arg isa AbstractString, ex.args)
827✔
1549
        # Join these and treat this as a path
1550
        path::String = join(ex.args)
410✔
1551
        r = last_arg_start:pos
410✔
1552

1553
        # Also try looking into the env path if the user wants to complete the first argument
1554
        use_envpath = length(args.args) < 2
410✔
1555

1556
        expanded = complete_expanduser(path, r)
410✔
1557
        was_expanded = expanded[3]
410✔
1558
        if was_expanded
410✔
1559
            path = (only(expanded[1])::PathCompletion).path
2✔
1560
            # If tab press, ispath and user expansion available, return it now
1561
            # otherwise see if we can complete the path further before returning with expanded ~
1562
            !hint && ispath(path) && return expanded::Completions
2✔
1563
        end
1564

1565
        paths, dir, success = complete_path(path, use_envpath=use_envpath, shell_escape=true, contract_user=was_expanded)
410✔
1566

1567
        if success && !isempty(dir)
410✔
1568
            let dir = do_shell_escape(dir)
100✔
1569
                # if escaping of dir matches scs prefix, remove that from the completions
1570
                # otherwise make it the whole completion
1571
                partial = string[last_arg_start:pos]
200✔
1572
                if endswith(dir, "/") && startswith(partial, dir)
100✔
1573
                    r = (last_arg_start + sizeof(dir)):pos
14✔
1574
                elseif startswith(partial, dir * "/")
88✔
1575
                    r = nextind(string, last_arg_start + sizeof(dir)):pos
84✔
1576
                else
1577
                    map!(paths, paths) do c::PathCompletion
4✔
1578
                        return PathCompletion(dir * "/" * c.path)
4✔
1579
                    end
1580
                end
1581
            end
1582
        end
1583
        # if ~ was expanded earlier and the incomplete string isn't a path
1584
        # return the path with contracted user to match what the hint shows. Otherwise expand ~
1585
        # i.e. require two tab presses to expand user
1586
        if was_expanded && !ispath(path)
410✔
UNCOV
1587
            map!(paths, paths) do c::PathCompletion
×
UNCOV
1588
                PathCompletion(contractuser(c.path))
×
1589
            end
1590
        end
1591
        return paths, r, success
410✔
1592
    end
1593
    return Completion[], 0:-1, false
1✔
1594
end
1595

1596
function __init__()
5✔
1597
    COMPLETION_WORLD[] = Base.get_world_counter()
5✔
1598
    return nothing
5✔
1599
end
1600

1601
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