Skip to content

Commit d44ce70

Browse files
committed
Interpret module expressions at top level in eval()
This is a step toward an iteration interface for lowering which can return a sequence of CodeInfo to be evaluated for top level and module expressions. This also restricts lowering of module expressions to be syntactically at top level (ie, not inside a top level thunk), consistent with the existing way that they're handled in eval.
1 parent 134d4ad commit d44ce70

File tree

7 files changed

+96
-115
lines changed

7 files changed

+96
-115
lines changed

src/desugaring.jl

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ function DesugaringContext(ctx, expr_compat_mode::Bool)
1919
DesugaringContext(graph,
2020
ctx.bindings,
2121
ctx.scope_layers,
22-
first(ctx.scope_layers).mod,
22+
current_layer(ctx).mod,
2323
expr_compat_mode)
2424
end
2525

@@ -4270,39 +4270,6 @@ function expand_public(ctx, ex)
42704270
]
42714271
end
42724272

4273-
#-------------------------------------------------------------------------------
4274-
# Expand module definitions
4275-
4276-
function expand_module(ctx, ex::SyntaxTree)
4277-
modname_ex = ex[1]
4278-
@chk kind(modname_ex) == K"Identifier"
4279-
modname = modname_ex.name_val
4280-
4281-
std_defs = !has_flags(ex, JuliaSyntax.BARE_MODULE_FLAG)
4282-
4283-
body = ex[2]
4284-
@chk kind(body) == K"block"
4285-
4286-
@ast ctx ex [K"block"
4287-
[K"assert"
4288-
"global_toplevel_only"::K"Symbol"
4289-
[K"inert" ex]
4290-
]
4291-
[K"call"
4292-
eval_module ::K"Value"
4293-
ctx.mod ::K"Value"
4294-
modname ::K"String"
4295-
std_defs ::K"Bool"
4296-
ctx.expr_compat_mode ::K"Bool"
4297-
[K"inert"(body)
4298-
[K"toplevel"
4299-
children(body)...
4300-
]
4301-
]
4302-
]
4303-
]
4304-
end
4305-
43064273
#-------------------------------------------------------------------------------
43074274
# Expand docstring-annotated expressions
43084275

@@ -4477,7 +4444,7 @@ function expand_forms_2(ctx::DesugaringContext, ex::SyntaxTree, docs=nothing)
44774444
elseif k == K"$"
44784445
throw(LoweringError(ex, "`\$` expression outside string or quote block"))
44794446
elseif k == K"module"
4480-
expand_module(ctx, ex)
4447+
throw(LoweringError(ex, "`module` is only allowed at top level"))
44814448
elseif k == K"import" || k == K"using"
44824449
expand_import_or_using(ctx, ex)
44834450
elseif k == K"export" || k == K"public"

src/eval.jl

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -358,18 +358,63 @@ end
358358

359359
#-------------------------------------------------------------------------------
360360
# Our version of eval takes our own data structures
361-
@fzone "JL: eval" function eval(mod::Module, ex::SyntaxTree; expr_compat_mode::Bool=false)
361+
@fzone "JL: eval" function eval(mod::Module, ex::SyntaxTree;
362+
expr_compat_mode::Bool=false, macro_world=Base.get_world_counter())
363+
graph = ensure_macro_attributes(syntax_graph(ex))
364+
ctx = MacroExpansionContext(graph, mod, expr_compat_mode, macro_world)
365+
_eval(ctx, ex)
366+
end
367+
368+
function _eval_stmts(ctx, exs)
369+
res = nothing
370+
for ex in exs
371+
res = _eval(ctx, ex)
372+
end
373+
res
374+
end
375+
376+
function _eval_module_body(ctx, mod, ex)
377+
new_layer = ScopeLayer(length(ctx.scope_layers)+1, mod,
378+
current_layer_id(ctx), false)
379+
push!(ctx.scope_layers, new_layer)
380+
push!(ctx.scope_layer_stack, new_layer.id)
381+
stmts = kind(ex[2]) == K"block" ? ex[2][1:end] : ex[2:2]
382+
_eval_stmts(ctx, stmts)
383+
pop!(ctx.scope_layer_stack)
384+
end
385+
386+
function _eval_module(ctx, ex)
387+
# Here we just use `eval()` with an Expr to create a module.
388+
# TODO: Refactor jl_eval_module_expr() in the runtime so that we can avoid
389+
# eval.
390+
std_defs = !has_flags(ex, JuliaSyntax.BARE_MODULE_FLAG)
391+
newmod_name = Symbol(ex[1].name_val)
392+
Core.eval(current_layer(ctx).mod,
393+
Expr(:module, std_defs, newmod_name,
394+
Expr(:block, Expr(:call,
395+
newmod->_eval_module_body(ctx, newmod, ex),
396+
newmod_name))))
397+
end
398+
399+
function _eval(ctx, ex::SyntaxTree)
362400
k = kind(ex)
363401
if k == K"toplevel"
364-
x = nothing
365-
for e in children(ex)
366-
x = eval(mod, e; expr_compat_mode)
402+
_eval_stmts(ctx, children(ex))
403+
elseif k == K"module"
404+
_eval_module(ctx, ex)
405+
else
406+
ex1 = expand_forms_1(ctx, ex)
407+
if kind(ex1) in KSet"toplevel module"
408+
_eval(ctx, ex1)
409+
else
410+
ctx2, ex2 = expand_forms_2(ctx, ex1)
411+
ctx3, ex3 = resolve_scopes(ctx2, ex2)
412+
ctx4, ex4 = convert_closures(ctx3, ex3)
413+
ctx5, ex5 = linearize_ir(ctx4, ex4)
414+
thunk = to_lowered_expr(ex5)
415+
Core.eval(current_layer(ctx).mod, thunk)
367416
end
368-
return x
369417
end
370-
linear_ir = lower(mod, ex; expr_compat_mode)
371-
thunk = to_lowered_expr(linear_ir)
372-
Core.eval(mod, thunk)
373418
end
374419

375420
"""

src/macro_expansion.jl

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -520,23 +520,28 @@ function expand_forms_1(ctx::MacroExpansionContext, ex::SyntaxTree)
520520
end
521521
end
522522

523+
function ensure_macro_attributes(graph)
524+
ensure_attributes(graph,
525+
var_id=IdTag,
526+
scope_layer=LayerId,
527+
__macro_ctx__=Nothing,
528+
meta=CompileHints)
529+
end
530+
523531
@fzone "JL: macroexpand" function expand_forms_1(mod::Module, ex::SyntaxTree, expr_compat_mode::Bool, macro_world::UInt)
524532
if kind(ex) == K"local"
525533
# This error assumes we're expanding the body of a top level thunk but
526534
# we might want to make that more explicit in the pass system.
527535
throw(LoweringError(ex, "local declarations have no effect outside a scope"))
528536
end
529-
graph = ensure_attributes(syntax_graph(ex),
530-
var_id=IdTag,
531-
scope_layer=LayerId,
532-
__macro_ctx__=Nothing,
533-
meta=CompileHints)
537+
graph = ensure_macro_attributes(syntax_graph(ex))
534538
ctx = MacroExpansionContext(graph, mod, expr_compat_mode, macro_world)
535539
ex2 = expand_forms_1(ctx, reparent(ctx, ex))
536540
graph2 = delete_attributes(graph, :__macro_ctx__)
537541
# TODO: Returning the context with pass-specific mutable data is a bad way
538542
# to carry state into the next pass. We might fix this by attaching such
539543
# data to the graph itself as global attributes?
540-
ctx2 = MacroExpansionContext(graph2, ctx.bindings, ctx.scope_layers, LayerId[], expr_compat_mode, macro_world)
544+
ctx2 = MacroExpansionContext(graph2, ctx.bindings, ctx.scope_layers, ctx.scope_layer_stack,
545+
expr_compat_mode, macro_world)
541546
return ctx2, reparent(ctx2, ex2)
542547
end

src/runtime.jl

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -207,35 +207,6 @@ end
207207
#--------------------------------------------------
208208
# Functions which create modules or mutate their bindings
209209

210-
# Construct new bare module including only the "default names"
211-
#
212-
# using Core
213-
# const modname = modval
214-
# public modname
215-
#
216-
# And run statments in the toplevel expression `body`
217-
function eval_module(parentmod::Module, modname::AbstractString, std_defs::Bool,
218-
expr_compat_mode::Bool, body::SyntaxTree)
219-
# Here we just use `eval()` with an Expr.
220-
# If we wanted to avoid this we'd need to reproduce a lot of machinery from
221-
# jl_eval_module_expr()
222-
#
223-
# 1. Register / deparent toplevel modules
224-
# 2. Set binding in parent module
225-
# 3. Deal with replacing modules
226-
# * Warn if replacing
227-
# * Root old module being replaced
228-
# 4. Run __init__
229-
# * Also run __init__ for any children after parent is defined
230-
# mod = @ccall jl_new_module(Symbol(modname)::Symbol, parentmod::Module)::Any
231-
# ...
232-
name = Symbol(modname)
233-
eval_module_body(mod) = eval(mod, body; expr_compat_mode=expr_compat_mode)
234-
Core.eval(parentmod,
235-
Expr(:module, std_defs, name,
236-
Expr(:block, Expr(:call, eval_module_body, name))))
237-
end
238-
239210
const _Base_has_eval_import = isdefined(Base, :_eval_import)
240211

241212
function eval_import(imported::Bool, to::Module, from::Union{Expr, Nothing}, paths::Expr...)
@@ -357,11 +328,7 @@ function (g::GeneratedFunctionStub)(world::UInt, source::Method, @nospecialize a
357328
graph = ensure_attributes(SyntaxGraph(), kind=Kind, syntax_flags=UInt16, source=SourceAttrType,
358329
value=Any, name_val=String)
359330
# Attributes for macro expansion
360-
graph = ensure_attributes(graph,
361-
var_id=IdTag,
362-
scope_layer=LayerId,
363-
__macro_ctx__=Nothing,
364-
meta=CompileHints,
331+
graph = ensure_attributes(ensure_macro_attributes(graph),
365332
# Additional attribute for resolve_scopes, for
366333
# adding our custom lambda below
367334
is_toplevel_thunk=Bool
@@ -393,7 +360,7 @@ function (g::GeneratedFunctionStub)(world::UInt, source::Method, @nospecialize a
393360
ex1 = expand_forms_1(ctx1, reparent(ctx1, ex0))
394361
ctx1 = MacroExpansionContext(delete_attributes(graph, :__macro_ctx__),
395362
ctx1.bindings, ctx1.scope_layers,
396-
LayerId[], false, macro_world)
363+
ctx1.scope_layer_stack, false, macro_world)
397364
ex1 = reparent(ctx1, ex1)
398365

399366
# Desugaring

test/demo.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -893,7 +893,7 @@ ex = parsestmt(SyntaxTree, src, filename="foo.jl")
893893
ctx3, ex_scoped,
894894
ctx4, ex_converted,
895895
ctx5, ex_compiled,
896-
ex_expr, eval_result) = debug_lower(M, ex; verbose=true)
896+
ex_expr, eval_result) = debug_lower(M, ex; verbose=true, do_eval=true)
897897

898898
# Automatic test reduction
899899
# bad = reduce_any_failing_toplevel(JuliaLowering, joinpath(@__DIR__, "../src/desugaring.jl"))

test/misc_ir.jl

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -183,38 +183,18 @@ LoweringError:
183183
# └─┘ ── Invalid named tuple element
184184

185185
########################################
186-
# Module lowering
187-
module Mod
188-
body
189-
stmts
190-
end
191-
#---------------------
192-
1 (call JuliaLowering.eval_module TestMod "Mod" true false (inert (toplevel body stmts)))
193-
2 (return %₁)
194-
195-
########################################
196-
# Bare module lowering
197-
baremodule BareMod
198-
body
199-
stmts
200-
end
201-
#---------------------
202-
1 (call JuliaLowering.eval_module TestMod "BareMod" false false (inert (toplevel body stmts)))
203-
2 (return %₁)
204-
205-
########################################
206-
# Error: Modules not allowed in local scope
207-
let
186+
# Error: Modules not allowed inside blocks
187+
begin
208188
module C
209189
end
210190
end
211191
#---------------------
212192
LoweringError:
213-
let
193+
begin
214194
# ┌───────
215195
module C
216196
end
217-
#─────┘ ── module is only allowed in global scope
197+
#─────┘ ── `module` is only allowed at top level
218198
end
219199

220200
########################################
@@ -229,7 +209,7 @@ function f()
229209
# ┌───────
230210
module C
231211
end
232-
#─────┘ ── module is only allowed in global scope
212+
#─────┘ ── `module` is only allowed at top level
233213
end
234214

235215
########################################

test/modules.jl

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@testset "JuliaLowering.jl" begin
1+
@testset "modules" begin
22

33
test_mod = Module()
44

@@ -26,12 +26,29 @@ end
2626
@test !isdefined(B, :eval)
2727
@test !isdefined(B, :Base)
2828

29-
# modules allowed in nested code in global scope
30-
@test typeof(JuliaLowering.include_string(test_mod, """
31-
begin
29+
# Module init order
30+
Amod = JuliaLowering.include_string(test_mod, """
31+
module A
32+
init_order = []
33+
__init__() = push!(init_order, "A")
34+
module B
35+
using ..A
36+
__init__() = push!(A.init_order, "B")
37+
end
3238
module C
39+
using ..A
40+
__init__() = push!(A.init_order, "C")
41+
module D
42+
using ...A
43+
__init__() = push!(A.init_order, "D")
44+
end
45+
module E
46+
using ...A
47+
__init__() = push!(A.init_order, "E")
48+
end
3349
end
3450
end
35-
""")) == Module
51+
""")
52+
@test Amod.init_order == ["B", "D", "E", "C", "A"]
3653

3754
end

0 commit comments

Comments
 (0)