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

JuliaLang / julia / #37527

pending completion
#37527

push

local

web-flow
make `IRShow.method_name` inferrable (#49607)

18 of 18 new or added lines in 3 files covered. (100.0%)

68710 of 81829 relevant lines covered (83.97%)

33068903.12 hits per line

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

1.37
/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
16
end
17

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

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

26
struct ModuleCompletion <: Completion
27
    parent::Module
28
    mod::String
29
end
30

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

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

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

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

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

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

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

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

68
# interface definition
69
function Base.getproperty(c::Completion, name::Symbol)
×
70
    if name === :text
×
71
        return getfield(c, :text)::String
×
72
    elseif name === :keyword
×
73
        return getfield(c, :keyword)::String
×
74
    elseif name === :path
×
75
        return getfield(c, :path)::String
×
76
    elseif name === :parent
×
77
        return getfield(c, :parent)::Module
×
78
    elseif name === :mod
×
79
        return getfield(c, :mod)::String
×
80
    elseif name === :package
×
81
        return getfield(c, :package)::String
×
82
    elseif name === :property
×
83
        return getfield(c, :property)::Symbol
×
84
    elseif name === :field
×
85
        return getfield(c, :field)::Symbol
×
86
    elseif name === :method
×
87
        return getfield(c, :method)::Method
×
88
    elseif name === :bslash
×
89
        return getfield(c, :bslash)::String
×
90
    elseif name === :text
×
91
        return getfield(c, :text)::String
×
92
    elseif name === :key
×
93
        return getfield(c, :key)::String
×
94
    elseif name === :kwarg
×
95
        return getfield(c, :kwarg)::String
×
96
    end
97
    return getfield(c, name)
×
98
end
99

100
_completion_text(c::TextCompletion) = c.text
×
101
_completion_text(c::KeywordCompletion) = c.keyword
×
102
_completion_text(c::PathCompletion) = c.path
×
103
_completion_text(c::ModuleCompletion) = c.mod
×
104
_completion_text(c::PackageCompletion) = c.package
×
105
_completion_text(c::PropertyCompletion) = sprint(Base.show_sym, c.property)
×
106
_completion_text(c::FieldCompletion) = sprint(Base.show_sym, c.field)
×
107
_completion_text(c::MethodCompletion) = repr(c.method)
×
108
_completion_text(c::BslashCompletion) = c.bslash
×
109
_completion_text(c::ShellCompletion) = c.text
×
110
_completion_text(c::DictCompletion) = c.key
×
111
_completion_text(c::KeywordArgumentCompletion) = c.kwarg*'='
×
112

113
completion_text(c) = _completion_text(c)::String
×
114

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

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

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

132
function filtered_mod_names(ffunc::Function, mod::Module, name::AbstractString, all::Bool = false, imported::Bool = false)
×
133
    ssyms = names(mod, all = all, imported = imported)
×
134
    filter!(ffunc, ssyms)
×
135
    macros = filter(x -> startswith(String(x), "@" * name), ssyms)
×
136
    syms = String[sprint((io,s)->Base.show_sym(io, s; allow_macroname=true), s) for s in ssyms if completes_global(String(s), name)]
×
137
    appendmacro!(syms, macros, "_str", "\"")
×
138
    appendmacro!(syms, macros, "_cmd", "`")
×
139
    return [ModuleCompletion(mod, sym) for sym in syms]
×
140
end
141

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

146
    lookup_module = true
×
147
    t = Union{}
×
148
    val = nothing
×
149
    if ex !== nothing
×
150
        res = repl_eval_ex(ex, context_module)
×
151
        res === nothing && return Completion[]
×
152
        if res isa Const
×
153
            val = res.val
×
154
            if isa(val, Module)
×
155
                mod = val
×
156
                lookup_module = true
×
157
            else
158
                lookup_module = false
×
159
                t = typeof(val)
×
160
            end
161
        else
162
            lookup_module = false
×
163
            t = CC.widenconst(res)
×
164
        end
165
    end
166

167
    suggestions = Completion[]
×
168
    if lookup_module
×
169
        # We will exclude the results that the user does not want, as well
170
        # as excluding Main.Main.Main, etc., because that's most likely not what
171
        # the user wants
172
        p = let mod=mod, modname=nameof(mod)
×
173
            s->(!Base.isdeprecated(mod, s) && s != modname && ffunc(mod, s)::Bool)
×
174
        end
175
        # Looking for a binding in a module
176
        if mod == context_module
×
177
            # Also look in modules we got through `using`
178
            mods = ccall(:jl_module_usings, Any, (Any,), context_module)::Vector
×
179
            for m in mods
×
180
                append!(suggestions, filtered_mod_names(p, m::Module, name))
×
181
            end
×
182
            append!(suggestions, filtered_mod_names(p, mod, name, true, true))
×
183
        else
184
            append!(suggestions, filtered_mod_names(p, mod, name, true, false))
×
185
        end
186
    elseif val !== nothing # looking for a property of an instance
×
187
        for property in propertynames(val, false)
×
188
            # TODO: support integer arguments (#36872)
189
            if property isa Symbol && startswith(string(property), name)
×
190
                push!(suggestions, PropertyCompletion(val, property))
×
191
            end
192
        end
×
193
    else
194
        # Looking for a member of a type
195
        if t isa DataType && t != Any
×
196
            # Check for cases like Type{typeof(+)}
197
            if Base.isType(t)
×
198
                t = typeof(t.parameters[1])
×
199
            end
200
            # Only look for fields if this is a concrete type
201
            if isconcretetype(t)
×
202
                fields = fieldnames(t)
×
203
                for field in fields
×
204
                    isa(field, Symbol) || continue # Tuple type has ::Int field name
×
205
                    s = string(field)
×
206
                    if startswith(s, name)
×
207
                        push!(suggestions, FieldCompletion(t, field))
×
208
                    end
209
                end
×
210
            end
211
        end
212
    end
213
    suggestions
×
214
end
215

216
const sorted_keywords = [
217
    "abstract type", "baremodule", "begin", "break", "catch", "ccall",
218
    "const", "continue", "do", "else", "elseif", "end", "export", "false",
219
    "finally", "for", "function", "global", "if", "import",
220
    "let", "local", "macro", "module", "mutable struct",
221
    "primitive type", "quote", "return", "struct",
222
    "true", "try", "using", "while"]
223

224
function complete_keyword(s::Union{String,SubString{String}})
×
225
    r = searchsorted(sorted_keywords, s)
×
226
    i = first(r)
×
227
    n = length(sorted_keywords)
×
228
    while i <= n && startswith(sorted_keywords[i],s)
×
229
        r = first(r):i
×
230
        i += 1
×
231
    end
×
232
    Completion[KeywordCompletion(kw) for kw in sorted_keywords[r]]
×
233
end
234

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

259
    matches = Set{String}()
×
260
    for file in files
×
261
        if startswith(file, prefix)
×
262
            id = try isdir(joinpath(dir, file)) catch; false end
×
263
            # joinpath is not used because windows needs to complete with double-backslash
264
            push!(matches, id ? file * (@static Sys.iswindows() ? "\\\\" : "/") : file)
×
265
        end
266
    end
×
267

268
    if use_envpath && length(dir) == 0
×
269
        # Look for files in PATH as well
270
        local pathdirs = split(ENV["PATH"], @static Sys.iswindows() ? ";" : ":")
×
271

272
        for pathdir in pathdirs
×
273
            local actualpath
×
274
            try
×
275
                actualpath = realpath(pathdir)
×
276
            catch
277
                # Bash doesn't expect every folder in PATH to exist, so neither shall we
278
                continue
×
279
            end
280

281
            if actualpath != pathdir && in(actualpath,pathdirs)
×
282
                # Remove paths which (after resolving links) are in the env path twice.
283
                # Many distros eg. point /bin to /usr/bin but have both in the env path.
284
                continue
×
285
            end
286

287
            local filesinpath
×
288
            try
×
289
                filesinpath = readdir(pathdir)
×
290
            catch e
291
                # Bash allows dirs in PATH that can't be read, so we should as well.
292
                if isa(e, Base.IOError) || isa(e, Base.ArgumentError)
×
293
                    continue
×
294
                else
295
                    # We only handle IOError and ArgumentError here
296
                    rethrow()
×
297
                end
298
            end
299

300
            for file in filesinpath
×
301
                # In a perfect world, we would filter on whether the file is executable
302
                # here, or even on whether the current user can execute the file in question.
303
                if startswith(file, prefix) && isfile(joinpath(pathdir, file))
×
304
                    push!(matches, file)
×
305
                end
306
            end
×
307
        end
×
308
    end
309

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

318
function complete_expanduser(path::AbstractString, r)
×
319
    expanded =
×
320
        try expanduser(path)
×
321
        catch e
322
            e isa ArgumentError || rethrow()
×
323
            path
×
324
        end
325
    return Completion[PathCompletion(expanded)], r, path != expanded
×
326
end
327

328
# Returns a range that includes the method name in front of the first non
329
# closed start brace from the end of the string.
330
function find_start_brace(s::AbstractString; c_start='(', c_end=')')
×
331
    braces = 0
×
332
    r = reverse(s)
×
333
    i = firstindex(r)
×
334
    in_single_quotes = false
×
335
    in_double_quotes = false
×
336
    in_back_ticks = false
×
337
    in_comment = 0
×
338
    while i <= ncodeunits(r)
×
339
        c, i = iterate(r, i)
×
340
        if c == '#' && i <= ncodeunits(r) && iterate(r, i)[1] == '='
×
341
            c, i = iterate(r, i) # consume '='
×
342
            new_comments = 1
×
343
            # handle #=#=#=#, by counting =# pairs
344
            while i <= ncodeunits(r) && iterate(r, i)[1] == '#'
×
345
                c, i = iterate(r, i) # consume '#'
×
346
                iterate(r, i)[1] == '=' || break
×
347
                c, i = iterate(r, i) # consume '='
×
348
                new_comments += 1
×
349
            end
×
350
            if c == '='
×
351
                in_comment += new_comments
×
352
            else
353
                in_comment -= new_comments
×
354
            end
355
        elseif !in_single_quotes && !in_double_quotes && !in_back_ticks && in_comment == 0
×
356
            if c == c_start
×
357
                braces += 1
×
358
            elseif c == c_end
×
359
                braces -= 1
×
360
            elseif c == '\''
×
361
                in_single_quotes = true
×
362
            elseif c == '"'
×
363
                in_double_quotes = true
×
364
            elseif c == '`'
×
365
                in_back_ticks = true
×
366
            end
367
        else
368
            if in_single_quotes &&
×
369
                c == '\'' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
370
                in_single_quotes = false
×
371
            elseif in_double_quotes &&
×
372
                c == '"' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
373
                in_double_quotes = false
×
374
            elseif in_back_ticks &&
×
375
                c == '`' && i <= ncodeunits(r) && iterate(r, i)[1] != '\\'
376
                in_back_ticks = false
×
377
            elseif in_comment > 0 &&
×
378
                c == '=' && i <= ncodeunits(r) && iterate(r, i)[1] == '#'
379
                # handle =#=#=#=, by counting #= pairs
380
                c, i = iterate(r, i) # consume '#'
×
381
                old_comments = 1
×
382
                while i <= ncodeunits(r) && iterate(r, i)[1] == '='
×
383
                    c, i = iterate(r, i) # consume '='
×
384
                    iterate(r, i)[1] == '#' || break
×
385
                    c, i = iterate(r, i) # consume '#'
×
386
                    old_comments += 1
×
387
                end
×
388
                if c == '#'
×
389
                    in_comment -= old_comments
×
390
                else
391
                    in_comment += old_comments
×
392
                end
393
            end
394
        end
395
        braces == 1 && break
×
396
    end
×
397
    braces != 1 && return 0:-1, -1
×
398
    method_name_end = reverseind(s, i)
×
399
    startind = nextind(s, something(findprev(in(non_identifier_chars), s, method_name_end), 0))::Int
×
400
    return (startind:lastindex(s), method_name_end)
×
401
end
402

403
struct REPLInterpreterCache
404
    dict::IdDict{MethodInstance,CodeInstance}
405
end
406
REPLInterpreterCache() = REPLInterpreterCache(IdDict{MethodInstance,CodeInstance}())
×
407
const REPL_INTERPRETER_CACHE = REPLInterpreterCache()
408

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

423
struct REPLInterpreter <: CC.AbstractInterpreter
424
    repl_frame::CC.InferenceResult
425
    world::UInt
426
    inf_params::CC.InferenceParams
427
    opt_params::CC.OptimizationParams
428
    inf_cache::Vector{CC.InferenceResult}
429
    code_cache::REPLInterpreterCache
430
    function REPLInterpreter(repl_frame::CC.InferenceResult;
×
431
                             world::UInt = Base.get_world_counter(),
432
                             inf_params::CC.InferenceParams = CC.InferenceParams(),
433
                             opt_params::CC.OptimizationParams = CC.OptimizationParams(),
434
                             inf_cache::Vector{CC.InferenceResult} = CC.InferenceResult[],
435
                             code_cache::REPLInterpreterCache = get_code_cache())
436
        return new(repl_frame, world, inf_params, opt_params, inf_cache, code_cache)
×
437
    end
438
end
439
CC.InferenceParams(interp::REPLInterpreter) = interp.inf_params
×
440
CC.OptimizationParams(interp::REPLInterpreter) = interp.opt_params
×
441
CC.get_world_counter(interp::REPLInterpreter) = interp.world
×
442
CC.get_inference_cache(interp::REPLInterpreter) = interp.inf_cache
×
443
CC.code_cache(interp::REPLInterpreter) = CC.WorldView(interp.code_cache, CC.WorldRange(interp.world))
×
444
CC.get(wvc::CC.WorldView{REPLInterpreterCache}, mi::MethodInstance, default) = get(wvc.cache.dict, mi, default)
×
445
CC.getindex(wvc::CC.WorldView{REPLInterpreterCache}, mi::MethodInstance) = getindex(wvc.cache.dict, mi)
×
446
CC.haskey(wvc::CC.WorldView{REPLInterpreterCache}, mi::MethodInstance) = haskey(wvc.cache.dict, mi)
×
447
CC.setindex!(wvc::CC.WorldView{REPLInterpreterCache}, ci::CodeInstance, mi::MethodInstance) = setindex!(wvc.cache.dict, ci, mi)
×
448

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

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

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

473
is_repl_frame(interp::REPLInterpreter, sv::CC.InferenceState) = interp.repl_frame === sv.result
×
474

475
# aggressive global binding resolution within `repl_frame`
476
function CC.abstract_eval_globalref(interp::REPLInterpreter, g::GlobalRef,
×
477
                                    sv::CC.InferenceState)
478
    if is_repl_frame(interp, sv)
×
479
        if CC.isdefined_globalref(g)
×
480
            return Const(ccall(:jl_get_globalref_value, Any, (Any,), g))
×
481
        end
482
        return Union{}
×
483
    end
484
    return @invoke CC.abstract_eval_globalref(interp::CC.AbstractInterpreter, g::GlobalRef,
×
485
                                              sv::CC.InferenceState)
486
end
487

488
function is_repl_frame_getproperty(interp::REPLInterpreter, sv::CC.InferenceState)
×
489
    def = sv.linfo.def
×
490
    def isa Method || return false
×
491
    def.name === :getproperty || return false
×
492
    sv.cached && return false
×
493
    return is_repl_frame(interp, sv.parent)
×
494
end
495

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

518
# aggressive concrete evaluation for `:inconsistent` frames within `repl_frame`
519
function CC.concrete_eval_eligible(interp::REPLInterpreter, @nospecialize(f),
×
520
                                   result::CC.MethodCallResult, arginfo::CC.ArgInfo,
521
                                   sv::CC.InferenceState)
522
    if is_repl_frame(interp, sv)
×
523
        neweffects = CC.Effects(result.effects; consistent=CC.ALWAYS_TRUE)
×
524
        result = CC.MethodCallResult(result.rt, result.edgecycle, result.edgelimited,
×
525
                                     result.edge, neweffects)
526
    end
527
return @invoke CC.concrete_eval_eligible(interp::CC.AbstractInterpreter, f::Any,
×
528
                                         result::CC.MethodCallResult, arginfo::CC.ArgInfo,
529
                                         sv::CC.InferenceState)
530
end
531

532
function resolve_toplevel_symbols!(mod::Module, src::Core.CodeInfo)
×
533
    newsrc = copy(src)
×
534
    @ccall jl_resolve_globals_in_ir(
×
535
        #=jl_array_t *stmts=# newsrc.code::Any,
536
        #=jl_module_t *m=# mod::Any,
537
        #=jl_svec_t *sparam_vals=# Core.svec()::Any,
538
        #=int binding_effects=# 0::Int)::Cvoid
539
    return newsrc
×
540
end
541

542
# lower `ex` and run type inference on the resulting top-level expression
543
function repl_eval_ex(@nospecialize(ex), context_module::Module)
×
544
    lwr = try
×
545
        Meta.lower(context_module, ex)
×
546
    catch # macro expansion failed, etc.
547
        return nothing
×
548
    end
549
    if lwr isa Symbol
×
550
        return isdefined(context_module, lwr) ? Const(getfield(context_module, lwr)) : nothing
×
551
    end
552
    lwr isa Expr || return Const(lwr) # `ex` is literal
×
553
    isexpr(lwr, :thunk) || return nothing # lowered to `Expr(:error, ...)` or similar
×
554
    src = lwr.args[1]::Core.CodeInfo
×
555

556
    # construct top-level `MethodInstance`
557
    mi = ccall(:jl_new_method_instance_uninit, Ref{Core.MethodInstance}, ());
×
558
    mi.specTypes = Tuple{}
×
559

560
    mi.def = context_module
×
561
    src = resolve_toplevel_symbols!(context_module, src)
×
562
    @atomic mi.uninferred = src
×
563

564
    result = CC.InferenceResult(mi)
×
565
    interp = REPLInterpreter(result)
×
566
    frame = CC.InferenceState(result, src, #=cache=#:no, interp)::CC.InferenceState
×
567

568
    CC.typeinf(interp, frame)
×
569

570
    result = frame.result.result
×
571
    result === Union{} && return nothing # for whatever reason, callers expect this as the Bottom and/or Top type instead
×
572
    return result
×
573
end
574

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

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

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

606
    # moreargs determines whether to accept more args, independently of the presence of a
607
    # semicolon for the ".?(" syntax
608
    moreargs && push!(args_ex, Vararg{Any})
×
609

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

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

648
    return out
×
649
end
650

651
function detect_invalid_kwarg!(kwargs_ex::Vector{Symbol}, @nospecialize(x), kwargs_flag::Int, possible_splat::Bool)
×
652
    n = isexpr(x, :kw) ? x.args[1] : x
×
653
    if n isa Symbol
×
654
        push!(kwargs_ex, n)
×
655
        return kwargs_flag
×
656
    end
657
    possible_splat && isexpr(x, :...) && return kwargs_flag
×
658
    return 2 # The kwarg is invalid
×
659
end
660

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

699
is_broadcasting_expr(ex::Expr) = ex.head === :. && isexpr(ex.args[2], :tuple)
×
700

701
function complete_methods_args(ex::Expr, context_module::Module, default_any::Bool, allow_broadcasting::Bool)
×
702
    if allow_broadcasting && is_broadcasting_expr(ex)
×
703
        return detect_args_kwargs((ex.args[2]::Expr).args, context_module, default_any, true)
×
704
    end
705
    return detect_args_kwargs(ex.args, context_module, default_any, false)
×
706
end
707

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

724
include("latex_symbols.jl")
725
include("emoji_symbols.jl")
726

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

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

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

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

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

798
function dict_identifier_key(str::String, tag::Symbol, context_module::Module=Main)
×
799
    if tag === :string
×
800
        str_close = str*"\""
×
801
    elseif tag === :cmd
×
802
        str_close = str*"`"
×
803
    else
804
        str_close = str
×
805
    end
806

807
    frange, end_of_identifier = find_start_brace(str_close, c_start='[', c_end=']')
×
808
    isempty(frange) && return (nothing, nothing, nothing)
×
809
    obj = context_module
×
810
    for name in split(str[frange[1]:end_of_identifier], '.')
×
811
        Base.isidentifier(name) || return (nothing, nothing, nothing)
×
812
        sym = Symbol(name)
×
813
        isdefined(obj, sym) || return (nothing, nothing, nothing)
×
814
        obj = getfield(obj, sym)
×
815
    end
×
816
    (isa(obj, AbstractDict) && length(obj)::Int < 1_000_000) || return (nothing, nothing, nothing)
×
817
    begin_of_key = something(findnext(!isspace, str, nextind(str, end_of_identifier) + 1), # +1 for [
×
818
                             lastindex(str)+1)
819
    return (obj::AbstractDict, str[begin_of_key:end], begin_of_key)
×
820
end
821

822
# This needs to be a separate non-inlined function, see #19441
823
@noinline function find_dict_matches(identifier::AbstractDict, partial_key)
×
824
    matches = String[]
×
825
    for key in keys(identifier)
×
826
        rkey = repr(key)
×
827
        startswith(rkey,partial_key) && push!(matches,rkey)
×
828
    end
×
829
    return matches
×
830
end
831

832
# Identify an argument being completed in a method call. If the argument is empty, method
833
# suggestions will be provided instead of argument completions.
834
function identify_possible_method_completion(partial, last_idx)
×
835
    fail = 0:-1, Expr(:nothing), 0:-1, 0
×
836

837
    # First, check that the last punctuation is either ',', ';' or '('
838
    idx_last_punct = something(findprev(x -> ispunct(x) && x != '_' && x != '!', partial, last_idx), 0)::Int
×
839
    idx_last_punct == 0 && return fail
×
840
    last_punct = partial[idx_last_punct]
×
841
    last_punct == ',' || last_punct == ';' || last_punct == '(' || return fail
×
842

843
    # Then, check that `last_punct` is only followed by an identifier or nothing
844
    before_last_word_start = something(findprev(in(non_identifier_chars), partial, last_idx), 0)
×
845
    before_last_word_start == 0 && return fail
×
846
    all(isspace, @view partial[nextind(partial, idx_last_punct):before_last_word_start]) || return fail
×
847

848
    # Check that `last_punct` is either the last '(' or placed after a previous '('
849
    frange, method_name_end = find_start_brace(@view partial[1:idx_last_punct])
×
850
    method_name_end ∈ frange || return fail
×
851

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

857
    # `wordrange` is the position of the last argument to complete
858
    wordrange = nextind(partial, before_last_word_start):last_idx
×
859
    return frange, ex, wordrange, method_name_end
×
860
end
861

862
# Provide completion for keyword arguments in function calls
863
function complete_keyword_argument(partial, last_idx, context_module)
×
864
    frange, ex, wordrange, = identify_possible_method_completion(partial, last_idx)
×
865
    fail = Completion[], 0:-1, frange
×
866
    ex.head === :call || is_broadcasting_expr(ex) || return fail
×
867

868
    kwargs_flag, funct, args_ex, kwargs_ex = _complete_methods(ex, context_module, true)::Tuple{Int, Any, Vector{Any}, Set{Symbol}}
×
869
    kwargs_flag == 2 && return fail # one of the previous kwargs is invalid
×
870

871
    methods = Completion[]
×
872
    complete_methods!(methods, funct, Any[Vararg{Any}], kwargs_ex, -1, kwargs_flag == 1)
×
873
    # TODO: use args_ex instead of Any[Vararg{Any}] and only provide kwarg completion for
874
    # method calls compatible with the current arguments.
875

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

896
    suggestions = Completion[KeywordArgumentCompletion(kwarg) for kwarg in kwargs]
×
897
    append!(suggestions, complete_symbol(nothing, last_word, Returns(true), context_module))
×
898

899
    return sort!(suggestions, by=completion_text), wordrange
×
900
end
901

902
function project_deps_get_completion_candidates(pkgstarts::String, project_file::String)
×
903
    loading_candidates = String[]
×
904
    d = Base.parsed_toml(project_file)
×
905
    pkg = get(d, "name", nothing)::Union{String, Nothing}
×
906
    if pkg !== nothing && startswith(pkg, pkgstarts)
×
907
        push!(loading_candidates, pkg)
×
908
    end
909
    deps = get(d, "deps", nothing)::Union{Dict{String, Any}, Nothing}
×
910
    if deps !== nothing
×
911
        for (pkg, _) in deps
×
912
            startswith(pkg, pkgstarts) && push!(loading_candidates, pkg)
×
913
        end
×
914
    end
915
    return Completion[PackageCompletion(name) for name in loading_candidates]
×
916
end
917

918
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)
×
919
    ex = nothing
×
920
    comp_keywords && append!(suggestions, complete_keyword(name))
×
921
    if dotpos > 1 && string[dotpos] == '.'
×
922
        s = string[1:dotpos-1]
×
923
        # First see if the whole string up to `pos` is a valid expression. If so, use it.
924
        ex = Meta.parse(s, raise=false, depwarn=false)
×
925
        if isexpr(ex, :incomplete)
×
926
            s = string[startpos:pos]
×
927
            # Heuristic to find the start of the expression. TODO: This would be better
928
            # done with a proper error-recovering parser.
929
            if 0 < startpos <= lastindex(string) && string[startpos] == '.'
×
930
                i = prevind(string, startpos)
×
931
                while 0 < i
×
932
                    c = string[i]
×
933
                    if c in (')', ']')
×
934
                        if c == ')'
×
935
                            c_start = '('
×
936
                            c_end = ')'
×
937
                        elseif c == ']'
×
938
                            c_start = '['
×
939
                            c_end = ']'
×
940
                        end
941
                        frange, end_of_identifier = find_start_brace(string[1:prevind(string, i)], c_start=c_start, c_end=c_end)
×
942
                        isempty(frange) && break # unbalanced parens
×
943
                        startpos = first(frange)
×
944
                        i = prevind(string, startpos)
×
945
                    elseif c in ('\'', '\"', '\`')
×
946
                        s = "$c$c"*string[startpos:pos]
×
947
                        break
×
948
                    else
949
                        break
×
950
                    end
951
                    s = string[startpos:pos]
×
952
                end
×
953
            end
954
            if something(findlast(in(non_identifier_chars), s), 0) < something(findlast(isequal('.'), s), 0)
×
955
                lookup_name, name = rsplit(s, ".", limit=2)
×
956
                name = String(name)
×
957

958
                ex = Meta.parse(lookup_name, raise=false, depwarn=false)
×
959
            end
960
            isexpr(ex, :incomplete) && (ex = nothing)
×
961
        end
962
    end
963
    append!(suggestions, complete_symbol(ex, name, ffunc, context_module))
×
964
    return sort!(unique(suggestions), by=completion_text), (dotpos+1):pos, true
×
965
end
966

967
function completions(string::String, pos::Int, context_module::Module=Main, shift::Bool=true)
×
968
    # First parse everything up to the current position
969
    partial = string[1:pos]
×
970
    inc_tag = Base.incomplete_tag(Meta.parse(partial, raise=false, depwarn=false))
×
971

972
    # ?(x, y)TAB lists methods you can call with these objects
973
    # ?(x, y TAB lists methods that take these objects as the first two arguments
974
    # MyModule.?(x, y)TAB restricts the search to names in MyModule
975
    rexm = match(r"(\w+\.|)\?\((.*)$", partial)
×
976
    if rexm !== nothing
×
977
        # Get the module scope
978
        if isempty(rexm.captures[1])
×
979
            callee_module = context_module
×
980
        else
981
            modname = Symbol(rexm.captures[1][1:end-1])
×
982
            if isdefined(context_module, modname)
×
983
                callee_module = getfield(context_module, modname)
×
984
                if !isa(callee_module, Module)
×
985
                    callee_module = context_module
×
986
                end
987
            else
988
                callee_module = context_module
×
989
            end
990
        end
991
        moreargs = !endswith(rexm.captures[2], ')')
×
992
        callstr = "_(" * rexm.captures[2]
×
993
        if moreargs
×
994
            callstr *= ')'
×
995
        end
996
        ex_org = Meta.parse(callstr, raise=false, depwarn=false)
×
997
        if isa(ex_org, Expr)
×
998
            return complete_any_methods(ex_org, callee_module::Module, context_module, moreargs, shift), (0:length(rexm.captures[1])+1) .+ rexm.offset, false
×
999
        end
1000
    end
1001

1002
    # if completing a key in a Dict
1003
    identifier, partial_key, loc = dict_identifier_key(partial, inc_tag, context_module)
×
1004
    if identifier !== nothing
×
1005
        matches = find_dict_matches(identifier, partial_key)
×
1006
        length(matches)==1 && (lastindex(string) <= pos || string[nextind(string,pos)] != ']') && (matches[1]*=']')
×
1007
        length(matches)>0 && return Completion[DictCompletion(identifier, match) for match in sort!(matches)], loc::Int:pos, true
×
1008
    end
1009

1010
    ffunc = Returns(true)
×
1011
    suggestions = Completion[]
×
1012

1013
    # Check if this is a var"" string macro that should be completed like
1014
    # an identifier rather than a string.
1015
    # TODO: It would be nice for the parser to give us more information here
1016
    # so that we can lookup the macro by identity rather than pattern matching
1017
    # its invocation.
1018
    varrange = findprev("var\"", string, pos)
×
1019

1020
    if varrange !== nothing
×
1021
        ok, ret = bslash_completions(string, pos)
×
1022
        ok && return ret
×
1023
        startpos = first(varrange) + 4
×
1024
        dotpos = something(findprev(isequal('.'), string, first(varrange)-1), 0)
×
1025
        return complete_identifiers!(Completion[], ffunc, context_module, string,
×
1026
            string[startpos:pos], pos, dotpos, startpos)
1027
    # otherwise...
1028
    elseif inc_tag in [:cmd, :string]
×
1029
        m = match(r"[\t\n\r\"`><=*?|]| (?!\\)", reverse(partial))
×
1030
        startpos = nextind(partial, reverseind(partial, m.offset))
×
1031
        r = startpos:pos
×
1032

1033
        expanded = complete_expanduser(replace(string[r], r"\\ " => " "), r)
×
1034
        expanded[3] && return expanded  # If user expansion available, return it
×
1035

1036
        paths, r, success = complete_path(replace(string[r], r"\\ " => " "), pos)
×
1037

1038
        if inc_tag === :string && close_path_completion(string, startpos, r, paths, pos)
×
1039
            paths[1] = PathCompletion((paths[1]::PathCompletion).path * "\"")
×
1040
        end
1041

1042
        #Latex symbols can be completed for strings
1043
        (success || inc_tag === :cmd) && return sort!(paths, by=p->p.path), r, success
×
1044
    end
1045

1046
    ok, ret = bslash_completions(string, pos)
×
1047
    ok && return ret
×
1048

1049
    # Make sure that only bslash_completions is working on strings
1050
    inc_tag === :string && return Completion[], 0:-1, false
×
1051
    if inc_tag === :other
×
1052
        frange, ex, wordrange, method_name_end = identify_possible_method_completion(partial, pos)
×
1053
        if last(frange) != -1 && all(isspace, @view partial[wordrange]) # no last argument to complete
×
1054
            if ex.head === :call
×
1055
                return complete_methods(ex, context_module, shift), first(frange):method_name_end, false
×
1056
            elseif is_broadcasting_expr(ex)
×
1057
                return complete_methods(ex, context_module, shift), first(frange):(method_name_end - 1), false
×
1058
            end
1059
        end
1060
    elseif inc_tag === :comment
×
1061
        return Completion[], 0:-1, false
×
1062
    end
1063

1064
    # Check whether we can complete a keyword argument in a function call
1065
    kwarg_completion, wordrange = complete_keyword_argument(partial, pos, context_module)
×
1066
    isempty(wordrange) || return kwarg_completion, wordrange, !isempty(kwarg_completion)
×
1067

1068
    dotpos = something(findprev(isequal('.'), string, pos), 0)
×
1069
    startpos = nextind(string, something(findprev(in(non_identifier_chars), string, pos), 0))
×
1070
    # strip preceding ! operator
1071
    if (m = match(r"\G\!+", partial, startpos)) isa RegexMatch
×
1072
        startpos += length(m.match)
×
1073
    end
1074

1075
    name = string[max(startpos, dotpos+1):pos]
×
1076
    comp_keywords = !isempty(name) && startpos > dotpos
×
1077
    if afterusing(string, startpos)
×
1078
        # We're right after using or import. Let's look only for packages
1079
        # and modules we can reach from here
1080

1081
        # If there's no dot, we're in toplevel, so we should
1082
        # also search for packages
1083
        s = string[startpos:pos]
×
1084
        if dotpos <= startpos
×
1085
            for dir in Base.load_path()
×
1086
                if basename(dir) in Base.project_names && isfile(dir)
×
1087
                    append!(suggestions, project_deps_get_completion_candidates(s, dir))
×
1088
                end
1089
                isdir(dir) || continue
×
1090
                for pname in readdir(dir)
×
1091
                    if pname[1] != '.' && pname != "METADATA" &&
×
1092
                        pname != "REQUIRE" && startswith(pname, s)
1093
                        # Valid file paths are
1094
                        #   <Mod>.jl
1095
                        #   <Mod>/src/<Mod>.jl
1096
                        #   <Mod>.jl/src/<Mod>.jl
1097
                        if isfile(joinpath(dir, pname))
×
1098
                            endswith(pname, ".jl") && push!(suggestions,
×
1099
                                                            PackageCompletion(pname[1:prevind(pname, end-2)]))
1100
                        else
1101
                            mod_name = if endswith(pname, ".jl")
×
1102
                                pname[1:prevind(pname, end-2)]
×
1103
                            else
1104
                                pname
×
1105
                            end
1106
                            if isfile(joinpath(dir, pname, "src",
×
1107
                                               "$mod_name.jl"))
1108
                                push!(suggestions, PackageCompletion(mod_name))
×
1109
                            end
1110
                        end
1111
                    end
1112
                end
×
1113
            end
×
1114
        end
1115
        ffunc = (mod,x)->(Base.isbindingresolved(mod, x) && isdefined(mod, x) && isa(getfield(mod, x), Module))
×
1116
        comp_keywords = false
×
1117
    end
1118

1119
    startpos == 0 && (pos = -1)
×
1120
    dotpos < startpos && (dotpos = startpos - 1)
×
1121
    return complete_identifiers!(suggestions, ffunc, context_module, string,
×
1122
        name, pos, dotpos, startpos, comp_keywords)
1123
end
1124

1125
function shell_completions(string, pos)
×
1126
    # First parse everything up to the current position
1127
    scs = string[1:pos]
×
1128
    local args, last_parse
×
1129
    try
×
1130
        args, last_parse = Base.shell_parse(scs, true)::Tuple{Expr,UnitRange{Int}}
×
1131
    catch
1132
        return Completion[], 0:-1, false
×
1133
    end
1134
    ex = args.args[end]::Expr
×
1135
    # Now look at the last thing we parsed
1136
    isempty(ex.args) && return Completion[], 0:-1, false
×
1137
    arg = ex.args[end]
×
1138
    if all(s -> isa(s, AbstractString), ex.args)
×
1139
        arg = arg::AbstractString
×
1140
        # Treat this as a path
1141

1142
        # As Base.shell_parse throws away trailing spaces (unless they are escaped),
1143
        # we need to special case here.
1144
        # If the last char was a space, but shell_parse ignored it search on "".
1145
        ignore_last_word = arg != " " && scs[end] == ' '
×
1146
        prefix = ignore_last_word ? "" : join(ex.args)
×
1147

1148
        # Also try looking into the env path if the user wants to complete the first argument
1149
        use_envpath = !ignore_last_word && length(args.args) < 2
×
1150

1151
        return complete_path(prefix, pos, use_envpath=use_envpath, shell_escape=true)
×
1152
    elseif isexpr(arg, :incomplete) || isexpr(arg, :error)
×
1153
        partial = scs[last_parse]
×
1154
        ret, range = completions(partial, lastindex(partial))
×
1155
        range = range .+ (first(last_parse) - 1)
×
1156
        return ret, range, true
×
1157
    end
1158
    return Completion[], 0:-1, false
×
1159
end
1160

1161
function UndefVarError_hint(io::IO, ex::UndefVarError)
3✔
1162
    var = ex.var
3✔
1163
    if var === :or
3✔
1164
        print(io, "\nsuggestion: Use `||` for short-circuiting boolean OR.")
×
1165
    elseif var === :and
3✔
1166
        print(io, "\nsuggestion: Use `&&` for short-circuiting boolean AND.")
×
1167
    elseif var === :help
3✔
1168
        println(io)
×
1169
        # Show friendly help message when user types help or help() and help is undefined
1170
        show(io, MIME("text/plain"), Base.Docs.parsedoc(Base.Docs.keywords[:help]))
×
1171
    elseif var === :quit
3✔
1172
        print(io, "\nsuggestion: To exit Julia, use Ctrl-D, or type exit() and press enter.")
×
1173
    end
1174
end
1175

1176
function __init__()
443✔
1177
    Base.Experimental.register_error_hint(UndefVarError_hint, UndefVarError)
443✔
1178
    nothing
443✔
1179
end
1180

1181
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