Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,65 @@ discussed in Adams' paper:

TODO: Write more here...


### Compatibility with `Expr` macros

In order to have compatibility with old-style macros which expect an `Expr`-based
data structure as input, we convert `SyntaxTree` to `Expr`, call the old-style
macro, then convert `SyntaxTree` back to `Expr` and continue with the expansion
process. This involves some loss of provenance precision but allows full
interoperability in the package ecosystem without a need to make breaking
changes.

Let's look at an example. Suppose a manually escaped old-style macro
`@oldstyle` is implemented as

```julia
macro oldstyle(a, b)
quote
x = "x in @oldstyle"
@newstyle $(esc(a)) $(esc(b)) x
end
end
```

along with two correctly escaped new-style macros:

```julia
macro call_oldstyle_macro(y)
quote
x = "x in call_oldstyle_macro"
@oldstyle $y x
end
end

macro newstyle(x, y, z)
quote
x = "x in @newstyle"
($x, $y, $z, x)
end
end
```

Then want some code like the following to "just work" with respect to hygiene

```julia
let
x = "x in outer ctx"
@call_oldstyle_macro x
end
```

When calling `@oldstyle`, we must convert `SyntaxTree` into `Expr`, but we need
to preserve the scope layer of the `x` from the outer context as it is passed
into `@oldstyle` as a macro argument. To do this, we use `Expr(:scope_layer,
:x, outer_layer_id)`. (In the old system, this would be `Expr(:escape, :x)`
instead, presuming that `@call_oldstyle_macro` was implemented using `esc()`.)

When receiving output from old style macro invocations, we preserve the escape
handling of the existing system for any symbols which aren't tagged with a
scope layer.

## Pass 2: Syntax desugaring

This pass recursively converts many special surface syntax forms to a smaller
Expand Down
4 changes: 2 additions & 2 deletions src/ast.jl
Original file line number Diff line number Diff line change
Expand Up @@ -662,8 +662,8 @@ function to_symbol(ctx, ex)
@ast ctx ex ex=>K"Symbol"
end

function new_scope_layer(ctx, mod_ref::Module=ctx.mod; is_macro_expansion=true)
new_layer = ScopeLayer(length(ctx.scope_layers)+1, ctx.mod, is_macro_expansion)
function new_scope_layer(ctx, mod_ref::Module=ctx.mod)
new_layer = ScopeLayer(length(ctx.scope_layers)+1, ctx.mod, 0, false)
push!(ctx.scope_layers, new_layer)
new_layer.id
end
Expand Down
10 changes: 9 additions & 1 deletion src/compat.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ function expr_to_syntaxtree(@nospecialize(e), lnn::Union{LineNumberNode, Nothing
SyntaxGraph(),
kind=Kind, syntax_flags=UInt16,
source=SourceAttrType, var_id=Int, value=Any,
name_val=String, is_toplevel_thunk=Bool)
name_val=String, is_toplevel_thunk=Bool,
scope_layer=LayerId)
expr_to_syntaxtree(graph, e, lnn)
end

Expand Down Expand Up @@ -423,6 +424,13 @@ function _insert_convert_expr(@nospecialize(e), graph::SyntaxGraph, src::SourceA
@assert nargs === 1
child_exprs[1] = Expr(:quoted_symbol, e.args[1])
end
elseif e.head === :scope_layer
@assert nargs === 2
@assert e.args[1] isa Symbol
@assert e.args[2] isa LayerId
st_id, src = _insert_convert_expr(e.args[1], graph, src)
setattr!(graph, st_id, scope_layer=e.args[2])
return st_id, src
elseif e.head === :symbolicgoto || e.head === :symboliclabel
@assert nargs === 1
st_k = e.head === :symbolicgoto ? K"symbolic_label" : K"symbolic_goto"
Expand Down
4 changes: 2 additions & 2 deletions src/desugaring.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ function DesugaringContext(ctx)
scope_type=Symbol, # :hard or :soft
var_id=IdTag,
is_toplevel_thunk=Bool)
DesugaringContext(graph, ctx.bindings, ctx.scope_layers, ctx.current_layer.mod)
DesugaringContext(graph, ctx.bindings, ctx.scope_layers, first(ctx.scope_layers).mod)
end

#-------------------------------------------------------------------------------
Expand Down Expand Up @@ -2555,7 +2555,7 @@ function keyword_function_defs(ctx, srcref, callex_srcref, name_str, typevar_nam
end
# TODO: Is the layer correct here? Which module should be the parent module
# of this body function?
layer = new_scope_layer(ctx; is_macro_expansion=false)
layer = new_scope_layer(ctx)
body_func_name = adopt_scope(@ast(ctx, callex_srcref, mangled_name::K"Identifier"), layer)

kwcall_arg_names = SyntaxList(ctx)
Expand Down
5 changes: 4 additions & 1 deletion src/kinds.jl
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,11 @@ function _register_kinds()
# Internal initializer for struct types, for inner constructors/functions
"new"
"splatnew"
# For expr-macro compatibility; gone after expansion
# Used for converting `esc()`'d expressions arising from old macro
# invocations during macro expansion (gone after macro expansion)
"escape"
# Used for converting the old-style macro hygienic-scope form (gone
# after macro expansion)
"hygienic_scope"
# An expression which will eventually be evaluated "statically" in
# the context of a CodeInfo and thus allows access only to globals
Expand Down
Loading
Loading