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

JuliaLang / julia / #37632

26 Sep 2023 06:44AM UTC coverage: 86.999% (-0.9%) from 87.914%
#37632

push

local

web-flow
inference: make `throw` block deoptimization concrete-eval friendly (#49235)

The deoptimization can sometimes destroy the effects analysis and
disable [semi-]concrete evaluation that is otherwise possible. This is
because the deoptimization was designed with the type domain
profitability in mind (#35982), and hasn't been adequately considering
the effects domain.

This commit makes the deoptimization aware of the effects domain more
and enables the `throw` block deoptimization only when the effects
already known to be ineligible for concrete-evaluation.

In our current effect system, `ALWAYS_FALSE`/`false` means that the
effect can not be refined to `ALWAYS_TRUE`/`true` anymore (unless given
user annotation later). Therefore we can enable the `throw` block
deoptimization without hindering the chance of concrete-evaluation when
any of the following conditions are met:
- `effects.consistent === ALWAYS_FALSE`
- `effects.effect_free === ALWAYS_FALSE`
- `effects.terminates === false`
- `effects.nonoverlayed === false`

Here are some numbers:

| Metric | master | this commit | #35982 reverted (set
`unoptimize_throw_blocks=false`) |

|-------------------------|-----------|-------------|--------------------------------------------|
| Base (seconds) | 15.579300 | 15.206645 | 15.296319 |
| Stdlibs (seconds) | 17.919013 | 17.667094 | 17.738128 |
| Total (seconds) | 33.499279 | 32.874737 | 33.035448 |
| Precompilation (seconds) | 49.967516 | 49.421121 | 49.999998 |
| First time `plot(rand(10,3))` [^1] | `2.476678 seconds (11.74 M
allocations)` | `2.430355 seconds (11.77 M allocations)` | `2.514874
seconds (11.64 M allocations)` |
| First time `solve(prob, QNDF())(5.0)` [^2] | `4.469492 seconds (15.32
M allocations)` | `4.499217 seconds (15.41 M allocations)` | `4.470772
seconds (15.38 M allocations)` |

[^1]: With disabling precompilation of Plots.jl.
[^2]: With disabling precompilation of OrdinaryDiffEq.

These numbers ma... (continued)

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

73407 of 84377 relevant lines covered (87.0%)

11275130.05 hits per line

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

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

3
module REPLCompletions
4

5
export completions, shell_completions, bslash_completions, completion_text
6

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

12
abstract type Completion end
13

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

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

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

26
struct PathCompletion <: Completion
27
    path::String
7,111✔
28
end
29

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

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

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

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

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

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

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

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

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

72
# interface definition
73
function Base.getproperty(c::Completion, name::Symbol)
3,027✔
74
    if name === :text
7,987,212✔
75
        return getfield(c, :text)::String
4✔
76
    elseif name === :keyword
7,985,501✔
77
        return getfield(c, :keyword)::String
1,707✔
78
    elseif name === :path
7,978,510✔
79
        return getfield(c, :path)::String
55,483✔
80
    elseif name === :parent
7,978,510✔
81
        return getfield(c, :parent)::Module
×
82
    elseif name === :mod
8,975✔
83
        return getfield(c, :mod)::String
7,969,535✔
84
    elseif name === :package
8,975✔
85
        return getfield(c, :package)::String
2,504✔
86
    elseif name === :property
6,471✔
87
        return getfield(c, :property)::Symbol
103✔
88
    elseif name === :field
6,368✔
89
        return getfield(c, :field)::Symbol
55✔
90
    elseif name === :method
5,597✔
91
        return getfield(c, :method)::Method
2,099✔
92
    elseif name === :bslash
433✔
93
        return getfield(c, :bslash)::String
5,164✔
94
    elseif name === :text
433✔
95
        return getfield(c, :text)::String
×
96
    elseif name === :key
433✔
97
        return getfield(c, :key)::String
128✔
98
    elseif name === :kwarg
305✔
99
        return getfield(c, :kwarg)::String
222✔
100
    end
101
    return getfield(c, name)
83✔
102
end
103

104
_completion_text(c::TextCompletion) = c.text
4✔
105
_completion_text(c::KeywordCompletion) = c.keyword
1,707✔
106
_completion_text(c::KeyvalCompletion) = c.keyval
83✔
107
_completion_text(c::PathCompletion) = c.path
6,970✔
108
_completion_text(c::ModuleCompletion) = c.mod
7,969,535✔
109
_completion_text(c::PackageCompletion) = c.package
2,504✔
110
_completion_text(c::PropertyCompletion) = sprint(Base.show_sym, c.property)
103✔
111
_completion_text(c::FieldCompletion) = sprint(Base.show_sym, c.field)
55✔
112
_completion_text(c::MethodCompletion) = repr(c.method)
705✔
113
_completion_text(c::BslashCompletion) = c.bslash
5,164✔
114
_completion_text(c::ShellCompletion) = c.text
×
115
_completion_text(c::DictCompletion) = c.key
128✔
116
_completion_text(c::KeywordArgumentCompletion) = c.kwarg*'='
222✔
117

118
completion_text(c) = _completion_text(c)::String
7,987,180✔
119

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

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

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

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

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

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

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

206
function add_field_completions!(suggestions::Vector{Completion}, name::String, @nospecialize(t))
21✔
207
    if isa(t, Union)
21✔
208
        add_field_completions!(suggestions, name, t.a)
2✔
209
        add_field_completions!(suggestions, name, t.b)
2✔
210
    elseif t isa DataType && t != Any
19✔
211
        # Check for cases like Type{typeof(+)}
212
        if Base.isType(t)
16✔
213
            t = typeof(t.parameters[1])
×
214
        end
215
        # Only look for fields if this is a concrete type
216
        if isconcretetype(t)
16✔
217
            fields = fieldnames(t)
16✔
218
            for field in fields
16✔
219
                isa(field, Symbol) || continue # Tuple type has ::Int field name
29✔
220
                s = string(field)
29✔
221
                if startswith(s, name)
58✔
222
                    push!(suggestions, FieldCompletion(t, field))
25✔
223
                end
224
            end
29✔
225
        end
226
    end
227
end
228

229
function complete_from_list(T::Type, list::Vector{String}, s::Union{String,SubString{String}})
1,253✔
230
    r = searchsorted(list, s)
1,253✔
231
    i = first(r)
1,253✔
232
    n = length(list)
1,253✔
233
    while i <= n && startswith(list[i],s)
2,600✔
234
        r = first(r):i
173✔
235
        i += 1
173✔
236
    end
173✔
237
    Completion[T(kw) for kw in list[r]]
1,253✔
238
end
239

240
const sorted_keywords = [
241
    "abstract type", "baremodule", "begin", "break", "catch", "ccall",
242
    "const", "continue", "do", "else", "elseif", "end", "export",
243
    "finally", "for", "function", "global", "if", "import",
244
    "let", "local", "macro", "module", "mutable struct",
245
    "primitive type", "quote", "return", "struct",
246
    "try", "using", "while"]
247

248
complete_keyword(s::Union{String,SubString{String}}) = complete_from_list(KeywordCompletion, sorted_keywords, s)
542✔
249

250
const sorted_keyvals = ["false", "true"]
251

252
complete_keyval(s::Union{String,SubString{String}}) = complete_from_list(KeyvalCompletion, sorted_keyvals, s)
711✔
253

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

281
    matches = Set{String}()
409✔
282
    for file in files
414✔
283
        if startswith(file, prefix)
79,614✔
284
            p = joinpath(dir, file)
6,750✔
285
            is_dir = try isdir(p) catch; false end
20,252✔
286
            push!(matches, is_dir ? joinpath(file, "") : file)
6,750✔
287
        end
288
    end
43,948✔
289

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

294
        for pathdir in pathdirs
18✔
295
            local actualpath
×
296
            try
228✔
297
                actualpath = realpath(pathdir)
260✔
298
            catch
299
                # Bash doesn't expect every folder in PATH to exist, so neither shall we
300
                continue
32✔
301
            end
302

303
            if actualpath != pathdir && in(actualpath,pathdirs)
244✔
304
                # Remove paths which (after resolving links) are in the env path twice.
305
                # Many distros eg. point /bin to /usr/bin but have both in the env path.
306
                continue
48✔
307
            end
308

309
            local filesinpath
×
310
            try
148✔
311
                filesinpath = readdir(pathdir)
149✔
312
            catch e
313
                # Bash allows dirs in PATH that can't be read, so we should as well.
314
                if isa(e, Base.IOError) || isa(e, Base.ArgumentError)
1✔
315
                    continue
1✔
316
                else
317
                    # We only handle IOError and ArgumentError here
318
                    rethrow()
×
319
                end
320
            end
321

322
            for file in filesinpath
195✔
323
                # In a perfect world, we would filter on whether the file is executable
324
                # here, or even on whether the current user can execute the file in question.
325
                if startswith(file, prefix) && isfile(joinpath(pathdir, file))
63,213✔
326
                    push!(matches, file)
589✔
327
                end
328
            end
34,645✔
329
        end
246✔
330
    end
331

332
    function do_escape(s)
409✔
333
        return shell_escape ? replace(s, r"(\s|\\)" => s"\\\0") :
10,767✔
334
               string_escape ? escape_string(s, ('\"','$')) :
335
               s
336
    end
337

338
    matchList = Completion[PathCompletion(do_escape(s)) for s in matches]
7,377✔
339
    startpos = pos - lastindex(do_escape(prefix)) + 1
543✔
340
    # The pos - lastindex(prefix) + 1 is correct due to `lastindex(prefix)-lastindex(prefix)==0`,
341
    # hence we need to add one to get the first index. This is also correct when considering
342
    # pos, because pos is the `lastindex` a larger string which `endswith(path)==true`.
343
    return matchList, startpos:pos, !isempty(matchList)
409✔
344
end
345

346
function complete_expanduser(path::AbstractString, r)
138✔
347
    expanded =
138✔
348
        try expanduser(path)
139✔
349
        catch e
350
            e isa ArgumentError || rethrow()
1✔
351
            path
139✔
352
        end
353
    return Completion[PathCompletion(expanded)], r, path != expanded
138✔
354
end
355

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

431
struct REPLInterpreterCache
432
    dict::IdDict{MethodInstance,CodeInstance}
×
433
end
434
REPLInterpreterCache() = REPLInterpreterCache(IdDict{MethodInstance,CodeInstance}())
×
435
const REPL_INTERPRETER_CACHE = REPLInterpreterCache()
436

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

451
struct REPLInterpreter <: CC.AbstractInterpreter
452
    repl_frame::CC.InferenceResult
453
    world::UInt
454
    inf_params::CC.InferenceParams
455
    opt_params::CC.OptimizationParams
456
    inf_cache::Vector{CC.InferenceResult}
457
    code_cache::REPLInterpreterCache
458
    function REPLInterpreter(repl_frame::CC.InferenceResult;
874✔
459
                             world::UInt = Base.get_world_counter(),
460
                             inf_params::CC.InferenceParams = CC.InferenceParams(;
461
                                unoptimize_throw_blocks=false),
462
                             opt_params::CC.OptimizationParams = CC.OptimizationParams(),
463
                             inf_cache::Vector{CC.InferenceResult} = CC.InferenceResult[],
464
                             code_cache::REPLInterpreterCache = get_code_cache())
465
        return new(repl_frame, world, inf_params, opt_params, inf_cache, code_cache)
437✔
466
    end
467
end
468
CC.InferenceParams(interp::REPLInterpreter) = interp.inf_params
125,895✔
469
CC.OptimizationParams(interp::REPLInterpreter) = interp.opt_params
×
470
CC.get_world_counter(interp::REPLInterpreter) = interp.world
34,664✔
471
CC.get_inference_cache(interp::REPLInterpreter) = interp.inf_cache
11,646✔
472
CC.code_cache(interp::REPLInterpreter) = CC.WorldView(interp.code_cache, CC.WorldRange(interp.world))
31,071✔
473
CC.get(wvc::CC.WorldView{REPLInterpreterCache}, mi::MethodInstance, default) = get(wvc.cache.dict, mi, default)
42,787✔
474
CC.getindex(wvc::CC.WorldView{REPLInterpreterCache}, mi::MethodInstance) = getindex(wvc.cache.dict, mi)
×
475
CC.haskey(wvc::CC.WorldView{REPLInterpreterCache}, mi::MethodInstance) = haskey(wvc.cache.dict, mi)
3,848✔
476
CC.setindex!(wvc::CC.WorldView{REPLInterpreterCache}, ci::CodeInstance, mi::MethodInstance) = setindex!(wvc.cache.dict, ci, mi)
3,848✔
477

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

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

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

502
is_repl_frame(interp::REPLInterpreter, sv::CC.InferenceState) = interp.repl_frame === sv.result
59,063✔
503

504
# aggressive global binding resolution within `repl_frame`
505
function CC.abstract_eval_globalref(interp::REPLInterpreter, g::GlobalRef,
×
506
                                    sv::CC.InferenceState)
507
    if is_repl_frame(interp, sv)
37,715✔
508
        if CC.isdefined_globalref(g)
583✔
509
            return Const(ccall(:jl_get_globalref_value, Any, (Any,), g))
578✔
510
        end
511
        return Union{}
5✔
512
    end
513
    return @invoke CC.abstract_eval_globalref(interp::CC.AbstractInterpreter, g::GlobalRef,
37,132✔
514
                                              sv::CC.InferenceState)
515
end
516

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

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

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

561
function resolve_toplevel_symbols!(mod::Module, src::Core.CodeInfo)
×
562
    newsrc = copy(src)
437✔
563
    @ccall jl_resolve_globals_in_ir(
437✔
564
        #=jl_array_t *stmts=# newsrc.code::Any,
565
        #=jl_module_t *m=# mod::Any,
566
        #=jl_svec_t *sparam_vals=# Core.svec()::Any,
567
        #=int binding_effects=# 0::Int)::Cvoid
568
    return newsrc
×
569
end
570

571
# lower `ex` and run type inference on the resulting top-level expression
572
function repl_eval_ex(@nospecialize(ex), context_module::Module)
1,176✔
573
    lwr = try
1,176✔
574
        Meta.lower(context_module, ex)
1,189✔
575
    catch # macro expansion failed, etc.
576
        return nothing
13✔
577
    end
578
    if lwr isa Symbol
1,163✔
579
        return isdefined(context_module, lwr) ? Const(getfield(context_module, lwr)) : nothing
626✔
580
    end
581
    lwr isa Expr || return Const(lwr) # `ex` is literal
628✔
582
    isexpr(lwr, :thunk) || return nothing # lowered to `Expr(:error, ...)` or similar
455✔
583
    src = lwr.args[1]::Core.CodeInfo
437✔
584

585
    # construct top-level `MethodInstance`
586
    mi = ccall(:jl_new_method_instance_uninit, Ref{Core.MethodInstance}, ());
437✔
587
    mi.specTypes = Tuple{}
437✔
588

589
    mi.def = context_module
437✔
590
    src = resolve_toplevel_symbols!(context_module, src)
437✔
591
    @atomic mi.uninferred = src
437✔
592

593
    result = CC.InferenceResult(mi)
437✔
594
    interp = REPLInterpreter(result)
874✔
595
    frame = CC.InferenceState(result, src, #=cache=#:no, interp)::CC.InferenceState
437✔
596

597
    # NOTE Use the fixed world here to make `REPLInterpreter` robust against
598
    #      potential invalidations of `Core.Compiler` methods.
599
    Base.invoke_in_world(COMPLETION_WORLD[], CC.typeinf, interp, frame)
437✔
600

601
    result = frame.result.result
437✔
602
    result === Union{} && return nothing # for whatever reason, callers expect this as the Bottom and/or Top type instead
437✔
603
    return result
430✔
604
end
605

606
# `COMPLETION_WORLD[]` will be initialized within `__init__`
607
# (to allow us to potentially remove REPL from the sysimage in the future).
608
# Note that inference from the `code_typed` call below will use the current world age
609
# rather than `typemax(UInt)`, since `Base.invoke_in_world` uses the current world age
610
# when the given world age is higher than the current one.
611
const COMPLETION_WORLD = Ref{UInt}(typemax(UInt))
612

613
# Generate code cache for `REPLInterpreter` now:
614
# This code cache will be available at the world of `COMPLETION_WORLD`,
615
# assuming no invalidation will happen before initializing REPL.
616
# Once REPL is loaded, `REPLInterpreter` will be resilient against future invalidations.
617
code_typed(CC.typeinf, (REPLInterpreter, CC.InferenceState))
618

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

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

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

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

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

682
    if !shift
13✔
683
        # Filter out methods where all arguments are `Any`
684
        filter!(out) do c
2✔
685
            isa(c, TextCompletion) && return false
11✔
686
            isa(c, MethodCompletion) || return true
11✔
687
            sig = Base.unwrap_unionall(c.method.sig)::DataType
11✔
688
            return !all(T -> T === Any || T === Vararg{Any}, sig.parameters[2:end])
19✔
689
        end
690
    end
691

692
    return out
13✔
693
end
694

695
function detect_invalid_kwarg!(kwargs_ex::Vector{Symbol}, @nospecialize(x), kwargs_flag::Int, possible_splat::Bool)
×
696
    n = isexpr(x, :kw) ? x.args[1] : x
57✔
697
    if n isa Symbol
55✔
698
        push!(kwargs_ex, n)
41✔
699
        return kwargs_flag
41✔
700
    end
701
    possible_splat && isexpr(x, :...) && return kwargs_flag
14✔
702
    return 2 # The kwarg is invalid
10✔
703
end
704

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

743
is_broadcasting_expr(ex::Expr) = ex.head === :. && isexpr(ex.args[2], :tuple)
1,774✔
744

745
function complete_methods_args(ex::Expr, context_module::Module, default_any::Bool, allow_broadcasting::Bool)
×
746
    if allow_broadcasting && is_broadcasting_expr(ex)
305✔
747
        return detect_args_kwargs((ex.args[2]::Expr).args, context_module, default_any, true)
6✔
748
    end
749
    return detect_args_kwargs(ex.args, context_module, default_any, false)
312✔
750
end
751

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

768
include("latex_symbols.jl")
769
include("emoji_symbols.jl")
770

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

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

783
# Aux function to detect whether we're right after a
784
# using or import keyword
785
function afterusing(string::String, startpos::Int)
1,465✔
786
    (isempty(string) || startpos == 0) && return false
1,465✔
787
    str = string[1:prevind(string,startpos)]
2,614✔
788
    isempty(str) && return false
1,462✔
789
    rstr = reverse(str)
1,152✔
790
    r = findfirst(r"\s(gnisu|tropmi)\b", rstr)
1,152✔
791
    r === nothing && return false
1,152✔
792
    fr = reverseind(str, last(r))
30✔
793
    return occursin(r"^\b(using|import)\s*((\w+[.])*\w+\s*,\s*)*$", str[fr:end])
30✔
794
end
795

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

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

842
function dict_identifier_key(str::String, tag::Symbol, context_module::Module=Main)
2,015✔
843
    if tag === :string
2,015✔
844
        str_close = str*"\""
158✔
845
    elseif tag === :cmd
1,856✔
846
        str_close = str*"`"
5✔
847
    else
848
        str_close = str
×
849
    end
850

851
    frange, end_of_identifier = find_start_brace(str_close, c_start='[', c_end=']')
2,014✔
852
    isempty(frange) && return (nothing, nothing, nothing)
2,014✔
853
    obj = context_module
×
854
    for name in split(str[frange[1]:end_of_identifier], '.')
104✔
855
        Base.isidentifier(name) || return (nothing, nothing, nothing)
163✔
856
        sym = Symbol(name)
121✔
857
        isdefined(obj, sym) || return (nothing, nothing, nothing)
121✔
858
        obj = getfield(obj, sym)
121✔
859
    end
204✔
860
    (isa(obj, AbstractDict) && length(obj)::Int < 1_000_000) || return (nothing, nothing, nothing)
84✔
861
    begin_of_key = something(findnext(!isspace, str, nextind(str, end_of_identifier) + 1), # +1 for [
158✔
862
                             lastindex(str)+1)
863
    return (obj::AbstractDict, str[begin_of_key:end], begin_of_key)
82✔
864
end
865

866
# This needs to be a separate non-inlined function, see #19441
867
@noinline function find_dict_matches(identifier::AbstractDict, partial_key)
81✔
868
    matches = String[]
81✔
869
    for key in keys(identifier)
124✔
870
        rkey = repr(key)
940✔
871
        startswith(rkey,partial_key) && push!(matches,rkey)
1,598✔
872
    end
1,462✔
873
    return matches
81✔
874
end
875

876
# Identify an argument being completed in a method call. If the argument is empty, method
877
# suggestions will be provided instead of argument completions.
878
function identify_possible_method_completion(partial, last_idx)
2,594✔
879
    fail = 0:-1, Expr(:nothing), 0:-1, 0
2,594✔
880

881
    # First, check that the last punctuation is either ',', ';' or '('
882
    idx_last_punct = something(findprev(x -> ispunct(x) && x != '_' && x != '!', partial, last_idx), 0)::Int
18,482✔
883
    idx_last_punct == 0 && return fail
2,594✔
884
    last_punct = partial[idx_last_punct]
4,204✔
885
    last_punct == ',' || last_punct == ';' || last_punct == '(' || return fail
3,994✔
886

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

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

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

901
    # `wordrange` is the position of the last argument to complete
902
    wordrange = nextind(partial, before_last_word_start):last_idx
620✔
903
    return frange, ex, wordrange, method_name_end
482✔
904
end
905

906
# Provide completion for keyword arguments in function calls
907
function complete_keyword_argument(partial, last_idx, context_module)
1,634✔
908
    frange, ex, wordrange, = identify_possible_method_completion(partial, last_idx)
1,634✔
909
    fail = Completion[], 0:-1, frange
1,634✔
910
    ex.head === :call || is_broadcasting_expr(ex) || return fail
3,100✔
911

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

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

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

940
    suggestions = Completion[KeywordArgumentCompletion(kwarg) for kwarg in kwargs]
216✔
941
    append!(suggestions, complete_symbol(nothing, last_word, Returns(true), context_module))
169✔
942
    append!(suggestions, complete_keyval(last_word))
169✔
943

944
    return sort!(suggestions, by=completion_text), wordrange
169✔
945
end
946

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

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

1006
                ex = Meta.parse(lookup_name, raise=false, depwarn=false)
545✔
1007
            end
1008
            isexpr(ex, :incomplete) && (ex = nothing)
873✔
1009
        end
1010
    end
1011
    append!(suggestions, complete_symbol(ex, name, ffunc, context_module))
1,468✔
1012
    return sort!(unique(suggestions), by=completion_text), (dotpos+1):pos, true
1,468✔
1013
end
1014

1015
function completions(string::String, pos::Int, context_module::Module=Main, shift::Bool=true)
2,366✔
1016
    # First parse everything up to the current position
1017
    partial = string[1:pos]
4,391✔
1018
    inc_tag = Base.incomplete_tag(Meta.parse(partial, raise=false, depwarn=false))
2,026✔
1019

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

1050
    # if completing a key in a Dict
1051
    identifier, partial_key, loc = dict_identifier_key(partial, inc_tag, context_module)
2,094✔
1052
    if identifier !== nothing
2,013✔
1053
        matches = find_dict_matches(identifier, partial_key)
81✔
1054
        length(matches)==1 && (lastindex(string) <= pos || string[nextind(string,pos)] != ']') && (matches[1]*=']')
85✔
1055
        length(matches)>0 && return Completion[DictCompletion(identifier, match) for match in sort!(matches)], loc::Int:pos, true
81✔
1056
    end
1057

1058
    ffunc = Returns(true)
×
1059
    suggestions = Completion[]
1,948✔
1060

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

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

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

1085
        paths, r, success = complete_path(replace(string[r], r"\\ " => " "), pos,
2✔
1086
                                          shell_escape=true)
1087

1088
        return sort!(paths, by=p->p.path), r, success
1✔
1089
    elseif inc_tag === :string
1,944✔
1090
        # Find first non-escaped quote
1091
        m = match(r"\"(?!\\)", reverse(partial))
137✔
1092
        startpos = nextind(partial, reverseind(partial, m.offset))
274✔
1093
        r = startpos:pos
159✔
1094

1095
        expanded = complete_expanduser(string[r], r)
252✔
1096
        expanded[3] && return expanded  # If user expansion available, return it
137✔
1097

1098
        path_prefix = try
135✔
1099
            unescape_string(replace(string[r], "\\\$"=>"\$", "\\\""=>"\""))
248✔
1100
        catch
1101
            nothing
135✔
1102
        end
1103
        if !isnothing(path_prefix)
270✔
1104
            paths, r, success = complete_path(path_prefix, pos, string_escape=true)
135✔
1105

1106
            if close_path_completion(string, startpos, r, paths, pos)
135✔
1107
                paths[1] = PathCompletion((paths[1]::PathCompletion).path * "\"")
5✔
1108
            end
1109

1110
            # Fallthrough allowed so that Latex symbols can be completed in strings
1111
            success && return sort!(paths, by=p->p.path), r, success
48,627✔
1112
        end
1113
    end
1114

1115
    ok, ret = bslash_completions(string, pos)
1,872✔
1116
    ok && return ret
1,872✔
1117

1118
    # Make sure that only bslash_completions is working on strings
1119
    inc_tag === :string && return Completion[], 0:-1, false
1,829✔
1120
    if inc_tag === :other
1,773✔
1121
        frange, ex, wordrange, method_name_end = identify_possible_method_completion(partial, pos)
960✔
1122
        if last(frange) != -1 && all(isspace, @view partial[wordrange]) # no last argument to complete
960✔
1123
            if ex.head === :call
138✔
1124
                return complete_methods(ex, context_module, shift), first(frange):method_name_end, false
135✔
1125
            elseif is_broadcasting_expr(ex)
3✔
1126
                return complete_methods(ex, context_module, shift), first(frange):(method_name_end - 1), false
3✔
1127
            end
1128
        end
1129
    elseif inc_tag === :comment
813✔
1130
        return Completion[], 0:-1, false
1✔
1131
    end
1132

1133
    # Check whether we can complete a keyword argument in a function call
1134
    kwarg_completion, wordrange = complete_keyword_argument(partial, pos, context_module)
3,099✔
1135
    isempty(wordrange) || return kwarg_completion, wordrange, !isempty(kwarg_completion)
1,803✔
1136

1137
    dotpos = something(findprev(isequal('.'), string, pos), 0)
2,246✔
1138
    startpos = nextind(string, something(findprev(in(non_identifier_chars), string, pos), 0))
2,615✔
1139
    # strip preceding ! operator
1140
    if (m = match(r"\G\!+", partial, startpos)) isa RegexMatch
1,465✔
1141
        startpos += length(m.match)
2✔
1142
    end
1143

1144
    name = string[max(startpos, dotpos+1):pos]
2,564✔
1145
    comp_keywords = !isempty(name) && startpos > dotpos
1,465✔
1146
    if afterusing(string, startpos)
1,465✔
1147
        # We're right after using or import. Let's look only for packages
1148
        # and modules we can reach from here
1149

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

1188
    startpos == 0 && (pos = -1)
1,465✔
1189
    dotpos < startpos && (dotpos = startpos - 1)
1,465✔
1190
    return complete_identifiers!(suggestions, ffunc, context_module, string,
1,465✔
1191
        name, pos, dotpos, startpos, comp_keywords)
1192
end
1193

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

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

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

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

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

1245
function __init__()
10✔
1246
    Base.Experimental.register_error_hint(UndefVarError_hint, UndefVarError)
10✔
1247
    COMPLETION_WORLD[] = Base.get_world_counter()
10✔
1248
    nothing
10✔
1249
end
1250

1251
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

© 2025 Coveralls, Inc