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

JuliaLang / julia / 1429

04 Feb 2026 03:34AM UTC coverage: 76.729% (+0.03%) from 76.703%
1429

push

buildkite

web-flow
Remove `Regex` / libpcre dependency from basic `path.jl` operations (#60677)

All regular expressions in `path.jl` are replaced with corresponding
string operations, with the goal of not altering any behavior
whatsoever. The differences between windows/unix is now captured by the
function `isseparator` (and different joinpath implementations, which I
did not modify).

Performance should not be degraded. With simple tests like `@btime
joinpath("foo", "bar")`, performance is in fact improved by at least a
factor of 2 everywhere. As extreme examples, `splitdrive("foo/bar")`
goes from 250 to 12 ns and `isabspath("foo/bar")` from 100 to 8 ns (also
just tested on windows).

69 of 98 new or added lines in 2 files covered. (70.41%)

68 existing lines in 9 files now uncovered.

62969 of 82067 relevant lines covered (76.73%)

23181663.76 hits per line

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

95.06
/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
using Base.JuliaSyntax: @K_str, @KSet_str, parseall, byte_range, children, is_prefix_call, is_trivia, kind
16

17
using ..REPL.LineEdit: NamedCompletion
18
using ..REPL.SyntaxUtil: CursorNode, find_parent, seek_pos, char_range, char_first, char_last, children_nt, find_delim
19

20
abstract type Completion end
21

22
struct TextCompletion <: Completion
23
    text::String
403✔
24
end
25

26
struct KeywordCompletion <: Completion
27
    keyword::String
42✔
28
end
29

30
struct KeyvalCompletion <: Completion
31
    keyval::String
10✔
32
end
33

34
struct PathCompletion <: Completion
35
    path::String
2,545✔
36
end
37

38
struct ModuleCompletion <: Completion
39
    parent::Module
10,120✔
40
    mod::String
41
end
42

43
struct PackageCompletion <: Completion
44
    package::String
272✔
45
end
46

47
struct PropertyCompletion <: Completion
48
    value
114✔
49
    property::Symbol
50
end
51

52
struct FieldCompletion <: Completion
53
    typ::DataType
36✔
54
    field::Symbol
55
end
56

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

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

69
struct ShellCompletion <: Completion
70
    text::String
71
end
72

73
struct DictCompletion <: Completion
74
    dict::AbstractDict
242✔
75
    key::String
76
end
77

78
struct KeywordArgumentCompletion <: Completion
79
    kwarg::String
138✔
80
end
81

82
# interface definition
83
function Base.getproperty(c::Completion, name::Symbol)
84
    if name === :text
379,460✔
85
        return getfield(c, :text)::String
399✔
86
    elseif name === :keyword
379,061✔
87
        return getfield(c, :keyword)::String
340✔
88
    elseif name === :path
378,721✔
89
        return getfield(c, :path)::String
17,438✔
90
    elseif name === :parent
361,283✔
91
        return getfield(c, :parent)::Module
×
92
    elseif name === :mod
361,283✔
93
        return getfield(c, :mod)::String
195,123✔
94
    elseif name === :package
166,160✔
95
        return getfield(c, :package)::String
3,192✔
96
    elseif name === :property
162,968✔
97
        return getfield(c, :property)::Symbol
414✔
98
    elseif name === :field
162,554✔
99
        return getfield(c, :field)::Symbol
70✔
100
    elseif name === :method
162,484✔
101
        return getfield(c, :method)::Method
156,576✔
102
    elseif name === :bslash
5,908✔
103
        return getfield(c, :bslash)::String
×
104
    elseif name === :text
5,908✔
105
        return getfield(c, :text)::String
×
106
    elseif name === :key
5,908✔
107
        return getfield(c, :key)::String
242✔
108
    elseif name === :kwarg
5,666✔
109
        return getfield(c, :kwarg)::String
246✔
110
    end
111
    return getfield(c, name)
5,420✔
112
end
113

114
_completion_text(c::TextCompletion) = c.text
399✔
115
_completion_text(c::KeywordCompletion) = c.keyword
340✔
116
_completion_text(c::KeyvalCompletion) = c.keyval
38✔
117
_completion_text(c::PathCompletion) = c.path
1,645✔
118
_completion_text(c::ModuleCompletion) = c.mod
195,123✔
119
_completion_text(c::PackageCompletion) = c.package
3,192✔
120
_completion_text(c::PropertyCompletion) = sprint(Base.show_sym, c.property)
414✔
121
_completion_text(c::FieldCompletion) = sprint(Base.show_sym, c.field)
70✔
122
_completion_text(c::MethodCompletion) = repr(c.method)
6,050✔
123
_completion_text(c::ShellCompletion) = c.text
×
124
_completion_text(c::DictCompletion) = c.key
242✔
125
_completion_text(c::KeywordArgumentCompletion) = c.kwarg*'='
246✔
126

127
completion_text(c) = _completion_text(c)::String
207,759✔
128

129
named_completion(c::BslashCompletion) = NamedCompletion(c.completion, c.name)
2,691✔
130

131
function named_completion(c)
10,976✔
132
    text = completion_text(c)::String
207,756✔
133
    return NamedCompletion(text, text)
207,756✔
134
end
135

136
named_completion_completion(c) = named_completion(c).completion::String
178,486✔
137

138
const Completions = Tuple{Vector{Completion}, UnitRange{Int}, Bool}
139

140
function completes_global(x, name)
141
    return startswith(x, name) && !('#' in x)
612,127✔
142
end
143

144
function appendmacro!(syms, macros, needle, endchar)
520✔
145
    for macsym in macros
520✔
146
        s = String(macsym)
880✔
147
        if endswith(s, needle)
880✔
148
            from = nextind(s, firstindex(s))
176✔
149
            to = prevind(s, sizeof(s)-sizeof(needle)+1)
88✔
150
            push!(syms, s[from:to]*endchar)
176✔
151
        end
152
    end
880✔
153
end
154

155
function append_filtered_mod_names!(ffunc::Function, suggestions::Vector{Completion},
260✔
156
                                    mod::Module, name::String, complete_internal_only::Bool)
157
    imported = usings = !complete_internal_only
260✔
158
    ssyms = names(mod; all=true, imported, usings)
260✔
159
    filter!(ffunc, ssyms)
260✔
160
    macros = filter(x -> startswith(String(x), "@" * name), ssyms)
315,414✔
161

162
    # don't complete string and command macros when the input matches the internal name like `r_` to `r"`
163
    if !startswith(name, "@")
260✔
164
        filter!(macros) do m
236✔
165
            s = String(m)
448✔
166
            if endswith(s, "_str") || endswith(s, "_cmd")
828✔
167
                occursin(name, first(s, length(s)-4))
96✔
168
            else
169
                true
170
            end
171
        end
172
    end
173

174
    syms = String[sprint((io,s)->Base.show_sym(io, s; allow_macroname=true), s) for s in ssyms if completes_global(String(s), name)]
10,292✔
175
    appendmacro!(syms, macros, "_str", "\"")
260✔
176
    appendmacro!(syms, macros, "_cmd", "`")
260✔
177
    for sym in syms
260✔
178
        push!(suggestions, ModuleCompletion(mod, sym))
10,120✔
179
    end
10,120✔
180
    return suggestions
260✔
181
end
182

183
# REPL Symbol Completions
184
function complete_symbol!(suggestions::Vector{Completion},
836✔
185
                          @nospecialize(prefix), name::String, context_module::Module;
186
                          complete_modules_only::Bool=false,
187
                          shift::Bool=false)
188
    local mod, t, val
418✔
189
    complete_internal_only = isempty(name)
418✔
190
    if prefix !== nothing
418✔
191
        res = repl_eval_ex(prefix, context_module)
262✔
192
        res === nothing && return Completion[]
262✔
193
        if res isa Const
228✔
194
            val = res.val
182✔
195
            if isa(val, Module)
182✔
196
                mod = val
104✔
197
                if !shift
104✔
198
                    # when module is explicitly accessed, show internal bindings that are
199
                    # defined by the module, unless shift key is pressed
200
                    complete_internal_only = true
12✔
201
                end
202
            else
203
                t = typeof(val)
78✔
204
            end
205
        else
206
            t = CC.widenconst(res)
46✔
207
        end
208
    else
209
        mod = context_module
156✔
210
    end
211

212
    if @isdefined(mod) # lookup names available within the module
384✔
213
        let mod_for_check = mod,
260✔
214
            modname = nameof(mod_for_check),
215
            is_main = mod_for_check === Main
216
            append_filtered_mod_names!(suggestions, mod, name, complete_internal_only) do s::Symbol
260✔
217
                if Base.isdeprecated(mod_for_check, s)
384,877✔
218
                    return false
×
219
                elseif s === modname
384,877✔
220
                    return false # exclude `Main.Main.Main`, etc.
360✔
221
                elseif complete_modules_only && !completes_module(mod_for_check, s)
384,517✔
222
                    return false
69,263✔
223
                elseif is_main && s === :MainInclude
315,254✔
224
                    return false
100✔
225
                end
226
                return true
315,154✔
227
            end
228
        end
229
    elseif @isdefined(val) # looking for a property of an instance
124✔
230
        try
78✔
231
            for property in propertynames(val, false)
78✔
232
                # TODO: support integer arguments (#36872)
233
                if property isa Symbol && startswith(string(property), name)
130✔
234
                    push!(suggestions, PropertyCompletion(val, property))
114✔
235
                end
236
            end
130✔
237
        catch
2✔
238
        end
239
    elseif @isdefined(t) && field_completion_eligible(t)
46✔
240
        # Looking for a member of a type
241
        add_field_completions!(suggestions, name, t)
28✔
242
    end
243
    return suggestions
384✔
244
end
245

246
completes_module(mod::Module, x::Symbol) = isdefined(mod, x) && isa(getglobal(mod, x), Module)
70,055✔
247

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

265
const GENERIC_PROPERTYNAMES_METHOD = which(propertynames, (Any,))
266

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

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

292
const sorted_keywords = let
293
    keywords = map(string, Base.JuliaSyntax.Tokenize.kws)
294
    excluded = ("type", "doc", "var", "VERSION")
295
    filter!(∉(excluded), keywords)
296
    compound = ("abstract", "mutable", "primitive")
297
    filter!(∉(compound), keywords)
298
    push!(keywords, "abstract type", "mutable struct", "primitive type")
299
    # Register additional keywords, not in JuliaSyntax keywords
300
    push!(keywords, "ccall")
301
    sort!(keywords)
302
end
303

304
complete_keyword!(suggestions::Vector{Completion}, s::String) =
134✔
305
    complete_from_list!(suggestions, KeywordCompletion, sorted_keywords, s)
306

307
const sorted_keyvals = ["false", "true"]
308

309
complete_keyval!(suggestions::Vector{Completion}, s::String) =
134✔
310
    complete_from_list!(suggestions, KeyvalCompletion, sorted_keyvals, s)
311

312
function do_cmd_escape(s)
1✔
313
    return Base.escape_raw_string(Base.shell_escape_posixly(s), '`')
1✔
314
end
315
function do_shell_escape(s)
316
    return Base.shell_escape_posixly(s)
1,272✔
317
end
318
function do_string_escape(s)
319
    return escape_string(s, ('\"','$'))
410✔
320
end
321
function do_string_unescape(s)
115✔
322
    s = replace(s, "\\\$"=>"\$")
115✔
323
    try
115✔
324
        unescape_string(s)
115✔
325
    catch e
326
        e isa ArgumentError || rethrow()
7✔
327
        s # it is unlikely, but if it isn't a valid string, maybe it was a valid path, and just needs escape_string called?
7✔
328
    end
329
end
330

331
function joinpath_withsep(dir, path; dirsep)
2,894✔
332
    dir == "" && return path
1,447✔
333
    dir[end] == dirsep ? dir * path : dir * dirsep * path
801✔
334
end
335

336
const PATH_cache_lock = Base.ReentrantLock()
337
const PATH_cache = Set{String}()
338
PATH_cache_task::Union{Task,Nothing} = nothing
339
PATH_cache_condition::Union{Threads.Condition, Nothing} = nothing # used for sync in tests
340
next_cache_update::Float64 = 0.0
341
function maybe_spawn_cache_PATH()
11✔
342
    global PATH_cache_task, PATH_cache_condition, next_cache_update
11✔
343
    @lock PATH_cache_lock begin
11✔
344
        # Extract to local variables to enable flow-sensitive type inference for these global variables
345
        PATH_cache_task_local = PATH_cache_task
11✔
346
        PATH_cache_task_local isa Task && !istaskdone(PATH_cache_task_local) && return
11✔
347
        time() < next_cache_update && return
11✔
348
        PATH_cache_task = PATH_cache_task_local = Threads.@spawn begin
8✔
349
            try
4✔
350
                REPLCompletions.cache_PATH()
4✔
351
            finally
352
                @lock PATH_cache_lock begin
4✔
353
                    next_cache_update = time() + 10 # earliest next update can run is 10s after
4✔
354
                    PATH_cache_task = nothing # release memory when done
4✔
355
                    PATH_cache_condition_local = PATH_cache_condition
4✔
356
                    PATH_cache_condition_local !== nothing && notify(PATH_cache_condition_local)
4✔
357
                end
358
            end
359
        end
360
        Base.errormonitor(PATH_cache_task_local)
4✔
361
    end
362
end
363

364
# caches all reachable files in PATH dirs
365
function cache_PATH()
4✔
366
    path = get(ENV, "PATH", nothing)
4✔
367
    path isa String || return
4✔
368

369
    # Calling empty! on PATH_cache would be annoying for async typing hints as completions would temporarily disappear.
370
    # 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.
371
    this_PATH_cache = Set{String}()
4✔
372

373
    @debug "caching PATH files" PATH=path
4✔
374
    pathdirs = split(path, @static Sys.iswindows() ? ";" : ":")
4✔
375

376
    next_yield_time = time() + 0.01
4✔
377

378
    t = @elapsed for pathdir in pathdirs
4✔
379
        actualpath = try
26✔
380
            realpath(pathdir)
28✔
381
        catch ex
382
            ex isa Base.IOError || rethrow()
2✔
383
            # Bash doesn't expect every folder in PATH to exist, so neither shall we
384
            continue
2✔
385
        end
386

387
        if actualpath != pathdir && in(actualpath, pathdirs)
48✔
388
            # Remove paths which (after resolving links) are in the env path twice.
389
            # Many distros eg. point /bin to /usr/bin but have both in the env path.
UNCOV
390
            continue
×
391
        end
392

393
        path_entries = try
24✔
394
            _readdirx(pathdir)
25✔
395
        catch e
396
            # Bash allows dirs in PATH that can't be read, so we should as well.
397
            if isa(e, Base.IOError) || isa(e, Base.ArgumentError)
1✔
398
                continue
1✔
399
            else
400
                # We only handle IOError and ArgumentError here
401
                rethrow()
×
402
            end
403
        end
404
        for entry in path_entries
23✔
405
            # In a perfect world, we would filter on whether the file is executable
406
            # here, or even on whether the current user can execute the file in question.
407
            try
4,797✔
408
                if isfile(entry)
9,594✔
409
                    @lock PATH_cache_lock push!(PATH_cache, entry.name)
4,450✔
410
                    push!(this_PATH_cache, entry.name)
4,452✔
411
                end
412
            catch e
413
                # `isfile()` can throw in rare cases such as when probing a
414
                # symlink that points to a file within a directory we do not
415
                # have read access to.
416
                if isa(e, Base.IOError)
2✔
417
                    continue
2✔
418
                else
419
                    rethrow()
×
420
                end
421
            end
422
            if time() >= next_yield_time
4,795✔
423
                yield() # to avoid blocking typing when -t1
6✔
424
                next_yield_time = time() + 0.01
6✔
425
            end
426
        end
4,797✔
427
    end
428

429
    @lock PATH_cache_lock begin
4✔
430
        intersect!(PATH_cache, this_PATH_cache) # remove entries from PATH_cache that weren't found this time
4✔
431
    end
432

433
    @debug "caching PATH files took $t seconds" length(pathdirs) length(PATH_cache)
4✔
434
    return PATH_cache
4✔
435
end
436

437
function complete_path(path::AbstractString;
222✔
438
                       use_envpath=false,
439
                       shell_escape=false,
440
                       cmd_escape=false,
441
                       string_escape=false,
442
                       contract_user=false,
443
                       dirsep=Sys.iswindows() ? '\\' : '/')
444
    @assert !(shell_escape && string_escape)
111✔
445
    if Base.Sys.isunix() && occursin(r"^~(?:/|$)", path)
111✔
446
        # if the path is just "~", don't consider the expanded username as a prefix
447
        if path == "~"
×
448
            dir, prefix = homedir(), ""
×
449
        else
450
            dir, prefix = splitdir(homedir() * path[2:end])
×
451
        end
452
    else
453
        dir, prefix = splitdir(path)
111✔
454
    end
455
    entries = try
111✔
456
        if isempty(dir)
111✔
457
            _readdirx()
63✔
458
        elseif isdir(dir)
48✔
459
            _readdirx(dir)
44✔
460
        else
461
            return Completion[], dir, false
111✔
462
        end
463
    catch ex
464
        ex isa Base.IOError || rethrow()
×
465
        return Completion[], dir, false
×
466
    end
467

468
    matches = Set{String}()
107✔
469
    for entry in entries
107✔
470
        if startswith(entry.name, prefix)
18,607✔
471
            is_dir = try isdir(entry) catch ex; ex isa Base.IOError ? false : rethrow() end
3,284✔
472
            push!(matches, is_dir ? joinpath_withsep(entry.name, ""; dirsep) : entry.name)
2,722✔
473
        end
474
    end
18,607✔
475

476
    if use_envpath && isempty(dir)
107✔
477
        # Look for files in PATH as well. These are cached in `cache_PATH` in an async task to not block typing.
478
        # If we cannot get lock because its still caching just pass over this so that typing isn't laggy.
479
        maybe_spawn_cache_PATH() # only spawns if enough time has passed and the previous caching task has completed
11✔
480
        @lock PATH_cache_lock begin
11✔
481
            for file in PATH_cache
22✔
482
                startswith(file, prefix) && push!(matches, file)
1,536✔
483
            end
3,072✔
484
        end
485
    end
486

487
    matches = ((shell_escape ? do_shell_escape(s) : string_escape ? do_string_escape(s) : s) for s in matches)
107✔
488
    matches = ((cmd_escape ? do_cmd_escape(s) : s) for s in matches)
107✔
489
    matches = Completion[PathCompletion(contract_user ? contractuser(s) : s) for s in matches]
1,685✔
490
    return matches, dir, !isempty(matches)
107✔
491
end
492

493
function complete_path(path::AbstractString,
×
494
                       pos::Int;
495
                       use_envpath=false,
496
                       shell_escape=false,
497
                       string_escape=false,
498
                       contract_user=false)
499
    ## TODO: enable this depwarn once Pkg is fixed
500
    #Base.depwarn("complete_path with pos argument is deprecated because the return value [2] is incorrect to use", :complete_path)
501
    paths, dir, success = complete_path(path; use_envpath, shell_escape, string_escape, dirsep='/')
×
502

503
    if Base.Sys.isunix() && occursin(r"^~(?:/|$)", path)
×
504
        # if the path is just "~", don't consider the expanded username as a prefix
505
        if path == "~"
×
506
            dir, prefix = homedir(), ""
×
507
        else
508
            dir, prefix = splitdir(homedir() * path[2:end])
×
509
        end
510
    else
511
        dir, prefix = splitdir(path)
×
512
    end
513
    startpos = pos - lastindex(prefix) + 1
×
514
    Sys.iswindows() && map!(paths, paths) do c::PathCompletion
×
515
        # emulation for unnecessarily complicated return value, since / is a
516
        # perfectly acceptable path character which does not require quoting
517
        # but is required by Pkg's awkward parser handling
518
        return endswith(c.path, "/") ? PathCompletion(chop(c.path) * "\\\\") : c
×
519
    end
520
    return paths, startpos:pos, success
×
521
end
522

523
struct REPLCacheToken end
116,089✔
524

525
struct REPLInterpreter <: CC.AbstractInterpreter
526
    limit_aggressive_inference::Bool
527
    world::UInt
528
    inf_params::CC.InferenceParams
529
    opt_params::CC.OptimizationParams
530
    inf_cache::Vector{CC.InferenceResult}
531
    function REPLInterpreter(limit_aggressive_inference::Bool=false;
503✔
532
                             world::UInt = Base.get_world_counter(),
533
                             inf_params::CC.InferenceParams = CC.InferenceParams(;
534
                                 aggressive_constant_propagation=true),
535
                             opt_params::CC.OptimizationParams = CC.OptimizationParams(),
536
                             inf_cache::Vector{CC.InferenceResult} = CC.InferenceResult[])
537
        return new(limit_aggressive_inference, world, inf_params, opt_params, inf_cache)
491✔
538
    end
539
end
540
CC.InferenceParams(interp::REPLInterpreter) = interp.inf_params
353,124✔
541
CC.OptimizationParams(interp::REPLInterpreter) = interp.opt_params
182✔
542
CC.get_inference_world(interp::REPLInterpreter) = interp.world
561,223✔
543
CC.get_inference_cache(interp::REPLInterpreter) = interp.inf_cache
193,217✔
544
CC.cache_owner(::REPLInterpreter) = REPLCacheToken()
116,089✔
545

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

549
# REPLInterpreter doesn't need any sources to be cached, so discard them aggressively
550
CC.transform_result_for_cache(::REPLInterpreter, ::CC.InferenceResult, edges::Core.SimpleVector) = nothing
7,287✔
551

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

555
# `REPLInterpreter` aggressively resolves global bindings to enable reasonable completions
556
# for lines like `Mod.a.|` (where `|` is the cursor position).
557
# Aggressive binding resolution poses challenges for the inference cache validation
558
# (until https://github.com/JuliaLang/julia/issues/40399 is implemented).
559
# To avoid the cache validation issues, `REPLInterpreter` only allows aggressive binding
560
# resolution for top-level frame representing REPL input code and for child uncached frames
561
# that are constant propagated from the top-level frame ("repl-frame"s). This works, even if
562
# those global bindings are not constant and may be mutated in the future, since:
563
# a.) "repl-frame"s are never cached, and
564
# b.) mutable values are never observed by any cached frames.
565
#
566
# `REPLInterpreter` also aggressively concrete evaluate `:inconsistent` calls within
567
# "repl-frame" to provide reasonable completions for lines like `Ref(Some(42))[].|`.
568
# Aggressive concrete evaluation allows us to get accurate type information about complex
569
# expressions that otherwise can not be constant folded, in a safe way, i.e. it still
570
# doesn't evaluate effectful expressions like `pop!(xs)`.
571
# Similarly to the aggressive binding resolution, aggressive concrete evaluation doesn't
572
# present any cache validation issues because "repl-frame" is never cached.
573

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

579
function is_call_stack_uncached(sv::CC.InferenceState)
447,862✔
580
    CC.is_cached(sv) && return false
647,950✔
581
    parent = CC.frame_parent(sv)
913,749✔
582
    parent === nothing && return true
465,887✔
583
    return is_call_stack_uncached(parent::CC.InferenceState)
447,862✔
584
end
585

586
# aggressive global binding resolution within `repl_frame`
587
function CC.abstract_eval_globalref(interp::REPLInterpreter, g::GlobalRef, bailed::Bool,
127,103✔
588
                                    sv::CC.InferenceState)
589
    # Ignore saw_latestworld
590
    if (interp.limit_aggressive_inference ? is_repl_frame(sv) : is_call_stack_uncached(sv))
253,884✔
591
        partition = CC.abstract_eval_binding_partition!(interp, g, sv)
12,072✔
592
        if CC.is_defined_const_binding(CC.binding_kind(partition))
15,663✔
593
            return CC.RTEffects(Const(CC.partition_restriction(partition)), Union{}, CC.EFFECTS_TOTAL)
11,924✔
594
        else
595
            b = convert(Core.Binding, g)
148✔
596
            if CC.binding_kind(partition) == CC.PARTITION_KIND_GLOBAL && isdefined(b, :value)
148✔
597
                return CC.RTEffects(Const(b.value), Union{}, CC.EFFECTS_TOTAL)
134✔
598
            end
599
        end
600
        return CC.RTEffects(Union{}, UndefVarError, CC.EFFECTS_THROWS)
14✔
601
    end
602
    return @invoke CC.abstract_eval_globalref(interp::CC.AbstractInterpreter, g::GlobalRef, bailed::Bool,
115,031✔
603
                                              sv::CC.InferenceState)
604
end
605

606
# aggressive concrete evaluation for `:inconsistent` frames within `repl_frame`
607
function CC.concrete_eval_eligible(interp::REPLInterpreter, @nospecialize(f),
73,475✔
608
                                   result::CC.MethodCallResult, arginfo::CC.ArgInfo,
609
                                   sv::CC.InferenceState)
610
    if (interp.limit_aggressive_inference ? is_repl_frame(sv) : is_call_stack_uncached(sv))
146,782✔
611
        neweffects = CC.Effects(result.effects; consistent=CC.ALWAYS_TRUE)
5,973✔
612
        result = CC.MethodCallResult(result.rt, result.exct, neweffects, result.edge,
11,946✔
613
                                     result.edgecycle, result.edgelimited, result.call_result)
614
    end
615
    ret = @invoke CC.concrete_eval_eligible(interp::CC.AbstractInterpreter, f::Any,
146,950✔
616
                                            result::CC.MethodCallResult, arginfo::CC.ArgInfo,
617
                                            sv::CC.InferenceState)
618
    if ret === :semi_concrete_eval
73,475✔
619
        # while the base eligibility check probably won't permit semi-concrete evaluation
620
        # for `REPLInterpreter` (given it completely turns off optimization),
621
        # this ensures we don't inadvertently enter irinterp
622
        ret = :none
×
623
    end
624
    return ret
73,475✔
625
end
626

627
# allow constant propagation for mutable constants
628
function CC.const_prop_argument_heuristic(interp::REPLInterpreter, arginfo::CC.ArgInfo, sv::CC.InferenceState)
629
    if !interp.limit_aggressive_inference
72,393✔
630
        any(@nospecialize(a)->isa(a, Const), arginfo.argtypes) && return true # even if mutable
144,578✔
631
    end
632
    return @invoke CC.const_prop_argument_heuristic(interp::CC.AbstractInterpreter, arginfo::CC.ArgInfo, sv::CC.InferenceState)
206✔
633
end
634

635
# Perform some post-hoc mutation on lowered code, as expected by some abstract interpretation
636
# routines, especially for `:foreigncall` and `:cglobal`.
637
function resolve_toplevel_symbols!(src::Core.CodeInfo, mod::Module)
638
    @ccall jl_resolve_definition_effects_in_ir(
479✔
639
        #=jl_array_t *stmts=# src.code::Any,
640
        #=jl_module_t *m=# mod::Any,
641
        #=jl_svec_t *sparam_vals=# Core.svec()::Any,
642
        #=jl_value_t *binding_edge=# C_NULL::Ptr{Cvoid},
643
        #=int binding_effects=# 0::Int)::Cvoid
644
    return src
479✔
645
end
646

647
function construct_toplevel_mi(src::Core.CodeInfo, context_module::Module)
648
    resolve_toplevel_symbols!(src, context_module)
479✔
649
    return @ccall jl_method_instance_for_thunk(src::Any, context_module::Any)::Ref{Core.MethodInstance}
479✔
650
end
651

652
# lower `ex` and run type inference on the resulting top-level expression
653
function repl_eval_ex(@nospecialize(ex), context_module::Module; limit_aggressive_inference::Bool=false)
2,018✔
654
    expr_has_error(ex) && return nothing
1,534✔
655
    if (isexpr(ex, :toplevel) || isexpr(ex, :tuple)) && !isempty(ex.args)
1,994✔
656
        # get the inference result for the last expression
657
        ex = ex.args[end]
2✔
658
    end
659
    lwr = try
997✔
660
        Meta.lower(context_module, ex)
997✔
661
    catch # macro expansion failed, etc.
662
        return nothing
10✔
663
    end
664
    if lwr isa Symbol
987✔
665
        return isdefined(context_module, lwr) ? Const(getfield(context_module, lwr)) : nothing
306✔
666
    end
667
    lwr isa Expr || return Const(lwr) # `ex` is literal
867✔
668
    isexpr(lwr, :thunk) || return nothing # lowered to `Expr(:error, ...)` or similar
511✔
669
    src = lwr.args[1]::Core.CodeInfo
479✔
670

671
    mi = construct_toplevel_mi(src, context_module)
479✔
672
    interp = REPLInterpreter(limit_aggressive_inference)
479✔
673
    result = CC.InferenceResult(mi)
479✔
674
    frame = CC.InferenceState(result, src, #=cache=#:no, interp)
479✔
675

676
    # NOTE Use the fixed world here to make `REPLInterpreter` robust against
677
    #      potential invalidations of `Core.Compiler` methods.
678
    Base.invoke_in_world(COMPLETION_WORLD[], CC.typeinf, interp, frame)
479✔
679

680
    result = frame.result.result
479✔
681
    result === Union{} && return nothing # for whatever reason, callers expect this as the Bottom and/or Top type instead
479✔
682
    return result
463✔
683
end
684

685
# `COMPLETION_WORLD[]` will be initialized within `__init__`
686
# (to allow us to potentially remove REPL from the sysimage in the future).
687
# Note that inference from the `code_typed` call below will use the current world age
688
# rather than `typemax(UInt)`, since `Base.invoke_in_world` uses the current world age
689
# when the given world age is higher than the current one.
690
const COMPLETION_WORLD = Ref{UInt}(typemax(UInt))
691

692
# Generate code cache for `REPLInterpreter` now:
693
# This code cache will be available at the world of `COMPLETION_WORLD`,
694
# assuming no invalidation will happen before initializing REPL.
695
# Once REPL is loaded, `REPLInterpreter` will be resilient against future invalidations.
696
code_typed(CC.typeinf, (REPLInterpreter, CC.InferenceState))
697

698
# Method completion on function call expression that look like :(max(1))
699
MAX_METHOD_COMPLETIONS::Int = 40
700
function _complete_methods(ex_org::Expr, context_module::Module, shift::Bool)
269✔
701
    isempty(ex_org.args) && return 2, nothing, [], Set{Symbol}()
269✔
702
    # Desugar do block call into call with lambda
703
    if ex_org.head === :do && length(ex_org.args) >= 2
269✔
704
        ex_call = ex_org.args[1]
2✔
705
        ex_args = [x for x in ex_call.args if !(x isa Expr && x.head === :parameters)]
2✔
706
        ex_params = findfirst(x -> x isa Expr && x.head === :parameters, ex_call.args)
6✔
707
        new_args = [ex_args[1], ex_org.args[end], ex_args[2:end]...]
2✔
708
        ex_params !== nothing && push!(new_args, ex_call.args[ex_params])
2✔
709
        ex_org = Expr(:call, new_args...)
2✔
710
    end
711
    funct = repl_eval_ex(ex_org.args[1], context_module)
269✔
712
    funct === nothing && return 2, nothing, [], Set{Symbol}()
269✔
713
    funct = CC.widenconst(funct)
267✔
714
    args_ex, kwargs_ex, kwargs_flag = complete_methods_args(ex_org, context_module, true, true)
267✔
715
    return kwargs_flag, funct, args_ex, kwargs_ex
267✔
716
end
717

718
# cursor_pos: either :positional (complete either kwargs or positional) or :kwargs (beyond semicolon)
719
function complete_methods(ex_org::Expr, context_module::Module=Main, shift::Bool=false, cursor_pos::Symbol=:positional)
3✔
720
    kwargs_flag, funct, args_ex, kwargs_ex = _complete_methods(ex_org, context_module, shift)::Tuple{Int, Any, Vector{Any}, Set{Symbol}}
170✔
721
    out = Completion[]
167✔
722
    # Allow more arguments when cursor before semicolon, even if kwargs are present
723
    cursor_pos == :positional && kwargs_flag == 1 && (kwargs_flag = 0)
167✔
724
    kwargs_flag == 2 && return out # one of the kwargs is invalid
167✔
725
    kwargs_flag == 0 && push!(args_ex, Vararg{Any}) # allow more arguments if there is no semicolon
147✔
726
    complete_methods!(out, funct, args_ex, kwargs_ex, shift ? -2 : MAX_METHOD_COMPLETIONS, kwargs_flag == 1)
153✔
727
    return out
147✔
728
end
729

730
MAX_ANY_METHOD_COMPLETIONS::Int = 10
731

732
function accessible(mod::Module, private::Bool)
28✔
733
    bindings = IdSet{Any}(Core.Typeof(getglobal(mod, s)) for s in names(mod; all=private, imported=private, usings=private)
28✔
734
                   if !Base.isdeprecated(mod, s) && !startswith(string(s), '#') && !startswith(string(s), '@') && isdefined(mod, s))
735
    delete!(bindings, Module)
28✔
736
    return collect(bindings)
28✔
737
end
738

739
function complete_any_methods(ex_org::Expr, callee_module::Module, context_module::Module, moreargs::Bool, shift::Bool)
28✔
740
    out = Completion[]
28✔
741
    args_ex, kwargs_ex, kwargs_flag = try
28✔
742
        # this may throw, since we set default_any to false
743
        complete_methods_args(ex_org, context_module, false, false)
28✔
744
    catch ex
745
        ex isa ArgumentError || rethrow()
×
746
        return out
28✔
747
    end
748
    kwargs_flag == 2 && return out # one of the kwargs is invalid
28✔
749

750
    # moreargs determines whether to accept more args, independently of the presence of a
751
    # semicolon for the ".?(" syntax
752
    moreargs && push!(args_ex, Vararg{Any})
28✔
753

754
    for seen_name in accessible(callee_module, callee_module === context_module)
28✔
755
        complete_methods!(out, seen_name, args_ex, kwargs_ex, MAX_ANY_METHOD_COMPLETIONS, false)
3,058✔
756
    end
3,058✔
757

758
    if !shift
28✔
759
        # Filter out methods where all arguments are `Any`
760
        filter!(out) do c
4✔
761
            isa(c, TextCompletion) && return false
22✔
762
            isa(c, MethodCompletion) || return true
22✔
763
            sig = Base.unwrap_unionall(c.method.sig)::DataType
22✔
764
            return !all(@nospecialize(T) -> T === Any || T === Vararg{Any}, sig.parameters[2:end])
38✔
765
        end
766
    end
767

768
    return out
28✔
769
end
770

771
function detect_invalid_kwarg!(kwargs_ex::Vector{Symbol}, @nospecialize(x), kwargs_flag::Int, possible_splat::Bool)
772
    n = isexpr(x, :kw) ? x.args[1] : x
174✔
773
    if n isa Symbol
174✔
774
        push!(kwargs_ex, n)
152✔
775
        return kwargs_flag
152✔
776
    end
777
    possible_splat && isexpr(x, :...) && return kwargs_flag
22✔
778
    return 2 # The kwarg is invalid
14✔
779
end
780

781
function detect_args_kwargs(funargs::Vector{Any}, context_module::Module, default_any::Bool, broadcasting::Bool)
295✔
782
    args_ex = Any[]
295✔
783
    kwargs_ex = Symbol[]
295✔
784
    kwargs_flag = 0
295✔
785
    # kwargs_flag is:
786
    # * 0 if there is no semicolon and no invalid kwarg
787
    # * 1 if there is a semicolon and no invalid kwarg
788
    # * 2 if there are two semicolons or more, or if some kwarg is invalid, which
789
    #        means that it is not of the form "bar=foo", "bar" or "bar..."
790
    for i in (1+!broadcasting):length(funargs)
330✔
791
        ex = funargs[i]
496✔
792
        if isexpr(ex, :parameters)
496✔
793
            kwargs_flag = ifelse(kwargs_flag == 0, 1, 2) # there should be at most one :parameters
128✔
794
            for x in ex.args
128✔
795
                kwargs_flag = detect_invalid_kwarg!(kwargs_ex, x, kwargs_flag, true)
150✔
796
            end
130✔
797
        elseif isexpr(ex, :kw)
368✔
798
            kwargs_flag = detect_invalid_kwarg!(kwargs_ex, ex, kwargs_flag, false)
44✔
799
        else
800
            if broadcasting
324✔
801
                # handle broadcasting, but only handle number of arguments instead of
802
                # argument types
803
                push!(args_ex, Any)
10✔
804
            else
805
                argt = repl_eval_ex(ex, context_module)
314✔
806
                if argt !== nothing
314✔
807
                    push!(args_ex, CC.widenconst(argt))
240✔
808
                elseif default_any
74✔
809
                    push!(args_ex, Any)
74✔
810
                else
811
                    throw(ArgumentError("argument not found"))
×
812
                end
813
            end
814
        end
815
    end
732✔
816
    return args_ex, Set{Symbol}(kwargs_ex), kwargs_flag
295✔
817
end
818

819
is_broadcasting_expr(ex::Expr) = ex.head === :. && isexpr(ex.args[2], :tuple)
533✔
820

821
function complete_methods_args(ex::Expr, context_module::Module, default_any::Bool, allow_broadcasting::Bool)
822
    if allow_broadcasting && is_broadcasting_expr(ex)
295✔
823
        return detect_args_kwargs((ex.args[2]::Expr).args, context_module, default_any, true)
12✔
824
    end
825
    return detect_args_kwargs(ex.args, context_module, default_any, false)
283✔
826
end
827

828
function complete_methods!(out::Vector{Completion}, @nospecialize(funct), args_ex::Vector{Any}, kwargs_ex::Set{Symbol}, max_method_completions::Int, exact_nargs::Bool)
3,305✔
829
    # Input types and number of arguments
830
    t_in = Tuple{funct, args_ex...}
3,305✔
831
    m = Base._methods_by_ftype(t_in, nothing, max_method_completions, Base.get_world_counter(),
3,305✔
832
        #=ambig=# true, Ref(typemin(UInt)), Ref(typemax(UInt)), Ptr{Int32}(C_NULL))
833
    if !isa(m, Vector)
3,305✔
834
        push!(out, TextCompletion(sprint(Base.show_signature_function, funct) * "( too many methods, use SHIFT-TAB to show )"))
403✔
835
        return
403✔
836
    end
837
    for match in m
2,902✔
838
        # TODO: if kwargs_ex, filter out methods without kwargs?
839
        push!(out, MethodCompletion(match.spec_types, match.method))
156,572✔
840
    end
156,572✔
841
    # TODO: filter out methods with wrong number of arguments if `exact_nargs` is set
842
end
843

844
include("latex_symbols.jl")
845
include("emoji_symbols.jl")
846

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

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

859
function bslash_completions(string::String, pos::Int, hint::Bool=false)
773✔
860
    slashpos = something(findprev(isequal('\\'), string, pos), 0)
1,633✔
861
    if (something(findprev(in(bslash_separators), string, pos), 0) < slashpos &&
820✔
862
        !(1 < slashpos && (string[prevind(string, slashpos)]=='\\')))
863
        # latex / emoji symbol substitution
864
        s = string[slashpos:pos]
166✔
865
        latex = get(latex_symbols, s, "")
83✔
866
        if !isempty(latex) # complete an exact match
83✔
867
            return (true, (Completion[BslashCompletion(latex)], slashpos:pos, true))
39✔
868
        elseif occursin(subscript_regex, s)
44✔
869
            sub = map(c -> subscripts[c], s[3:end])
44✔
870
            return (true, (Completion[BslashCompletion(sub)], slashpos:pos, true))
6✔
871
        elseif occursin(superscript_regex, s)
38✔
872
            sup = map(c -> superscripts[c], s[3:end])
52✔
873
            return (true, (Completion[BslashCompletion(sup)], slashpos:pos, true))
6✔
874
        end
875
        emoji = get(emoji_symbols, s, "")
32✔
876
        if !isempty(emoji)
32✔
877
            return (true, (Completion[BslashCompletion(emoji)], slashpos:pos, true))
4✔
878
        end
879
        # return possible matches; these cannot be mixed with regular
880
        # Julian completions as only latex / emoji symbols contain the leading \
881
        symbol_dict = startswith(s, "\\:") ? emoji_symbols : latex_symbols
28✔
882
        namelist = Iterators.filter(k -> startswith(k, s), keys(symbol_dict))
68,890✔
883
        completions = Completion[BslashCompletion(name, "$(symbol_dict[name]) $name") for name in sort!(collect(namelist))]
28✔
884
        return (true, (completions, slashpos:pos, true))
28✔
885
    end
886
    return (false, (Completion[], 1:0, false))
690✔
887
end
888

889
# This needs to be a separate non-inlined function, see #19441
890
@noinline function find_dict_matches(identifier::AbstractDict, partial_key)
142✔
891
    matches = String[]
142✔
892
    for key in keys(identifier)
216✔
893
        rkey = repr(key)
1,682✔
894
        startswith(rkey,partial_key) && push!(matches,rkey)
1,682✔
895
    end
2,548✔
896
    return matches
142✔
897
end
898

899
# Provide completion for keyword arguments in function calls
900
# Returns true if the current argument must be a keyword because the cursor is beyond the semicolon
901
function complete_keyword_argument!(suggestions::Vector{Completion},
204✔
902
                                    ex::Expr, last_word::String,
903
                                    context_module::Module,
904
                                    arg_pos::Symbol; shift::Bool=false)
905
    kwargs_flag, funct, args_ex, kwargs_ex = _complete_methods(ex, context_module, true)::Tuple{Int, Any, Vector{Any}, Set{Symbol}}
102✔
906
    kwargs_flag == 2 && return false # one of the previous kwargs is invalid
102✔
907

908
    methods = Completion[]
100✔
909
    complete_methods!(methods, funct, Any[Vararg{Any}], kwargs_ex, -1, arg_pos == :kwargs)
100✔
910
    # TODO: use args_ex instead of Any[Vararg{Any}] and only provide kwarg completion for
911
    # method calls compatible with the current arguments.
912

913
    # For each method corresponding to the function call, provide completion suggestions
914
    # for each keyword that starts like the last word and that is not already used
915
    # previously in the expression. The corresponding suggestion is "kwname=".
916
    # If the keyword corresponds to an existing name, also include "kwname" as a suggestion
917
    # since the syntax "foo(; kwname)" is equivalent to "foo(; kwname=kwname)".
918
    kwargs = Set{String}()
100✔
919
    for m in methods
100✔
920
        # if MAX_METHOD_COMPLETIONS is hit a single TextCompletion is return by complete_methods! with an explanation
921
        # which can be ignored here
922
        m isa TextCompletion && continue
150,504✔
923
        m::MethodCompletion
150,504✔
924
        possible_kwargs = Base.kwarg_decl(m.method)
150,504✔
925
        current_kwarg_candidates = String[]
150,504✔
926
        for _kw in possible_kwargs
150,504✔
927
            kw = String(_kw)
14,134✔
928
            # HACK: Should consider removing current arg from AST.
929
            if !endswith(kw, "...") && startswith(kw, last_word) && (_kw ∉ kwargs_ex || kw == last_word)
14,150✔
930
                push!(current_kwarg_candidates, kw)
136✔
931
            end
932
        end
14,134✔
933
        union!(kwargs, current_kwarg_candidates)
150,504✔
934
    end
150,504✔
935

936
    for kwarg in kwargs
200✔
937
        push!(suggestions, KeywordArgumentCompletion(kwarg))
110✔
938
    end
220✔
939
    return kwargs_flag != 0 && arg_pos == :kwargs
100✔
940
end
941

942
function get_loading_candidates(pkgstarts::String, project_file::String)
2✔
943
    loading_candidates = String[]
2✔
944
    d = Base.parsed_toml(project_file)
2✔
945
    pkg = get(d, "name", nothing)::Union{String, Nothing}
2✔
946
    if pkg !== nothing && startswith(pkg, pkgstarts)
2✔
947
        push!(loading_candidates, pkg)
2✔
948
    end
949
    deps = get(d, "deps", nothing)::Union{Dict{String, Any}, Nothing}
2✔
950
    if deps !== nothing
2✔
951
        for (pkg, _) in deps
4✔
952
            startswith(pkg, pkgstarts) && push!(loading_candidates, pkg)
2✔
953
        end
2✔
954
    end
955
    return loading_candidates
2✔
956
end
957

958
function complete_loading_candidates!(suggestions::Vector{Completion}, s::String)
18✔
959
    for name in ("Core", "Base")
18✔
960
        startswith(name, s) && push!(suggestions, PackageCompletion(name))
36✔
961
    end
54✔
962

963
    # If there's no dot, we're in toplevel, so we should
964
    # also search for packages
965
    for dir in Base.load_path()
18✔
966
        if basename(dir) in Base.project_names && isfile(dir)
48✔
967
            for name in get_loading_candidates(s, dir)
2✔
968
                push!(suggestions, PackageCompletion(name))
4✔
969
            end
4✔
970
        end
971
        isdir(dir) || continue
24✔
972
        for entry in _readdirx(dir)
20✔
973
            pname = entry.name
1,120✔
974
            if pname[1] != '.' && pname != "METADATA" &&
2,240✔
975
                pname != "REQUIRE" && startswith(pname, s)
976
                # Valid file paths are
977
                #   <Mod>.jl
978
                #   <Mod>/src/<Mod>.jl
979
                #   <Mod>.jl/src/<Mod>.jl
980
                if isfile(entry)
524✔
981
                    endswith(pname, ".jl") && push!(suggestions,
×
982
                                                    PackageCompletion(pname[1:prevind(pname, end-2)]))
983
                else
984
                    mod_name = if endswith(pname, ".jl")
262✔
985
                        pname[1:prevind(pname, end-2)]
×
986
                    else
987
                        pname
524✔
988
                    end
989
                    if isfile(joinpath(entry, "src",
262✔
990
                                       "$mod_name.jl"))
991
                        push!(suggestions, PackageCompletion(mod_name))
260✔
992
                    end
993
                end
994
            end
995
        end
1,120✔
996
    end
24✔
997
end
998

999
function completions(string::String, pos::Int, context_module::Module=Main, shift::Bool=true, hint::Bool=false)
997✔
1000
    # filename needs to be string so macro can be evaluated
1001
    # TODO: JuliaSyntax version API here
1002
    node = parseall(CursorNode, string, ignore_errors=true, keep_parens=true, filename="none")
1,893✔
1003
    cur = @something seek_pos(node, pos) node
974✔
1004

1005
    # Back up before whitespace to get a more useful AST node.
1006
    pos_not_ws = findprev(!isspace, string, pos)
964✔
1007
    cur_not_ws = something(seek_pos(node, pos_not_ws), node)
1,918✔
1008

1009
    suggestions = Completion[]
964✔
1010
    sort_suggestions() = sort!(unique!(named_completion, suggestions), by=named_completion_completion)
1,466✔
1011

1012
    # Search for methods (requires tab press):
1013
    #   ?(x, y)TAB           lists methods you can call with these objects
1014
    #   ?(x, y TAB           lists methods that take these objects as the first two arguments
1015
    #   MyModule.?(x, y)TAB  restricts the search to names in MyModule
1016
    if !hint
964✔
1017
        cs = method_search(view(string, 1:pos), context_module, shift)
942✔
1018
        cs !== nothing && return cs
942✔
1019
    end
1020

1021
    # Complete keys in a Dict:
1022
    #   my_dict[ TAB
1023
    n, key, closed = find_ref_key(cur_not_ws, pos)
1,086✔
1024
    if n !== nothing
936✔
1025
        key::UnitRange{Int}
150✔
1026
        obj = dict_eval(Expr(n), context_module)
150✔
1027
        if obj !== nothing
150✔
1028
            # Skip leading whitespace inside brackets.
1029
            i = @something findnext(!isspace, string, first(key)) nextind(string, last(key))
152✔
1030
            key = intersect(i:last(key), 1:pos)
152✔
1031
            s = string[key]
274✔
1032
            matches = find_dict_matches(obj, s)
142✔
1033
            length(matches) == 1 && !closed && (matches[1] *= ']')
142✔
1034
            if length(matches) > 0
142✔
1035
                ret = Completion[DictCompletion(obj, match) for match in sort!(matches)]
126✔
1036
                return ret, key, true
126✔
1037
            end
1038
        end
1039
    end
1040

1041
    # Complete Cmd strings:
1042
    #   `fil TAB                 => `file
1043
    #   `file ~/exa TAB          => `file ~/example.txt
1044
    #   `file ~/example.txt TAB  => `file /home/user/example.txt
1045
    if (n = find_parent(cur, K"CmdString")) !== nothing
810✔
1046
        off = char_first(n) - 1
3✔
1047
        ret, r, success = shell_completions(string[char_range(n)], pos - off, hint, cmd_escape=true)
6✔
1048
        success && return ret, r .+ off, success
3✔
1049
    end
1050

1051
    # Complete ordinary strings:
1052
    #  "~/exa TAB         => "~/example.txt"
1053
    #  "~/example.txt TAB => "/home/user/example.txt"
1054
    r, closed = find_str(cur)
892✔
1055
    if r !== nothing
809✔
1056
        r = intersect(r, 1:pos)
83✔
1057
        s = do_string_unescape(string[r])
166✔
1058
        ret, success = complete_path_string(s, hint; string_escape=true,
83✔
1059
                                            dirsep=Sys.iswindows() ? '\\' : '/')
1060
        if length(ret) == 1 && !closed && close_path_completion(ret[1].path)
83✔
1061
            ret[1] = PathCompletion(ret[1].path * '"')
12✔
1062
        end
1063
        success && return ret, r, success
83✔
1064
    end
1065

1066
    # Backlash symbols:
1067
    #   \pi => π
1068
    # Comes after string completion so backslash escapes are not misinterpreted.
1069
    ok, ret = bslash_completions(string, pos)
763✔
1070
    ok && return ret
763✔
1071

1072
    # Don't fall back to symbol completion inside strings or comments.
1073
    inside_cmdstr = find_parent(cur, K"cmdstring") !== nothing
690✔
1074
    (kind(cur) in KSet"String Comment ErrorEofMultiComment" || inside_cmdstr) &&
1,368✔
1075
         return Completion[], 1:0, false
1076

1077
    n, arg_pos = find_prefix_call(cur_not_ws)
932✔
1078
    if n !== nothing
666✔
1079
        func = first(children_nt(n))
266✔
1080
        e = Expr(n)
266✔
1081
        # Remove arguments past the first parse error (allows unclosed parens)
1082
        if is_broadcasting_expr(e)
266✔
1083
            i = findfirst(x -> x isa Expr && x.head == :error, e.args[2].args)
48✔
1084
            i !== nothing && deleteat!(e.args[2].args, i:lastindex(e.args[2].args))
12✔
1085
        else
1086
            i = findfirst(x -> x isa Expr && x.head == :error, e.args)
2,134✔
1087
            i !== nothing && deleteat!(e.args, i:lastindex(e.args))
254✔
1088
        end
1089

1090
        # Method completion:
1091
        #   foo( TAB     => list of method signatures for foo
1092
        #   foo(x, TAB   => list of methods signatures for foo with x as first argument
1093
        if kind(cur_not_ws) in KSet"( , ;"
510✔
1094
            # Don't provide method completions unless the cursor is after: '(' ',' ';'
1095
            return complete_methods(e, context_module, shift, arg_pos), char_range(func), false
164✔
1096

1097
        # Keyword argument completion:
1098
        #   foo(ar TAB   => keyword arguments like `arg1=`
1099
        elseif kind(cur) == K"Identifier"
102✔
1100
            r = intersect(char_range(cur), 1:pos)
102✔
1101
            s = string[r]
204✔
1102
            # Return without adding more suggestions if kwargs only
1103
            complete_keyword_argument!(suggestions, e, s, context_module, arg_pos; shift) &&
102✔
1104
                return sort_suggestions(), r, true
1105
        end
1106
    end
1107

1108
    # Symbol completion
1109
    # TODO: Should completions replace the identifier at the cursor?
1110
    looks_like_ident = Base.isidentifier(@view string[intersect(char_range(cur), 1:pos)])
446✔
1111
    if cur.parent !== nothing && kind(cur.parent) === K"var"
862✔
1112
        # Replace the entire var"foo", but search using only "foo".
1113
        r = intersect(char_range(cur.parent), 1:pos)
6✔
1114
        r2 = char_range(children_nt(cur.parent)[1])
8✔
1115
        s = string[intersect(r2, 1:pos)]
10✔
1116
    elseif cur.parent !== nothing && kind(cur.parent) === K"macro_name"
850✔
1117
        # Include the `@`
1118
        r = intersect(prevind(string, char_first(cur)):char_last(cur), 1:pos)
24✔
1119
        s = string[r]
24✔
1120
    elseif looks_like_ident || kind(cur) in KSet"Bool Identifier @"
742✔
1121
        r = intersect(char_range(cur), 1:pos)
242✔
1122
        s = string[r]
242✔
1123
    else
1124
        r = nextind(string, pos):pos
328✔
1125
        s = ""
164✔
1126
    end
1127

1128
    complete_modules_only = false
436✔
1129
    prefix = node_prefix(cur, context_module)
436✔
1130
    comp_keywords = prefix === nothing && !isempty(s)
436✔
1131

1132
    # Complete loadable module names:
1133
    #   import Mod TAB
1134
    #   import Mod1, Mod2 TAB
1135
    #   using Mod TAB
1136
    if (n = find_parent(cur, K"importpath")) !== nothing
436✔
1137
        # Given input lines like `using Foo|`, `import Foo, Bar|` and `using Foo.Bar, Baz, |`:
1138
        # Let's look only for packages and modules we can reach from here
1139
        if prefix === nothing
64✔
1140
            complete_loading_candidates!(suggestions, s)
18✔
1141
            return sort_suggestions(), r, true
18✔
1142
        end
1143

1144
        # Allow completion for `import Mod.name` (where `name` is not a module)
1145
        complete_modules_only = prefix === nothing || kind(n.parent) === K"using"
92✔
1146
        comp_keywords = false
46✔
1147
    end
1148

1149
    if comp_keywords
418✔
1150
        complete_keyword!(suggestions, s)
134✔
1151
        complete_keyval!(suggestions, s)
134✔
1152
    end
1153

1154
    complete_symbol!(suggestions, prefix, s, context_module; complete_modules_only, shift)
418✔
1155
    return sort_suggestions(), r, true
418✔
1156
end
1157

1158
function close_path_completion(path)
1159
    path = expanduser(path)
32✔
1160
    path = do_string_unescape(path)
32✔
1161
    !Base.isaccessibledir(path)
32✔
1162
end
1163

1164
# Lowering can misbehave with nested error expressions.
1165
function expr_has_error(@nospecialize(e))
1,480✔
1166
    e isa Expr || return false
4,213✔
1167
    e.head === :error &&  return true
765✔
1168
    any(expr_has_error, e.args)
753✔
1169
end
1170

1171
# Is the cursor inside the square brackets of a ref expression?  If so, returns:
1172
# - The ref node
1173
# - The range of characters for the brackets
1174
# - A flag indicating if the closing bracket is present
1175
function find_ref_key(cur::CursorNode, pos::Int)
1176
    n = find_parent(cur, K"ref")
936✔
1177
    n !== nothing || return nothing, nothing, nothing
1,720✔
1178
    key, closed = find_delim(n, K"[", K"]")
304✔
1179
    if key === nothing || !(first(key) - 1 <= pos <= last(key))
304✔
1180
        return nothing, nothing, nothing
2✔
1181
    end
1182
    return n, key, closed
150✔
1183
end
1184

1185
# If the cursor is in a literal string, return the contents and char range
1186
# inside the quotes.  Ignores triple strings.
1187
function find_str(cur::CursorNode)
1188
    n = find_parent(cur, K"string")
809✔
1189
    n !== nothing || return nothing, nothing
1,535✔
1190
    find_delim(n, K"\"", K"\"")
83✔
1191
end
1192

1193
# Is the cursor directly inside of the arguments of a prefix call (no nested
1194
# expressions)?  If so, return:
1195
#   - The call node
1196
#   - Either :positional or :kwargs, if the cursor is before or after the `;`
1197
function find_prefix_call(cur::CursorNode)
1198
    n = cur.parent
666✔
1199
    n !== nothing || return nothing, nothing
676✔
1200
    is_call(n) = kind(n) in KSet"call dotcall" && is_prefix_call(n)
1,700✔
1201
    if kind(n) == K"parameters"
656✔
1202
        is_call(n.parent) || return nothing, nothing
120✔
1203
        n.parent, :kwargs
116✔
1204
    else
1205
        # Check that we are beyond the function name.
1206
        is_call(n) && cur.index > children_nt(n)[1].index || return nothing, nothing
926✔
1207
        n, :positional
150✔
1208
    end
1209
end
1210

1211
# If node is the field in a getfield-like expression, return the value
1212
# complete_symbol! should use as the prefix.
1213
function node_prefix(node::CursorNode, context_module::Module)
436✔
1214
    node.parent !== nothing || return nothing
446✔
1215
    p = node.parent
426✔
1216
    # In x.var"y", the parent is the "var" when the cursor is on "y".
1217
    kind(p) == K"var" && (p = p.parent)
852✔
1218
    kind(p) == K"macro_name" && (p = p.parent)
852✔
1219

1220
    # expr.node => expr
1221
    if kind(p) == K"."
852✔
1222
        n = children_nt(p)[1]
216✔
1223
        # Don't use prefix if we are the value
1224
        n !== node || return nothing
216✔
1225
        return Expr(n)
216✔
1226
    end
1227

1228
    if kind(p) == K"importpath"
420✔
1229
        if p.parent !== nothing && kind(p.parent) == K":" && p.index_nt > 1
128✔
1230
            # import A.B: C.node
1231
            chain = children_nt(children_nt(p.parent)[1])
24✔
1232
            append!(chain, children_nt(p)[1:end-1])
12✔
1233
        else
1234
            # import A.node
1235
            # import A.node: ...
1236
            chain = children_nt(p)[1:node.index_nt]
104✔
1237
            # Don't include the node under cursor in prefix unless it is `.`
1238
            kind(chain[end]) != K"." && deleteat!(chain, lastindex(chain))
52✔
1239
        end
1240
        length(chain) > 0 || return nothing
82✔
1241

1242
        # (:importpath :x :y :z) => (:. (:. :x :y) :z)
1243
        # (:importpath :. :. :z) => (:. (parentmodule context_module) :z)
1244
        if (i = findlast(x -> kind(x) == K".", chain)) !== nothing
194✔
1245
            init = context_module
38✔
1246
            for j in 2:i
38✔
1247
                init = parentmodule(init)
8✔
1248
            end
10✔
1249
            deleteat!(chain, 1:i)
76✔
1250
        else
1251
            # No leading `.`, init is the first element of the path
1252
            init = chain[1].val
8✔
1253
            deleteat!(chain, 1)
8✔
1254
        end
1255

1256
        # Convert the "chain" into nested (. a b) expressions.
1257
        all(x -> kind(x) == K"Identifier", chain) || return nothing
74✔
1258
        return foldl((x, y) -> Expr(:., x, Expr(:quote, y.val)), chain; init)
74✔
1259
    end
1260

1261
    nothing
146✔
1262
end
1263

1264
function dict_eval(@nospecialize(e), context_module::Module=Main)
150✔
1265
    objt = repl_eval_ex(e.args[1], context_module)
150✔
1266
    isa(objt, Core.Const) || return nothing
156✔
1267
    obj = objt.val
144✔
1268
    isa(obj, AbstractDict) || return nothing
146✔
1269
    (Base.haslength(obj) && length(obj)::Int < 1_000_000) || return nothing
142✔
1270
    return obj
142✔
1271
end
1272

1273
function method_search(partial::AbstractString, context_module::Module, shift::Bool)
942✔
1274
    rexm = match(r"([\w.]+.)?\?\((.*)$", partial)
942✔
1275
    if rexm !== nothing
942✔
1276
        # Get the module scope
1277
        callee_module = context_module
28✔
1278
        if !isnothing(rexm.captures[1])
54✔
1279
            modnames = map(Symbol, split(something(rexm.captures[1]), '.'))
52✔
1280
            for m in modnames
26✔
1281
                if isdefined(callee_module, m)
52✔
1282
                    callee_module = getfield(callee_module, m)
26✔
1283
                    if !isa(callee_module, Module)
26✔
1284
                        callee_module = context_module
×
1285
                        break
×
1286
                    end
1287
                end
1288
            end
52✔
1289
        end
1290
        moreargs = !endswith(rexm.captures[2], ')')
28✔
1291
        callstr = "_(" * rexm.captures[2]
28✔
1292
        if moreargs
28✔
1293
            callstr *= ')'
16✔
1294
        end
1295
        ex_org = Meta.parse(callstr, raise=false, depwarn=false)
28✔
1296
        if isa(ex_org, Expr)
28✔
1297
            pos_q = isnothing(rexm.captures[1]) ? 1 : sizeof(something(rexm.captures[1]))+1 # position after ?
54✔
1298
            return complete_any_methods(ex_org, callee_module::Module, context_module, moreargs, shift), (0:pos_q) .+ rexm.offset, false
28✔
1299
        end
1300
    end
1301
end
1302

1303
function shell_completions(str, pos, hint::Bool=false; cmd_escape::Bool=false)
100✔
1304
    # First parse everything up to the current position
1305
    scs = str[1:pos]
72✔
1306
    args, last_arg_start = try
36✔
1307
        Base.shell_parse(scs, true)::Tuple{Expr,Int}
39✔
1308
    catch ex
1309
        ex isa ArgumentError || ex isa ErrorException || rethrow()
6✔
1310
        return Completion[], 1:0, false
36✔
1311
    end
1312
    ex = args.args[end]::Expr
33✔
1313
    # Now look at the last thing we parsed
1314
    isempty(ex.args) && return Completion[], 1:0, false
33✔
1315
    # Concatenate every string fragment so dir\file completes correctly.
1316
    lastarg = all(x -> x isa String, ex.args) ? string(ex.args...) : ex.args[end]
99✔
1317

1318
    # As Base.shell_parse throws away trailing spaces (unless they are escaped),
1319
    # we need to special case here.
1320
    # If the last char was a space, but shell_parse ignored it search on "".
1321
    if isexpr(lastarg, :incomplete) || isexpr(lastarg, :error)
63✔
1322
        partial = str[last_arg_start:pos]
6✔
1323
        ret, range = completions(partial, lastindex(partial), Main, true, hint)
6✔
1324
        range = range .+ (last_arg_start - 1)
3✔
1325
        return ret, range, true
3✔
1326
    elseif endswith(scs, ' ') && !endswith(scs, "\\ ")
30✔
1327
        r = pos+1:pos
4✔
1328
        paths, dir, success = complete_path(""; use_envpath=false, shell_escape=!cmd_escape, cmd_escape, dirsep='/')
2✔
1329
        return paths, r, success
2✔
1330
    elseif all(@nospecialize(arg) -> arg isa AbstractString, ex.args)
68✔
1331
        # Join these and treat this as a path
1332
        path::String = join(ex.args)
26✔
1333
        r = last_arg_start:pos
26✔
1334

1335
        # Also try looking into the env path if the user wants to complete the first argument
1336
        use_envpath = length(args.args) < 2
26✔
1337

1338
        paths, success = complete_path_string(path, hint; use_envpath, shell_escape=!cmd_escape, cmd_escape, dirsep='/')
26✔
1339
        return paths, r, success
26✔
1340
    end
1341
    return Completion[], 1:0, false
2✔
1342
end
1343

1344
function complete_path_string(path, hint::Bool=false;
218✔
1345
                              shell_escape::Bool=false,
1346
                              cmd_escape::Bool=false,
1347
                              string_escape::Bool=false,
1348
                              dirsep='/',
1349
                              kws...)
1350
    # Expand "~" and remember if we expanded it.
1351
    local expanded
109✔
1352
    try
109✔
1353
        let p = expanduser(path)
109✔
1354
            expanded = path != p
108✔
1355
            path = p
109✔
1356
        end
1357
    catch e
1358
        e isa ArgumentError || rethrow()
1✔
1359
        expanded = false
1✔
1360
    end
1361

1362
    function escape(p)
998✔
1363
        shell_escape && (p = do_shell_escape(p))
889✔
1364
        string_escape && (p = do_string_escape(p))
889✔
1365
        cmd_escape && (p = do_cmd_escape(p))
889✔
1366
        p
889✔
1367
    end
1368

1369
    paths, dir, success = complete_path(path; dirsep, kws...)
109✔
1370

1371
    # Expand '~' if the user hits TAB after exhausting completions (either
1372
    # because we have found an existing file, or there is no such file).
1373
    full_path = try
109✔
1374
        ispath(path) || isempty(paths)
189✔
1375
    catch err
1376
        # access(2) errors unhandled by ispath: EACCES, EIO, ELOOP, ENAMETOOLONG
1377
        if err isa Base.IOError
1✔
1378
            false
1✔
1379
        elseif err isa Base.ArgumentError && occursin("embedded NULs", err.msg)
×
1380
            false
×
1381
        else
1382
            rethrow()
110✔
1383
        end
1384
    end
1385
    expanded && !hint && full_path && return Completion[PathCompletion(escape(path))], true
109✔
1386

1387
    # Expand '~' if the user hits TAB on a path ending in '/'.
1388
    expanded && (hint || path != dir * "/") && (dir = contractuser(dir))
106✔
1389
    local dir_for_paths = dir
105✔
1390

1391
    map!(paths) do c::PathCompletion
929✔
1392
        p = joinpath_withsep(dir_for_paths, c.path; dirsep)
885✔
1393
        PathCompletion(escape(p))
885✔
1394
    end
1395
    return sort!(paths, by=p->p.path), success
14,969✔
1396
end
1397

1398
function __init__()
15✔
1399
    COMPLETION_WORLD[] = Base.get_world_counter()
15✔
1400
    return nothing
15✔
1401
end
1402

1403
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