Skip to content

Commit 8a96ed2

Browse files
authored
fix #17997, don't load packages in Main (#23579)
1 parent 83a89a1 commit 8a96ed2

27 files changed

+429
-306
lines changed

NEWS.md

+4
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,10 @@ This section lists changes that do not have deprecation warnings.
213213
* The `openspecfun` library is no longer built and shipped with Julia, as it is no longer
214214
used internally ([#22390]).
215215

216+
* All loaded packges used to have bindings in `Main` (e.g. `Main.Package`). This is no
217+
longer the case; now bindings will only exist for packages brought into scope by
218+
typing `using Package` or `import Package` ([#17997]).
219+
216220
* `slicedim(b::BitVector, 1, x)` now consistently returns the same thing that `b[x]` would,
217221
consistent with its documentation. Previously it would return a `BitArray{0}` for scalar
218222
`x` ([#20233]).

base/interactiveutil.jl

+10-13
Original file line numberDiff line numberDiff line change
@@ -566,7 +566,7 @@ end
566566
Return an array of methods with an argument of type `typ`.
567567
568568
The optional second argument restricts the search to a particular module or function
569-
(the default is all modules, starting from Main).
569+
(the default is all top-level modules).
570570
571571
If optional `showparents` is `true`, also return arguments with a parent type of `typ`,
572572
excluding type `Any`.
@@ -588,7 +588,7 @@ function methodswith(t::Type, f::Function, showparents::Bool=false, meths = Meth
588588
return meths
589589
end
590590

591-
function methodswith(t::Type, m::Module, showparents::Bool=false)
591+
function _methodswith(t::Type, m::Module, showparents::Bool)
592592
meths = Method[]
593593
for nm in names(m)
594594
if isdefined(m, nm)
@@ -601,17 +601,12 @@ function methodswith(t::Type, m::Module, showparents::Bool=false)
601601
return unique(meths)
602602
end
603603

604+
methodswith(t::Type, m::Module, showparents::Bool=false) = _methodswith(t, m, showparents)
605+
604606
function methodswith(t::Type, showparents::Bool=false)
605607
meths = Method[]
606-
mainmod = Main
607-
# find modules in Main
608-
for nm in names(mainmod)
609-
if isdefined(mainmod, nm)
610-
mod = getfield(mainmod, nm)
611-
if isa(mod, Module)
612-
append!(meths, methodswith(t, mod, showparents))
613-
end
614-
end
608+
for mod in loaded_modules_array()
609+
append!(meths, _methodswith(t, mod, showparents))
615610
end
616611
return unique(meths)
617612
end
@@ -678,8 +673,10 @@ download(url, filename)
678673
workspace()
679674
680675
Replace the top-level module (`Main`) with a new one, providing a clean workspace. The
681-
previous `Main` module is made available as `LastMain`. A previously-loaded package can be
682-
accessed using a statement such as `using LastMain.Package`.
676+
previous `Main` module is made available as `LastMain`.
677+
678+
If `Package` was previously loaded, `using Package` in the new `Main` will re-use the
679+
loaded copy. Run `reload("Package")` first to load a fresh copy.
683680
684681
This function should only be used interactively.
685682
"""

base/loading.jl

+81-25
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ function reload(name::AbstractString)
288288
error("use `include` instead of `reload` to load source files")
289289
else
290290
# reload("Package") is ok
291+
unreference_module(Symbol(name))
291292
require(Symbol(name))
292293
end
293294
end
@@ -315,21 +316,78 @@ all platforms, including those with case-insensitive filesystems like macOS and
315316
Windows.
316317
"""
317318
function require(mod::Symbol)
318-
_require(mod)
319-
# After successfully loading, notify downstream consumers
320-
if toplevel_load[] && myid() == 1 && nprocs() > 1
321-
# broadcast top-level import/using from node 1 (only)
322-
@sync for p in procs()
323-
p == 1 && continue
324-
@async remotecall_wait(p) do
325-
if !isbindingresolved(Main, mod) || !isdefined(Main, mod)
326-
_require(mod)
319+
if !root_module_exists(mod)
320+
_require(mod)
321+
# After successfully loading, notify downstream consumers
322+
if toplevel_load[] && myid() == 1 && nprocs() > 1
323+
# broadcast top-level import/using from node 1 (only)
324+
@sync for p in procs()
325+
p == 1 && continue
326+
@async remotecall_wait(p) do
327+
require(mod)
328+
nothing
327329
end
328330
end
329331
end
332+
for callback in package_callbacks
333+
invokelatest(callback, mod)
334+
end
330335
end
331-
for callback in package_callbacks
332-
invokelatest(callback, mod)
336+
return root_module(mod)
337+
end
338+
339+
const loaded_modules = ObjectIdDict()
340+
const module_keys = ObjectIdDict()
341+
342+
function register_root_module(key, m::Module)
343+
if haskey(loaded_modules, key)
344+
oldm = loaded_modules[key]
345+
if oldm !== m
346+
name = module_name(oldm)
347+
warn("replacing module $name.")
348+
end
349+
end
350+
loaded_modules[key] = m
351+
module_keys[m] = key
352+
nothing
353+
end
354+
355+
register_root_module(:Core, Core)
356+
register_root_module(:Base, Base)
357+
register_root_module(:Main, Main)
358+
359+
is_root_module(m::Module) = haskey(module_keys, m)
360+
361+
root_module_key(m::Module) = module_keys[m]
362+
363+
# This is used as the current module when loading top-level modules.
364+
# It has the special behavior that modules evaluated in it get added
365+
# to the loaded_modules table instead of getting bindings.
366+
baremodule __toplevel__
367+
using Base
368+
end
369+
370+
# get a top-level Module from the given key
371+
# for now keys can only be Symbols, but that will change
372+
root_module(key::Symbol) = loaded_modules[key]
373+
374+
root_module_exists(key::Symbol) = haskey(loaded_modules, key)
375+
376+
loaded_modules_array() = collect(values(loaded_modules))
377+
378+
function unreference_module(key)
379+
if haskey(loaded_modules, key)
380+
m = pop!(loaded_modules, key)
381+
# need to ensure all modules are GC rooted; will still be referenced
382+
# in module_keys
383+
end
384+
end
385+
386+
function register_all(a)
387+
for m in a
388+
if module_parent(m) === m
389+
register_root_module(module_name(m), m)
390+
end
333391
end
334392
end
335393

@@ -364,7 +422,8 @@ function _require(mod::Symbol)
364422
if JLOptions().use_compiled_modules != 0
365423
doneprecompile = _require_search_from_serialized(mod, path)
366424
if !isa(doneprecompile, Bool)
367-
return # success
425+
register_all(doneprecompile)
426+
return
368427
end
369428
end
370429

@@ -391,14 +450,16 @@ function _require(mod::Symbol)
391450
warn(m, prefix="WARNING: ")
392451
# fall-through, TODO: disable __precompile__(true) error so that the normal include will succeed
393452
else
394-
return # success
453+
register_all(m)
454+
return
395455
end
396456
end
397457

398458
# just load the file normally via include
399459
# for unknown dependencies
400460
try
401-
Base.include_relative(Main, path)
461+
Base.include_relative(__toplevel__, path)
462+
return
402463
catch ex
403464
if doneprecompile === true || JLOptions().use_compiled_modules == 0 || !precompilableerror(ex, true)
404465
rethrow() # rethrow non-precompilable=true errors
@@ -411,6 +472,7 @@ function _require(mod::Symbol)
411472
# TODO: disable __precompile__(true) error and do normal include instead of error
412473
error("Module $mod declares __precompile__(true) but require failed to create a usable precompiled cache file.")
413474
end
475+
register_all(m)
414476
end
415477
finally
416478
toplevel_load[] = last
@@ -532,7 +594,7 @@ function create_expr_cache(input::String, output::String, concrete_deps::Vector{
532594
task_local_storage()[:SOURCE_PATH] = $(source)
533595
end)
534596
end
535-
serialize(in, :(Base.include(Main, $(abspath(input)))))
597+
serialize(in, :(Base.include(Base.__toplevel__, $(abspath(input)))))
536598
if source !== nothing
537599
serialize(in, :(delete!(task_local_storage(), :SOURCE_PATH)))
538600
end
@@ -570,15 +632,9 @@ function compilecache(name::String)
570632
cachefile::String = abspath(cachepath, name*".ji")
571633
# build up the list of modules that we want the precompile process to preserve
572634
concrete_deps = copy(_concrete_dependencies)
573-
for existing in names(Main)
574-
if isdefined(Main, existing)
575-
mod = getfield(Main, existing)
576-
if isa(mod, Module) && !(mod === Main || mod === Core || mod === Base)
577-
mod = mod::Module
578-
if module_parent(mod) === Main && module_name(mod) === existing
579-
push!(concrete_deps, (existing, module_uuid(mod)))
580-
end
581-
end
635+
for (key,mod) in loaded_modules
636+
if !(mod === Main || mod === Core || mod === Base)
637+
push!(concrete_deps, (key, module_uuid(mod)))
582638
end
583639
end
584640
# run the expression and cache the result
@@ -675,7 +731,7 @@ function stale_cachefile(modpath::String, cachefile::String)
675731
if mod == :Main || mod == :Core || mod == :Base
676732
continue
677733
# Module is already loaded
678-
elseif isbindingresolved(Main, mod)
734+
elseif root_module_exists(mod)
679735
continue
680736
end
681737
name = string(mod)

base/pkg/entry.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,7 @@ function resolve(
539539
info("$(up)grading $pkg: v$ver1 => v$ver2")
540540
Write.update(pkg, Read.sha1(pkg,ver2))
541541
pkgsym = Symbol(pkg)
542-
if Base.isbindingresolved(Main, pkgsym) && isa(getfield(Main, pkgsym), Module)
542+
if Base.root_module_exists(pkgsym)
543543
push!(imported, "- $pkg")
544544
end
545545
end

base/reflection.jl

+21-10
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,17 @@ julia> fullname(Main)
5656
```
5757
"""
5858
function fullname(m::Module)
59-
m === Main && return ()
60-
m === Base && return (:Base,) # issue #10653
6159
mn = module_name(m)
60+
if m === Main || m === Base || m === Core
61+
return (mn,)
62+
end
6263
mp = module_parent(m)
6364
if mp === m
64-
# not Main, but is its own parent, means a prior Main module
65-
n = ()
65+
if mn !== :Main
66+
return (mn,)
67+
end
68+
# top-level module, not Main, called :Main => prior Main module
69+
n = (:Main,)
6670
this = Main
6771
while this !== m
6872
if isdefined(this, :LastMain)
@@ -530,15 +534,22 @@ function _subtypes(m::Module, x::Union{DataType,UnionAll},
530534
end
531535
return sts
532536
end
533-
function subtypes(m::Module, x::Union{DataType,UnionAll})
534-
if isabstract(x)
535-
sort!(collect(_subtypes(m, x)), by=string)
536-
else
537+
538+
function _subtypes_in(mods::Array, x::Union{DataType,UnionAll})
539+
if !isabstract(x)
537540
# Fast path
538-
Union{DataType,UnionAll}[]
541+
return Union{DataType,UnionAll}[]
542+
end
543+
sts = Set{Union{DataType,UnionAll}}()
544+
visited = Set{Module}()
545+
for m in mods
546+
_subtypes(m, x, sts, visited)
539547
end
548+
return sort!(collect(sts), by=string)
540549
end
541550

551+
subtypes(m::Module, x::Union{DataType,UnionAll}) = _subtypes_in([m], x)
552+
542553
"""
543554
subtypes(T::DataType)
544555
@@ -555,7 +566,7 @@ julia> subtypes(Integer)
555566
Unsigned
556567
```
557568
"""
558-
subtypes(x::Union{DataType,UnionAll}) = subtypes(Main, x)
569+
subtypes(x::Union{DataType,UnionAll}) = _subtypes_in(loaded_modules_array(), x)
559570

560571
function to_tuple_type(@nospecialize(t))
561572
@_pure_meta

base/serialize.jl

+17-12
Original file line numberDiff line numberDiff line change
@@ -347,9 +347,10 @@ function serialize(s::AbstractSerializer, d::Dict)
347347
end
348348

349349
function serialize_mod_names(s::AbstractSerializer, m::Module)
350-
p = module_parent(m)
351-
if m !== p
352-
serialize_mod_names(s, p)
350+
if Base.is_root_module(m)
351+
serialize(s, Base.root_module_key(m))
352+
else
353+
serialize_mod_names(s, module_parent(m))
353354
serialize(s, module_name(m))
354355
end
355356
end
@@ -820,21 +821,25 @@ function deserialize_svec(s::AbstractSerializer)
820821
end
821822

822823
function deserialize_module(s::AbstractSerializer)
823-
path = deserialize(s)
824-
m = Main
825-
if isa(path,Tuple) && path !== ()
826-
# old version
827-
for mname in path
828-
m = getfield(m,mname)::Module
824+
mkey = deserialize(s)
825+
if isa(mkey, Tuple)
826+
# old version, TODO: remove
827+
if mkey === ()
828+
return Main
829+
end
830+
m = Base.root_module(mkey[1])
831+
for i = 2:length(mkey)
832+
m = getfield(m, mkey[i])::Module
829833
end
830834
else
831-
mname = path
835+
m = Base.root_module(mkey)
836+
mname = deserialize(s)
832837
while mname !== ()
833-
m = getfield(m,mname)::Module
838+
m = getfield(m, mname)::Module
834839
mname = deserialize(s)
835840
end
836841
end
837-
m
842+
return m
838843
end
839844

840845
function deserialize(s::AbstractSerializer, ::Type{Method})

base/show.jl

+2-2
Original file line numberDiff line numberDiff line change
@@ -380,8 +380,8 @@ function show(io::IO, p::Pair)
380380
end
381381

382382
function show(io::IO, m::Module)
383-
if m === Main
384-
print(io, "Main")
383+
if is_root_module(m)
384+
print(io, module_name(m))
385385
else
386386
print(io, join(fullname(m),"."))
387387
end

doc/src/devdocs/require.md

+1-9
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,7 @@ The callback below is an example of how to do that:
2626
```julia
2727
# Get the fully-qualified name of a module.
2828
function module_fqn(name::Symbol)
29-
fqn = Symbol[name]
30-
mod = getfield(Main, name)
31-
parent = Base.module_parent(mod)
32-
while parent !== Main
33-
push!(fqn, Base.module_name(parent))
34-
parent = Base.module_parent(parent)
35-
end
36-
fqn = reverse!(fqn)
29+
fqn = fullname(Base.root_module(name))
3730
return join(fqn, '.')
3831
end
3932
```
40-

0 commit comments

Comments
 (0)