Skip to content

Commit

Permalink
clean up identifiers defined in Main (JuliaLang#51878)
Browse files Browse the repository at this point in the history
A re-do of JuliaLang#51411 that should be non-breaking.

- Loaded packages do not need explicit bindings
- The name `MainInclude` does not need to be visible
- Put Main's eval and include in the module like all other modules and
hide them explicitly instead
  • Loading branch information
JeffBezanson committed Nov 1, 2023
1 parent 5185487 commit e2a6424
Show file tree
Hide file tree
Showing 8 changed files with 62 additions and 58 deletions.
59 changes: 8 additions & 51 deletions base/client.jl
Original file line number Diff line number Diff line change
Expand Up @@ -261,8 +261,8 @@ function exec_options(opts)
distributed_mode = (opts.worker == 1) || (opts.nprocs > 0) || (opts.machine_file != C_NULL)
if distributed_mode
let Distributed = require(PkgId(UUID((0x8ba89e20_285c_5b6f, 0x9357_94700520ee1b)), "Distributed"))
Core.eval(Main, :(const Distributed = $Distributed))
Core.eval(Main, :(using .Distributed))
Core.eval(MainInclude, :(const Distributed = $Distributed))
Core.eval(Main, :(using Base.MainInclude.Distributed))
end

invokelatest(Main.Distributed.process_opts, opts)
Expand Down Expand Up @@ -384,19 +384,18 @@ _atreplinit(repl) = invokelatest(__atreplinit, repl)

function load_InteractiveUtils(mod::Module=Main)
# load interactive-only libraries
if !isdefined(mod, :InteractiveUtils)
if !isdefined(MainInclude, :InteractiveUtils)
try
let InteractiveUtils = require(PkgId(UUID(0xb77e0a4c_d291_57a0_90e8_8db25a27a240), "InteractiveUtils"))
Core.eval(mod, :(const InteractiveUtils = $InteractiveUtils))
Core.eval(mod, :(using .InteractiveUtils))
return InteractiveUtils
Core.eval(MainInclude, :(const InteractiveUtils = $InteractiveUtils))
end
catch ex
@warn "Failed to import InteractiveUtils into module $mod" exception=(ex, catch_backtrace())
return nothing
end
return nothing
end
return getfield(mod, :InteractiveUtils)
Core.eval(mod, :(using Base.MainInclude.InteractiveUtils))
return MainInclude.InteractiveUtils
end

function load_REPL()
Expand Down Expand Up @@ -486,17 +485,9 @@ function run_main_repl(interactive::Bool, quiet::Bool, banner::Symbol, history_f
nothing
end

# MainInclude exists to hide Main.include and eval from `names(Main)`.
# MainInclude exists to weakly add certain identifiers to Main
baremodule MainInclude
using ..Base
# These definitions calls Base._include rather than Base.include to get
# one-frame stacktraces for the common case of using include(fname) in Main.
include(mapexpr::Function, fname::AbstractString) = Base._include(mapexpr, Main, fname)
function include(fname::AbstractString)
isa(fname, String) || (fname = Base.convert(String, fname)::String)
Base._include(identity, Main, fname)
end
eval(x) = Core.eval(Main, x)

"""
ans
Expand All @@ -515,49 +506,15 @@ global err = nothing

# weakly exposes ans and err variables to Main
export ans, err

end

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

function should_use_main_entrypoint()
isdefined(Main, :main) || return false
M_binding_owner = Base.binding_module(Main, :main)
(isdefined(M_binding_owner, Symbol("#__main_is_entrypoint__#")) && M_binding_owner.var"#__main_is_entrypoint__#") || return false
return true
end

"""
include([mapexpr::Function,] path::AbstractString)
Evaluate the contents of the input source file in the global scope of the containing module.
Every module (except those defined with `baremodule`) has its own
definition of `include`, which evaluates the file in that module.
Returns the result of the last evaluated expression of the input file. During including,
a task-local include path is set to the directory containing the file. Nested calls to
`include` will search relative to that path. This function is typically used to load source
interactively, or to combine files in packages that are broken into multiple source files.
The argument `path` is normalized using [`normpath`](@ref) which will resolve
relative path tokens such as `..` and convert `/` to the appropriate path separator.
The optional first argument `mapexpr` can be used to transform the included code before
it is evaluated: for each parsed expression `expr` in `path`, the `include` function
actually evaluates `mapexpr(expr)`. If it is omitted, `mapexpr` defaults to [`identity`](@ref).
Use [`Base.include`](@ref) to evaluate a file into another module.
!!! compat "Julia 1.5"
Julia 1.5 is required for passing the `mapexpr` argument.
"""
MainInclude.include

function _start()
empty!(ARGS)
append!(ARGS, Core.ARGS)
Expand Down
4 changes: 2 additions & 2 deletions base/docs/basedocs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ kw"__init__"
baremodule
`baremodule` declares a module that does not contain `using Base` or local definitions of
[`eval`](@ref Base.MainInclude.eval) and [`include`](@ref Base.include). It does still import `Core`. In other words,
[`eval`](@ref Main.eval) and [`include`](@ref Base.include). It does still import `Core`. In other words,
```julia
module Mod
Expand Down Expand Up @@ -217,7 +217,7 @@ kw"primitive type"
A macro maps a sequence of argument expressions to a returned expression, and the
resulting expression is substituted directly into the program at the point where
the macro is invoked.
Macros are a way to run generated code without calling [`eval`](@ref Base.MainInclude.eval),
Macros are a way to run generated code without calling [`eval`](@ref Main.eval),
since the generated code instead simply becomes part of the surrounding program.
Macro arguments may include expressions, literal values, and symbols. Macros can be defined for
variable number of arguments (varargs), but do not accept keyword arguments.
Expand Down
42 changes: 41 additions & 1 deletion base/sysimg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,46 @@ using .Base

# Set up Main module
using Base.MainInclude # ans, err, and sometimes Out
import Base.MainInclude: eval, include

# These definitions calls Base._include rather than Base.include to get
# one-frame stacktraces for the common case of using include(fname) in Main.

"""
include([mapexpr::Function,] path::AbstractString)
Evaluate the contents of the input source file in the global scope of the containing module.
Every module (except those defined with `baremodule`) has its own
definition of `include`, which evaluates the file in that module.
Returns the result of the last evaluated expression of the input file. During including,
a task-local include path is set to the directory containing the file. Nested calls to
`include` will search relative to that path. This function is typically used to load source
interactively, or to combine files in packages that are broken into multiple source files.
The argument `path` is normalized using [`normpath`](@ref) which will resolve
relative path tokens such as `..` and convert `/` to the appropriate path separator.
The optional first argument `mapexpr` can be used to transform the included code before
it is evaluated: for each parsed expression `expr` in `path`, the `include` function
actually evaluates `mapexpr(expr)`. If it is omitted, `mapexpr` defaults to [`identity`](@ref).
Use [`Base.include`](@ref) to evaluate a file into another module.
!!! compat "Julia 1.5"
Julia 1.5 is required for passing the `mapexpr` argument.
"""
include(mapexpr::Function, fname::AbstractString) = Base._include(mapexpr, Main, fname)
function include(fname::AbstractString)
isa(fname, String) || (fname = Base.convert(String, fname)::String)
Base._include(identity, Main, fname)
end

"""
eval(expr)
Evaluate an expression in the global scope of the containing module.
Every `Module` (except those defined with `baremodule`) has its own 1-argument
definition of `eval`, which evaluates expressions in that module.
"""
eval(x) = Core.eval(Main, x)

# Ensure this file is also tracked
pushfirst!(Base._included_files, (@__MODULE__, abspath(@__FILE__)))
Expand Down Expand Up @@ -80,6 +119,7 @@ let
Base.init_load_path() # want to be able to find external packages in userimg.jl

ccall(:jl_clear_implicit_imports, Cvoid, (Any,), Main)

tot_time_userimg = @elapsed (isfile("userimg.jl") && Base.include(Main, "userimg.jl"))

tot_time_base = (Base.end_base_include - Base.start_base_include) * 10.0^(-9)
Expand Down
4 changes: 2 additions & 2 deletions doc/src/base/base.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Base.isinteractive
Base.summarysize
Base.__precompile__
Base.include
Base.MainInclude.include
Main.include
Base.include_string
Base.include_dependency
__init__
Expand Down Expand Up @@ -285,7 +285,7 @@ Base.Fix2

```@docs
Core.eval
Base.MainInclude.eval
Main.eval
Base.@eval
Base.evalfile
Base.esc
Expand Down
4 changes: 4 additions & 0 deletions src/ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ JL_DLLEXPORT jl_sym_t *jl_aliasscope_sym;
JL_DLLEXPORT jl_sym_t *jl_popaliasscope_sym;
JL_DLLEXPORT jl_sym_t *jl_optlevel_sym;
JL_DLLEXPORT jl_sym_t *jl_thismodule_sym;
JL_DLLEXPORT jl_sym_t *jl_eval_sym;
JL_DLLEXPORT jl_sym_t *jl_include_sym;
JL_DLLEXPORT jl_sym_t *jl_atom_sym;
JL_DLLEXPORT jl_sym_t *jl_statement_sym;
JL_DLLEXPORT jl_sym_t *jl_all_sym;
Expand Down Expand Up @@ -365,6 +367,8 @@ void jl_init_common_symbols(void)
jl_aliasscope_sym = jl_symbol("aliasscope");
jl_popaliasscope_sym = jl_symbol("popaliasscope");
jl_thismodule_sym = jl_symbol("thismodule");
jl_eval_sym = jl_symbol("eval");
jl_include_sym = jl_symbol("include");
jl_block_sym = jl_symbol("block");
jl_atom_sym = jl_symbol("atom");
jl_statement_sym = jl_symbol("statement");
Expand Down
2 changes: 2 additions & 0 deletions src/julia_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -1570,6 +1570,8 @@ extern JL_DLLEXPORT jl_sym_t *jl_aliasscope_sym;
extern JL_DLLEXPORT jl_sym_t *jl_popaliasscope_sym;
extern JL_DLLEXPORT jl_sym_t *jl_optlevel_sym;
extern JL_DLLEXPORT jl_sym_t *jl_thismodule_sym;
extern JL_DLLEXPORT jl_sym_t *jl_eval_sym;
extern JL_DLLEXPORT jl_sym_t *jl_include_sym;
extern JL_DLLEXPORT jl_sym_t *jl_atom_sym;
extern JL_DLLEXPORT jl_sym_t *jl_statement_sym;
extern JL_DLLEXPORT jl_sym_t *jl_all_sym;
Expand Down
3 changes: 2 additions & 1 deletion src/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -951,9 +951,10 @@ JL_DLLEXPORT jl_value_t *jl_module_names(jl_module_t *m, int all, int imported)
break;
jl_sym_t *asname = b->globalref->name;
int hidden = jl_symbol_name(asname)[0]=='#';
int main_public = (m == jl_main_module && !(asname == jl_eval_sym || asname == jl_include_sym));
if ((b->publicp ||
(imported && b->imported) ||
(jl_atomic_load_relaxed(&b->owner) == b && !b->imported && (all || m == jl_main_module))) &&
(jl_atomic_load_relaxed(&b->owner) == b && !b->imported && (all || main_public))) &&
(all || (!b->deprecated && !hidden))) {
jl_array_grow_end(a, 1);
// n.b. change to jl_arrayset if array storage allocation for Array{Symbols,1} changes:
Expand Down
2 changes: 1 addition & 1 deletion stdlib/REPL/src/REPLCompletions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ function complete_symbol(@nospecialize(ex), name::String, @nospecialize(ffunc),
# as excluding Main.Main.Main, etc., because that's most likely not what
# the user wants
p = let mod=mod, modname=nameof(mod)
(s::Symbol) -> !Base.isdeprecated(mod, s) && s != modname && ffunc(mod, s)::Bool
(s::Symbol) -> !Base.isdeprecated(mod, s) && s != modname && ffunc(mod, s)::Bool && !(mod === Main && s === :MainInclude)
end
# Looking for a binding in a module
if mod == context_module
Expand Down

0 comments on commit e2a6424

Please sign in to comment.