Skip to content

Commit d862d9e

Browse files
mlechuc42f
andauthored
expr_to_syntaxtree: Quoted symbol fixes (#78)
Fix #54. QuoteNode was previously converted to `(quote <contents>)` which lowers to a runtime call. - For plain symbol contents, this was too dynamic for ccall to handle, and optimizable in any case. - For Expr contents, I don't think using non-interpolating quote was correct in the first place. - For LineNumberNode contents, we wrap with a QuoteNode on the way out, so we need to unwrap it on the way in. I still need to think more about what the various forms of quoting mean when Expr and SyntaxTree are mixed, but this is at least an upgrade in number of passing tests. I've added some cases from things I broke along the way. Other fixes: - Bool isa Integer, so prevent `expr_to_syntaxtree` from giving booleans `K"Integer"` kind - enable logging of failures from `expr_to_syntaxtree` in the core lowering hook --------- Co-authored-by: Claire Foster <[email protected]>
1 parent 9ef7b97 commit d862d9e

File tree

6 files changed

+88
-35
lines changed

6 files changed

+88
-35
lines changed

src/compat.jl

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ function expr_to_syntaxtree(@nospecialize(e), lnn::Union{LineNumberNode, Nothing
2525
kind=Kind, syntax_flags=UInt16,
2626
source=SourceAttrType, var_id=Int, value=Any,
2727
name_val=String, is_toplevel_thunk=Bool,
28-
scope_layer=LayerId)
28+
scope_layer=LayerId, meta=CompileHints)
2929
expr_to_syntaxtree(graph, e, lnn)
3030
end
3131

@@ -188,17 +188,23 @@ function _insert_convert_expr(@nospecialize(e), graph::SyntaxGraph, src::SourceA
188188
if isnothing(e)
189189
st_id = _insert_tree_node(graph, K"core", src; name_val="nothing")
190190
return st_id, src
191+
elseif e isa LineNumberNode
192+
# A LineNumberNode in value position evaluates to nothing
193+
st_id = _insert_tree_node(graph, K"core", src; name_val="nothing")
194+
return st_id, e
191195
elseif e isa Symbol
192196
st_id = _insert_tree_node(graph, K"Identifier", src; name_val=String(e))
193197
return st_id, src
194-
elseif e isa QuoteNode && e.value isa Symbol
195-
# Undo special handling from st->expr
196-
return _insert_convert_expr(Expr(:quote, e.value), graph, src)
197-
# elseif e isa QuoteNode
198-
# st_id = _insert_tree_node(graph, K"inert", src)
199-
# quote_child, _ = _insert_convert_expr(e.value, graph, src)
200-
# setchildren!(graph, st_id, NodeId[quote_child])
201-
# return st_id, src
198+
elseif e isa QuoteNode
199+
if e.value isa Symbol
200+
return _insert_convert_expr(Expr(:quoted_symbol, e.value), graph, src)
201+
elseif e.value isa Expr
202+
return _insert_convert_expr(Expr(:inert, e.value), graph, src)
203+
elseif e.value isa LineNumberNode
204+
return _insert_tree_node(graph, K"Value", src; value=e.value), src
205+
else
206+
return _insert_convert_expr(e.value, graph, src)
207+
end
202208
elseif e isa String
203209
st_id = _insert_tree_node(graph, K"string", src)
204210
id_inner = _insert_tree_node(graph, K"String", src; value=e)
@@ -207,7 +213,9 @@ function _insert_convert_expr(@nospecialize(e), graph::SyntaxGraph, src::SourceA
207213
elseif !(e isa Expr)
208214
# There are other kinds we could potentially back-convert (e.g. Float),
209215
# but Value should work fine.
210-
st_k = e isa Integer ? K"Integer" : find_kind(string(typeof(e)))
216+
st_k = e isa Bool ? K"Bool" :
217+
e isa Integer ? K"Integer" :
218+
find_kind(string(typeof(e)))
211219
st_id = _insert_tree_node(graph, isnothing(st_k) ? K"Value" : st_k, src; value=e)
212220
return st_id, src
213221
end
@@ -354,6 +362,7 @@ function _insert_convert_expr(@nospecialize(e), graph::SyntaxGraph, src::SourceA
354362
lam_args = Any[]
355363
lam_eqs = Any[]
356364
for a in a1.args
365+
a isa LineNumberNode && continue
357366
a isa Expr && a.head === :(=) ? push!(lam_eqs, a) : push!(lam_args, a)
358367
end
359368
!isempty(lam_eqs) && push!(lam_args, Expr(:parameters, lam_eqs...))
@@ -399,6 +408,10 @@ function _insert_convert_expr(@nospecialize(e), graph::SyntaxGraph, src::SourceA
399408
callargs = collect_expr_parameters(e.args[1], 2)
400409
if e.args[1].head === :macrocall
401410
st_k = K"macrocall"
411+
if callargs[2] isa LineNumberNode
412+
src = callargs[2]
413+
end
414+
deleteat!(callargs, 2)
402415
c1,c1_esc = unwrap_esc(callargs[1])
403416
callargs[1] = c1_esc(Expr(:MacroName, c1))
404417
else
@@ -442,12 +455,6 @@ function _insert_convert_expr(@nospecialize(e), graph::SyntaxGraph, src::SourceA
442455
st_k = K"latestworld_if_toplevel"
443456
elseif e.head === Symbol("hygienic-scope")
444457
st_k = K"hygienic_scope"
445-
elseif e.head === :escape
446-
if length(e.args) == 1 && unwrap_esc_(e.args[1]) isa LineNumberNode
447-
# escape containing only a LineNumberNode will become empty and
448-
# thus must be removed before lowering sees it.
449-
st_k = K"TOMBSTONE"
450-
end
451458
elseif e.head === :meta
452459
# Messy and undocumented. Only sometimes we want a K"meta".
453460
@assert e.args[1] isa Symbol
@@ -549,25 +556,27 @@ function _insert_convert_expr(@nospecialize(e), graph::SyntaxGraph, src::SourceA
549556
if isnothing(child_exprs)
550557
return st_id, src
551558
else
552-
st_child_ids, last_src = _insert_child_exprs(child_exprs, graph, src)
559+
st_child_ids, last_src = _insert_child_exprs(e.head, child_exprs, graph, src)
553560
setchildren!(graph, st_id, st_child_ids)
554561
return st_id, last_src
555562
end
556563
end
557564

558-
function _insert_child_exprs(child_exprs::Vector{Any}, graph::SyntaxGraph,
559-
src::SourceAttrType)
565+
function _insert_child_exprs(head::Symbol, child_exprs::Vector{Any},
566+
graph::SyntaxGraph, src::SourceAttrType)
560567
st_child_ids = NodeId[]
561568
last_src = src
562-
for c in child_exprs
563-
if c isa LineNumberNode
564-
last_src = c
569+
for (i, c) in enumerate(child_exprs)
570+
c_unwrapped, _ = unwrap_esc(c)
571+
# If c::LineNumberNode is anywhere in a block OR c is not in tail
572+
# position, we don't need to insert `nothing` here
573+
if c_unwrapped isa LineNumberNode && (head === :block || head === :toplevel && i != length(child_exprs))
574+
last_src = c_unwrapped
565575
else
566-
(c_id, c_src) = _insert_convert_expr(c, graph, last_src)
576+
(c_id, last_src) = _insert_convert_expr(c, graph, last_src)
567577
if !isnothing(c_id)
568578
push!(st_child_ids, c_id)
569579
end
570-
last_src = something(c_src, src)
571580
end
572581
end
573582
return st_child_ids, last_src

src/hooks.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ function core_lowering_hook(@nospecialize(code), mod::Module,
1515
file = file isa Ptr{UInt8} ? unsafe_string(file) : file
1616
line = !(line isa Int64) ? Int64(line) : line
1717

18-
st0 = code isa Expr ? expr_to_syntaxtree(code, LineNumberNode(line, file)) : code
18+
local st0 = nothing
1919
try
20+
st0 = code isa Expr ? expr_to_syntaxtree(code, LineNumberNode(line, file)) : code
2021
ctx1, st1 = expand_forms_1( mod, st0, true, world)
2122
ctx2, st2 = expand_forms_2( ctx1, st1)
2223
ctx3, st3 = resolve_scopes( ctx2, st2)

src/syntax_graph.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,9 @@ function JuliaSyntax._expr_leaf_val(ex::SyntaxTree, _...)
577577
name = get(ex, :name_val, nothing)
578578
if !isnothing(name)
579579
n = Symbol(name)
580-
if hasattr(ex, :scope_layer)
580+
if kind(ex) === K"Symbol"
581+
return QuoteNode(n)
582+
elseif hasattr(ex, :scope_layer)
581583
Expr(:scope_layer, n, ex.scope_layer)
582584
else
583585
n

test/compat.jl

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -608,7 +608,24 @@ const JL = JuliaLowering
608608
"x"::K"Identifier"
609609
]
610610

611-
@test JuliaLowering.expr_to_syntaxtree(Expr(:block, esc(LineNumberNode(1)))) @ast_ [K"block"]
611+
@test JuliaLowering.expr_to_syntaxtree(Expr(:block, LineNumberNode(1)))
612+
@ast_ [K"block"]
613+
@test JuliaLowering.expr_to_syntaxtree(Expr(:block, esc(LineNumberNode(1))))
614+
@ast_ [K"block"]
615+
@test JuliaLowering.expr_to_syntaxtree(Expr(:block, QuoteNode(LineNumberNode(1))))
616+
@ast_ [K"block" LineNumberNode(1)::K"Value"]
617+
618+
# toplevel (and all other non-block forms) keep LineNumberNodes in value position
619+
@test JuliaLowering.expr_to_syntaxtree(Expr(:toplevel, esc(LineNumberNode(1))))
620+
@ast_ [K"toplevel" [K"escape" "nothing"::K"core"]]
621+
@test JuliaLowering.expr_to_syntaxtree(Expr(:toplevel, LineNumberNode(1)))
622+
@ast_ [K"toplevel" "nothing"::K"core"]
623+
@test JuliaLowering.expr_to_syntaxtree(Expr(:toplevel, QuoteNode(LineNumberNode(1))))
624+
@ast_ [K"toplevel" LineNumberNode(1)::K"Value"]
625+
@test JuliaLowering.expr_to_syntaxtree(Expr(:call, :identity, LineNumberNode(1)))
626+
@ast_ [K"call" "identity"::K"Identifier" "nothing"::K"core"]
627+
@test JuliaLowering.expr_to_syntaxtree(Expr(:call, :identity, QuoteNode(LineNumberNode(1))))
628+
@ast_ [K"call" "identity"::K"Identifier" LineNumberNode(1)::K"Value"]
612629

613630
end
614631
end

test/hooks.jl

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,38 @@ const JL = JuliaLowering
2020
end
2121

2222
if isdefined(Core, :_lower)
23+
function jeval(str)
24+
prog = parseall(Expr, str)
25+
local out
26+
try
27+
JL.activate!()
28+
out = Core.eval(test_mod, prog)
29+
finally
30+
JL.activate!(false)
31+
end
32+
end
2333
@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)
34+
out = jeval("global asdf = 1")
2835
@test out === 1
2936
@test isdefined(test_mod, :asdf)
3037

31-
prog = parseall(Expr, "module M; x = 1; end")
32-
JL.activate!()
33-
out = Core.eval(test_mod, prog)
34-
JL.activate!(false)
38+
out = jeval("module M; x = 1; end")
3539
@test out isa Module
3640
@test isdefined(test_mod, :M)
3741
@test isdefined(test_mod.M, :x)
42+
43+
# Tricky cases with symbols
44+
out = jeval("""module M
45+
Base.@constprop :aggressive function f(x); x; end
46+
const what = ccall(:jl_value_ptr, Ptr{Cvoid}, (Any,), Core.nothing)
47+
end""")
48+
@test out isa Module
49+
@test isdefined(test_mod, :M)
50+
@test isdefined(test_mod.M, :f)
51+
@test isdefined(test_mod.M, :what)
52+
53+
# TODO: broken, commented to prevent error logging
54+
# @test jeval("Base.@propagate_inbounds @inline meta_double_quote_issue(x) = x") isa Function
3855
end
3956
end
4057
end

test/macros.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,13 @@ end
346346
end
347347
isglobal_chk(1)
348348
""") === (false, false, false, false)
349+
350+
# @test appears to be the only macro in base to use :inert
351+
test_result = JuliaLowering.include_string(test_mod, """
352+
using Test
353+
@test identity(123) === 123
354+
"""; expr_compat_mode=true)
355+
@test test_result.value === true
349356
end
350357

351358
end

0 commit comments

Comments
 (0)