Skip to content

Commit 59e9ada

Browse files
authored
Add builtin functions Core._import, Core._using; implement import/using logic in Julia (#57965)
Currently, `import` and `using` statements are compiled into calls to `jl_toplevel_eval` with the Expr as an argument. It can be useful in an interactive environment to do import/using programmatically, for which `eval(Expr(:import, ...))` or a macro is the only option at the moment (see for example `InteractiveUtils.@activate`). This PR adds two functions, `_module_import ` and `_module_using`, with these signatures, and removes `Expr(:import/:using, ...)` from the lowered IR: ``` _module_import(explicit::Bool, to::Module, ctx::Union{Expr, Nothing}, names::Expr...) _module_using(to::Module, from::Expr{Symbol}) ``` ## Lowering example `import` statements and `using X:` statements are lowered to `_module_import` like so: ``` import A => _module_import(true, Main, nothing, Expr(:., :A)) import A.b => _module_import(true, Main, nothing, Expr(:., :A, :b)) import A.b as c => _module_import(true, Main, nothing, Expr(:as, Expr(:., :A, :b), :c)) import A.B: C.d, e => _module_import(true, Main, Expr(:., :A, :B), Expr(:., :C, :d), Expr(:., :e)) import A.B: C.d as e => _module_import(true, Main, Expr(:., :A, :B), Expr(:as, Expr(:., :C, :d), :e)) using A.B: C.d, e => _module_import(false, Main, Expr(:., :A, :B), Expr(:., :C, :d), Expr(:., :e)) ``` Plain `using` statements become `_module_using`: ``` using A.B => _module_using(Main, Expr(:., :A, :B)) ``` Multiple comma-separated `using` or `import` paths are lowered to multiple calls to the appropriate builtin: ``` julia> Meta.@lower using A.B, C :($(Expr(:thunk, CodeInfo( 1 ─ builtin Core._module_using(Main, $(QuoteNode(:($(Expr(:., :A, :B)))))) │ builtin Core._module_using(Main, $(QuoteNode(:($(Expr(:., :C)))))) │ $(Expr(:latestworld)) └── return nothing )))) ```
1 parent 0b40de7 commit 59e9ada

24 files changed

+409
-352
lines changed

Compiler/src/Compiler.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ using Base: @_foldable_meta, @_gc_preserve_begin, @_gc_preserve_end, @nospeciali
6565
structdiff, tls_world_age, unconstrain_vararg_length, unionlen, uniontype_layout,
6666
uniontypes, unsafe_convert, unwrap_unionall, unwrapva, vect, widen_diagonal,
6767
_uncompressed_ir, maybe_add_binding_backedge!, datatype_min_ninitialized,
68-
partialstruct_init_undefs, fieldcount_noerror
68+
partialstruct_init_undefs, fieldcount_noerror, _eval_import, _eval_using
6969
using Base.Order
7070

7171
import Base: ==, _topmod, append!, convert, copy, copy!, findall, first, get, get!,

base/Base_compiler.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
module Base
44

5+
Core._import(Base, Core, :_eval_import, :_eval_import, true)
6+
Core._import(Base, Core, :_eval_using, :_eval_using, true)
7+
58
using .Core.Intrinsics, .Core.IR
69

710
# to start, we're going to use a very simple definition of `include`
@@ -340,6 +343,7 @@ include("ordering.jl")
340343
using .Order
341344

342345
include("coreir.jl")
346+
include("module.jl")
343347
include("invalidation.jl")
344348

345349
BUILDROOT::String = ""

base/boot.jl

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,54 @@ function Symbol(a::Array{UInt8, 1})
695695
end
696696
Symbol(s::Symbol) = s
697697

698+
# Minimal implementations of using/import for bootstrapping (supports only
699+
# `import .M: a, b, c, ...`, little error checking)
700+
let
701+
fail() = throw(ArgumentError("unsupported import/using while bootstrapping"))
702+
length(a::Array{T, 1}) where {T} = getfield(getfield(a, :size), 1)
703+
function getindex(A::Array, i::Int)
704+
Intrinsics.ult_int(Intrinsics.bitcast(UInt, Intrinsics.sub_int(i, 1)), Intrinsics.bitcast(UInt, length(A))) || fail()
705+
memoryrefget(memoryrefnew(getfield(A, :ref), i, false), :not_atomic, false)
706+
end
707+
x == y = Intrinsics.eq_int(x, y)
708+
x + y = Intrinsics.add_int(x, y)
709+
x <= y = Intrinsics.sle_int(x, y)
710+
711+
global function _eval_import(explicit::Bool, to::Module, from::Union{Expr, Nothing}, paths::Expr...)
712+
from isa Expr || fail()
713+
if length(from.args) == 2 && getindex(from.args, 1) === :.
714+
from = getglobal(to, getindex(from.args, 2))
715+
elseif length(from.args) == 1 && getindex(from.args, 1) === :Core
716+
from = Core
717+
elseif length(from.args) == 1 && getindex(from.args, 1) === :Base
718+
from = Main.Base
719+
else
720+
fail()
721+
end
722+
from isa Module || fail()
723+
i = 1
724+
while i <= nfields(paths)
725+
a = getfield(paths, i).args
726+
length(a) == 1 || fail()
727+
s = getindex(a, 1)
728+
Core._import(to, from, s, s, explicit)
729+
i += 1
730+
end
731+
end
732+
733+
global function _eval_using(to::Module, path::Expr)
734+
getindex(path.args, 1) === :. || fail()
735+
from = getglobal(to, getindex(path.args, 2))
736+
i = 3
737+
while i <= length(path.args)
738+
from = getfield(from, getindex(path.args, i))
739+
i += 1
740+
end
741+
from isa Module || fail()
742+
Core._using(to, from)
743+
end
744+
end
745+
698746
# module providing the IR object model
699747
module IR
700748

base/c.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# definitions related to C interface
44

5-
import Core.Intrinsics: cglobal
5+
import .Intrinsics: cglobal
66

77
"""
88
cglobal((symbol, library) [, type=Cvoid])

base/checked.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@ export checked_neg, checked_abs, checked_add, checked_sub, checked_mul,
1616
checked_div, checked_rem, checked_fld, checked_mod, checked_cld, checked_pow,
1717
checked_length, add_with_overflow, sub_with_overflow, mul_with_overflow
1818

19-
import Core.Intrinsics:
19+
import Core: Intrinsics
20+
import .Intrinsics:
2021
checked_sadd_int, checked_ssub_int, checked_smul_int, checked_sdiv_int,
2122
checked_srem_int,
2223
checked_uadd_int, checked_usub_int, checked_umul_int, checked_udiv_int,
2324
checked_urem_int
24-
import ..no_op_err, ..@inline, ..@noinline, ..checked_length
25+
import Base: no_op_err, @inline, @noinline, checked_length
2526

2627
# define promotion behavior for checked operations
2728
checked_add(x::Integer, y::Integer) = checked_add(promote(x,y)...)

base/docs/basedocs.jl

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2753,6 +2753,25 @@ See also [`setpropertyonce!`](@ref Base.setpropertyonce!) and [`setglobal!`](@re
27532753
"""
27542754
setglobalonce!
27552755

2756+
"""
2757+
_import(to::Module, from::Module, asname::Symbol, [sym::Symbol, imported::Bool])
2758+
2759+
With all five arguments, imports `sym` from module `from` into `to` with name
2760+
`asname`. `imported` is true for bindings created with `import` (set it to
2761+
false for `using A: ...`).
2762+
2763+
With only the first three arguments, creates a binding for the module `from`
2764+
with name `asname` in `to`.
2765+
"""
2766+
Core._import
2767+
2768+
"""
2769+
_using(to::Module, from::Module)
2770+
2771+
Add `from` to the usings list of `to`.
2772+
"""
2773+
Core._using
2774+
27562775
"""
27572776
typeof(x)
27582777

base/iterators.jl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Methods for working with Iterators.
66
baremodule Iterators
77

88
# small dance to make this work from Base or Intrinsics
9-
import ..@__MODULE__, ..parentmodule
9+
import Base: @__MODULE__, parentmodule
1010
const Base = parentmodule(@__MODULE__)
1111
using .Base:
1212
@inline, Pair, Pairs, AbstractDict, IndexLinear, IndexStyle, AbstractVector, Vector,
@@ -17,14 +17,14 @@ using .Base:
1717
any, _counttuple, eachindex, ntuple, zero, prod, reduce, in, firstindex, lastindex,
1818
tail, fieldtypes, min, max, minimum, zero, oneunit, promote, promote_shape, LazyString,
1919
afoldl
20-
using Core
20+
using .Core
2121
using Core: @doc
2222

23-
using .Base:
24-
cld, fld, resize!, IndexCartesian
25-
using .Base.Checked: checked_mul
23+
using Base:
24+
cld, fld, resize!, IndexCartesian, Checked
25+
using .Checked: checked_mul
2626

27-
import .Base:
27+
import Base:
2828
first, last,
2929
isempty, length, size, axes, ndims,
3030
eltype, IteratorSize, IteratorEltype, promote_typejoin,

base/loading.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2332,7 +2332,7 @@ function require(into::Module, mod::Symbol)
23322332
if world == typemax(UInt)
23332333
world = get_world_counter()
23342334
end
2335-
return invoke_in_world(world, __require, into, mod)
2335+
return Compiler.@zone "LOAD_Require" invoke_in_world(world, __require, into, mod)
23362336
end
23372337

23382338
function check_for_hint(into, mod)

base/module.jl

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# This file is a part of Julia. License is MIT: https://julialang.org/license
2+
3+
# Full-featured versions of _eval_import and _eval_using
4+
5+
for m in methods(_eval_import)
6+
delete_method(m)
7+
end
8+
for m in methods(_eval_using)
9+
delete_method(m)
10+
end
11+
12+
function eval_import_path(at::Module, from::Union{Module, Nothing}, path::Expr, keyword::String)
13+
isempty(path.args) && error("malformed import statement")
14+
15+
i::Int = 1
16+
function next!()
17+
i <= length(path.args) || error("invalid module path")
18+
v = path.args[i]
19+
i += 1
20+
v isa Symbol || throw(TypeError(Symbol(keyword), "", Symbol, v))
21+
v
22+
end
23+
v = next!()
24+
m = nothing
25+
26+
if from !== nothing
27+
m = from
28+
elseif v !== :.
29+
# `A.B`: call the loader to obtain the root A in the current environment.
30+
if v === :Core
31+
m = Core
32+
elseif v === :Base
33+
m = Base
34+
else
35+
m = require(at, v)
36+
m isa Module || error("failed to load module $v")
37+
end
38+
i > lastindex(path.args) && return m, nothing
39+
v = next!()
40+
else
41+
# `.A.B.C`: strip off leading dots by following parent links
42+
m = at
43+
while (v = next!()) === :.
44+
m = parentmodule(m)
45+
end
46+
end
47+
48+
while true
49+
v === :. && error("invalid $keyword path: \".\" in identifier path")
50+
i > lastindex(path.args) && break
51+
m = getglobal(m, v)
52+
m isa Module || error("invalid $keyword path: \"$v\" does not name a module")
53+
v = next!()
54+
end
55+
m, v
56+
end
57+
58+
function eval_import_path_all(at::Module, path::Expr, keyword::String)
59+
m, v = eval_import_path(at, nothing, path, keyword)
60+
if v !== nothing
61+
m = getglobal(m, v)
62+
m isa Module || error("invalid $keyword path: \"$v\" does not name a module")
63+
end
64+
m
65+
end
66+
67+
function check_macro_rename(from::Symbol, to::Symbol, keyword::String)
68+
c1(sym) = bitcast(Char, UInt32(unsafe_load(unsafe_convert(Ptr{UInt8}, sym))) << 24)
69+
from_c, to_c = c1(from), c1(to)
70+
if from_c == '@' && to_c != '@'
71+
error("cannot rename macro \"$from\" to non-macro \"$to\" in \"$keyword\"")
72+
end
73+
if from_c != '@' && to_c == '@'
74+
error("cannot rename non-macro \"$from\" to macro \"$to\" in \"$keyword\"")
75+
end
76+
end
77+
78+
"""
79+
_eval_import(imported::Bool, to::Module, from::Union{Expr, Nothing}, paths::Expr...)
80+
81+
Evaluate the import paths, calling `Core._import` for each name to be imported.
82+
`imported` imports are created with `import`, `using A: x` sets this to false.
83+
The `from` is the part of the import path before the `:`. This is the lowered
84+
form of `import`, `import ...:`, and `using ...:`.
85+
86+
```
87+
import A => _eval_import(true, Main, nothing, Expr(:., :A))
88+
import A.b => _eval_import(true, Main, nothing, Expr(:., :A, :b))
89+
import A.b as c => _eval_import(true, Main, nothing, Expr(:as, Expr(:., :A, :b), :c))
90+
import A.B: C.d, e => _eval_import(true, Main, Expr(:., :A, :B), Expr(:., :C, :d), Expr(:., :e))
91+
import A.B: C.d as e => _eval_import(true, Main, Expr(:., :A, :B), Expr(:as, Expr(:., :C, :d), :e))
92+
using A.B: C.d, e => _eval_import(false, Main, Expr(:., :A, :B), Expr(:., :C, :d), Expr(:., :e))
93+
94+
See also [`_import`](@ref Core._import).
95+
```
96+
"""
97+
function _eval_import(imported::Bool, to::Module, from::Union{Expr, Nothing}, paths::Expr...)
98+
keyword = imported ? "import" : "using"
99+
fail() = error("malformed \"$keyword\" statement")
100+
from = from !== nothing ? eval_import_path_all(to, from, keyword) : nothing
101+
102+
for path in paths
103+
path isa Expr || fail()
104+
asname = nothing
105+
if path.head === :as && length(path.args) == 2
106+
path, asname = path.args
107+
elseif path.head !== :.
108+
fail()
109+
end
110+
old_from = from
111+
from, name = eval_import_path(to, from, path, keyword)
112+
113+
if name !== nothing
114+
asname = asname === nothing ? name : asname
115+
check_macro_rename(name, asname, keyword)
116+
Core._import(to, from, asname, name, imported)
117+
else
118+
Core._import(to, from, asname === nothing ? nameof(from) : asname)
119+
end
120+
end
121+
end
122+
123+
"""
124+
_eval_using(to::Module, path::Expr)
125+
126+
Evaluate the import path to a module and call [`Core._using`](@ref) on it,
127+
making its exports available to the `to` module; this is the lowered form of
128+
`using A`.
129+
130+
```
131+
using A.B => _module_using(Main, Expr(:., :A, :B))
132+
```
133+
134+
See also [`_using`](@ref Core._using).
135+
"""
136+
function _eval_using(to::Module, path::Expr)
137+
from = eval_import_path_all(to, path, "using")
138+
Core._using(to, from)
139+
is_package = length(path.args) == 1 && path.args[1] !== :.
140+
if to == Main && is_package
141+
Core._import(to, from, nameof(from))
142+
end
143+
end

base/ordering.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
module Order
44

55

6-
import ..@__MODULE__, ..parentmodule
6+
import Base: @__MODULE__, parentmodule
77
const Base = parentmodule(@__MODULE__)
88
import .Base:
99
AbstractVector, @propagate_inbounds, isless, identity, getindex, reverse,

0 commit comments

Comments
 (0)