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

0 commit comments

Comments
 (0)