• 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

64.79
/base/client.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
## client.jl - frontend handling command line options, environment setup,
4
##             and REPL
5

6
have_color = nothing
7
const default_color_warn = :yellow
8
const default_color_error = :light_red
9
const default_color_info = :cyan
10
const default_color_debug = :blue
11
const default_color_input = :normal
12
const default_color_answer = :normal
13
const color_normal = text_colors[:normal]
14

15
function repl_color(key, default)
2,660✔
16
    env_str = get(ENV, key, "")
2,660✔
17
    c = tryparse(Int, env_str)
2,660✔
18
    c_conv = something(c, Symbol(env_str))
2,660✔
19
    haskey(text_colors, c_conv) ? c_conv : default
2,660✔
20
end
21

22
error_color() = repl_color("JULIA_ERROR_COLOR", default_color_error)
2,243✔
23
warn_color()  = repl_color("JULIA_WARN_COLOR" , default_color_warn)
74✔
24
info_color()  = repl_color("JULIA_INFO_COLOR" , default_color_info)
334✔
25
debug_color()  = repl_color("JULIA_DEBUG_COLOR" , default_color_debug)
34✔
26

27
input_color()  = text_colors[repl_color("JULIA_INPUT_COLOR", default_color_input)]
×
28
answer_color() = text_colors[repl_color("JULIA_ANSWER_COLOR", default_color_answer)]
×
29

30
stackframe_lineinfo_color() = repl_color("JULIA_STACKFRAME_LINEINFO_COLOR", :bold)
×
31
stackframe_function_color() = repl_color("JULIA_STACKFRAME_FUNCTION_COLOR", :bold)
×
32

33
function repl_cmd(cmd, out)
1✔
34
    shell = shell_split(get(ENV, "JULIA_SHELL", get(ENV, "SHELL", "/bin/sh")))
1✔
35
    shell_name = Base.basename(shell[1])
1✔
36

37
    # Immediately expand all arguments, so that typing e.g. ~/bin/foo works.
38
    cmd.exec .= expanduser.(cmd.exec)
1✔
39

40
    if isempty(cmd.exec)
×
41
        throw(ArgumentError("no cmd to execute"))
×
42
    elseif cmd.exec[1] == "cd"
×
43
        new_oldpwd = pwd()
×
44
        if length(cmd.exec) > 2
×
45
            throw(ArgumentError("cd method only takes one argument"))
×
46
        elseif length(cmd.exec) == 2
×
47
            dir = cmd.exec[2]
×
48
            if dir == "-"
×
49
                if !haskey(ENV, "OLDPWD")
×
50
                    error("cd: OLDPWD not set")
×
51
                end
52
                dir = ENV["OLDPWD"]
×
53
            end
54
            cd(dir)
×
55
        else
56
            cd()
×
57
        end
58
        ENV["OLDPWD"] = new_oldpwd
×
59
        println(out, pwd())
×
60
    else
61
        @static if !Sys.iswindows()
×
62
            if shell_name == "fish"
×
63
                shell_escape_cmd = "begin; $(shell_escape_posixly(cmd)); and true; end"
×
64
            else
65
                shell_escape_cmd = "($(shell_escape_posixly(cmd))) && true"
×
66
            end
67
            cmd = `$shell -c $shell_escape_cmd`
×
68
        end
69
        try
×
70
            run(ignorestatus(cmd))
×
71
        catch
72
            # Windows doesn't shell out right now (complex issue), so Julia tries to run the program itself
73
            # Julia throws an exception if it can't find the program, but the stack trace isn't useful
74
            lasterr = current_exceptions()
×
75
            lasterr = ExceptionStack([(exception = e[1], backtrace = [] ) for e in lasterr])
×
76
            invokelatest(display_error, lasterr)
×
77
        end
78
    end
79
    nothing
×
80
end
81

82
# deprecated function--preserved for DocTests.jl
83
function ip_matches_func(ip, func::Symbol)
×
84
    for fr in StackTraces.lookup(ip)
×
85
        if fr === StackTraces.UNKNOWN || fr.from_c
×
86
            return false
×
87
        end
88
        fr.func === func && return true
×
89
    end
×
90
    return false
×
91
end
92

93
function scrub_repl_backtrace(bt)
36✔
94
    if bt !== nothing && !(bt isa Vector{Any}) # ignore our sentinel value types
36✔
95
        bt = bt isa Vector{StackFrame} ? copy(bt) : stacktrace(bt)
36✔
96
        # remove REPL-related frames from interactive printing
97
        eval_ind = findlast(frame -> !frame.from_c && frame.func === :eval, bt)
366✔
98
        eval_ind === nothing || deleteat!(bt, eval_ind:length(bt))
66✔
99
    end
100
    return bt
36✔
101
end
102
scrub_repl_backtrace(stack::ExceptionStack) =
34✔
103
    ExceptionStack(Any[(;x.exception, backtrace = scrub_repl_backtrace(x.backtrace)) for x in stack])
104

105
istrivialerror(stack::ExceptionStack) =
4✔
106
    length(stack) == 1 && length(stack[1].backtrace) ≤ 1
107
    # frame 1 = top level; assumes already went through scrub_repl_backtrace
108

109
function display_error(io::IO, stack::ExceptionStack)
35✔
110
    printstyled(io, "ERROR: "; bold=true, color=Base.error_color())
35✔
111
    show_exception_stack(IOContext(io, :limit => true), stack)
34✔
112
    println(io)
34✔
113
end
114
display_error(stack::ExceptionStack) = display_error(stderr, stack)
26✔
115

116
# these forms are depended on by packages outside Julia
117
function display_error(io::IO, er, bt)
×
118
    printstyled(io, "ERROR: "; bold=true, color=Base.error_color())
×
119
    showerror(IOContext(io, :limit => true), er, bt, backtrace = bt!==nothing)
×
120
    println(io)
×
121
end
122
display_error(er, bt=nothing) = display_error(stderr, er, bt)
×
123

124
function eval_user_input(errio, @nospecialize(ast), show_value::Bool)
62✔
125
    errcount = 0
62✔
126
    lasterr = nothing
62✔
127
    have_color = get(stdout, :color, false)::Bool
62✔
128
    while true
67✔
129
        try
67✔
130
            if have_color
67✔
131
                print(color_normal)
×
132
            end
133
            if lasterr !== nothing
67✔
134
                lasterr = scrub_repl_backtrace(lasterr)
5✔
135
                istrivialerror(lasterr) || setglobal!(Base.MainInclude, :err, lasterr)
8✔
136
                invokelatest(display_error, errio, lasterr)
5✔
137
                errcount = 0
5✔
138
                lasterr = nothing
5✔
139
            else
140
                ast = Meta.lower(Main, ast)
62✔
141
                value = Core.eval(Main, ast)
62✔
142
                setglobal!(Base.MainInclude, :ans, value)
57✔
143
                if !(value === nothing) && show_value
57✔
144
                    if have_color
13✔
145
                        print(answer_color())
×
146
                    end
147
                    try
13✔
148
                        invokelatest(display, value)
13✔
149
                    catch
150
                        @error "Evaluation succeeded, but an error occurred while displaying the value" typeof(value)
×
151
                        rethrow()
×
152
                    end
153
                end
154
            end
155
            break
67✔
156
        catch
157
            if errcount > 0
5✔
158
                @error "SYSTEM: display_error(errio, lasterr) caused an error"
×
159
            end
160
            errcount += 1
5✔
161
            lasterr = scrub_repl_backtrace(current_exceptions())
5✔
162
            setglobal!(Base.MainInclude, :err, lasterr)
5✔
163
            if errcount > 2
5✔
164
                @error "It is likely that something important is broken, and Julia will not be able to continue normally" errcount
×
165
                break
5✔
166
            end
167
        end
168
    end
5✔
169
    isa(stdin, TTY) && println()
62✔
170
    nothing
62✔
171
end
172

173
function _parse_input_line_core(s::String, filename::String)
×
174
    ex = Meta.parseall(s, filename=filename)
206✔
175
    if ex isa Expr && ex.head === :toplevel
206✔
176
        if isempty(ex.args)
206✔
177
            return nothing
6✔
178
        end
179
        last = ex.args[end]
200✔
180
        if last isa Expr && (last.head === :error || last.head === :incomplete)
399✔
181
            # if a parse error happens in the middle of a multi-line input
182
            # return only the error, so that none of the input is evaluated.
183
            return last
7✔
184
        end
185
    end
186
    return ex
193✔
187
end
188

189
function parse_input_line(s::String; filename::String="none", depwarn=true)
404✔
190
    # For now, assume all parser warnings are depwarns
191
    ex = if depwarn
×
192
        _parse_input_line_core(s, filename)
406✔
193
    else
194
        with_logger(NullLogger()) do
×
195
            _parse_input_line_core(s, filename)
196
        end
197
    end
198
    return ex
206✔
199
end
200
parse_input_line(s::AbstractString) = parse_input_line(String(s))
×
201

202
# detect the reason which caused an :incomplete expression
203
# from the error message
204
# NOTE: the error messages are defined in src/julia-parser.scm
205
incomplete_tag(ex) = :none
1✔
206
function incomplete_tag(ex::Expr)
25✔
207
    Meta.isexpr(ex, :incomplete) || return :none
25✔
208
    msg = ex.args[1]
25✔
209
    occursin("string", msg) && return :string
25✔
210
    occursin("comment", msg) && return :comment
23✔
211
    occursin("requires end", msg) && return :block
22✔
212
    occursin("\"`\"", msg) && return :cmd
13✔
213
    occursin("character", msg) && return :char
12✔
214
    return :other
11✔
215
end
216

217
function exec_options(opts)
443✔
218
    quiet                 = (opts.quiet != 0)
443✔
219
    startup               = (opts.startupfile != 2)
443✔
220
    history_file          = (opts.historyfile != 0)
443✔
221
    color_set             = (opts.color != 0) # --color!=auto
443✔
222
    global have_color     = color_set ? (opts.color == 1) : nothing # --color=on
443✔
223
    global is_interactive = (opts.isinteractive != 0)
443✔
224

225
    # pre-process command line argument list
226
    arg_is_program = !isempty(ARGS)
443✔
227
    repl = !arg_is_program
443✔
228
    cmds = unsafe_load_commands(opts.commands)
443✔
229
    for (cmd, arg) in cmds
705✔
230
        if cmd == 'e'
183✔
231
            arg_is_program = false
×
232
            repl = false
164✔
233
        elseif cmd == 'E'
19✔
234
            arg_is_program = false
×
235
            repl = false
19✔
236
        elseif cmd == 'L'
×
237
            # nothing
238
        elseif cmd == 'B' # --bug-report
×
239
            # If we're doing a bug report, don't load anything else. We will
240
            # spawn a child in which to execute these options.
241
            let InteractiveUtils = load_InteractiveUtils()
×
242
                InteractiveUtils.report_bug(arg)
×
243
            end
244
            return nothing
×
245
        else
246
            @warn "Unexpected command -$cmd'$arg'"
×
247
        end
248
    end
364✔
249

250
    # remove filename from ARGS
251
    global PROGRAM_FILE = arg_is_program ? popfirst!(ARGS) : ""
443✔
252

253
    # Load Distributed module only if any of the Distributed options have been specified.
254
    distributed_mode = (opts.worker == 1) || (opts.nprocs > 0) || (opts.machine_file != C_NULL)
814✔
255
    if distributed_mode
443✔
256
        let Distributed = require(PkgId(UUID((0x8ba89e20_285c_5b6f, 0x9357_94700520ee1b)), "Distributed"))
76✔
257
            Core.eval(Main, :(const Distributed = $Distributed))
76✔
258
            Core.eval(Main, :(using .Distributed))
76✔
259
        end
260

261
        invokelatest(Main.Distributed.process_opts, opts)
76✔
262
    end
263

264
    interactiveinput = (repl || is_interactive::Bool) && isa(stdin, TTY)
741✔
265
    is_interactive::Bool |= interactiveinput
371✔
266

267
    # load ~/.julia/config/startup.jl file
268
    if startup
371✔
269
        try
5✔
270
            load_julia_startup()
5✔
271
        catch
272
            invokelatest(display_error, scrub_repl_backtrace(current_exceptions()))
×
273
            !(repl || is_interactive::Bool) && exit(1)
×
274
        end
275
    end
276

277
    # process cmds list
278
    for (cmd, arg) in cmds
561✔
279
        if cmd == 'e'
183✔
280
            Core.eval(Main, parse_input_line(arg))
328✔
281
        elseif cmd == 'E'
19✔
282
            invokelatest(show, Core.eval(Main, parse_input_line(arg)))
38✔
283
            println()
18✔
284
        elseif cmd == 'L'
×
285
            # load file immediately on all processors
286
            if !distributed_mode
×
287
                include(Main, arg)
×
288
            else
289
                # TODO: Move this logic to Distributed and use a callback
290
                @sync for p in invokelatest(Main.procs)
×
291
                    @async invokelatest(Main.remotecall_wait, include, p, Main, arg)
×
292
                end
×
293
            end
294
        end
295
    end
258✔
296

297
    # load file
298
    if arg_is_program
318✔
299
        # program
300
        if !is_interactive::Bool
189✔
301
            exit_on_sigint(true)
189✔
302
        end
303
        try
189✔
304
            if PROGRAM_FILE == "-"
189✔
305
                include_string(Main, read(stdin, String), "stdin")
117✔
306
            else
307
                include(Main, PROGRAM_FILE)
217✔
308
            end
309
        catch
310
            invokelatest(display_error, scrub_repl_backtrace(current_exceptions()))
16✔
311
            if !is_interactive::Bool
16✔
312
                exit(1)
16✔
313
            end
314
        end
315
    end
316
    if repl || is_interactive::Bool
547✔
317
        if interactiveinput
3✔
318
            banner = (opts.banner != 0) # --banner!=no
×
319
        else
320
            banner = (opts.banner == 1) # --banner=yes
3✔
321
        end
322
        run_main_repl(interactiveinput, quiet, banner, history_file, color_set)
3✔
323
    end
324
    nothing
274✔
325
end
326

327
function _global_julia_startup_file()
×
328
    # If the user built us with a specific Base.SYSCONFDIR, check that location first for a startup.jl file
329
    # If it is not found, then continue on to the relative path based on Sys.BINDIR
330
    BINDIR = Sys.BINDIR
5✔
331
    SYSCONFDIR = Base.SYSCONFDIR
×
332
    if !isempty(SYSCONFDIR)
5✔
333
        p1 = abspath(BINDIR, SYSCONFDIR, "julia", "startup.jl")
5✔
334
        isfile(p1) && return p1
5✔
335
    end
336
    p2 = abspath(BINDIR, "..", "etc", "julia", "startup.jl")
×
337
    isfile(p2) && return p2
×
338
    return nothing
×
339
end
340

341
function _local_julia_startup_file()
×
342
    if !isempty(DEPOT_PATH)
5✔
343
        path = abspath(DEPOT_PATH[1], "config", "startup.jl")
5✔
344
        isfile(path) && return path
5✔
345
    end
346
    return nothing
5✔
347
end
348

349
function load_julia_startup()
5✔
350
    global_file = _global_julia_startup_file()
5✔
351
    (global_file !== nothing) && include(Main, global_file)
5✔
352
    local_file = _local_julia_startup_file()
10✔
353
    (local_file !== nothing) && include(Main, local_file)
5✔
354
    return nothing
5✔
355
end
356

357
const repl_hooks = []
358

359
"""
360
    atreplinit(f)
361

362
Register a one-argument function to be called before the REPL interface is initialized in
363
interactive sessions; this is useful to customize the interface. The argument of `f` is the
364
REPL object. This function should be called from within the `.julia/config/startup.jl`
365
initialization file.
366
"""
367
atreplinit(f::Function) = (pushfirst!(repl_hooks, f); nothing)
443✔
368

369
function __atreplinit(repl)
×
370
    for f in repl_hooks
×
371
        try
×
372
            f(repl)
×
373
        catch err
374
            showerror(stderr, err)
×
375
            println(stderr)
×
376
        end
377
    end
×
378
end
379
_atreplinit(repl) = invokelatest(__atreplinit, repl)
×
380

381
function load_InteractiveUtils(mod::Module=Main)
3✔
382
    # load interactive-only libraries
383
    if !isdefined(mod, :InteractiveUtils)
6✔
384
        try
3✔
385
            let InteractiveUtils = require(PkgId(UUID(0xb77e0a4c_d291_57a0_90e8_8db25a27a240), "InteractiveUtils"))
3✔
386
                Core.eval(mod, :(const InteractiveUtils = $InteractiveUtils))
3✔
387
                Core.eval(mod, :(using .InteractiveUtils))
3✔
388
                return InteractiveUtils
3✔
389
            end
390
        catch ex
391
            @warn "Failed to import InteractiveUtils into module $mod" exception=(ex, catch_backtrace())
×
392
        end
393
        return nothing
×
394
    end
395
    return getfield(mod, :InteractiveUtils)
×
396
end
397

398
global active_repl
399

400
# run the requested sort of evaluation loop on stdio
401
function run_main_repl(interactive::Bool, quiet::Bool, banner::Bool, history_file::Bool, color_set::Bool)
3✔
402
    load_InteractiveUtils()
3✔
403

404
    if interactive && isassigned(REPL_MODULE_REF)
3✔
405
        invokelatest(REPL_MODULE_REF[]) do REPL
×
406
            term_env = get(ENV, "TERM", @static Sys.iswindows() ? "" : "dumb")
407
            term = REPL.Terminals.TTYTerminal(term_env, stdin, stdout, stderr)
408
            banner && Base.banner(term)
409
            if term.term_type == "dumb"
410
                repl = REPL.BasicREPL(term)
411
                quiet || @warn "Terminal not fully functional"
412
            else
413
                repl = REPL.LineEditREPL(term, get(stdout, :color, false), true)
414
                repl.history_file = history_file
415
            end
416
            global active_repl = repl
417
            # Make sure any displays pushed in .julia/config/startup.jl ends up above the
418
            # REPLDisplay
419
            pushdisplay(REPL.REPLDisplay(repl))
420
            _atreplinit(repl)
421
            REPL.run_repl(repl, backend->(global active_repl_backend = backend))
422
        end
423
    else
424
        # otherwise provide a simple fallback
425
        if interactive && !quiet
3✔
426
            @warn "REPL provider not available: using basic fallback"
×
427
        end
428
        banner && Base.banner()
3✔
429
        let input = stdin
3✔
430
            if isa(input, File) || isa(input, IOStream)
6✔
431
                # for files, we can slurp in the whole thing at once
432
                ex = parse_input_line(read(input, String))
2✔
433
                if Meta.isexpr(ex, :toplevel)
4✔
434
                    # if we get back a list of statements, eval them sequentially
435
                    # as if we had parsed them sequentially
436
                    for stmt in ex.args
×
437
                        eval_user_input(stderr, stmt, true)
×
438
                    end
×
439
                    body = ex.args
×
440
                else
441
                    eval_user_input(stderr, ex, true)
2✔
442
                end
443
            else
444
                while isopen(input) || !eof(input)
27✔
445
                    if interactive
13✔
446
                        print("julia> ")
×
447
                        flush(stdout)
×
448
                    end
449
                    try
13✔
450
                        line = ""
×
451
                        ex = nothing
×
452
                        while !eof(input)
19✔
453
                            line *= readline(input, keep=true)
38✔
454
                            ex = parse_input_line(line)
34✔
455
                            if !(isa(ex, Expr) && ex.head === :incomplete)
19✔
456
                                break
13✔
457
                            end
458
                        end
6✔
459
                        eval_user_input(stderr, ex, true)
13✔
460
                    catch err
461
                        isa(err, InterruptException) ? print("\n\n") : rethrow()
×
462
                    end
463
                end
13✔
464
            end
465
        end
466
    end
467
    nothing
3✔
468
end
469

470
# MainInclude exists to hide Main.include and eval from `names(Main)`.
471
baremodule MainInclude
472
using ..Base
473
# These definitions calls Base._include rather than Base.include to get
474
# one-frame stacktraces for the common case of using include(fname) in Main.
475
include(mapexpr::Function, fname::AbstractString) = Base._include(mapexpr, Main, fname)
×
476
function include(fname::AbstractString)
120✔
477
    isa(fname, String) || (fname = Base.convert(String, fname)::String)
120✔
478
    Base._include(identity, Main, fname)
120✔
479
end
480
eval(x) = Core.eval(Main, x)
62✔
481

482
"""
483
    ans
484

485
A variable referring to the last computed value, automatically imported to the interactive prompt.
486
"""
487
global ans = nothing
488

489
"""
490
    err
491

492
A variable referring to the last thrown errors, automatically imported to the interactive prompt.
493
The thrown errors are collected in a stack of exceptions.
494
"""
495
global err = nothing
496

497
# weakly exposes ans and err variables to Main
498
export ans, err
499

500
end
501

502
"""
503
    eval(expr)
504

505
Evaluate an expression in the global scope of the containing module.
506
Every `Module` (except those defined with `baremodule`) has its own 1-argument
507
definition of `eval`, which evaluates expressions in that module.
508
"""
509
MainInclude.eval
510

511
"""
512
    include([mapexpr::Function,] path::AbstractString)
513

514
Evaluate the contents of the input source file in the global scope of the containing module.
515
Every module (except those defined with `baremodule`) has its own
516
definition of `include`, which evaluates the file in that module.
517
Returns the result of the last evaluated expression of the input file. During including,
518
a task-local include path is set to the directory containing the file. Nested calls to
519
`include` will search relative to that path. This function is typically used to load source
520
interactively, or to combine files in packages that are broken into multiple source files.
521
The argument `path` is normalized using [`normpath`](@ref) which will resolve
522
relative path tokens such as `..` and convert `/` to the appropriate path separator.
523

524
The optional first argument `mapexpr` can be used to transform the included code before
525
it is evaluated: for each parsed expression `expr` in `path`, the `include` function
526
actually evaluates `mapexpr(expr)`.  If it is omitted, `mapexpr` defaults to [`identity`](@ref).
527

528
Use [`Base.include`](@ref) to evaluate a file into another module.
529

530
!!! compat "Julia 1.5"
531
    Julia 1.5 is required for passing the `mapexpr` argument.
532
"""
533
MainInclude.include
534

535
function _start()
443✔
536
    empty!(ARGS)
443✔
537
    append!(ARGS, Core.ARGS)
632✔
538
    # clear any postoutput hooks that were saved in the sysimage
539
    empty!(Base.postoutput_hooks)
443✔
540
    try
443✔
541
        exec_options(JLOptions())
453✔
542
    catch
543
        invokelatest(display_error, scrub_repl_backtrace(current_exceptions()))
10✔
544
        exit(1)
9✔
545
    end
546
    if is_interactive && get(stdout, :color, false)
274✔
547
        print(color_normal)
×
548
    end
549
end
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