Skip to content

Commit 90a6868

Browse files
authored
Expand import/using to newer Base runtime calls (#43)
Calls to `import` and `using` are expanded by lowering as of the changes in JuliaLang/julia#57965 and no longer dealt with by the C function `jl_toplevel_eval_flex`. This implies we can't use `eval()` for these if we want to activate JuliaLowering in Core, or we'll hit a stack overflow. I've chosen to duplicate the flisp lowering here for consistency and import paths are thus lowered to a restricted kind of quoted `Expr`. (It's mildly annoying to rely on quoted `Expr` in the lowered paths than the previous use of `Core.svec` but deleting the svec representation allows us to use `Base._eval_import` and `Base._eval_using` directly so seems like a worthy simplification.) Similarly, use a precomputed vector of names in public/export expansion - this list can be computed at expansion time rather than emitting each element into the lowered code individually. Includes minor test+CI fixes julia 1.12 in support of JETLS.
1 parent cf29215 commit 90a6868

File tree

7 files changed

+169
-100
lines changed

7 files changed

+169
-100
lines changed

.github/workflows/CI.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ jobs:
1919
fail-fast: false
2020
matrix:
2121
version:
22+
- '~1.12.0-rc1'
2223
- 'nightly'
2324
os:
2425
- ubuntu-latest

src/desugaring.jl

Lines changed: 68 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4098,7 +4098,9 @@ end
40984098
#-------------------------------------------------------------------------------
40994099
# Expand import / using / export
41004100

4101-
function _append_importpath(ctx, path_spec, path)
4101+
function expand_importpath(path)
4102+
@chk kind(path) == K"importpath"
4103+
path_spec = Expr(:.)
41024104
prev_was_dot = true
41034105
for component in children(path)
41044106
k = kind(component)
@@ -4114,13 +4116,12 @@ function _append_importpath(ctx, path_spec, path)
41144116
throw(LoweringError(component, "invalid import path: `.` in identifier path"))
41154117
end
41164118
prev_was_dot = is_dot
4117-
push!(path_spec, @ast(ctx, component, name::K"String"))
4119+
push!(path_spec.args, Symbol(name))
41184120
end
4119-
path_spec
4121+
return path_spec
41204122
end
41214123

4122-
function expand_import(ctx, ex)
4123-
is_using = kind(ex) == K"using"
4124+
function expand_import_or_using(ctx, ex)
41244125
if kind(ex[1]) == K":"
41254126
# import M: x.y as z, w
41264127
# (import (: (importpath M) (as (importpath x y) z) (importpath w)))
@@ -4131,57 +4132,87 @@ function expand_import(ctx, ex)
41314132
# (call core.svec 2 "x" "y" "z" 1 "w" "w"))
41324133
@chk numchildren(ex[1]) >= 2
41334134
from = ex[1][1]
4134-
@chk kind(from) == K"importpath"
4135-
from_path = @ast ctx from [K"call"
4136-
"svec"::K"core"
4137-
_append_importpath(ctx, SyntaxList(ctx), from)...
4138-
]
4135+
from_path = @ast ctx from QuoteNode(expand_importpath(from))::K"Value"
41394136
paths = ex[1][2:end]
41404137
else
41414138
# import A.B
41424139
# (using (importpath A B))
4143-
# (call module_import true nothing (call core.svec 1 "w"))
4140+
# (call eval_import true nothing (call core.svec 1 "w"))
41444141
@chk numchildren(ex) >= 1
4145-
from_path = nothing_(ctx, ex)
4142+
from_path = nothing
41464143
paths = children(ex)
41474144
end
4148-
path_spec = SyntaxList(ctx)
4149-
for path in paths
4145+
# Here we represent the paths as quoted `Expr` data structures
4146+
path_specs = SyntaxList(ctx)
4147+
for spec in paths
41504148
as_name = nothing
4151-
if kind(path) == K"as"
4152-
@chk numchildren(path) == 2
4153-
as_name = path[2]
4154-
@chk kind(as_name) == K"Identifier"
4155-
path = path[1]
4149+
if kind(spec) == K"as"
4150+
@chk numchildren(spec) == 2
4151+
@chk kind(spec[2]) == K"Identifier"
4152+
as_name = Symbol(spec[2].name_val)
4153+
path = QuoteNode(Expr(:as, expand_importpath(spec[1]), as_name))
4154+
else
4155+
path = QuoteNode(expand_importpath(spec))
41564156
end
4157-
@chk kind(path) == K"importpath"
4158-
push!(path_spec, @ast(ctx, path, numchildren(path)::K"Integer"))
4159-
_append_importpath(ctx, path_spec, path)
4160-
push!(path_spec, isnothing(as_name) ? nothing_(ctx, ex) :
4161-
@ast(ctx, as_name, as_name.name_val::K"String"))
4157+
push!(path_specs, @ast ctx spec path::K"Value")
41624158
end
4163-
@ast ctx ex [K"block"
4164-
[K"assert" "toplevel_only"::K"Symbol" [K"inert" ex]]
4165-
[K"call"
4166-
module_import ::K"Value"
4159+
is_using = kind(ex) == K"using"
4160+
stmts = SyntaxList(ctx)
4161+
if isnothing(from_path)
4162+
for spec in path_specs
4163+
if is_using
4164+
push!(stmts,
4165+
@ast ctx spec [K"call"
4166+
eval_using ::K"Value"
4167+
ctx.mod ::K"Value"
4168+
spec
4169+
]
4170+
)
4171+
else
4172+
push!(stmts,
4173+
@ast ctx spec [K"call"
4174+
eval_import ::K"Value"
4175+
(!is_using) ::K"Bool"
4176+
ctx.mod ::K"Value"
4177+
"nothing" ::K"top"
4178+
spec
4179+
]
4180+
)
4181+
end
4182+
# latestworld required between imports so that previous symbols
4183+
# become visible
4184+
push!(stmts, @ast ctx spec (::K"latestworld"))
4185+
end
4186+
else
4187+
push!(stmts, @ast ctx ex [K"call"
4188+
eval_import ::K"Value"
4189+
(!is_using) ::K"Bool"
41674190
ctx.mod ::K"Value"
4168-
is_using ::K"Value"
41694191
from_path
4170-
[K"call"
4171-
"svec"::K"core"
4172-
path_spec...
4173-
]
4174-
]
4192+
path_specs...
4193+
])
4194+
push!(stmts, @ast ctx ex (::K"latestworld"))
4195+
end
4196+
@ast ctx ex [K"block"
4197+
[K"assert" "toplevel_only"::K"Symbol" [K"inert" ex]]
4198+
stmts...
4199+
[K"removable" "nothing"::K"core"]
41754200
]
41764201
end
41774202

41784203
# Expand `public` or `export`
41794204
function expand_public(ctx, ex)
4205+
identifiers = String[]
4206+
for e in children(ex)
4207+
@chk kind(e) == K"Identifier" (ex, "Expected identifier")
4208+
push!(identifiers, e.name_val)
4209+
end
4210+
(e.name_val::K"String" for e in children(ex))
41804211
@ast ctx ex [K"call"
4181-
module_public::K"Value"
4212+
eval_public::K"Value"
41824213
ctx.mod::K"Value"
41834214
(kind(ex) == K"export")::K"Bool"
4184-
(e.name_val::K"String" for e in children(ex))...
4215+
identifiers::K"Value"
41854216
]
41864217
end
41874218

@@ -4421,7 +4452,7 @@ function expand_forms_2(ctx::DesugaringContext, ex::SyntaxTree, docs=nothing)
44214452
elseif k == K"module"
44224453
expand_module(ctx, ex)
44234454
elseif k == K"import" || k == K"using"
4424-
expand_import(ctx, ex)
4455+
expand_import_or_using(ctx, ex)
44254456
elseif k == K"export" || k == K"public"
44264457
expand_public(ctx, ex)
44274458
elseif k == K"abstract" || k == K"primitive"

src/linear_ir.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -855,6 +855,9 @@ function compile(ctx::LinearIRContext, ex, needs_value, in_tail_pos)
855855
end
856856
ctx.is_toplevel_thunk && emit_latestworld(ctx, ex)
857857
elseif k == K"latestworld"
858+
if needs_value
859+
throw(LoweringError(ex, "misplaced latestsworld"))
860+
end
858861
emit_latestworld(ctx, ex)
859862
elseif k == K"latestworld_if_toplevel"
860863
ctx.is_toplevel_thunk && emit_latestworld(ctx, ex)

src/runtime.jl

Lines changed: 20 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ end
110110

111111
#--------------------------------------------------
112112
# Functions called by closure conversion
113-
function eval_closure_type(mod, closure_type_name, field_names, field_is_box)
113+
function eval_closure_type(mod::Module, closure_type_name::Symbol, field_names, field_is_box)
114114
type_params = Core.TypeVar[]
115115
field_types = []
116116
for (name, isbox) in zip(field_names, field_is_box)
@@ -129,7 +129,7 @@ function eval_closure_type(mod, closure_type_name, field_names, field_is_box)
129129
false,
130130
length(field_names))
131131
Core._setsuper!(type, Core.Function)
132-
Base.eval(mod, :(const $closure_type_name = $type))
132+
@ccall jl_set_const(mod::Module, closure_type_name::Symbol, type::Any)::Cvoid
133133
Core._typebody!(false, type, Core.svec(field_types...))
134134
type
135135
end
@@ -176,39 +176,29 @@ function eval_module(parentmod, modname, body)
176176
))
177177
end
178178

179-
# Evaluate content of `import` or `using` statement
180-
function module_import(into_mod::Module, is_using::Bool,
181-
from_mod::Union{Nothing,Core.SimpleVector}, paths::Core.SimpleVector)
182-
# For now, this function converts our lowered representation back to Expr
183-
# and calls eval() to avoid replicating all of the fiddly logic in
184-
# jl_toplevel_eval_flex.
185-
# TODO: ccall Julia runtime functions directly?
186-
# * jl_module_using jl_module_use_as
187-
# * import_module jl_module_import_as
188-
path_args = []
189-
i = 1
190-
while i < length(paths)
191-
nsyms = paths[i]::Int
192-
n = i + nsyms
193-
path = Expr(:., [Symbol(paths[i+j]::String) for j = 1:nsyms]...)
194-
as_name = paths[i+nsyms+1]
195-
push!(path_args, isnothing(as_name) ? path :
196-
Expr(:as, path, Symbol(as_name)))
197-
i += nsyms + 2
179+
const _Base_has_eval_import = isdefined(Base, :_eval_import)
180+
181+
function eval_import(imported::Bool, to::Module, from::Union{Expr, Nothing}, paths::Expr...)
182+
if _Base_has_eval_import
183+
Base._eval_import(imported, to, from, paths...)
184+
else
185+
head = imported ? :import : :using
186+
ex = isnothing(from) ?
187+
Expr(head, paths...) :
188+
Expr(head, Expr(Symbol(":"), from, paths...))
189+
Base.eval(to, ex)
198190
end
199-
ex = if isnothing(from_mod)
200-
Expr(is_using ? :using : :import,
201-
path_args...)
191+
end
192+
193+
function eval_using(to::Module, path::Expr)
194+
if _Base_has_eval_import
195+
Base._eval_using(to, path)
202196
else
203-
from_path = Expr(:., [Symbol(s::String) for s in from_mod]...)
204-
Expr(is_using ? :using : :import,
205-
Expr(:(:), from_path, path_args...))
197+
Base.eval(to, Expr(:using, path))
206198
end
207-
eval(into_mod, ex)
208-
nothing
209199
end
210200

211-
function module_public(mod::Module, is_exported::Bool, identifiers...)
201+
function eval_public(mod::Module, is_exported::Bool, identifiers)
212202
# symbol jl_module_public is no longer exported as of #57765
213203
eval(mod, Expr((is_exported ? :export : :public), map(Symbol, identifiers)...))
214204
end

test/hooks.jl

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,22 @@ const JL = JuliaLowering
1919
end
2020
end
2121

22-
@testset "integration: `JuliaLowering.activate!`" begin
23-
prog = parseall(Expr, "global asdf = 1")
24-
JL.activate!()
25-
out = Core.eval(test_mod, prog)
26-
JL.activate!(false)
27-
@test out === 1
28-
@test isdefined(test_mod, :asdf)
22+
if isdefined(Core, :_lower)
23+
@testset "integration: `JuliaLowering.activate!`" begin
24+
prog = parseall(Expr, "global asdf = 1")
25+
JL.activate!()
26+
out = Core.eval(test_mod, prog)
27+
JL.activate!(false)
28+
@test out === 1
29+
@test isdefined(test_mod, :asdf)
2930

30-
prog = parseall(Expr, "module M; x = 1; end")
31-
JL.activate!()
32-
out = Core.eval(test_mod, prog)
33-
JL.activate!(false)
34-
@test out isa Module
35-
@test isdefined(test_mod, :M)
36-
@test isdefined(test_mod.M, :x)
31+
prog = parseall(Expr, "module M; x = 1; end")
32+
JL.activate!()
33+
out = Core.eval(test_mod, prog)
34+
JL.activate!(false)
35+
@test out isa Module
36+
@test isdefined(test_mod, :M)
37+
@test isdefined(test_mod.M, :x)
38+
end
3739
end
3840
end

test/import.jl

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,37 @@ end
3939
""")
4040
@test C.D.f === C.E.f
4141

42+
# Test that `using` F brings in the symbol G immediately
43+
F = JuliaLowering.include_string(test_mod, """
44+
module F
45+
export G
46+
module G
47+
export G_global
48+
G_global = "exported from G"
49+
end
50+
end
51+
""")
52+
JuliaLowering.include_string(test_mod, """
53+
using .F, .G
54+
""")
55+
@test test_mod.F === F
56+
@test test_mod.G === F.G
57+
@test test_mod.G_global === "exported from G"
58+
59+
# Similarly, that import makes symbols available immediately
60+
H = JuliaLowering.include_string(test_mod, """
61+
module H
62+
module I
63+
module J
64+
end
65+
end
66+
end
67+
""")
68+
JuliaLowering.include_string(test_mod, """
69+
import .H.I, .I.J
70+
""")
71+
@test test_mod.I === H.I
72+
@test test_mod.J === H.I.J
73+
@test test_mod.G_global === "exported from G"
74+
4275
end

0 commit comments

Comments
 (0)