Skip to content

Commit 5bdb3d1

Browse files
committed
macroexpand: stop pre-running the hygiene pass
This prepares us to delete the separate hygiene pass, and make it part of lowering resolve-scopes. This affects a few macros which expect to run macroexpand then mangle the result more, and the result of those macros is not already wrapped in `esc()`. The damage seems relatively minor however, and generally a good improvement.
1 parent c6fc12c commit 5bdb3d1

File tree

15 files changed

+125
-83
lines changed

15 files changed

+125
-83
lines changed

base/docs/Docs.jl

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,16 @@ function unblock(@nospecialize ex)
292292
return unblock(exs[1])
293293
end
294294

295+
# peek through ex to figure out what kind of expression it may eventually act like
296+
# but ignoring scopes and line numbers
297+
function unescape(@nospecialize ex)
298+
ex = unblock(ex)
299+
while isexpr(ex, :escape) || isexpr(ex, :var"hygienic-scope")
300+
ex = unblock(ex.args[1])
301+
end
302+
return ex
303+
end
304+
295305
uncurly(@nospecialize ex) = isexpr(ex, :curly) ? ex.args[1] : ex
296306

297307
namify(@nospecialize x) = astname(x, isexpr(x, :macro))::Union{Symbol,Expr,GlobalRef}
@@ -351,18 +361,19 @@ function metadata(__source__, __module__, expr, ismodule)
351361
fields = P[]
352362
last_docstr = nothing
353363
for each in (expr.args[3]::Expr).args
354-
if isa(each, Symbol) || isexpr(each, :(::))
364+
eachex = unescape(each)
365+
if isa(eachex, Symbol) || isexpr(eachex, :(::))
355366
# a field declaration
356367
if last_docstr !== nothing
357-
push!(fields, P(namify(each::Union{Symbol,Expr}), last_docstr))
368+
push!(fields, P(namify(eachex::Union{Symbol,Expr}), last_docstr))
358369
last_docstr = nothing
359370
end
360-
elseif isexpr(each, :function) || isexpr(each, :(=))
371+
elseif isexpr(eachex, :function) || isexpr(eachex, :(=))
361372
break
362-
elseif isa(each, String) || isexpr(each, :string) || isexpr(each, :call) ||
363-
(isexpr(each, :macrocall) && each.args[1] === Symbol("@doc_str"))
373+
elseif isa(eachex, String) || isexpr(eachex, :string) || isexpr(eachex, :call) ||
374+
(isexpr(eachex, :macrocall) && eachex.args[1] === Symbol("@doc_str"))
364375
# forms that might be doc strings
365-
last_docstr = each::Union{String,Expr}
376+
last_docstr = each
366377
end
367378
end
368379
dict = :($(Dict{Symbol,Any})($([(:($(P)($(quot(f)), $d)))::Expr for (f, d) in fields]...)))

base/osutils.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ macro static(ex)
1616
@label loop
1717
hd = ex.head
1818
if hd (:if, :elseif, :&&, :||)
19-
cond = Core.eval(__module__, ex.args[1])
19+
cond = Core.eval(__module__, ex.args[1])::Bool
2020
if xor(cond, hd === :||)
2121
return esc(ex.args[2])
2222
elseif length(ex.args) == 3

base/util.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,7 @@ macro kwdef(expr)
604604
kwdefs = nothing
605605
end
606606
return quote
607-
Base.@__doc__ $(esc(expr))
607+
$(esc(:($Base.@__doc__ $expr)))
608608
$kwdefs
609609
end
610610
end
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
f3d5df21fefba26f5568f719fc237bac
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
7ff5f580c1f66e5fff875601193918ec9dc40a9d5bfbb9de1900075d1a52cfbf3c702cd7293a0ddcca1f2f23f70e19d5824257256bc7b208cdc952022961d3a2

deps/checksums/Pkg-daf02a458ae6daa402a5dd6683c40d6910325c4e.tar.gz/md5

Lines changed: 0 additions & 1 deletion
This file was deleted.

deps/checksums/Pkg-daf02a458ae6daa402a5dd6683c40d6910325c4e.tar.gz/sha512

Lines changed: 0 additions & 1 deletion
This file was deleted.

doc/make.jl

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -265,12 +265,6 @@ DocMeta.setdocmeta!(
265265
maybe_revise(:(using Base.BinaryPlatforms));
266266
recursive=true, warn=false,
267267
)
268-
DocMeta.setdocmeta!(
269-
Pkg.LazilyInitializedFields,
270-
:DocTestSetup,
271-
maybe_revise(:(using Pkg.LazilyInitializedFields));
272-
recursive=true, warn=false,
273-
)
274268

275269
let r = r"buildroot=(.+)", i = findfirst(x -> occursin(r, x), ARGS)
276270
global const buildroot = i === nothing ? (@__DIR__) : first(match(r, ARGS[i]).captures)

doc/src/devdocs/reflection.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ be passed instead!). For example:
8383

8484
```jldoctest; setup = :(using InteractiveUtils)
8585
julia> macroexpand(@__MODULE__, :(@edit println("")) )
86-
:(InteractiveUtils.edit(println, (Base.typesof)("")))
86+
:($(Expr(Symbol("hygienic-scope"), :(edit($(Expr(:escape, :println)), (Base.typesof)($(Expr(:escape, ""))))), InteractiveUtils)))
8787
```
8888

8989
The functions `Base.Meta.show_sexpr` and [`dump`](@ref) are used to display S-expr style views

doc/src/manual/metaprogramming.md

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -535,20 +535,21 @@ this is an extremely useful tool for debugging macros):
535535

536536
```julia-repl sayhello2
537537
julia> ex = macroexpand(Main, :(@sayhello("human")) )
538-
:(Main.println("Hello, ", "human"))
538+
:($(Expr(Symbol("hygienic-scope"), :(println("Hello, ", "human")), Main)))
539539
540540
julia> typeof(ex)
541541
Expr
542542
```
543543

544-
We can see that the `"human"` literal has been interpolated into the expression.
544+
We can see that the macro expression has been replaced by a `hygienic-scope` expression in
545+
its place and the `"human"` literal has been interpolated into the expression.
545546

546547
There also exists a macro [`@macroexpand`](@ref) that is perhaps a bit more convenient than the `macroexpand` function:
547548

548549

549550
```jldoctest sayhello2
550551
julia> @macroexpand @sayhello "human"
551-
:(println("Hello, ", "human"))
552+
:($(Expr(Symbol("hygienic-scope"), :(println("Hello, ", "human")), Main)))
552553
```
553554

554555
### Hold up: why macros?
@@ -579,7 +580,7 @@ julia> typeof(ex)
579580
Expr
580581
581582
julia> ex
582-
:(println("I execute at runtime. The argument is: ", $(Expr(:copyast, :($(QuoteNode(:((1, 2, 3)))))))))
583+
:($(Expr(Symbol("hygienic-scope"), :(println("I execute at runtime. The argument is: ", $(Expr(:copyast, :($(QuoteNode(:((1, 2, 3))))))))), Main)))
583584
584585
julia> eval(ex)
585586
I execute at runtime. The argument is: (1, 2, 3)
@@ -719,18 +720,18 @@ of a macro expansion with the aptly named [`@macroexpand`](@ref) macro:
719720

720721
```julia-repl assert2
721722
julia> @macroexpand @assert a == b
722-
:(if Main.a == Main.b
723-
Main.nothing
724-
else
725-
Main.throw(Main.AssertionError("a == b"))
726-
end)
723+
:($(Expr(Symbol("hygienic-scope"), :(if $(Expr(:escape, :(a == b)))
724+
nothing
725+
else
726+
throw(AssertionError("a == b"))
727+
end), Base)))
727728
728729
julia> @macroexpand @assert a==b "a should equal b!"
729-
:(if Main.a == Main.b
730-
Main.nothing
731-
else
732-
Main.throw(Main.AssertionError("a should equal b!"))
733-
end)
730+
:($(Expr(Symbol("hygienic-scope"), :(if $(Expr(:escape, :(a == b)))
731+
nothing
732+
else
733+
throw(AssertionError("a should equal b!"))
734+
end), Base)))
734735
```
735736

736737
There is yet another case that the actual `@assert` macro handles: what if, in addition to printing
@@ -768,15 +769,22 @@ of expressions inside the macro body.
768769
### Hygiene
769770

770771
An issue that arises in more complex macros is that of [hygiene](https://en.wikipedia.org/wiki/Hygienic_macro).
771-
In short, macros must ensure that the variables they introduce in their returned expressions do
772-
not accidentally clash with existing variables in the surrounding code they expand into. Conversely,
773-
the expressions that are passed into a macro as arguments are often *expected* to evaluate in
774-
the context of the surrounding code, interacting with and modifying the existing variables. Another
775-
concern arises from the fact that a macro may be called in a different module from where it was
776-
defined. In this case we need to ensure that all global variables are resolved to the correct
777-
module. Julia already has a major advantage over languages with textual macro expansion (like
778-
C) in that it only needs to consider the returned expression. All the other variables (such as
779-
`msg` in `@assert` above) follow the [normal scoping block behavior](@ref scope-of-variables).
772+
We saw this a bit earlier with the hygienic-scope expression in the result of macroexpand
773+
that demarcated where our expression came from as a result of interpolation of the expanded
774+
macro into the rest of the expression.
775+
776+
This already gives Julia a major advantage over languages with textual macro expansion (like
777+
C) in that it only needs to consider the returned expression, and it cannot change the
778+
parsing of the expression surrounding it. Thus, all the other variables (such as `msg` in
779+
`@assert` above) follow the [normal scoping block behavior](@ref scope-of-variables).
780+
781+
Hygiene markers exists because macros must ensure that the variables they introduce in their
782+
returned expressions do not accidentally clash with existing variables in the surrounding
783+
code they expand into. Conversely, the expressions that are passed into a macro as arguments
784+
are often *expected* to evaluate in the context of the surrounding code, interacting with
785+
and modifying the existing variables. Another concern arises from the fact that a macro may
786+
be called in a different module from where it was defined. In this case we need to ensure
787+
that all global variables are resolved to the correct module.
780788

781789
To demonstrate these issues, let us consider writing a `@time` macro that takes an expression
782790
as its argument, records the time, evaluates the expression, records the time again, prints the

src/ast.c

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -998,6 +998,44 @@ int jl_has_meta(jl_array_t *body, jl_sym_t *sym) JL_NOTSAFEPOINT
998998
return 0;
999999
}
10001000

1001+
// Utility function to return whether `e` is any of the special AST types or
1002+
// will always evaluate to itself exactly unchanged. This corresponds to
1003+
// `is_self_quoting` in Core.Compiler utilities.
1004+
int jl_is_ast_node(jl_value_t *e) JL_NOTSAFEPOINT
1005+
{
1006+
return jl_is_newvarnode(e)
1007+
|| jl_is_code_info(e)
1008+
|| jl_is_linenode(e)
1009+
|| jl_is_gotonode(e)
1010+
|| jl_is_gotoifnot(e)
1011+
|| jl_is_returnnode(e)
1012+
|| jl_is_ssavalue(e)
1013+
|| jl_is_slotnumber(e)
1014+
|| jl_is_argument(e)
1015+
|| jl_is_quotenode(e)
1016+
|| jl_is_globalref(e)
1017+
|| jl_is_symbol(e)
1018+
|| jl_is_pinode(e)
1019+
|| jl_is_phinode(e)
1020+
|| jl_is_phicnode(e)
1021+
|| jl_is_upsilonnode(e)
1022+
|| jl_is_expr(e);
1023+
}
1024+
1025+
// any AST, except those that cannot contain symbols
1026+
// and have no side effects
1027+
int need_esc_node(jl_value_t *e) JL_NOTSAFEPOINT
1028+
{
1029+
if (jl_is_linenode(e)
1030+
|| jl_is_ssavalue(e)
1031+
|| jl_is_slotnumber(e)
1032+
|| jl_is_argument(e)
1033+
|| jl_is_quotenode(e))
1034+
return 0;
1035+
// note: jl_is_globalref(e) is not included here, since we care a little about about having a line number for it
1036+
return jl_is_ast_node(e);
1037+
}
1038+
10011039
static jl_value_t *jl_invoke_julia_macro(jl_array_t *args, jl_module_t *inmodule, jl_module_t **ctx, size_t world, int throw_load_error)
10021040
{
10031041
jl_task_t *ct = jl_current_task;
@@ -1080,7 +1118,11 @@ static jl_value_t *jl_expand_macros(jl_value_t *expr, jl_module_t *inmodule, str
10801118
newctx.parent = macroctx;
10811119
jl_value_t *a = jl_exprarg(e, 0);
10821120
jl_value_t *a2 = jl_expand_macros(a, inmodule, &newctx, onelevel, world, throw_load_error);
1083-
if (a != a2)
1121+
if (jl_is_expr(a2) && ((jl_expr_t*)a2)->head == jl_escape_sym)
1122+
expr = jl_exprarg(a2, 0);
1123+
else if (!need_esc_node(a2))
1124+
expr = a2;
1125+
else if (a != a2)
10841126
jl_array_ptr_set(e->args, 0, a2);
10851127
return expr;
10861128
}
@@ -1089,6 +1131,8 @@ static jl_value_t *jl_expand_macros(jl_value_t *expr, jl_module_t *inmodule, str
10891131
newctx.m = macroctx ? macroctx->m : inmodule;
10901132
newctx.parent = macroctx;
10911133
jl_value_t *result = jl_invoke_julia_macro(e->args, inmodule, &newctx.m, world, throw_load_error);
1134+
if (!need_esc_node(result))
1135+
return result;
10921136
jl_value_t *wrap = NULL;
10931137
JL_GC_PUSH3(&result, &wrap, &newctx.m);
10941138
// copy and wrap the result in `(hygienic-scope ,result ,newctx)
@@ -1099,10 +1143,13 @@ static jl_value_t *jl_expand_macros(jl_value_t *expr, jl_module_t *inmodule, str
10991143
result = jl_copy_ast(result);
11001144
if (!onelevel)
11011145
result = jl_expand_macros(result, inmodule, wrap ? &newctx : macroctx, onelevel, world, throw_load_error);
1102-
if (wrap) {
1146+
if (wrap && need_esc_node(result)) {
11031147
jl_exprargset(wrap, 0, result);
11041148
jl_exprargset(wrap, 1, newctx.m);
1105-
result = wrap;
1149+
if (jl_is_expr(result) && ((jl_expr_t*)result)->head == jl_escape_sym)
1150+
result = jl_exprarg(result, 0);
1151+
else
1152+
result = wrap;
11061153
}
11071154
JL_GC_POP();
11081155
return result;
@@ -1144,7 +1191,6 @@ JL_DLLEXPORT jl_value_t *jl_macroexpand(jl_value_t *expr, jl_module_t *inmodule)
11441191
JL_GC_PUSH1(&expr);
11451192
expr = jl_copy_ast(expr);
11461193
expr = jl_expand_macros(expr, inmodule, NULL, 0, jl_atomic_load_acquire(&jl_world_counter), 0);
1147-
expr = jl_call_scm_on_ast("jl-expand-macroscope", expr, inmodule);
11481194
JL_GC_POP();
11491195
return expr;
11501196
}
@@ -1155,7 +1201,6 @@ JL_DLLEXPORT jl_value_t *jl_macroexpand1(jl_value_t *expr, jl_module_t *inmodule
11551201
JL_GC_PUSH1(&expr);
11561202
expr = jl_copy_ast(expr);
11571203
expr = jl_expand_macros(expr, inmodule, NULL, 1, jl_atomic_load_acquire(&jl_world_counter), 0);
1158-
expr = jl_call_scm_on_ast("jl-expand-macroscope", expr, inmodule);
11591204
JL_GC_POP();
11601205
return expr;
11611206
}

src/jlfrontend.scm

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,10 +173,6 @@
173173
(define (jl-expand-to-thunk-stmt expr file line)
174174
(expand-to-thunk-stmt- expr file line))
175175

176-
(define (jl-expand-macroscope expr)
177-
(error-wrap (lambda ()
178-
(julia-expand-macroscope expr))))
179-
180176
;; construct default definitions of `eval` for non-bare modules
181177
;; called by jl_eval_module_expr
182178
(define (module-default-defs e)

stdlib/Pkg.version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
PKG_BRANCH = master
2-
PKG_SHA1 = daf02a458ae6daa402a5dd6683c40d6910325c4e
2+
PKG_SHA1 = 33b5e806ffbb4d61c0216b17fbca159b4a65bdd4
33
PKG_GIT_URL := https://github.com/JuliaLang/Pkg.jl.git
44
PKG_TAR_URL = https://api.github.com/repos/JuliaLang/Pkg.jl/tarball/$1

test/docs.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,7 @@ macro m1_11993()
642642
end
643643

644644
macro m2_11993()
645-
Symbol("@m1_11993")
645+
esc(Symbol("@m1_11993"))
646646
end
647647

648648
@doc "This should document @m1... since its the result of expansion" @m2_11993

0 commit comments

Comments
 (0)