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

JuliaLang / julia / #38002

06 Feb 2025 06:14AM UTC coverage: 20.322% (-2.4%) from 22.722%
#38002

push

local

web-flow
bpart: Fully switch to partitioned semantics (#57253)

This is the final PR in the binding partitions series (modulo bugs and
tweaks), i.e. it closes #54654 and thus closes #40399, which was the
original design sketch.

This thus activates the full designed semantics for binding partitions,
in particular allowing safe replacement of const bindings. It in
particular allows struct redefinitions. This thus closes
timholy/Revise.jl#18 and also closes #38584.

The biggest semantic change here is probably that this gets rid of the
notion of "resolvedness" of a binding. Previously, a lot of the behavior
of our implementation depended on when bindings were "resolved", which
could happen at basically an arbitrary point (in the compiler, in REPL
completion, in a different thread), making a lot of the semantics around
bindings ill- or at least implementation-defined. There are several
related issues in the bugtracker, so this closes #14055 closes #44604
closes #46354 closes #30277

It is also the last step to close #24569.
It also supports bindings for undef->defined transitions and thus closes
#53958 closes #54733 - however, this is not activated yet for
performance reasons and may need some further optimization.

Since resolvedness no longer exists, we need to replace it with some
hopefully more well-defined semantics. I will describe the semantics
below, but before I do I will make two notes:

1. There are a number of cases where these semantics will behave
slightly differently than the old semantics absent some other task going
around resolving random bindings.
2. The new behavior (except for the replacement stuff) was generally
permissible under the old semantics if the bindings happened to be
resolved at the right time.

With all that said, there are essentially three "strengths" of bindings:

1. Implicit Bindings: Anything implicitly obtained from `using Mod`, "no
binding", plus slightly more exotic corner cases around conflicts

2. Weakly declared bindin... (continued)

11 of 111 new or added lines in 7 files covered. (9.91%)

1273 existing lines in 68 files now uncovered.

9908 of 48755 relevant lines covered (20.32%)

105126.48 hits per line

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

28.65
/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
have_truecolor = nothing
8
const default_color_warn = :yellow
9
const default_color_error = :light_red
10
const default_color_info = :cyan
11
const default_color_debug = :blue
12
const default_color_input = :normal
13
const default_color_answer = :normal
14
const color_normal = text_colors[:normal]
15

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

23
error_color() = repl_color("JULIA_ERROR_COLOR", default_color_error)
23✔
24
warn_color()  = repl_color("JULIA_WARN_COLOR" , default_color_warn)
5✔
25
info_color()  = repl_color("JULIA_INFO_COLOR" , default_color_info)
24✔
26
debug_color()  = repl_color("JULIA_DEBUG_COLOR" , default_color_debug)
×
27

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

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

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

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

41
    if isempty(cmd.exec)
×
42
        throw(ArgumentError("no cmd to execute"))
×
43
    elseif cmd.exec[1] == "cd"
×
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
        else
55
            dir = homedir()
×
56
        end
57
        try
×
58
            ENV["OLDPWD"] = pwd()
×
59
        catch ex
60
            ex isa IOError || rethrow()
×
61
            # if current dir has been deleted, then pwd() will throw an IOError: pwd(): no such file or directory (ENOENT)
62
            delete!(ENV, "OLDPWD")
×
63
        end
64
        cd(dir)
×
65
        println(out, pwd())
×
66
    else
67
        @static if !Sys.iswindows()
×
68
            if shell_name == "fish"
×
69
                shell_escape_cmd = "begin; $(shell_escape_posixly(cmd)); and true; end"
×
70
            else
71
                shell_escape_cmd = "($(shell_escape_posixly(cmd))) && true"
×
72
            end
73
            cmd = `$shell -c $shell_escape_cmd`
×
74
        end
75
        try
×
76
            run(ignorestatus(cmd))
×
77
        catch
78
            # Windows doesn't shell out right now (complex issue), so Julia tries to run the program itself
79
            # Julia throws an exception if it can't find the program, but the stack trace isn't useful
80
            lasterr = current_exceptions()
×
81
            lasterr = ExceptionStack([(exception = e[1], backtrace = [] ) for e in lasterr])
×
82
            invokelatest(display_error, lasterr)
×
83
        end
84
    end
85
    nothing
×
86
end
87

88
# deprecated function--preserved for DocTests.jl
89
function ip_matches_func(ip, func::Symbol)
×
90
    for fr in StackTraces.lookup(ip)
×
91
        if fr === StackTraces.UNKNOWN || fr.from_c
×
92
            return false
×
93
        end
94
        fr.func === func && return true
×
95
    end
×
96
    return false
×
97
end
98

99
function scrub_repl_backtrace(bt)
12✔
100
    if bt !== nothing && !(bt isa Vector{Any}) # ignore our sentinel value types
12✔
101
        bt = bt isa Vector{StackFrame} ? copy(bt) : stacktrace(bt)
14✔
102
        # remove REPL-related frames from interactive printing
103
        eval_ind = findlast(frame -> !frame.from_c && startswith(String(frame.func), "__repl_entry"), bt)
19✔
104
        eval_ind === nothing || deleteat!(bt, eval_ind:length(bt))
12✔
105
    end
106
    return bt
12✔
107
end
108
scrub_repl_backtrace(stack::ExceptionStack) =
1✔
109
    ExceptionStack(Any[(;x.exception, backtrace = scrub_repl_backtrace(x.backtrace)) for x in stack])
110

111
istrivialerror(stack::ExceptionStack) =
×
112
    length(stack) == 1 && length(stack[1].backtrace) ≤ 1 && !isa(stack[1].exception, MethodError)
113
    # frame 1 = top level; assumes already went through scrub_repl_backtrace; MethodError see #50803
114

115
function display_error(io::IO, stack::ExceptionStack)
9✔
116
    printstyled(io, "ERROR: "; bold=true, color=Base.error_color())
18✔
117
    show_exception_stack(IOContext(io, :limit => true), stack)
9✔
118
    println(io)
9✔
119
end
120
display_error(stack::ExceptionStack) = display_error(stderr, stack)
3✔
121

122
# these forms are depended on by packages outside Julia
123
function display_error(io::IO, er, bt)
×
124
    printstyled(io, "ERROR: "; bold=true, color=Base.error_color())
×
125
    showerror(IOContext(io, :limit => true), er, bt, backtrace = bt!==nothing)
×
126
    println(io)
×
127
end
128
display_error(er, bt=nothing) = display_error(stderr, er, bt)
×
129

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

179
function _parse_input_line_core(s::String, filename::String)
180
    ex = Meta.parseall(s, filename=filename)
1✔
181
    if ex isa Expr && ex.head === :toplevel
1✔
182
        if isempty(ex.args)
1✔
183
            return nothing
×
184
        end
185
        last = ex.args[end]
1✔
186
        if last isa Expr && (last.head === :error || last.head === :incomplete)
2✔
187
            # if a parse error happens in the middle of a multi-line input
188
            # return only the error, so that none of the input is evaluated.
189
            return last
×
190
        end
191
    end
192
    return ex
1✔
193
end
194

195
function parse_input_line(s::String; filename::String="none", depwarn=true)
2✔
196
    # For now, assume all parser warnings are depwarns
197
    ex = if depwarn
×
198
        _parse_input_line_core(s, filename)
2✔
199
    else
200
        with_logger(NullLogger()) do
×
201
            _parse_input_line_core(s, filename)
202
        end
203
    end
204
    return ex
1✔
205
end
206
parse_input_line(s::AbstractString) = parse_input_line(String(s))
×
207

208
# detect the reason which caused an :incomplete expression
209
# from the error message
210
# NOTE: the error messages are defined in src/julia-parser.scm
211
function fl_incomplete_tag(msg::AbstractString)
×
212
    occursin("string", msg) && return :string
×
213
    occursin("comment", msg) && return :comment
×
214
    occursin("requires end", msg) && return :block
×
215
    occursin("\"`\"", msg) && return :cmd
×
216
    occursin("character", msg) && return :char
×
217
    return :other
×
218
end
219

220
incomplete_tag(ex) = :none
×
221
function incomplete_tag(ex::Expr)
×
222
    if ex.head !== :incomplete
×
223
        return :none
×
224
    elseif isempty(ex.args)
×
225
        return :other
×
226
    elseif ex.args[1] isa String
×
227
        return fl_incomplete_tag(ex.args[1])
×
228
    else
229
        return incomplete_tag(ex.args[1])
×
230
    end
231
end
232
incomplete_tag(exc::Meta.ParseError) = incomplete_tag(exc.detail)
×
233

234
function exec_options(opts)
1✔
235
    startup               = (opts.startupfile != 2)
1✔
236
    global have_color     = colored_text(opts)
2✔
237
    global is_interactive = (opts.isinteractive != 0)
1✔
238

239
    # pre-process command line argument list
240
    arg_is_program = !isempty(ARGS)
1✔
241
    repl = !arg_is_program
1✔
242
    cmds = unsafe_load_commands(opts.commands)
1✔
243
    for (cmd, arg) in cmds
1✔
244
        if cmd_suppresses_program(cmd)
2✔
245
            arg_is_program = false
×
246
            repl = false
×
247
        elseif cmd == 'L' || cmd == 'm'
×
248
            # nothing
249
        elseif cmd == 'B' # --bug-report
×
250
            # If we're doing a bug report, don't load anything else. We will
251
            # spawn a child in which to execute these options.
252
            let InteractiveUtils = load_InteractiveUtils()
×
253
                invokelatest(InteractiveUtils.report_bug, arg)
×
254
            end
255
            return false
×
256
        else
257
            @warn "Unexpected command -$cmd'$arg'"
×
258
        end
259
    end
1✔
260

261
    # remove filename from ARGS
262
    global PROGRAM_FILE = arg_is_program ? popfirst!(ARGS) : ""
1✔
263

264
    # Load Distributed module only if any of the Distributed options have been specified.
265
    distributed_mode = (opts.worker == 1) || (opts.nprocs > 0) || (opts.machine_file != C_NULL)
2✔
266
    if distributed_mode
1✔
267
        let Distributed = require(PkgId(UUID((0x8ba89e20_285c_5b6f, 0x9357_94700520ee1b)), "Distributed"))
×
268
            Core.eval(MainInclude, :(const Distributed = $Distributed))
×
269
            Core.eval(Main, :(using Base.MainInclude.Distributed))
×
NEW
270
            invokelatest(Distributed.process_opts, opts)
×
271
        end
272
    end
273

274
    interactiveinput = (repl || is_interactive::Bool) && isa(stdin, TTY)
2✔
275
    is_interactive::Bool |= interactiveinput
1✔
276

277
    # load terminfo in for styled printing
278
    term_env = get(ENV, "TERM", @static Sys.iswindows() ? "" : "dumb")
1✔
279
    global current_terminfo = load_terminfo(term_env)
1✔
280

281
    # load ~/.julia/config/startup.jl file
282
    if startup
1✔
UNCOV
283
        try
×
UNCOV
284
            load_julia_startup()
×
285
        catch
286
            invokelatest(display_error, scrub_repl_backtrace(current_exceptions()))
×
287
            !(repl || is_interactive::Bool) && exit(1)
×
288
        end
289
    end
290

291
    # process cmds list
292
    for (cmd, arg) in cmds
1✔
293
        if cmd == 'e'
1✔
UNCOV
294
            Core.eval(Main, parse_input_line(arg))
×
295
        elseif cmd == 'E'
1✔
296
            invokelatest(show, Core.eval(Main, parse_input_line(arg)))
2✔
297
            println()
×
298
        elseif cmd == 'm'
×
299
            entrypoint = push!(split(arg, "."), "main")
×
300
            Base.eval(Main, Expr(:import, Expr(:., Symbol.(entrypoint)...)))
×
301
            if !invokelatest(should_use_main_entrypoint)
×
302
                error("`main` in `$arg` not declared as entry point (use `@main` to do so)")
×
303
            end
304
            return false
×
305
        elseif cmd == 'L'
×
306
            # load file immediately on all processors
307
            if !distributed_mode
×
308
                include(Main, arg)
×
309
            else
310
                # TODO: Move this logic to Distributed and use a callback
311
                @sync for p in invokelatest(Main.procs)
×
312
                    @async invokelatest(Main.remotecall_wait, include, p, Main, arg)
×
313
                end
314
            end
315
        end
316
    end
×
317

318
    # load file
319
    if arg_is_program
×
320
        # program
321
        if !is_interactive::Bool
×
322
            exit_on_sigint(true)
×
323
        end
324
        try
×
325
            if PROGRAM_FILE == "-"
×
326
                include_string(Main, read(stdin, String), "stdin")
×
327
            else
328
                include(Main, PROGRAM_FILE)
×
329
            end
330
        catch
331
            invokelatest(display_error, scrub_repl_backtrace(current_exceptions()))
×
332
            if !is_interactive::Bool
×
333
                exit(1)
×
334
            end
335
        end
336
    end
337

338
    return repl
×
339
end
340

UNCOV
341
function _global_julia_startup_file()
×
342
    # If the user built us with a specific Base.SYSCONFDIR, check that location first for a startup.jl file
343
    # If it is not found, then continue on to the relative path based on Sys.BINDIR
UNCOV
344
    BINDIR = Sys.BINDIR
×
UNCOV
345
    SYSCONFDIR = Base.SYSCONFDIR
×
346
    p1 = nothing
×
347
    if !isempty(SYSCONFDIR)
×
UNCOV
348
        p1 = abspath(BINDIR, SYSCONFDIR, "julia", "startup.jl")
×
UNCOV
349
        isfile(p1) && return p1
×
350
    end
351
    p2 = abspath(BINDIR, "..", "etc", "julia", "startup.jl")
×
352
    p1 == p2 && return nothing # don't check the same path twice
×
353
    isfile(p2) && return p2
×
354
    return nothing
×
355
end
356

357
function _local_julia_startup_file()
UNCOV
358
    if !isempty(DEPOT_PATH)
×
UNCOV
359
        path = abspath(DEPOT_PATH[1], "config", "startup.jl")
×
UNCOV
360
        isfile(path) && return path
×
361
    end
UNCOV
362
    return nothing
×
363
end
364

UNCOV
365
function load_julia_startup()
×
UNCOV
366
    global_file = _global_julia_startup_file()
×
UNCOV
367
    (global_file !== nothing) && include(Main, global_file)
×
UNCOV
368
    local_file = _local_julia_startup_file()
×
UNCOV
369
    (local_file !== nothing) && include(Main, local_file)
×
UNCOV
370
    return nothing
×
371
end
372

373
const repl_hooks = []
374

375
"""
376
    atreplinit(f)
377

378
Register a one-argument function to be called before the REPL interface is initialized in
379
interactive sessions; this is useful to customize the interface. The argument of `f` is the
380
REPL object. This function should be called from within the `.julia/config/startup.jl`
381
initialization file.
382
"""
383
atreplinit(f::Function) = (pushfirst!(repl_hooks, f); nothing)
×
384

385
function __atreplinit(repl)
×
386
    for f in repl_hooks
×
387
        try
×
388
            f(repl)
×
389
        catch err
390
            showerror(stderr, err)
×
391
            println(stderr)
×
392
        end
393
    end
×
394
end
395
_atreplinit(repl) = invokelatest(__atreplinit, repl)
×
396

397
function load_InteractiveUtils(mod::Module=Main)
×
398
    # load interactive-only libraries
399
    if !isdefined(MainInclude, :InteractiveUtils)
×
400
        try
×
401
            # TODO: we have to use require_stdlib here because it is a dependency of REPL, but we would sort of prefer not to
402
            let InteractiveUtils = require_stdlib(PkgId(UUID(0xb77e0a4c_d291_57a0_90e8_8db25a27a240), "InteractiveUtils"))
×
403
                Core.eval(MainInclude, :(const InteractiveUtils = $InteractiveUtils))
×
404
            end
405
        catch ex
406
            @warn "Failed to import InteractiveUtils into module $mod" exception=(ex, catch_backtrace())
×
407
            return nothing
×
408
        end
409
    end
410
    return Core.eval(mod, :(using Base.MainInclude.InteractiveUtils; Base.MainInclude.InteractiveUtils))
×
411
end
412

413
function load_REPL()
×
414
    # load interactive-only libraries
415
    try
×
416
        return Base.require_stdlib(PkgId(UUID(0x3fa0cd96_eef1_5676_8a61_b3b8758bbffb), "REPL"))
×
417
    catch ex
418
        @warn "Failed to import REPL" exception=(ex, catch_backtrace())
×
419
    end
420
    return nothing
×
421
end
422

423
global active_repl::Any
424
global active_repl_backend = nothing
425

426
function run_fallback_repl(interactive::Bool)
×
427
    let input = stdin
×
428
        if isa(input, File) || isa(input, IOStream)
×
429
            # for files, we can slurp in the whole thing at once
430
            ex = parse_input_line(read(input, String))
×
431
            if Meta.isexpr(ex, :toplevel)
×
432
                # if we get back a list of statements, eval them sequentially
433
                # as if we had parsed them sequentially
434
                for stmt in ex.args
×
435
                    eval_user_input(stderr, stmt, true)
×
436
                end
×
437
                body = ex.args
×
438
            else
439
                eval_user_input(stderr, ex, true)
×
440
            end
441
        else
442
            while !eof(input)
×
443
                if interactive
×
444
                    print("julia> ")
×
445
                    flush(stdout)
×
446
                end
447
                try
×
448
                    line = ""
×
449
                    ex = nothing
×
450
                    while !eof(input)
×
451
                        line *= readline(input, keep=true)
×
452
                        ex = parse_input_line(line)
×
453
                        if !(isa(ex, Expr) && ex.head === :incomplete)
×
454
                            break
×
455
                        end
456
                    end
×
457
                    eval_user_input(stderr, ex, true)
×
458
                catch err
459
                    isa(err, InterruptException) ? print("\n\n") : rethrow()
×
460
                end
461
            end
×
462
        end
463
    end
464
    nothing
×
465
end
466

467
function run_std_repl(REPL::Module, quiet::Bool, banner::Symbol, history_file::Bool)
×
468
    term_env = get(ENV, "TERM", @static Sys.iswindows() ? "" : "dumb")
×
469
    term = REPL.Terminals.TTYTerminal(term_env, stdin, stdout, stderr)
×
470
    banner == :no || REPL.banner(term, short=banner==:short)
×
471
    if term.term_type == "dumb"
×
472
        repl = REPL.BasicREPL(term)
×
473
        quiet || @warn "Terminal not fully functional"
×
474
    else
475
        repl = REPL.LineEditREPL(term, get(stdout, :color, false), true)
×
476
        repl.history_file = history_file
×
477
    end
478
    # Make sure any displays pushed in .julia/config/startup.jl ends up above the
479
    # REPLDisplay
480
    d = REPL.REPLDisplay(repl)
×
481
    last_active_repl = @isdefined(active_repl) ? active_repl : nothing
×
482
    last_active_repl_backend = active_repl_backend
×
483
    global active_repl = repl
×
484
    pushdisplay(d)
×
485
    try
×
486
        global active_repl = repl
×
487
        _atreplinit(repl)
×
488
        REPL.run_repl(repl, backend->(global active_repl_backend = backend))
×
489
    finally
490
        popdisplay(d)
×
491
        active_repl = last_active_repl
×
492
        active_repl_backend = last_active_repl_backend
×
493
    end
494
    nothing
×
495
end
496

497
# run the requested sort of evaluation loop on stdio
498
function run_main_repl(interactive::Bool, quiet::Bool, banner::Symbol, history_file::Bool)
×
499
    fallback_repl = parse(Bool, get(ENV, "JULIA_FALLBACK_REPL", "false"))
×
500
    if !fallback_repl && interactive
×
501
        load_InteractiveUtils()
×
502
        REPL = REPL_MODULE_REF[]
×
503
        if REPL === Base
×
504
            load_REPL()
×
505
        end
506
    end
507
    REPL = REPL_MODULE_REF[]
×
508
    if !fallback_repl && interactive && REPL !== Base
×
509
        invokelatest(run_std_repl, REPL, quiet, banner, history_file)
×
510
    else
511
        if !fallback_repl && interactive && !quiet
×
512
            @warn "REPL provider not available: using basic fallback" LOAD_PATH=join(Base.LOAD_PATH, Sys.iswindows() ? ';' : ':')
×
513
        end
514
        run_fallback_repl(interactive)
×
515
    end
516
    nothing
×
517
end
518

519
# MainInclude exists to weakly add certain identifiers to Main
520
baremodule MainInclude
521
using ..Base
522

523
"""
524
    ans
525

526
A variable referring to the last computed value, automatically imported to the interactive prompt.
527
"""
528
global ans = nothing
529

530
"""
531
    err
532

533
A variable referring to the last thrown errors, automatically imported to the interactive prompt.
534
The thrown errors are collected in a stack of exceptions.
535
"""
536
global err = nothing
537

538
# weakly exposes ans and err variables to Main
539
export ans, err
540
end
541

542
function should_use_main_entrypoint()
2✔
543
    isdefined(Main, :main) || return false
2✔
544
    M_binding_owner = Base.binding_module(Main, :main)
4✔
545
    (isdefined(M_binding_owner, Symbol("#__main_is_entrypoint__#")) && M_binding_owner.var"#__main_is_entrypoint__#") || return false
2✔
546
    return true
2✔
547
end
548

549
function _start()
1✔
550
    empty!(ARGS)
1✔
551
    append!(ARGS, Core.ARGS)
1✔
552
    # clear any postoutput hooks that were saved in the sysimage
553
    empty!(Base.postoutput_hooks)
1✔
554
    local ret = 0
×
555
    try
1✔
556
        repl_was_requested = exec_options(JLOptions())
1✔
557
        if invokelatest(should_use_main_entrypoint) && !is_interactive
×
558
            main = invokelatest(getglobal, Main, :main)
×
559
            if Base.generating_output()
×
560
                precompile(main, (typeof(ARGS),))
×
561
            else
562
                ret = invokelatest(main, ARGS)
×
563
            end
564
        elseif (repl_was_requested || is_interactive)
×
565
            # Run the Base `main`, which will either load the REPL stdlib
566
            # or run the fallback REPL
567
            ret = repl_main(ARGS)
×
568
        end
569
        ret === nothing && (ret = 0)
×
570
        ret = Cint(ret)
×
571
    catch
572
        ret = Cint(1)
×
573
        invokelatest(display_error, scrub_repl_backtrace(current_exceptions()))
1✔
574
    end
575
    if is_interactive && get(stdout, :color, false)
1✔
576
        print(color_normal)
×
577
    end
578
    return ret
1✔
579
end
580

581
function repl_main(_)
582
    opts = Base.JLOptions()
×
583
    interactiveinput = isa(stdin, Base.TTY)
×
584
    b = opts.banner
×
585
    auto = b == -1
×
586
    banner = b == 0 || (auto && !interactiveinput) ? :no  :
×
587
             b == 1 || (auto && interactiveinput)  ? :yes :
588
             :short # b == 2
589

590
    quiet                 = (opts.quiet != 0)
×
591
    history_file          = (opts.historyfile != 0)
×
592
    return run_main_repl(interactiveinput, quiet, banner, history_file)
×
593
end
594

595
"""
596
    @main
597

598
This macro is used to mark that the binding `main` in the current module is considered an
599
entrypoint. The precise semantics of the entrypoint depend on the CLI driver.
600

601
In the `julia` driver, if `Main.main` is marked as an entrypoint, it will be automatically called upon
602
the completion of script execution.
603

604
The `@main` macro may be used standalone or as part of the function definition, though in the latter
605
case, parentheses are required. In particular, the following are equivalent:
606

607
```
608
function (@main)(args)
609
    println("Hello World")
610
end
611
```
612

613
```
614
function main(args)
615
end
616
@main
617
```
618

619
## Detailed semantics
620

621
The entrypoint semantics attach to the owner of the binding owner. In particular, if a marked entrypoint is
622
imported into `Main`, it will be treated as an entrypoint in `Main`:
623

624
```
625
module MyApp
626
    export main
627
    (@main)(args) = println("Hello World")
628
end
629
using .MyApp
630
# `julia` Will execute MyApp.main at the conclusion of script execution
631
```
632

633
Note that in particular, the semantics do not attach to the method
634
or the name:
635
```
636
module MyApp
637
    (@main)(args) = println("Hello World")
638
end
639
const main = MyApp.main
640
# `julia` Will *NOT* execute MyApp.main unless there is a separate `@main` annotation in `Main`
641
```
642

643
!!! compat "Julia 1.11"
644
    This macro is new in Julia 1.11. At present, the precise semantics of `@main` are still subject to change.
645
"""
646
macro main(args...)
4✔
647
    if !isempty(args)
4✔
648
        error("`@main` is expected to be used as `(@main)` without macro arguments.")
×
649
    end
650
    if isdefined(__module__, :main)
4✔
651
        if Base.binding_module(__module__, :main) !== __module__
×
652
            error("Symbol `main` is already a resolved import in module $(__module__). `@main` must be used in the defining module.")
×
653
        end
654
    end
655
    Core.eval(__module__, quote
4✔
656
        # Force the binding to resolve to this module
657
        global main
658
        global var"#__main_is_entrypoint__#"::Bool = true
659
    end)
660
    esc(:main)
4✔
661
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

© 2026 Coveralls, Inc