Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Macroexpand1 #21662

Merged
merged 6 commits into from
Jul 13, 2017
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ Library improvements
`ntuple`, `Base.literal_pow`, `sqrtm`, `lufact`, `lufact!`, `qrfact`, `qrfact!`,
`cholfact`, `cholfact!`, `_broadcast!`, `reshape`, `cat` and `cat_t`.

* A new `@macroexpand1` macro for non recursive macro expansion ([#21662]).

Compiler/Runtime improvements
-----------------------------

Expand Down
1 change: 1 addition & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -964,6 +964,7 @@ export
expand,
gensym,
macroexpand,
@macroexpand1,
@macroexpand,
parse,

Expand Down
55 changes: 47 additions & 8 deletions base/expr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -55,22 +55,51 @@ See also [`code_lowered`](@ref).
expand(m::Module, x::ANY) = ccall(:jl_expand, Any, (Any, Any), x, m)

"""
macroexpand(m, x)
macroexpand(m::Module, x; recursive=true)

Takes the expression `x` and returns an equivalent expression with all macros removed (expanded)
for executing in module `m`.
The `recursive` keyword controls whether deeper levels of nested macros are also expanded.
This is demonstrated in the example below:
```julia
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be jldoctest, but would be good for someone else to confirm

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It contains stuff like

:(#= REPL[16]:6 =# M.@m1)

that probably makes the doctest very fragile?

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be julia-repl if it uses repl format but is not a doctest.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks was not aware of julia-repl.

julia> module M
macro m1()
42
end
macro m2()
:(@m1())
end
end
M

julia> macroexpand(M, :(@m2()), recursive=true)
42

julia> macroexpand(M, :(@m2()), recursive=false)
:(#= REPL[16]:6 =# M.@m1)
```
"""
macroexpand(m::Module, x::ANY) = ccall(:jl_macroexpand, Any, (Any, Any), x, m)
function macroexpand(m::Module, x::ANY; recursive=true)
if recursive
ccall(:jl_macroexpand, Any, (Any, Any), x, m)
else
ccall(:jl_macroexpand1, Any, (Any, Any), x, m)
end
end

"""
@macroexpand

Return equivalent expression with all macros removed (expanded).

There is a difference between `@macroexpand` and `macroexpand` in that the `macroexpand` function
also takes a module where the expansion takes place.
This is best seen in the following example:
There are differences between `@macroexpand` and [`macroexpand`](@ref).

* While [`macroexpand`](@ref) takes a keyword argument `recursive`, `@macroexpand`
is always recursive. For a non recursive macro version, see [`@macroexpand1`](@ref).

* While [`macroexpand`](@ref) has an explicit `module` argument, `@macroexpand` always
expands with respect to the module in which it is called.
This is best seen in the following example:
```jldoctest
julia> module M
macro m()
Expand All @@ -93,11 +122,21 @@ julia> macro m()
julia> M.f()
(1, 1, 2)
```
With `@macroexpand` the expression expands where `@macroexpand` appears in the code (module
`M` in the example). With `macroexpand` the expression expands in the module given as the first argument.
With `@macroexpand` the expression expands where `@macroexpand` appears in the code (module `M` in the example).
With `macroexpand` the expression expands in the module given as the first argument.
"""
macro macroexpand(code)
return :(macroexpand($__module__, $(QuoteNode(code))))
return :(macroexpand($__module__, $(QuoteNode(code)), recursive=true))
end


"""
@macroexpand1

Non recursive version of [`@macroexpand`](@ref).
"""
macro macroexpand1(code)
return :(macroexpand($__module__, $(QuoteNode(code)), recursive=false))
end

## misc syntax ##
Expand Down
1 change: 1 addition & 0 deletions doc/src/stdlib/base.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ Base.gc
Base.gc_enable
Base.macroexpand
Base.@macroexpand
Base.@macroexpand1
Base.expand
Base.code_lowered
Base.@code_lowered
Expand Down
6 changes: 6 additions & 0 deletions src/ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,12 @@ JL_DLLEXPORT jl_value_t *jl_macroexpand(jl_value_t *expr, jl_module_t *inmodule)
return jl_call_scm_on_ast("jl-macroexpand", expr, inmodule);
}

JL_DLLEXPORT jl_value_t *jl_macroexpand1(jl_value_t *expr, jl_module_t *inmodule)
{
JL_TIMING(LOWERING);
return jl_call_scm_on_ast("jl-macroexpand-1", expr, inmodule);
}

// wrap expr in a thunk AST
jl_code_info_t *jl_wrap_expr(jl_value_t *expr)
{
Expand Down
5 changes: 5 additions & 0 deletions src/jlfrontend.scm
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,11 @@
(parser-wrap (lambda ()
(julia-expand-macros expr))))

(define (jl-macroexpand-1 expr)
(reset-gensyms)
(parser-wrap (lambda ()
(julia-expand-macros-limited expr 1))))

; run whole frontend on a string. useful for testing.
(define (fe str)
(expand-toplevel-expr (julia-parse str)))
Expand Down
15 changes: 9 additions & 6 deletions src/macroexpand.scm
Original file line number Diff line number Diff line change
Expand Up @@ -466,11 +466,12 @@

;; macro expander entry point

(define (julia-expand-macros e)
(cond ((not (pair? e)) e)
(define (julia-expand-macros-limited e max-depth)
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it might be more straightforward to define this as a boolean with a default value:
(define (julia-expand-macros e (recursive #t))
I'm not sure there's going to be a use case for arbitrary depths, so it's nice just to keep the implementation simple.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find the max depth implementation actually slightly simpler.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I will try a default value (define (julia-expand-macros e (max-depth -1)). Did not expect this feature.

(cond ((= max-depth 0) e)
((not (pair? e)) e)
((eq? (car e) 'quote)
;; backquote is essentially a built-in macro at the moment
(julia-expand-macros (julia-bq-expand (cadr e) 0)))
(julia-expand-macros-limited (julia-bq-expand (cadr e) 0) max-depth))
((eq? (car e) 'inert) e)
((eq? (car e) 'macrocall)
;; expand macro
Expand All @@ -483,8 +484,10 @@
(m (cdr form)))
;; m is the macro's def module
(rename-symbolic-labels
(julia-expand-macros
(resolve-expansion-vars form m))))))
(julia-expand-macros-limited (resolve-expansion-vars form m) (- max-depth 1))
))))
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

closing parens should be all on the same (previous) line

((eq? (car e) 'module) e)
(else
(map julia-expand-macros e))))
(map (lambda (ex) (julia-expand-macros-limited ex max-depth) ) e))))
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no space before closing parens


(define (julia-expand-macros e) (julia-expand-macros-limited e -1))
26 changes: 26 additions & 0 deletions test/replutil.jl
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,32 @@ let
@test (@macroexpand @seven_dollar 1+$x) == :(1 + $(Expr(:$, :x)))
end

macro nest1(code)
code
end

macro nest2(code)
:(@nest1 $code)
end

macro nest2b(code)
:(@nest1($code); @nest1($code))
end

@testset "@macroexpand1" begin
M = @__MODULE__
_macroexpand1(ex) = macroexpand(M, ex, recursive=false)
ex = :(@nest1 42)
@test _macroexpand1(ex) == macroexpand(M,ex)
ex = :(@nest2 42)
@test _macroexpand1(ex) != macroexpand(M,ex)
@test _macroexpand1(_macroexpand1(ex)) == macroexpand(M,ex)
ex = :(@nest2b 42)
@test _macroexpand1(ex) != macroexpand(M,ex)
@test _macroexpand1(_macroexpand1(ex)) == macroexpand(M, ex)
@test (@macroexpand1 @nest2b 42) == _macroexpand1(ex)
end

foo_9965(x::Float64; w=false) = x
foo_9965(x::Int) = 2x

Expand Down