Skip to content

Commit 0b7124c

Browse files
mlechuc42f
andauthored
Interpolation and type-stability improvements (#105)
* Interpolation and type-stability improvements Should be a quick fix for #94. Also improve the interpolation algorithm: instead of starting with a copy of the AST and re-scanning the tree for interpolations with each call to `_interpolate_ast`, do one full unconditional pass over the initial tree that copies and interpolates. Also fixes interpolation into QuoteNode in expr compat mode (e.g. `@eval Base.$x`) --------- Co-authored-by: Claire Foster <[email protected]>
1 parent e7ff290 commit 0b7124c

File tree

3 files changed

+67
-51
lines changed

3 files changed

+67
-51
lines changed

src/runtime.jl

Lines changed: 31 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -48,22 +48,9 @@ _syntax_list(ctx::ExprInterpolationContext) = Any[]
4848
_interp_makenode(ctx::InterpolationContext, ex, args) = makenode(ctx, ex, ex, args)
4949
_interp_makenode(ctx::ExprInterpolationContext, ex, args) = Expr((ex::Expr).head, args...)
5050

51-
_to_syntax_tree(ex::SyntaxTree) = ex
52-
_to_syntax_tree(@nospecialize(ex)) = expr_to_syntaxtree(ex)
53-
54-
55-
function _contains_active_interp(ex, depth)
56-
k = _interp_kind(ex)
57-
if k == K"$" && depth == 0
58-
return true
59-
elseif _numchildren(ex) == 0
60-
return false
61-
end
62-
inner_depth = k == K"quote" ? depth + 1 :
63-
k == K"$" ? depth - 1 :
64-
depth
65-
return any(_contains_active_interp(c, inner_depth) for c in _children(ex))
66-
end
51+
_is_leaf(ex::SyntaxTree) = is_leaf(ex)
52+
_is_leaf(ex::Expr) = false
53+
_is_leaf(@nospecialize(ex)) = true
6754

6855
# Produce interpolated node for `$x` syntax
6956
function _interpolated_value(ctx::InterpolationContext, srcref, ex)
@@ -86,22 +73,17 @@ function _interpolated_value(::ExprInterpolationContext, _, ex)
8673
ex
8774
end
8875

89-
function copy_ast(::ExprInterpolationContext, @nospecialize(ex))
90-
@ccall(jl_copy_ast(ex::Any)::Any)
76+
function _interpolate_ast(ctx::ExprInterpolationContext, ex::QuoteNode, depth)
77+
out = _interpolate_ast(ctx, Expr(:inert, ex.value), depth)
78+
QuoteNode(only(out.args))
9179
end
9280

93-
function _interpolate_ast(ctx, ex, depth)
94-
if ctx.current_index[] > length(ctx.values) || !_contains_active_interp(ex, depth)
95-
return ex
96-
end
97-
98-
# We have an interpolation deeper in the tree somewhere - expand to an
99-
# expression which performs the interpolation.
81+
function _interpolate_ast(ctx, @nospecialize(ex), depth)
82+
_is_leaf(ex) && return ex
10083
k = _interp_kind(ex)
10184
inner_depth = k == K"quote" ? depth + 1 :
10285
k == K"$" ? depth - 1 :
10386
depth
104-
10587
expanded_children = _syntax_list(ctx)
10688

10789
for e in _children(ex)
@@ -120,7 +102,10 @@ function _interpolate_ast(ctx, ex, depth)
120102
_interp_makenode(ctx, ex, expanded_children)
121103
end
122104

123-
function _setup_interpolation(::Type{SyntaxTree}, ex, values)
105+
# Produced by expanding K"quote". Must create a copy of the AST. Note that
106+
# wrapping `ex` in an extra node handles the edge case where the root `ex` is
107+
# `$` (our recursion is one step removed due to forms like `($ a b)`.)
108+
function interpolate_ast(::Type{SyntaxTree}, ex::SyntaxTree, values...)
124109
# Construct graph for interpolation context. We inherit this from the macro
125110
# context where possible by detecting it using __macro_ctx__. This feels
126111
# hacky though.
@@ -137,34 +122,32 @@ function _setup_interpolation(::Type{SyntaxTree}, ex, values)
137122
end
138123
end
139124
if isnothing(graph)
140-
graph = ensure_attributes(SyntaxGraph(), kind=Kind, syntax_flags=UInt16, source=SourceAttrType,
141-
value=Any, name_val=String, scope_layer=LayerId)
125+
graph = ensure_attributes(
126+
SyntaxGraph(), kind=Kind, syntax_flags=UInt16, source=SourceAttrType,
127+
value=Any, name_val=String, scope_layer=LayerId)
142128
end
143129
ctx = InterpolationContext(graph, values, Ref(1))
144-
return ctx
145-
end
146130

147-
function _setup_interpolation(::Type{Expr}, ex, values)
148-
return ExprInterpolationContext(values, Ref(1))
131+
# We must copy the AST into our context to use it as the source reference of
132+
# generated expressions.
133+
ex1 = copy_ast(ctx, ex)
134+
out = _interpolate_ast(ctx, @ast(ctx, ex1, [K"None" ex1]), 0)
135+
length(children(out)) === 1 || throw(
136+
LoweringError(ex1, "More than one value in bare `\$` expression"))
137+
return only(children(out))
149138
end
150139

151-
function interpolate_ast(::Type{T}, ex, values...) where {T}
152-
ctx = _setup_interpolation(T, ex, values)
153-
154-
# We must copy the AST into our context to use it as the source reference
155-
# of generated expressions (and in the Expr case at least, to avoid mutation)
156-
ex1 = copy_ast(ctx, ex)
157-
if _interp_kind(ex1) == K"$"
158-
@assert length(values) == 1
159-
vs = values[1]
160-
if length(vs) > 1
161-
# :($($(xs...))) where xs is more than length 1
162-
throw(LoweringError(_to_syntax_tree(ex1),
163-
"More than one value in bare `\$` expression"))
140+
function interpolate_ast(::Type{Expr}, @nospecialize(ex), values...)
141+
ctx = ExprInterpolationContext(values, Ref(1))
142+
if ex isa Expr && ex.head === :$
143+
@assert length(values) === 1
144+
if length(ex.args) !== 1
145+
throw(LoweringError(
146+
expr_to_syntaxtree(ex), "More than one value in bare `\$` expression"))
164147
end
165-
_interpolated_value(ctx, ex1, only(vs))
148+
only(values[1])
166149
else
167-
_interpolate_ast(ctx, ex1, 0)
150+
_interpolate_ast(ctx, ex, 0)
168151
end
169152
end
170153

src/syntax_graph.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ function JuliaSyntax.head(ex::SyntaxTree)
313313
end
314314

315315
function JuliaSyntax.kind(ex::SyntaxTree)
316-
ex.kind
316+
ex.kind::JuliaSyntax.Kind
317317
end
318318

319319
function JuliaSyntax.flags(ex::SyntaxTree)
@@ -695,6 +695,7 @@ macro SyntaxTree(ex_old)
695695
# 3. Using the current file and line number, dig into the re-parsed tree and
696696
# discover the piece of AST which should be returned.
697697
ex = _find_SyntaxTree_macro(full_ex, __source__.line)
698+
isnothing(ex) && error("_find_SyntaxTree_macro failed")
698699
# 4. Do the first step of JuliaLowering's syntax lowering to get
699700
# syntax interpolations to work
700701
_, ex1 = expand_forms_1(__module__, ex, false, Base.tls_world_age())

test/quoting.jl

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ end
2727
@test sprint(io->showprov(io, ex[1][3], tree=true)) == raw"""
2828
(call g z)
2929
├─ (call g z)
30-
│ └─ @ string:3
30+
│ └─ (call g z)
31+
│ └─ @ string:3
3132
└─ ($ y)
3233
└─ @ string:5
3334
"""
@@ -184,7 +185,31 @@ let
184185
:(:($$(args...)))
185186
end
186187
""")
187-
@test_throws LoweringError JuliaLowering.eval(test_mod, multi_interp_ex)
188+
@test try
189+
JuliaLowering.eval(test_mod, multi_interp_ex)
190+
nothing
191+
catch exc
192+
@test exc isa LoweringError
193+
sprint(io->Base.showerror(io, exc, show_detail=false))
194+
end == raw"""
195+
LoweringError:
196+
let
197+
args = (:(x), :(y))
198+
:(:($$(args...)))
199+
# └─────────┘ ── More than one value in bare `$` expression
200+
end"""
201+
202+
@test try
203+
JuliaLowering.eval(test_mod, multi_interp_ex, expr_compat_mode=true)
204+
nothing
205+
catch exc
206+
@test exc isa LoweringError
207+
sprint(io->Base.showerror(io, exc, show_detail=false))
208+
end == raw"""
209+
LoweringError:
210+
No source for expression
211+
└ ── More than one value in bare `$` expression"""
212+
# ^ TODO: Improve error messages involving expr_to_syntaxtree!
188213

189214
# Interpolation of SyntaxTree Identifier vs plain Symbol
190215
symbol_interp = JuliaLowering.include_string(test_mod, raw"""
@@ -246,6 +271,13 @@ end
246271
end
247272
exs
248273
""", expr_compat_mode=true) == Any[Expr(:call, :f, :x, :y, :z), Expr(:call, :f, :x, :y, :z)]
274+
275+
# Test interpolation into QuoteNode
276+
@test JuliaLowering.include_string(test_mod, raw"""
277+
let x = :push!
278+
@eval Base.$x
279+
end
280+
"""; expr_compat_mode=true) == Base.push!
249281
end
250282

251283
end

0 commit comments

Comments
 (0)