diff --git a/NEWS.md b/NEWS.md index ade5947627d89..6db05acddd450 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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 ----------------------------- diff --git a/base/exports.jl b/base/exports.jl index 3b4de05aa3da0..67c6c53914b41 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -964,6 +964,7 @@ export expand, gensym, macroexpand, + @macroexpand1, @macroexpand, parse, diff --git a/base/expr.jl b/base/expr.jl index 0b95636d3c45e..b005b441b3fe3 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -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-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() @@ -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 ## diff --git a/doc/src/stdlib/base.md b/doc/src/stdlib/base.md index 8ea21fe4f9301..fd97c0a598010 100644 --- a/doc/src/stdlib/base.md +++ b/doc/src/stdlib/base.md @@ -276,6 +276,7 @@ Base.gc Base.gc_enable Base.macroexpand Base.@macroexpand +Base.@macroexpand1 Base.expand Base.code_lowered Base.@code_lowered diff --git a/src/ast.c b/src/ast.c index aa1e5f3eee975..5ada8cef0d86a 100644 --- a/src/ast.c +++ b/src/ast.c @@ -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) { diff --git a/src/jlfrontend.scm b/src/jlfrontend.scm index 59828a423173e..a556310a881d0 100644 --- a/src/jlfrontend.scm +++ b/src/jlfrontend.scm @@ -225,6 +225,11 @@ (parser-wrap (lambda () (julia-expand-macros expr)))) +(define (jl-macroexpand-1 expr) + (reset-gensyms) + (parser-wrap (lambda () + (julia-expand-macros expr 1)))) + ; run whole frontend on a string. useful for testing. (define (fe str) (expand-toplevel-expr (julia-parse str))) diff --git a/src/macroexpand.scm b/src/macroexpand.scm index 8f2ad5784bb87..1031d0f54060f 100644 --- a/src/macroexpand.scm +++ b/src/macroexpand.scm @@ -466,11 +466,12 @@ ;; macro expander entry point -(define (julia-expand-macros e) - (cond ((not (pair? e)) e) +(define (julia-expand-macros e (max-depth -1)) + (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 (julia-bq-expand (cadr e) 0) max-depth)) ((eq? (car e) 'inert) e) ((eq? (car e) 'macrocall) ;; expand macro @@ -483,8 +484,7 @@ (m (cdr form))) ;; m is the macro's def module (rename-symbolic-labels - (julia-expand-macros - (resolve-expansion-vars form m)))))) + (julia-expand-macros (resolve-expansion-vars form m) (- max-depth 1)))))) ((eq? (car e) 'module) e) (else - (map julia-expand-macros e)))) + (map (lambda (ex) (julia-expand-macros ex max-depth)) e)))) diff --git a/test/replutil.jl b/test/replutil.jl index 39451b44c05c3..e0f6b72872b53 100644 --- a/test/replutil.jl +++ b/test/replutil.jl @@ -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