Skip to content

Commit

Permalink
Merge pull request #22623 from c42f/propagate-sourceloc
Browse files Browse the repository at this point in the history
provide `rewrite_sourceloc!` function
  • Loading branch information
vtjnash authored Oct 30, 2020
2 parents 959aa8f + 7a6495c commit 10b0a01
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 6 deletions.
40 changes: 37 additions & 3 deletions base/meta.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@ using ..CoreLogging

export quot,
isexpr,
replace_sourceloc!,
show_sexpr,
@dump

"""
Meta.quot(ex)::Expr
Quote expression `ex` to produce an expression with head `quote`. This can for instance be used to represent objects of type `Expr` in the AST.
See also the manual section about [QuoteNode](@ref man-quote-node).
Quote expression `ex` to produce an expression with head `quote`. This can for
instance be used to represent objects of type `Expr` in the AST. See also the
manual section about [QuoteNode](@ref man-quote-node).
# Examples
```jldoctest
Expand All @@ -38,7 +40,10 @@ quot(ex) = Expr(:quote, ex)
"""
Meta.isexpr(ex, head[, n])::Bool
Check if `ex` is an expression with head `head` and `n` arguments.
Return true if `ex` is an `Expr` with the given type `head` and optionally that
the argument list is of length `n`. `head` may be a `Symbol` or collection of
`Symbol`s. For example, to check that a macro was passed a function call
expression, you might use `isexpr(ex, :call)`.
# Examples
```jldoctest
Expand Down Expand Up @@ -66,6 +71,35 @@ isexpr(@nospecialize(ex), heads) = isa(ex, Expr) && in(ex.head, heads)
isexpr(@nospecialize(ex), head::Symbol, n::Int) = isa(ex, Expr) && ex.head === head && length(ex.args) == n
isexpr(@nospecialize(ex), heads, n::Int) = isa(ex, Expr) && in(ex.head, heads) && length(ex.args) == n

"""
replace_sourceloc!(location, expr)
Overwrite the caller source location for each macro call in `expr`, returning
the resulting AST. This is useful when you need to wrap a macro inside a
macro, and want the inner macro to see the `__source__` location of the outer
macro. For example:
```
macro test_is_one(ex)
replace_sourceloc!(__source__, :(@test \$(esc(ex)) == 1))
end
@test_is_one 2
```
`@test` now reports the location of the call `@test_is_one 2` to the user,
rather than line 2 where `@test` is used as an implementation detail.
"""
function replace_sourceloc!(sourceloc, @nospecialize(ex))
if ex isa Expr
if ex.head == :macrocall
ex.args[2] = sourceloc
end
map!(e -> replace_sourceloc!(sourceloc, e), ex.args, ex.args)
end
return ex
end


"""
Meta.show_sexpr([io::IO,], ex)
Expand Down
3 changes: 3 additions & 0 deletions doc/src/base/base.md
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,9 @@ Base.parentmodule
Base.pathof(::Module)
Base.moduleroot
Base.@__MODULE__
Base.@__FILE__
Base.@__DIR__
Base.@__LINE__
Base.fullname
Base.names
Core.nfields
Expand Down
3 changes: 0 additions & 3 deletions doc/src/base/file.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,6 @@ Base.Filesystem.issticky
Base.Filesystem.homedir
Base.Filesystem.dirname
Base.Filesystem.basename
Base.@__FILE__
Base.@__DIR__
Base.@__LINE__
Base.Filesystem.isabspath
Base.Filesystem.isdirpath
Base.Filesystem.joinpath
Expand Down
10 changes: 10 additions & 0 deletions test/meta.jl
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,16 @@ using Base.Meta
@test isexpr(:(1+1),(:call,))
@test isexpr(1,:call)==false
@test isexpr(:(1+1),:call,3)

let
fakeline = LineNumberNode(100000,"A")
# Interop with __LINE__
@test macroexpand(@__MODULE__, replace_sourceloc!(fakeline, :(@__LINE__))) == fakeline.line
# replace_sourceloc! should recurse:
@test replace_sourceloc!(fakeline, :((@a) + 1)).args[2].args[2] == fakeline
@test replace_sourceloc!(fakeline, :(@a @b)).args[3].args[2] == fakeline
end

ioB = IOBuffer()
show_sexpr(ioB,:(1+1))

Expand Down

0 comments on commit 10b0a01

Please sign in to comment.