Skip to content

Commit

Permalink
pass file and line information as an argument named __source__ to a…
Browse files Browse the repository at this point in the history
…ll macros

also emit an explicit push_loc in @generated functions
rather than depending on the existence of a LineNumberNode
and other lowering heuristics to produce it
  • Loading branch information
ihnorton authored and vtjnash committed May 22, 2017
1 parent 78881f1 commit 54a185b
Show file tree
Hide file tree
Showing 35 changed files with 491 additions and 248 deletions.
10 changes: 10 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ This section lists changes that do not have deprecation warnings.
* `@__DIR__` returns the current working directory rather than `nothing` when not run
from a file ([#21759]).

* `@__FILE__` and `@__DIR__` return information relative to the file that it was parsed from,
rather than from the task-local `SOURCE_PATH` global when it was expanded.

* All macros receive an extra argument `__source__::LineNumberNode` which describes the
parser location in the source file for the `@` of the the macro call.
It can be accessed as a normal argument variable in the body of the macro.
This is implemented by inserting an extra leading argument into the
`Expr(:macrocall, :@name, LineNumberNode(...), args...)`
surface syntax. ([#21746])

* Passing the same keyword argument multiple times is now a syntax error ([#16937]).


Expand Down
4 changes: 3 additions & 1 deletion base/boot.jl
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@

#struct LineNumberNode
# line::Int
# file::Any # nominally Union{Symbol,Void}
#end

#struct LabelNode
Expand Down Expand Up @@ -281,7 +282,8 @@ _new(:GotoNode, :Int)
_new(:NewvarNode, :SlotNumber)
_new(:QuoteNode, :ANY)
_new(:SSAValue, :Int)
eval(:((::Type{LineNumberNode})(l::Int) = $(Expr(:new, :LineNumberNode, :l))))
eval(:((::Type{LineNumberNode})(l::Int) = $(Expr(:new, :LineNumberNode, :l, nothing))))
eval(:((::Type{LineNumberNode})(l::Int, f::ANY) = $(Expr(:new, :LineNumberNode, :l, :f))))
eval(:((::Type{GlobalRef})(m::Module, s::Symbol) = $(Expr(:new, :GlobalRef, :m, :s))))
eval(:((::Type{SlotNumber})(n::Int) = $(Expr(:new, :SlotNumber, :n))))
eval(:((::Type{TypedSlot})(n::Int, t::ANY) = $(Expr(:new, :TypedSlot, :n, :t))))
Expand Down
12 changes: 7 additions & 5 deletions base/docs/Docs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,11 @@ function initmeta(m::Module = current_module())
end

function signature!(tv, expr::Expr)
if isexpr(expr, (:call, :macrocall))
is_macrocall = isexpr(expr, :macrocall)
if is_macrocall || isexpr(expr, :call)
sig = :(Union{Tuple{}})
for arg in expr.args[2:end]
first_arg = is_macrocall ? 3 : 2 # skip function arguments
for arg in expr.args[first_arg:end]
isexpr(arg, :parameters) && continue
if isexpr(arg, :kw) # optional arg
push!(sig.args, :(Tuple{$(sig.args[end].args[2:end]...)}))
Expand Down Expand Up @@ -599,7 +601,7 @@ function __doc__!(meta, def, define)
# the Base image). We just need to convert each `@__doc__` marker to an `@doc`.
finddoc(def) do each
each.head = :macrocall
each.args = [Symbol("@doc"), meta, each.args[end], define]
each.args = [Symbol("@doc"), nothing, meta, each.args[end], define] # TODO: forward line number info
end
else
# `def` has already been defined during Base image gen so we just need to find and
Expand Down Expand Up @@ -642,7 +644,7 @@ const BINDING_HEADS = [:typealias, :const, :global, :(=)] # deprecation: remove
isquotedmacrocall(x) =
isexpr(x, :copyast, 1) &&
isa(x.args[1], QuoteNode) &&
isexpr(x.args[1].value, :macrocall, 1)
isexpr(x.args[1].value, :macrocall, 2)
# Simple expressions / atoms the may be documented.
isbasicdoc(x) = isexpr(x, :.) || isa(x, Union{QuoteNode, Symbol})
is_signature(x) = isexpr(x, :call) || (isexpr(x, :(::), 2) && isexpr(x.args[1], :call)) || isexpr(x, :where)
Expand Down Expand Up @@ -730,7 +732,7 @@ function docm(ex)
parsedoc(keywords[ex])
elseif isa(ex, Union{Expr, Symbol})
binding = esc(bindingexpr(namify(ex)))
if isexpr(ex, [:call, :macrocall])
if isexpr(ex, :call) || isexpr(ex, :macrocall)
sig = esc(signature(ex))
:($(doc)($binding, $sig))
else
Expand Down
7 changes: 0 additions & 7 deletions base/docs/basedocs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -644,13 +644,6 @@ to be set after construction. See `struct` and the manual for more information.
"""
kw"mutable struct"

"""
@__LINE__ -> Int
`@__LINE__` expands to the line number of the call-site.
"""
kw"@__LINE__"

"""
ans
Expand Down
2 changes: 1 addition & 1 deletion base/docs/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ function repl(io::IO, s::Symbol)
$(_repl(s))
end
end
isregex(x) = isexpr(x, :macrocall, 2) && x.args[1] === Symbol("@r_str") && !isempty(x.args[2])
isregex(x) = isexpr(x, :macrocall, 3) && x.args[1] === Symbol("@r_str") && !isempty(x.args[3])
repl(io::IO, ex::Expr) = isregex(ex) ? :(apropos($io, $ex)) : _repl(ex)
repl(io::IO, str::AbstractString) = :(apropos($io, $str))
repl(io::IO, other) = :(@doc $(esc(other)))
Expand Down
1 change: 1 addition & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1247,6 +1247,7 @@ export
# parser internal
@__FILE__,
@__DIR__,
@__LINE__,
@int128_str,
@uint128_str,
@big_str,
Expand Down
11 changes: 9 additions & 2 deletions base/expr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -276,9 +276,16 @@ end

remove_linenums!(ex) = ex
function remove_linenums!(ex::Expr)
filter!(x->!((isa(x,Expr) && x.head === :line) || isa(x,LineNumberNode)), ex.args)
if ex.head === :body || ex.head === :block || ex.head === :quote
# remove line number expressions from metadata (not argument literal or inert) position
filter!(ex.args) do x
isa(x, Expr) && x.head === :line && return false
isa(x, LineNumberNode) && return false
return true
end
end
for subex in ex.args
remove_linenums!(subex)
end
ex
return ex
end
37 changes: 18 additions & 19 deletions base/inference.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4110,39 +4110,38 @@ function inlineable(f::ANY, ft::ANY, e::Expr, atypes::Vector{Any}, sv::Inference
end

do_coverage = coverage_enabled()
if do_coverage
line = method.line
if !isempty(stmts) && isa(stmts[1], LineNumberNode)
line = (shift!(stmts)::LineNumberNode).line
line::Int = method.line
file = method.file
if !isempty(stmts)
if !do_coverage && all(inlining_ignore, stmts)
empty!(stmts)
elseif isa(stmts[1], LineNumberNode)
linenode = shift!(stmts)::LineNumberNode
line = linenode.line
isa(linenode.file, Symbol) && (file = linenode.file)
end
end
if do_coverage
# Check if we are switching module, which is necessary to catch user
# code inlined into `Base` with `--code-coverage=user`.
# Assume we are inlining directly into `enclosing` instead of another
# function inlined in it
mod = method.module
if mod === sv.mod
unshift!(stmts, Expr(:meta, :push_loc, method.file,
unshift!(stmts, Expr(:meta, :push_loc, file,
method.name, line))
else
unshift!(stmts, Expr(:meta, :push_loc, method.file,
unshift!(stmts, Expr(:meta, :push_loc, file,
method.name, line, mod))
end
push!(stmts, Expr(:meta, :pop_loc))
elseif !isempty(stmts)
if all(inlining_ignore, stmts)
empty!(stmts)
unshift!(stmts, Expr(:meta, :push_loc, file,
method.name, line))
if isa(stmts[end], LineNumberNode)
stmts[end] = Expr(:meta, :pop_loc)
else
line::Int = method.line
if isa(stmts[1], LineNumberNode)
line = (shift!(stmts)::LineNumberNode).line
end
unshift!(stmts, Expr(:meta, :push_loc, method.file,
method.name, line))
if isa(stmts[end], LineNumberNode)
stmts[end] = Expr(:meta, :pop_loc)
else
push!(stmts, Expr(:meta, :pop_loc))
end
push!(stmts, Expr(:meta, :pop_loc))
end
end
if !isempty(stmts) && !propagate_inbounds
Expand Down
6 changes: 3 additions & 3 deletions base/interactiveutil.jl
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ function code_warntype(io::IO, f, t::ANY)
end
code_warntype(f, t::ANY) = code_warntype(STDOUT, f, t)

typesof(args...) = Tuple{map(a->(isa(a,Type) ? Type{a} : typeof(a)), args)...}
typesof(args...) = Tuple{Any[ Core.Typeof(a) for a in args ]...}

gen_call_with_extracted_types(fcn, ex0::Symbol) = Expr(:call, fcn, Meta.quot(ex0))
function gen_call_with_extracted_types(fcn, ex0)
Expand All @@ -371,9 +371,9 @@ function gen_call_with_extracted_types(fcn, ex0)
exret = Expr(:none)
is_macro = false
ex = expand(ex0)
if isa(ex0, Expr) && ex0.head == :macrocall # Make @edit @time 1+2 edit the macro
if isa(ex0, Expr) && ex0.head == :macrocall # Make @edit @time 1+2 edit the macro by using the types of the *expressions*
is_macro = true
exret = Expr(:call, fcn, esc(ex0.args[1]), typesof(ex0.args[2:end]...))
exret = Expr(:call, fcn, esc(ex0.args[1]), Tuple{#=__source__=#LineNumberNode, Any[ Core.Typeof(a) for a in ex0.args[3:end] ]...})
elseif !isa(ex, Expr)
exret = Expr(:call, :error, "expression is not a function call or symbol")
elseif ex.head == :call
Expand Down
53 changes: 35 additions & 18 deletions base/loading.jl
Original file line number Diff line number Diff line change
Expand Up @@ -538,24 +538,6 @@ function source_dir()
p === nothing ? pwd() : dirname(p)
end

"""
@__FILE__ -> AbstractString
`@__FILE__` expands to a string with the absolute file path of the file containing the
macro. Returns `nothing` if run from a REPL or an empty string if evaluated by
`julia -e <expr>`. Alternatively see [`PROGRAM_FILE`](@ref).
"""
macro __FILE__() source_path() end

"""
@__DIR__ -> AbstractString
`@__DIR__` expands to a string with the directory part of the absolute path of the file
containing the macro. Returns the current working directory if run from a REPL or if
evaluated by `julia -e <expr>`.
"""
macro __DIR__() source_dir() end

include_from_node1(path::AbstractString) = include_from_node1(String(path))
function include_from_node1(_path::String)
path, prev = _include_dependency(_path)
Expand Down Expand Up @@ -823,3 +805,38 @@ function stale_cachefile(modpath::String, cachefile::String)
close(io)
end
end

"""
@__LINE__ -> Int
`@__LINE__` expands to the line number of the location of the macrocall.
Returns `0` if the line number could not be determined.
"""
macro __LINE__()
return __source__.line
end

"""
@__FILE__ -> AbstractString
`@__FILE__` expands to a string with the path to the file containing the
macrocall, or an empty string if evaluated by `julia -e <expr>`.
Returns `nothing` if the macro was missing parser source information.
Alternatively see [`PROGRAM_FILE`](@ref).
"""
macro __FILE__()
__source__.file === nothing && return nothing
return String(__source__.file)
end

"""
@__DIR__ -> AbstractString
`@__DIR__` expands to a string with the absolute path to the directory of the file
containing the macrocall.
Returns the current working directory if run from a REPL or if evaluated by `julia -e <expr>`.
"""
macro __DIR__()
__source__.file === nothing && return nothing
return abspath(dirname(String(__source__.file)))
end
2 changes: 1 addition & 1 deletion base/math.jl
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ macro evalpoly(z, p...)
:(s = muladd(x, x, y*y)),
as...,
:(muladd($ai, tt, $b)))
R = Expr(:macrocall, Symbol("@horner"), :tt, map(esc, p)...)
R = Expr(:macrocall, Symbol("@horner"), (), :tt, map(esc, p)...)
:(let tt = $(esc(z))
isa(tt, Complex) ? $C : $R
end)
Expand Down
36 changes: 22 additions & 14 deletions base/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -505,10 +505,6 @@ const prec_decl = operator_precedence(:(::))
is_expr(ex, head::Symbol) = (isa(ex, Expr) && (ex.head == head))
is_expr(ex, head::Symbol, n::Int) = is_expr(ex, head) && length(ex.args) == n

is_linenumber(ex::LineNumberNode) = true
is_linenumber(ex::Expr) = (ex.head == :line)
is_linenumber(ex) = false

is_quoted(ex) = false
is_quoted(ex::QuoteNode) = true
is_quoted(ex::Expr) = is_expr(ex, :quote, 1) || is_expr(ex, :inert, 1)
Expand Down Expand Up @@ -538,18 +534,22 @@ end

emphasize(io, str::AbstractString) = have_color ? print_with_color(Base.error_color(), io, str; bold = true) : print(io, uppercase(str))

show_linenumber(io::IO, line) = print(io," # line ",line,':')
show_linenumber(io::IO, line, file) = print(io," # ", file,", line ",line,':')
show_linenumber(io::IO, line) = print(io, "#= line ", line, " =#")
show_linenumber(io::IO, line, file) = print(io, "#= ", file, ":", line, " =#")
show_linenumber(io::IO, line, file::Void) = show_linenumber(io, line)

# show a block, e g if/for/etc
function show_block(io::IO, head, args::Vector, body, indent::Int)
print(io, head, ' ')
show_list(io, args, ", ", indent)
print(io, head)
if !isempty(args)
print(io, ' ')
show_list(io, args, ", ", indent)
end

ind = head === :module || head === :baremodule ? indent : indent + indent_width
exs = (is_expr(body, :block) || is_expr(body, :body)) ? body.args : Any[body]
for ex in exs
if !is_linenumber(ex); print(io, '\n', " "^ind); end
print(io, '\n', " "^ind)
show_unquoted(io, ex, ind, -1)
end
print(io, '\n', " "^indent)
Expand All @@ -566,7 +566,7 @@ end
# show an indented list
function show_list(io::IO, items, sep, indent::Int, prec::Int=0, enclose_operators::Bool=false)
n = length(items)
if n == 0; return end
n == 0 && return
indent += indent_width
first = true
for item in items
Expand Down Expand Up @@ -613,7 +613,7 @@ end
## AST printing ##

show_unquoted(io::IO, sym::Symbol, ::Int, ::Int) = print(io, sym)
show_unquoted(io::IO, ex::LineNumberNode, ::Int, ::Int) = show_linenumber(io, ex.line)
show_unquoted(io::IO, ex::LineNumberNode, ::Int, ::Int) = show_linenumber(io, ex.line, ex.file)
show_unquoted(io::IO, ex::LabelNode, ::Int, ::Int) = print(io, ex.label, ": ")
show_unquoted(io::IO, ex::GotoNode, ::Int, ::Int) = print(io, "goto ", ex.label)
show_unquoted(io::IO, ex::GlobalRef, ::Int, ::Int) = print(io, ex.mod, '.', ex.name)
Expand Down Expand Up @@ -913,12 +913,20 @@ function show_unquoted(io::IO, ex::Expr, indent::Int, prec::Int)
print(io, head, ' ')
show_list(io, args, ", ", indent)

elseif head === :macrocall && nargs >= 1
elseif head === :macrocall && nargs >= 2
# first show the line number argument as a comment
if isa(args[2], LineNumberNode) || is_expr(args[2], :line)
print(io, args[2], ' ')
end
# Use the functional syntax unless specifically designated with prec=-1
# and hide the line number argument from the argument list
if prec >= 0
show_call(io, :call, ex.args[1], ex.args[2:end], indent)
show_call(io, :call, args[1], args[3:end], indent)
else
show_list(io, args, ' ', indent)
show_args = Vector{Any}(length(args) - 1)
show_args[1] = args[1]
show_args[2:end] = args[3:end]
show_list(io, show_args, ' ', indent)
end

elseif head === :line && 1 <= nargs <= 2
Expand Down
2 changes: 1 addition & 1 deletion doc/REQUIRE
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
Compat 0.25.0 0.25.0+
DocStringExtensions 0.3.3 0.3.3+
Documenter 0.10.2 0.10.2+
Documenter 0.10.3 0.10.3+
Loading

0 comments on commit 54a185b

Please sign in to comment.