From 1ee84c8ec4fe0adcac571fe2ab3a8a1c7422c3f0 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Fri, 5 May 2017 15:59:50 -0400 Subject: [PATCH 1/3] get loaddocs to give line numbers when it fails --- base/docs/Docs.jl | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/base/docs/Docs.jl b/base/docs/Docs.jl index 005d4a6a9f881..53552372a265d 100644 --- a/base/docs/Docs.jl +++ b/base/docs/Docs.jl @@ -749,11 +749,21 @@ include("utils.jl") # Swap out the bootstrap macro with the real one. Core.atdoc!(docm) +macro local_hygiene(expr) + # removes `esc` Exprs relative to the module argument to expand + # and resolves everything else relative to this (Doc) module + # this allows us to get good errors and backtraces + # from calling docm (by not using macros), + # while also getting macro-expansion correct (by using the macro-expander) + return expr +end function loaddocs(docs) + unescape = GlobalRef(Docs, Symbol("@local_hygiene")) for (mod, ex, str, file, line) in docs data = Dict(:path => string(file), :linenumber => line) doc = docstr(str, data) - eval(mod, :(@doc($doc, $ex, false))) + docstring = eval(mod, Expr(:body, Expr(:return, Expr(:call, QuoteNode(docm), QuoteNode(doc), QuoteNode(ex), false)))) # expand the real @doc macro now (using a hack because macroexpand takes current-module as an implicit argument) + eval(mod, Expr(:macrocall, unescape, nothing, docstring)) end empty!(docs) end From fcdf4376415fde3b76faeb1c6f6841735497c8ea Mon Sep 17 00:00:00 2001 From: Isaiah Date: Fri, 5 May 2017 15:57:25 -0400 Subject: [PATCH 2/3] pass file and line information as an argument named `__source__` to all 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 --- NEWS.md | 10 ++ base/boot.jl | 4 +- base/docs/Docs.jl | 12 +- base/docs/basedocs.jl | 7 - base/docs/utils.jl | 2 +- base/exports.jl | 1 + base/expr.jl | 11 +- base/inference.jl | 37 +++-- base/interactiveutil.jl | 6 +- base/loading.jl | 53 ++++--- base/math.jl | 2 +- base/show.jl | 36 +++-- doc/REQUIRE | 2 +- doc/src/devdocs/ast.md | 36 ++--- doc/src/manual/metaprogramming.md | 47 ++++++ doc/src/stdlib/file.md | 2 +- .../clustermanager/simple/UnixDomainCM.jl | 3 +- examples/clustermanager/simple/test_simple.jl | 2 +- src/ast.c | 50 +++++-- src/codegen.cpp | 6 +- src/jltypes.c | 4 +- src/julia-parser.scm | 66 +++++---- src/julia-syntax.scm | 1 + src/julia.h | 17 +-- src/method.c | 50 +++++-- src/rtutils.c | 4 +- test/ambiguous.jl | 2 +- test/docs.jl | 21 +-- test/loading.jl | 12 +- test/parse.jl | 40 +++--- test/reflection.jl | 8 +- test/replutil.jl | 43 +++--- test/show.jl | 136 +++++++++++++----- test/stacktraces.jl | 4 +- test/worlds.jl | 2 +- 35 files changed, 491 insertions(+), 248 deletions(-) diff --git a/NEWS.md b/NEWS.md index 03667d85d753d..5a00f40c02b00 100644 --- a/NEWS.md +++ b/NEWS.md @@ -24,6 +24,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 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]). * `getsockname` on a `TCPSocket` now returns the locally bound address and port diff --git a/base/boot.jl b/base/boot.jl index d0a686e761bf5..c818bdfd0dead 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -87,6 +87,7 @@ #struct LineNumberNode # line::Int +# file::Any # nominally Union{Symbol,Void} #end #struct LabelNode @@ -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)))) diff --git a/base/docs/Docs.jl b/base/docs/Docs.jl index 53552372a265d..40bbb74f42729 100644 --- a/base/docs/Docs.jl +++ b/base/docs/Docs.jl @@ -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]...)})) @@ -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 @@ -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) @@ -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 diff --git a/base/docs/basedocs.jl b/base/docs/basedocs.jl index 9134bbc3925f7..4221f061bfcc3 100644 --- a/base/docs/basedocs.jl +++ b/base/docs/basedocs.jl @@ -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 diff --git a/base/docs/utils.jl b/base/docs/utils.jl index 0daf26f78c183..4a3a8b1e7a479 100644 --- a/base/docs/utils.jl +++ b/base/docs/utils.jl @@ -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))) diff --git a/base/exports.jl b/base/exports.jl index 376a9704b6d55..3a4c2fafdbd11 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -1248,6 +1248,7 @@ export # parser internal @__FILE__, @__DIR__, + @__LINE__, @int128_str, @uint128_str, @big_str, diff --git a/base/expr.jl b/base/expr.jl index 21d66ec4a445c..d23a1909701f6 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -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 diff --git a/base/inference.jl b/base/inference.jl index 9f73259dafcf0..45c030af11efb 100644 --- a/base/inference.jl +++ b/base/inference.jl @@ -4140,39 +4140,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 diff --git a/base/interactiveutil.jl b/base/interactiveutil.jl index 5b959f063ea2a..34f7724736493 100644 --- a/base/interactiveutil.jl +++ b/base/interactiveutil.jl @@ -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) @@ -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 diff --git a/base/loading.jl b/base/loading.jl index 201b483ace14f..18c4765790531 100644 --- a/base/loading.jl +++ b/base/loading.jl @@ -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 `. 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 `. -""" -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) @@ -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 `. +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 `. +""" +macro __DIR__() + __source__.file === nothing && return nothing + return abspath(dirname(String(__source__.file))) +end diff --git a/base/math.jl b/base/math.jl index 4d8eb64fc8d32..a383ca43aeb8c 100644 --- a/base/math.jl +++ b/base/math.jl @@ -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) diff --git a/base/show.jl b/base/show.jl index 3e39b419f284b..f21483c699771 100644 --- a/base/show.jl +++ b/base/show.jl @@ -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) @@ -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) @@ -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 @@ -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) @@ -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 diff --git a/doc/REQUIRE b/doc/REQUIRE index e8869bde5e55a..aed1a491d5225 100644 --- a/doc/REQUIRE +++ b/doc/REQUIRE @@ -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+ diff --git a/doc/src/devdocs/ast.md b/doc/src/devdocs/ast.md index 62006cf4d62d3..157dd60a624c0 100644 --- a/doc/src/devdocs/ast.md +++ b/doc/src/devdocs/ast.md @@ -407,21 +407,21 @@ call. Finally, chains of comparisons have their own special expression structure ### Macros -| Input | AST | -|:------------- |:------------------------------------- | -| `@m x y` | `(macrocall @m x y)` | -| `Base.@m x y` | `(macrocall (. Base (quote @m)) x y)` | -| `@Base.m x y` | `(macrocall (. Base (quote @m)) x y)` | +| Input | AST | +|:------------- |:-------------------------------------------- | +| `@m x y` | `(macrocall @m (line) x y)` | +| `Base.@m x y` | `(macrocall (. Base (quote @m)) (line) x y)` | +| `@Base.m x y` | `(macrocall (. Base (quote @m)) (line) x y)` | ### Strings -| Input | AST | -|:--------------- |:---------------------------- | -| `"a"` | `"a"` | -| `x"y"` | `(macrocall @x_str "y")` | -| `x"y"z` | `(macrocall @x_str "y" "z")` | -| `"x = $x"` | `(string "x = " x)` | -| ``` `a b c` ``` | `(macrocall @cmd "a b c")` | +| Input | AST | +|:--------------- |:----------------------------------- | +| `"a"` | `"a"` | +| `x"y"` | `(macrocall @x_str (line) "y")` | +| `x"y"z` | `(macrocall @x_str (line) "y" "z")` | +| `"x = $x"` | `(string "x = " x)` | +| ``` `a b c` ``` | `(macrocall @cmd (line) "a b c")` | Doc string syntax: @@ -430,7 +430,7 @@ Doc string syntax: f(x) = x ``` -parses as `(macrocall (|.| Core '@doc) "some docs" (= (call f x) (block x)))`. +parses as `(macrocall (|.| Core '@doc) (line) "some docs" (= (call f x) (block x)))`. ### Imports and such @@ -449,11 +449,11 @@ parses as `(macrocall (|.| Core '@doc) "some docs" (= (call f x) (block x)))`. Julia supports more number types than many scheme implementations, so not all numbers are represented directly as scheme numbers in the AST. -| Input | AST | -|:----------------------- |:------------------------------------------------ | -| `11111111111111111111` | `(macrocall @int128_str "11111111111111111111")` | -| `0xfffffffffffffffff` | `(macrocall @uint128_str "0xfffffffffffffffff")` | -| `1111...many digits...` | `(macrocall @big_str "1111....")` | +| Input | AST | +|:----------------------- |:------------------------------------------------------- | +| `11111111111111111111` | `(macrocall @int128_str (null) "11111111111111111111")` | +| `0xfffffffffffffffff` | `(macrocall @uint128_str (null) "0xfffffffffffffffff")` | +| `1111...many digits...` | `(macrocall @big_str (null) "1111....")` | ### Block forms diff --git a/doc/src/manual/metaprogramming.md b/doc/src/manual/metaprogramming.md index 6e937ba18b8c2..50d8c6de6f924 100644 --- a/doc/src/manual/metaprogramming.md +++ b/doc/src/manual/metaprogramming.md @@ -509,6 +509,27 @@ julia> @showarg(println("Yo!")) :(println("Yo!")) ``` +In addition to the given argument list, every macro is passed an extra argument named `__source__` +providing information (in the form of a `LineNumberNode` object) about the parser location +of the `@` sign from the macro invocation. + +This allows macros to include better error diagnostic information, +and is commonly used by logging, string-parser macros, and docs, for example, +as well as to implement the `@__LINE__`, `@__FILE__`, and `@__DIR__` macros. + +The location information can be accessed by referencing `__source__.line` and `__source__.file`: + +```jldoctest +julia> macro __LOCATION__(); return QuoteNode(__source__); end + +julia> dump( + @__LOCATION__( + )) +LineNumberNode + line: Int64 2 + file: Symbol REPL[2] +``` + ### Building an advanced macro Here is a simplified definition of Julia's `@assert` macro: @@ -708,6 +729,32 @@ julia> foo() This kind of manipulation of variables should be used judiciously, but is occasionally quite handy. +Getting the hygiene rules correct can be a formidable challenge. +Before using a macro, you might want to consider whether a function closure +would be sufficient. Another useful strategy is to defer as much work as possible to runtime. +For example, many macros simply wrap their arguments in a QuoteNode or other similar Expr. +Some examples of this include `@task body` which simply returns `schedule(Task(() -> $body))`, +and `@eval expr`, which simply returns `eval(QuoteNode(expr))`. + +To demonstrate, we might rewrite the `@time` example above as: + +```julia +macro time(expr) + return :(timeit(() -> $(esc(expr)))) +end +function timeit(f) + t0 = time() + val = f() + t1 = time() + println("elapsed time: ", t1-t0, " seconds") + return val +end +``` + +However, we don't do this for a good reason: wrapping the `expr` in a new scope block (the anonymous function) +also slightly changes the meaning of the expression (the scope of any variables in it), +while we want `@time` to be usable with minimum impact on the wrapped code. + ## Code Generation When a significant amount of repetitive boilerplate code is required, it is common to generate diff --git a/doc/src/stdlib/file.md b/doc/src/stdlib/file.md index e0f5874d0d527..26e9f9d577928 100644 --- a/doc/src/stdlib/file.md +++ b/doc/src/stdlib/file.md @@ -49,7 +49,7 @@ Base.Filesystem.dirname Base.Filesystem.basename Base.@__FILE__ Base.@__DIR__ -@__LINE__ +Base.@__LINE__ Base.Filesystem.isabspath Base.Filesystem.isdirpath Base.Filesystem.joinpath diff --git a/examples/clustermanager/simple/UnixDomainCM.jl b/examples/clustermanager/simple/UnixDomainCM.jl index e3a1f7f9d350a..afffa610a21b9 100644 --- a/examples/clustermanager/simple/UnixDomainCM.jl +++ b/examples/clustermanager/simple/UnixDomainCM.jl @@ -12,7 +12,8 @@ function launch(manager::UnixDomainCM, params::Dict, launched::Array, c::Conditi for i in 1:manager.np sockname = tempname() try - cmd = `$(params[:exename]) --startup-file=no $(@__FILE__) udwrkr $sockname $cookie` + __file__ = @__FILE__ + cmd = `$(params[:exename]) --startup-file=no $__file__ udwrkr $sockname $cookie` pobj = open(cmd) wconfig = WorkerConfig() diff --git a/examples/clustermanager/simple/test_simple.jl b/examples/clustermanager/simple/test_simple.jl index 57e8a8a0f28fe..2460eadd77b53 100644 --- a/examples/clustermanager/simple/test_simple.jl +++ b/examples/clustermanager/simple/test_simple.jl @@ -1,6 +1,6 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -cmanpath = joinpath(dirname(@__FILE__), "UnixDomainCM.jl") +cmanpath = joinpath(@__DIR__, "UnixDomainCM.jl") include(cmanpath) npids = addprocs(UnixDomainCM(2)) diff --git a/src/ast.c b/src/ast.c index 6b9ac084649ff..9b3b8c124e6a6 100644 --- a/src/ast.c +++ b/src/ast.c @@ -176,19 +176,40 @@ value_t fl_invoke_julia_macro(fl_context_t *fl_ctx, value_t *args, uint32_t narg { JL_TIMING(MACRO_INVOCATION); jl_ptls_t ptls = jl_get_ptls_states(); - if (nargs < 1) - argcount(fl_ctx, "invoke-julia-macro", nargs, 1); + if (nargs < 2) // macro name and location + argcount(fl_ctx, "invoke-julia-macro", nargs, 2); jl_method_instance_t *mfunc = NULL; jl_value_t **margs; // Reserve one more slot for the result JL_GC_PUSHARGS(margs, nargs + 1); int i; - for(i=1; i < nargs; i++) margs[i] = scm_to_julia(fl_ctx, args[i], 1); + margs[0] = scm_to_julia(fl_ctx, args[0], 1); + // __source__ argument + jl_value_t *lno = scm_to_julia(fl_ctx, args[1], 1); + margs[1] = lno; + if (jl_is_expr(lno) && ((jl_expr_t*)lno)->head == line_sym) { + jl_value_t *file = jl_nothing; + jl_value_t *line = NULL; + switch (jl_expr_nargs(lno)) { // fall-through is intentional + case 2: + file = jl_exprarg(lno, 1); // file + case 1: + line = jl_exprarg(lno, 0); // line + default: ; + } + if (line == NULL) + line = jl_box_long(0); + margs[1] = jl_new_struct(jl_linenumbernode_type, line, file); + } + else if (!jl_typeis(lno, jl_linenumbernode_type)) { + margs[1] = jl_new_struct(jl_linenumbernode_type, jl_box_long(0), jl_nothing); + } + for (i = 2; i < nargs; i++) + margs[i] = scm_to_julia(fl_ctx, args[i], 1); jl_value_t *result = NULL; size_t world = jl_get_ptls_states()->world_age; JL_TRY { - margs[0] = scm_to_julia(fl_ctx, args[0], 1); margs[0] = jl_toplevel_eval(margs[0]); mfunc = jl_method_lookup(jl_gf_mtable(margs[0]), margs, nargs, 1, world); if (mfunc == NULL) { @@ -560,10 +581,13 @@ static jl_value_t *scm_to_julia_(fl_context_t *fl_ctx, value_t e, int eo) else n++; if (!eo) { - if (sym == line_sym && n==1) { + if (sym == line_sym && (n == 1 || n == 2)) { jl_value_t *linenum = scm_to_julia_(fl_ctx, car_(e), 0); - JL_GC_PUSH1(&linenum); - jl_value_t *temp = jl_new_struct(jl_linenumbernode_type, linenum); + jl_value_t *file = jl_nothing; + JL_GC_PUSH2(&linenum, &file); + if (n == 2) + file = scm_to_julia_(fl_ctx, car_(cdr_(e)), 0); + jl_value_t *temp = jl_new_struct(jl_linenumbernode_type, linenum, file); JL_GC_POP(); return temp; } @@ -724,8 +748,16 @@ static value_t julia_to_scm_(fl_context_t *fl_ctx, jl_value_t *v) // shouldn't allocate in this case. if (jl_typeis(v, jl_labelnode_type)) return julia_to_list2(fl_ctx, (jl_value_t*)label_sym, jl_fieldref(v,0)); - if (jl_typeis(v, jl_linenumbernode_type)) - return julia_to_list2(fl_ctx, (jl_value_t*)line_sym, jl_fieldref(v,0)); + if (jl_typeis(v, jl_linenumbernode_type)) { + jl_value_t *file = jl_fieldref(v,1); // non-allocating + jl_value_t *line = jl_fieldref(v,0); // allocating + value_t args = julia_to_list2(fl_ctx, line, file); + fl_gc_handle(fl_ctx, &args); + value_t hd = julia_to_scm_(fl_ctx, (jl_value_t*)line_sym); + value_t scmv = fl_cons(fl_ctx, hd, args); + fl_free_gc_handles(fl_ctx, 1); + return scmv; + } if (jl_typeis(v, jl_gotonode_type)) return julia_to_list2(fl_ctx, (jl_value_t*)goto_sym, jl_fieldref(v,0)); if (jl_typeis(v, jl_quotenode_type)) diff --git a/src/codegen.cpp b/src/codegen.cpp index 35061fe614f11..acd0c23ed5fa4 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -4083,13 +4083,13 @@ static jl_cgval_t emit_expr(jl_value_t *expr, jl_codectx_t *ctx) return emit_getfield((jl_value_t*)jl_globalref_mod(expr), jl_globalref_name(expr), ctx); } if (jl_is_labelnode(expr)) { - jl_error("Labelnode in value position"); + jl_error("LabelNode in value position"); } if (jl_is_linenode(expr)) { - jl_error("Linenode in value position"); + jl_error("LineNumberNode in value position"); } if (jl_is_gotonode(expr)) { - jl_error("Gotonode in value position"); + jl_error("GotoNode in value position"); } if (!jl_is_expr(expr)) { int needroot = true; diff --git a/src/jltypes.c b/src/jltypes.c index bc130b86cf63f..91a579b36ae50 100644 --- a/src/jltypes.c +++ b/src/jltypes.c @@ -1850,8 +1850,8 @@ void jl_init_types(void) jl_linenumbernode_type = jl_new_datatype(jl_symbol("LineNumberNode"), jl_any_type, jl_emptysvec, - jl_perm_symsvec(1, "line"), - jl_svec(1, jl_long_type), 0, 0, 1); + jl_perm_symsvec(2, "line", "file"), + jl_svec(2, jl_long_type, jl_any_type), 0, 0, 2); jl_labelnode_type = jl_new_datatype(jl_symbol("LabelNode"), jl_any_type, jl_emptysvec, diff --git a/src/julia-parser.scm b/src/julia-parser.scm index 6626739da4af7..7e05db3f7ff89 100644 --- a/src/julia-parser.scm +++ b/src/julia-parser.scm @@ -347,10 +347,10 @@ ((eq? pred char-bin?) (fix-uint-neg neg (sized-uint-literal n s 1))) (is-float32-literal (numchk n s) (float n)) (n (if (and (integer? n) (> n 9223372036854775807)) - `(macrocall @int128_str ,s) + `(macrocall @int128_str (null) ,s) n)) - ((within-int128? s) `(macrocall @int128_str ,s)) - (else `(macrocall @big_str ,s)))))) + ((within-int128? s) `(macrocall @int128_str (null) ,s)) + (else `(macrocall @big_str (null) ,s)))))) (define (fix-uint-neg neg n) (if neg @@ -366,7 +366,7 @@ ((<= l 16) (numchk n s) (uint16 n)) ((<= l 32) (numchk n s) (uint32 n)) ((<= l 64) (numchk n s) (uint64 n)) - ((<= l 128) `(macrocall @uint128_str ,s)) + ((<= l 128) `(macrocall @uint128_str (null) ,s)) (else (error "Hex or binary literal too large for UInt128"))))) (define (sized-uint-oct-literal n s) @@ -379,7 +379,7 @@ (else (uint64 n))) (begin (if (equal? s "0o") (numchk n s)) (if (oct-within-uint128? s) - `(macrocall @uint128_str ,s) + `(macrocall @uint128_str (null) ,s) (error "Octal literal too large for UInt128")))))) (define (strip-leading-0s s) @@ -856,11 +856,11 @@ (define (maybe-negate op num) (if (eq? op '-) (if (large-number? num) - (if (eqv? (caddr num) "-170141183460469231731687303715884105728") - `(macrocall @big_str "170141183460469231731687303715884105728") - `(,(car num) ,(cadr num) ,(string.tail (caddr num) 1))) + (if (eqv? (cadddr num) "-170141183460469231731687303715884105728") + `(macrocall @big_str (null) "170141183460469231731687303715884105728") + `(,(car num) ,(cadr num) ,(caddr num) ,(string.tail (cadddr num) 1))) (if (= num -9223372036854775808) - `(macrocall @int128_str "9223372036854775808") + `(macrocall @int128_str (null) "9223372036854775808") (- num))) num)) @@ -1100,7 +1100,7 @@ (else (let ((name (parse-atom s))) (if (and (pair? name) (eq? (car name) 'macrocall)) - `(macrocall (|.| ,ex (quote ,(cadr name))) + `(macrocall (|.| ,ex (quote ,(cadr name))) ; move macrocall outside by rewriting A.@B as @A.B ,@(cddr name)) `(|.| ,ex (quote ,name)))))))) ((|.'| |'|) @@ -1117,16 +1117,17 @@ (not (operator? ex)) (not (ts:space? s))) ;; custom string and command literals; x"s" => @x_str "s" - (let* ((macstr (begin (take-token s) + (let* ((startloc (line-number-node s)) + (macstr (begin (take-token s) (parse-raw-literal s t))) (nxt (peek-token s)) (macname (macroify-name ex (macsuffix t)))) (if (and (symbol? nxt) (not (operator? nxt)) (not (ts:space? s))) ;; string literal suffix, "s"x - (loop `(macrocall ,macname ,macstr + (loop `(macrocall ,macname ,startloc ,macstr ,(string (take-token s)))) - (loop `(macrocall ,macname ,macstr)))) + (loop `(macrocall ,macname ,startloc ,macstr)))) ex)) (else ex)))))) @@ -2122,26 +2123,27 @@ ((eqv? t #\@) (take-token s) (with-space-sensitive - (let ((head (if (eq? (peek-token s) '|.|) + (let ((startloc (line-number-node s)) + (head (if (eq? (peek-token s) '|.|) (begin (take-token s) '__dot__) (parse-unary-prefix s)))) - (if (eq? head '__LINE__) - (input-port-line (ts:port s)) - (begin - (peek-token s) - (if (ts:space? s) - `(macrocall ,(macroify-name head) - ,@(parse-space-separated-exprs s)) - (let ((call (parse-call-chain s head #t))) - (if (and (pair? call) (eq? (car call) 'call)) - `(macrocall ,(macroify-name (cadr call)) ,@(cddr call)) - `(macrocall ,(macroify-name call) - ,@(parse-space-separated-exprs s)))))))))) - + (peek-token s) + (if (ts:space? s) + `(macrocall ,(macroify-name head) + ,startloc + ,@(parse-space-separated-exprs s)) + (let ((call (parse-call-chain s head #t))) + (if (and (pair? call) (eq? (car call) 'call)) + `(macrocall ,(macroify-name (cadr call)) + ,startloc + ,@(cddr call)) + `(macrocall ,(macroify-name call) + ,startloc + ,@(parse-space-separated-exprs s)))))))) ;; command syntax ((eqv? t #\`) (take-token s) - `(macrocall @cmd ,(parse-raw-literal s #\`))) + `(macrocall @cmd ,(line-number-node s) ,(parse-raw-literal s #\`))) ((or (string? t) (number? t) (large-number? t)) (take-token s)) @@ -2167,16 +2169,20 @@ (and (pair? e) (eq? 'string (car e))) ; string interpolation (and (length= e 3) (eq? (car e) 'macrocall) (simple-string-literal? (caddr e)) + (eq? (cadr e) '@doc_str)) + (and (length= e 4) (eq? (car e) 'macrocall) + (simple-string-literal? (cadddr e)) (eq? (cadr e) '@doc_str)))) (define (parse-docstring s production) - (let* ((ex (production s))) + (let ((startloc (line-number-node s)) ; be sure to use the line number from the head of the docstring + (ex (production s))) (if (and (doc-string-literal? ex) (let loop ((t (peek-token s))) (cond ((closing-token? t) #f) ((newline? t) (take-token s) (loop (peek-token s))) (else #t)))) - `(macrocall (core @doc) ,ex ,(production s)) + `(macrocall (core @doc) ,startloc ,ex ,(production s)) ex))) ;; --- main entry point --- diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 86ed90c300ac6..78bdb2cd76d55 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -1162,6 +1162,7 @@ (error "macros cannot accept keyword arguments")) (expand-forms `(function (call ,(symbol (string #\@ (cadr (cadr e)))) + (|::| __source__ (core LineNumberNode)) ,@(map (lambda (v) (if (symbol? v) `(|::| ,v (core ANY)) diff --git a/src/julia.h b/src/julia.h index 3af8aad0bf450..9dbeff414a06e 100644 --- a/src/julia.h +++ b/src/julia.h @@ -741,17 +741,18 @@ STATIC_INLINE void jl_array_uint8_set(void *a, size_t i, uint8_t x) #define jl_exprargset(e, n, v) jl_array_ptr_set(((jl_expr_t*)(e))->args, n, v) #define jl_expr_nargs(e) jl_array_len(((jl_expr_t*)(e))->args) -#define jl_fieldref(s,i) jl_get_nth_field(((jl_value_t*)s),i) +#define jl_fieldref(s,i) jl_get_nth_field(((jl_value_t*)(s)),i) #define jl_nfields(v) jl_datatype_nfields(jl_typeof(v)) // Not using jl_fieldref to avoid allocations -#define jl_linenode_line(x) (((intptr_t*)x)[0]) -#define jl_labelnode_label(x) (((intptr_t*)x)[0]) -#define jl_slot_number(x) (((intptr_t*)x)[0]) -#define jl_typedslot_get_type(x) (((jl_value_t**)x)[1]) -#define jl_gotonode_label(x) (((intptr_t*)x)[0]) -#define jl_globalref_mod(s) (*(jl_module_t**)s) -#define jl_globalref_name(s) (((jl_sym_t**)s)[1]) +#define jl_linenode_line(x) (((intptr_t*)(x))[0]) +#define jl_linenode_file(x) (((jl_value_t**)(x))[1]) +#define jl_labelnode_label(x) (((intptr_t*)(x))[0]) +#define jl_slot_number(x) (((intptr_t*)(x))[0]) +#define jl_typedslot_get_type(x) (((jl_value_t**)(x))[1]) +#define jl_gotonode_label(x) (((intptr_t*)(x))[0]) +#define jl_globalref_mod(s) (*(jl_module_t**)(s)) +#define jl_globalref_name(s) (((jl_sym_t**)(s))[1]) #define jl_nparams(t) jl_svec_len(((jl_datatype_t*)(t))->parameters) #define jl_tparam0(t) jl_svecref(((jl_datatype_t*)(t))->parameters, 0) diff --git a/src/method.c b/src/method.c index 5a4f9f8100f92..30e7a702823e2 100644 --- a/src/method.c +++ b/src/method.c @@ -300,19 +300,27 @@ JL_DLLEXPORT jl_code_info_t *jl_code_for_staged(jl_method_instance_t *linfo) jl_array_ptr_set(ex->args, 0, argnames); jl_fill_argnames((jl_array_t*)linfo->def->source, argnames); + // build the rest of the body to pass to expand jl_expr_t *scopeblock = jl_exprn(jl_symbol("scope-block"), 1); jl_array_ptr_set(ex->args, 1, scopeblock); - jl_expr_t *body = jl_exprn(jl_symbol("block"), 2); - jl_array_ptr_set(((jl_expr_t*)jl_exprarg(ex,1))->args, 0, body); + jl_expr_t *body = jl_exprn(jl_symbol("block"), 3); + jl_array_ptr_set(((jl_expr_t*)jl_exprarg(ex, 1))->args, 0, body); + + // add location meta linenum = jl_box_long(linfo->def->line); - jl_value_t *linenode = jl_new_struct(jl_linenumbernode_type, linenum); + jl_value_t *linenode = jl_new_struct(jl_linenumbernode_type, linenum, linfo->def->file); jl_array_ptr_set(body->args, 0, linenode); + jl_expr_t *pushloc = jl_exprn(meta_sym, 3); + jl_array_ptr_set(body->args, 1, pushloc); + jl_array_ptr_set(pushloc->args, 0, jl_symbol("push_loc")); + jl_array_ptr_set(pushloc->args, 1, linfo->def->file); // file + jl_array_ptr_set(pushloc->args, 2, jl_symbol("@generated body")); // function // invoke code generator assert(jl_nparams(tt) == jl_array_len(argnames) || (linfo->def->isva && (jl_nparams(tt) >= jl_array_len(argnames) - 1))); - jl_array_ptr_set(body->args, 1, - jl_call_staged(sparam_vals, generator, jl_svec_data(tt->parameters), jl_nparams(tt))); + jl_value_t *generated_body = jl_call_staged(sparam_vals, generator, jl_svec_data(tt->parameters), jl_nparams(tt)); + jl_array_ptr_set(body->args, 2, generated_body); if (linfo->def->sparam_syms != jl_emptysvec) { // mark this function as having the same static parameters as the generator @@ -335,8 +343,17 @@ JL_DLLEXPORT jl_code_info_t *jl_code_for_staged(jl_method_instance_t *linfo) jl_array_t *stmts = (jl_array_t*)func->code; size_t i, l; for (i = 0, l = jl_array_len(stmts); i < l; i++) { - jl_array_ptr_set(stmts, i, jl_resolve_globals(jl_array_ptr_ref(stmts, i), linfo->def->module, env)); + jl_value_t *stmt = jl_array_ptr_ref(stmts, i); + stmt = jl_resolve_globals(stmt, linfo->def->module, env); + jl_array_ptr_set(stmts, i, stmt); } + + // add pop_loc meta + jl_array_ptr_1d_push(stmts, jl_nothing); + jl_expr_t *poploc = jl_exprn(meta_sym, 1); + jl_array_ptr_set(stmts, jl_array_len(stmts) - 1, poploc); + jl_array_ptr_set(poploc->args, 0, jl_symbol("pop_loc")); + ptls->in_pure_callback = last_in; jl_lineno = last_lineno; ptls->current_module = last_m; @@ -401,10 +418,25 @@ static void jl_method_set_source(jl_method_t *m, jl_code_info_t *src) int set_lineno = 0; for (i = 0; i < n; i++) { jl_value_t *st = jl_array_ptr_ref(stmts, i); - if (jl_is_expr(st) && ((jl_expr_t*)st)->head == line_sym) { + if (jl_is_linenode(st)) { if (!set_lineno) { - m->line = jl_unbox_long(jl_exprarg(st, 0)); - m->file = (jl_sym_t*)jl_exprarg(st, 1); + m->line = jl_linenode_line(st); + jl_value_t *file = jl_linenode_file(st); + if (jl_is_symbol(file)) + m->file = (jl_sym_t*)file; + st = jl_nothing; + set_lineno = 1; + } + } + else if (jl_is_expr(st) && ((jl_expr_t*)st)->head == line_sym) { + if (!set_lineno) { + switch (jl_expr_nargs(st)) { // fall-through is intentional + case 2: + m->file = (jl_sym_t*)jl_exprarg(st, 1); + case 1: + m->line = jl_unbox_long(jl_exprarg(st, 0)); + default: ; + } st = jl_nothing; set_lineno = 1; } diff --git a/src/rtutils.c b/src/rtutils.c index 9397cab1ec0a0..b04ad0228a28f 100644 --- a/src/rtutils.c +++ b/src/rtutils.c @@ -757,7 +757,9 @@ static size_t jl_static_show_x_(JL_STREAM *out, jl_value_t *v, jl_datatype_t *vt n += jl_printf(out, ">"); } else if (vt == jl_linenumbernode_type) { - n += jl_printf(out, "# line %" PRIuPTR, jl_linenode_line(v)); + n += jl_printf(out, "#= "); + n += jl_static_show_x(out, jl_linenode_file(v), depth); + n += jl_printf(out, ":%" PRIuPTR " =#", jl_linenode_line(v)); } else if (vt == jl_expr_type) { jl_expr_t *e = (jl_expr_t*)v; diff --git a/test/ambiguous.jl b/test/ambiguous.jl index 32139c496a44f..049cffb728158 100644 --- a/test/ambiguous.jl +++ b/test/ambiguous.jl @@ -1,7 +1,7 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license # DO NOT ALTER ORDER OR SPACING OF METHODS BELOW -const lineoffset = @__LINE__ + 0 # XXX: __LINE__ at the end of a line is off-by-one +const lineoffset = @__LINE__ ambig(x, y) = 1 ambig(x::Integer, y) = 2 ambig(x, y::Integer) = 3 diff --git a/test/docs.jl b/test/docs.jl index 46b1523e56401..d1d65d5615ae7 100644 --- a/test/docs.jl +++ b/test/docs.jl @@ -48,7 +48,7 @@ end # General tests for docstrings. -const LINE_NUMBER = @__LINE__+1 +const LINE_NUMBER = @__LINE__() + 1 "DocsTest" module DocsTest @@ -906,23 +906,24 @@ let x = Binding(Main, :⊕) @test parse(string(x)) == :(⊕) end +doc_util_path = Symbol(joinpath("docs", "utils.jl")) # Docs.helpmode tests: we test whether the correct expressions are being generated here, # rather than complete integration with Julia's REPL mode system. for (line, expr) in Pair[ "sin" => :sin, "Base.sin" => :(Base.sin), - "@time(x)" => :(@time(x)), - "@time" => :(:@time), - ":@time" => :(:@time), - "@time()" => :(@time), - "Base.@time()" => :(Base.@time), + "@time(x)" => Expr(:macrocall, Symbol("@time"), LineNumberNode(1, :none), :x), + "@time" => Expr(:macrocall, Symbol("@time"), LineNumberNode(1, :none)), + ":@time" => Expr(:quote, (Expr(:macrocall, Symbol("@time"), LineNumberNode(1, :none)))), + "@time()" => Expr(:macrocall, Symbol("@time"), LineNumberNode(1, :none)), + "Base.@time()" => Expr(:macrocall, Expr(:., :Base, QuoteNode(Symbol("@time"))), LineNumberNode(1, :none)), "ccall" => :ccall, # keyword "while " => :while, # keyword, trailing spaces should be stripped. "0" => 0, "\"...\"" => "...", - "r\"...\"" => :(r"..."), + "r\"...\"" => Expr(:macrocall, Symbol("@r_str"), LineNumberNode(1, :none), "...") ] - @test Docs.helpmode(line) == :(Base.Docs.@repl($STDOUT, $expr)) + @test Docs.helpmode(line) == Expr(:macrocall, Expr(:., Expr(:., :Base, QuoteNode(:Docs)), QuoteNode(Symbol("@repl"))), LineNumberNode(117, doc_util_path), STDOUT, expr) buf = IOBuffer() @test eval(Base, Docs.helpmode(buf, line)) isa Union{Base.Markdown.MD,Void} end @@ -961,8 +962,8 @@ dynamic_test.x = "test 2" @test @doc(dynamic_test) == "test 2 Union{}" @test @doc(dynamic_test(::String)) == "test 2 Tuple{String}" -@test Docs._repl(:(dynamic_test(1.0))) == :(@doc $(Expr(:escape, :(dynamic_test(::typeof(1.0)))))) -@test Docs._repl(:(dynamic_test(::String))) == :(@doc $(Expr(:escape, :(dynamic_test(::String))))) +@test Docs._repl(:(dynamic_test(1.0))) == Expr(:macrocall, Symbol("@doc"), LineNumberNode(204, doc_util_path), esc(:(dynamic_test(::typeof(1.0))))) +@test Docs._repl(:(dynamic_test(::String))) == Expr(:macrocall, Symbol("@doc"), LineNumberNode(204, doc_util_path), esc(:(dynamic_test(::String)))) # Equality testing diff --git a/test/loading.jl b/test/loading.jl index 80f14a8ba08db..b50ca7960966e 100644 --- a/test/loading.jl +++ b/test/loading.jl @@ -2,22 +2,24 @@ using Base.Test -@test @__LINE__ == 5 +@test @__LINE__() == 5 include("test_sourcepath.jl") thefname = "the fname!//\\&\1*" include_string_test_func = include_string("include_string_test() = @__FILE__", thefname) -@test include_string_test_func() == Base.source_path() +@test include_string_test_func() == thefname @test include_string("Base.source_path()", thefname) == Base.source_path() @test basename(@__FILE__) == "loading.jl" @test isabspath(@__FILE__) @test isdir(@__DIR__) @test @__DIR__() == dirname(@__FILE__) -let exename = `$(Base.julia_cmd()) --precompiled=yes --startup-file=no` - wd = sprint(show, pwd()) +let exename = `$(Base.julia_cmd()) --precompiled=yes --startup-file=no`, + wd = sprint(show, abspath(pwd(), "")), + s_dir = sprint(show, joinpath(realpath(tempdir()), "")) + @test wd != s_dir @test readchomp(`$exename -E "@__DIR__" -i`) == wd - @test readchomp(`$exename -E "cd(()->eval(:(@__DIR__)), tempdir())" -i`) != wd + @test readchomp(`$exename -E "cd(()->eval(:(@__DIR__)), $s_dir)" -i`) == s_dir @test readchomp(`$exename -E "@__DIR__"`) == wd # non-interactive end diff --git a/test/parse.jl b/test/parse.jl index 12377dbdbb01e..c45e8e297a398 100644 --- a/test/parse.jl +++ b/test/parse.jl @@ -147,10 +147,10 @@ macro test999_str(args...); args; end # issue 11970 @test parseall(""" -macro f(args...) end; @f "" + macro f(args...) end; @f "macro argument" """) == Expr(:toplevel, - Expr(:macro, Expr(:call, :f, Expr(:..., :args)), Expr(:block, Expr(:line, 1, :none))), - Expr(:macrocall, Symbol("@f"), "")) + Expr(:macro, Expr(:call, :f, Expr(:..., :args)), Expr(:block, LineNumberNode(1, :none))), + Expr(:macrocall, Symbol("@f"), LineNumberNode(1, :none), "macro argument")) # blocks vs. tuples @test parse("()") == Expr(:tuple) @@ -351,7 +351,7 @@ parsehex(s) = parse(Int,s,16) # issue #17705 @test parse("2e3_") == Expr(:call, :*, 2e3, :_) @test parse("2e-3_") == Expr(:call, :*, 2e-3, :_) -@test parse("2e3_\"x\"") == Expr(:call, :*, 2e3, Expr(:macrocall, Symbol("@__str"), "x")) +@test parse("2e3_\"x\"") == Expr(:call, :*, 2e3, Expr(:macrocall, Symbol("@__str"), LineNumberNode(1, :none), "x")) # multibyte spaces @test parse(Int, "3\u2003\u202F") == 3 @@ -514,7 +514,7 @@ let b = IOBuffer(""" end f() """) - @test Base.parse_input_line(b) == Expr(:let, Expr(:block, Expr(:line, 2, :none), :x), Expr(:(=), :x, :x)) + @test Base.parse_input_line(b) == Expr(:let, Expr(:block, LineNumberNode(2, :none), :x), Expr(:(=), :x, :x)) @test Base.parse_input_line(b) == Expr(:call, :f) @test Base.parse_input_line(b) === nothing end @@ -574,7 +574,7 @@ f16517() = try error(); catch 0; end # issue #16671 @test parse("1.") === 1.0 -isline(x) = isa(x,Expr) && x.head === :line +isline(x) = isa(x, LineNumberNode) # issue #16672 @test count(isline, parse("begin end").args) == 1 @@ -584,9 +584,9 @@ isline(x) = isa(x,Expr) && x.head === :line # issue #16736 let - local lineoffset0 = @__LINE__ + 1 - local lineoffset1 = @__LINE__ - local lineoffset2 = @__LINE__ - 1 + local lineoffset0 = @__LINE__() + 1 + local lineoffset1 = @__LINE__() + local lineoffset2 = @__LINE__() - 1 @test lineoffset0 == lineoffset1 == lineoffset2 end @@ -596,13 +596,13 @@ end y end") == Expr(:try, Expr(:block, - Expr(:line, 1, :none), + LineNumberNode(1, :none), :x), false, Expr(:block, - Expr(:line, 2, :none), + LineNumberNode(2, :none), Expr(:call, :test), - Expr(:line, 3, :none), + LineNumberNode(3, :none), :y)) # test that pre 0.5 deprecated syntax is a parse error @@ -775,12 +775,12 @@ module B15838 end @test A15838.@f() === nothing @test A15838.@f(1) === :b -let nometh = expand(:(A15838.@f(1, 2))) +let nometh = expand(:(A15838.@f(1, 2))), __source__ = LineNumberNode(@__LINE__, Symbol(@__FILE__)) @test (nometh::Expr).head === :error @test length(nometh.args) == 1 e = nometh.args[1]::MethodError @test e.f === getfield(A15838, Symbol("@f")) - @test e.args === (1,2) + @test e.args === (__source__, 1, 2) end # issue 10046 @@ -904,8 +904,8 @@ f1_exprs = get_expr_list(@code_typed(f1(1))[1]) f2_exprs = get_expr_list(@code_typed(f2(1))[1]) @test Meta.isexpr(f1_exprs[end], :return) -@test is_pop_loc(f2_exprs[end - 1]) -@test Meta.isexpr(f2_exprs[end], :return) +@test is_pop_loc(f2_exprs[end]) +@test Meta.isexpr(f2_exprs[end - 1], :return) if Base.JLOptions().code_coverage != 0 && Base.JLOptions().can_inline != 0 @test count_meta_loc(f1_exprs) == 1 @@ -924,10 +924,10 @@ end @test :(x`s\`"\x\$\\`) == :(@x_cmd "s`\"\\x\\\$\\\\") # Check multiline command literals -@test :``` +@test :(@cmd "multiline\ncommand\n") == :``` multiline command -``` == :(@cmd "multiline\ncommand\n") +``` macro julia_cmd(s) Meta.quot(parse(s)) @@ -980,7 +980,7 @@ let ..(x,y) = x + y end # issue #7669 -@test parse("@a(b=1, c=2)") == Expr(:macrocall, Symbol("@a"), :(b=1), :(c=2)) +@test parse("@a(b=1, c=2)") == Expr(:macrocall, Symbol("@a"), LineNumberNode(1, :none), :(b=1), :(c=2)) # issue #19685 let f = function (x; kw...) @@ -1070,7 +1070,7 @@ end @test expand(:(@err20000)) == Expr(:error, "oops!") # issue #20000 -@test parse("@m(a; b=c)") == Expr(:macrocall, Symbol("@m"), +@test parse("@m(a; b=c)") == Expr(:macrocall, Symbol("@m"), LineNumberNode(1, :none), Expr(:parameters, Expr(:kw, :b, :c)), :a) # issue #21054 diff --git a/test/reflection.jl b/test/reflection.jl index d66098e3fde77..0e00763f70c26 100644 --- a/test/reflection.jl +++ b/test/reflection.jl @@ -337,7 +337,7 @@ end return true end end -@test functionloc(f14346)[2] == @__LINE__-4 +@test functionloc(f14346)[2] == @__LINE__() - 4 # test jl_get_llvm_fptr. We test functions both in and definitely not in the system image definitely_not_in_sysimg() = nothing @@ -366,10 +366,10 @@ let using .MacroTest a = 1 m = getfield(current_module(), Symbol("@macrotest")) - @test which(m, Tuple{Int,Symbol})==@which @macrotest 1 a - @test which(m, Tuple{Int,Int})==@which @macrotest 1 1 + @test which(m, Tuple{LineNumberNode, Int, Symbol}) == @which @macrotest 1 a + @test which(m, Tuple{LineNumberNode, Int, Int}) == @which @macrotest 1 1 - @test first(methods(m,Tuple{Int, Int}))==@which MacroTest.@macrotest 1 1 + @test first(methods(m, Tuple{LineNumberNode, Int, Int})) == @which MacroTest.@macrotest 1 1 @test functionloc(@which @macrotest 1 1) == @functionloc @macrotest 1 1 end diff --git a/test/replutil.jl b/test/replutil.jl index 63e3091c9b7a7..ac4bec2258976 100644 --- a/test/replutil.jl +++ b/test/replutil.jl @@ -12,7 +12,7 @@ function test_have_color(buf, color, no_color) end cfile = " at $(@__FILE__):" -c1line = @__LINE__ + 1 +c1line = @__LINE__() + 1 method_c1(x::Float64, s::AbstractString...) = true buf = IOBuffer() @@ -59,7 +59,7 @@ color = "\e[0m\nClosest candidates are:\n method_c2(\e[1m\e[31m::Int32\e[0m, :: no_color = no_color = "\nClosest candidates are:\n method_c2(!Matched::Int32, ::Float64, ::Any...)$cfile$(c2line+2)\n method_c2(!Matched::Int32, ::Any...)$cfile$(c2line+1)\n method_c2(::T<:Real, ::T<:Real, !Matched::T<:Real) where T<:Real$cfile$(c2line+5)\n ..." test_have_color(buf, color, no_color) -c3line = @__LINE__ + 1 +c3line = @__LINE__() + 1 method_c3(x::Float64, y::Float64) = true Base.show_method_candidates(buf, Base.MethodError(method_c3,(1.,))) color = "\e[0m\nClosest candidates are:\n method_c3(::Float64, \e[1m\e[31m::Float64\e[0m)$cfile$c3line\e[0m" @@ -75,7 +75,7 @@ test_have_color(buf, "\e[0m\nClosest candidates are:\n method_c4(::AbstractString)$cfile$(c4line+2)\n method_c4()$cfile$(c4line+1)\e[0m", "\nClosest candidates are:\n method_c4(::AbstractString)$cfile$(c4line+2)\n method_c4()$cfile$(c4line+1)") -c5line = @__LINE__ + 1 +c5line = @__LINE__() + 1 method_c5(::Type{Float64}) = true Base.show_method_candidates(buf, MethodError(method_c5,(Float64,))) test_have_color(buf, "\e[0m\nClosest candidates are:\n method_c5(::Type{Float64})$cfile$c5line\e[0m", @@ -92,12 +92,12 @@ for f in [getindex, setindex!] test_have_color(buf, "", "") end -PR16155line = @__LINE__ + 2 +PR16155line = @__LINE__() + 2 mutable struct PR16155 a::Int64 b end -PR16155line2 = @__LINE__ + 1 +PR16155line2 = @__LINE__() + 1 (::Type{T}){T<:PR16155}(arg::Any) = "replace call-to-convert method from sysimg" Base.show_method_candidates(buf, MethodError(PR16155,(1.0, 2.0, Int64(3)))) @@ -146,12 +146,12 @@ else @test contains(error_out3, "method_c6_in_module(::Any; y)$cfile$(c6mline + 3) got unsupported keyword argument \"x\"") end -c7line = @__LINE__ + 1 +c7line = @__LINE__() + 1 method_c7(a, b; kargs...) = a Base.show_method_candidates(buf, MethodError(method_c7, (1, 1)), [(:x, 1), (:y, 2)]) test_have_color(buf, "\e[0m\nClosest candidates are:\n method_c7(::Any, ::Any; kargs...)$cfile$c7line\e[0m", "\nClosest candidates are:\n method_c7(::Any, ::Any; kargs...)$cfile$c7line") -c8line = @__LINE__ + 1 +c8line = @__LINE__() + 1 method_c8(a, b; y=1, w=1) = a Base.show_method_candidates(buf, MethodError(method_c8, (1, 1)), [(:x, 1), (:y, 2), (:z, 1), (:w, 1)]) test_have_color(buf, "\e[0m\nClosest candidates are:\n method_c8(::Any, ::Any; y, w)$cfile$c8line\e[1m\e[31m got unsupported keyword arguments \"x\", \"z\"\e[0m\e[0m", @@ -344,7 +344,7 @@ end @test stringmime("text/plain", FunctionLike()) == "(::FunctionLike) (generic function with 0 methods)" @test ismatch(r"^@doc \(macro with \d+ method[s]?\)$", stringmime("text/plain", getfield(Base, Symbol("@doc")))) -method_defs_lineno = @__LINE__+1 +method_defs_lineno = @__LINE__() + 1 Base.Symbol() = throw(ErrorException("1")) (::Symbol)() = throw(ErrorException("2")) EightBitType() = throw(ErrorException("3")) @@ -361,15 +361,24 @@ let err_str, sp = Base.source_path() sn = basename(sp) - @test sprint(show, which(Symbol, Tuple{})) == "Symbol() in $curmod_str at $sp:$(method_defs_lineno + 0)" - @test sprint(show, which(:a, Tuple{})) == "(::Symbol)() in $curmod_str at $sp:$(method_defs_lineno + 1)" - @test sprint(show, which(EightBitType, Tuple{})) == "$(curmod_prefix)EightBitType() in $curmod_str at $sp:$(method_defs_lineno + 2)" - @test sprint(show, which(reinterpret(EightBitType, 0x54), Tuple{})) == "(::$(curmod_prefix)EightBitType)() in $curmod_str at $sp:$(method_defs_lineno + 3)" - @test sprint(show, which(EightBitTypeT, Tuple{})) == "(::Type{$(curmod_prefix)EightBitTypeT})() in $curmod_str at $sp:$(method_defs_lineno + 4)" - @test sprint(show, which(EightBitTypeT{Int32}, Tuple{})) == "(::Type{$(curmod_prefix)EightBitTypeT{T}})() where T in $curmod_str at $sp:$(method_defs_lineno + 5)" - @test sprint(show, which(reinterpret(EightBitTypeT{Int32}, 0x54), Tuple{})) == "(::$(curmod_prefix)EightBitTypeT)() in $curmod_str at $sp:$(method_defs_lineno + 6)" - @test startswith(sprint(show, which(getfield(Base, Symbol("@doc")), Tuple{Vararg{Any}})), "@doc(x...) in Core at boot.jl:") - @test startswith(sprint(show, which(FunctionLike(), Tuple{})), "(::$(curmod_prefix)FunctionLike)() in $curmod_str at $sp:$(method_defs_lineno + 7)") + @test sprint(show, which(Symbol, Tuple{})) == + "Symbol() in $curmod_str at $sp:$(method_defs_lineno + 0)" + @test sprint(show, which(:a, Tuple{})) == + "(::Symbol)() in $curmod_str at $sp:$(method_defs_lineno + 1)" + @test sprint(show, which(EightBitType, Tuple{})) == + "$(curmod_prefix)EightBitType() in $curmod_str at $sp:$(method_defs_lineno + 2)" + @test sprint(show, which(reinterpret(EightBitType, 0x54), Tuple{})) == + "(::$(curmod_prefix)EightBitType)() in $curmod_str at $sp:$(method_defs_lineno + 3)" + @test sprint(show, which(EightBitTypeT, Tuple{})) == + "(::Type{$(curmod_prefix)EightBitTypeT})() in $curmod_str at $sp:$(method_defs_lineno + 4)" + @test sprint(show, which(EightBitTypeT{Int32}, Tuple{})) == + "(::Type{$(curmod_prefix)EightBitTypeT{T}})() where T in $curmod_str at $sp:$(method_defs_lineno + 5)" + @test sprint(show, which(reinterpret(EightBitTypeT{Int32}, 0x54), Tuple{})) == + "(::$(curmod_prefix)EightBitTypeT)() in $curmod_str at $sp:$(method_defs_lineno + 6)" + @test startswith(sprint(show, which(getfield(Base, Symbol("@doc")), Tuple{LineNumberNode, Vararg{Any}})), + "@doc(__source__::LineNumberNode, x...) in Core at boot.jl:") + @test startswith(sprint(show, which(FunctionLike(), Tuple{})), + "(::$(curmod_prefix)FunctionLike)() in $curmod_str at $sp:$(method_defs_lineno + 7)") @test stringmime("text/plain", FunctionLike()) == "(::FunctionLike) (generic function with 1 method)" @test stringmime("text/plain", Core.arraysize) == "arraysize (built-in function)" diff --git a/test/show.jl b/test/show.jl index 44e3cca5a3199..ca12dd1c8d336 100644 --- a/test/show.jl +++ b/test/show.jl @@ -15,8 +15,8 @@ struct T5589 end @test replstr(T5589(Array{String,1}(100))) == "$(curmod_prefix)T5589(String[#undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef … #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef, #undef])" -@test replstr(parse("mutable struct X end")) == ":(mutable struct X # none, line 1:\n end)" -@test replstr(parse("struct X end")) == ":(struct X # none, line 1:\n end)" +@test replstr(parse("mutable struct X end")) == ":(mutable struct X\n #= none:1 =#\n end)" +@test replstr(parse("struct X end")) == ":(struct X\n #= none:1 =#\n end)" s = "ccall(:f, Int, (Ptr{Void},), &x)" @test replstr(parse(s)) == ":($s)" @@ -42,12 +42,14 @@ macro test_repr(x) local x1 = parse($x) local x2 = eval(parse(repr(x1))) local x3 = eval(parse(repr(x2))) - x3 == x1 ? nothing : error(string( - "repr test failed:", - "\noriginal: ", $x, - "\n\nparsed: ", x2, "\n", sprint(dump, x2), - "\n\nreparsed: ", x3, "\n", sprint(dump, x3) - )) + if x3 != x1 + error(string( + "repr test failed:", + "\noriginal: ", $x, + "\n\nparsed: ", x2, "\n", sprint(dump, x2), + "\n\nreparsed: ", x3, "\n", sprint(dump, x3) + )) + end end end end @@ -94,76 +96,128 @@ end # control structures (shamelessly stolen from base/bitarray.jl) @test_repr """mutable struct BitArray{N} <: AbstractArray{Bool, N} + # line meta chunks::Vector{UInt64} + # line meta len::Int + # line meta dims::NTuple{N,Int} + # line meta function BitArray(dims::Int...) + # line meta if length(dims) != N + # line meta error(\"number of dimensions must be \$N (got \$(length(dims)))\") end + # line meta n = 1 + # line meta for d in dims + # line meta if d < 0 + # line meta error(\"dimension size must be nonnegative (got \$d)\") end + # line meta n *= d end + # line meta nc = num_bit_chunks(n) + # line meta chunks = Array{UInt64,1}(nc) + # line meta if nc > 0 + # line meta chunks[end] = UInt64(0) end + # line meta b = new(chunks, n) + # line meta if N != 1 + # line meta b.dims = dims end + # line meta return b end end""" @test_repr """function copy_chunks(dest::Vector{UInt64}, pos_d::Integer, src::Vector{UInt64}, pos_s::Integer, numbits::Integer) + # line meta if numbits == 0 + # line meta return end + # line meta if dest === src && pos_d > pos_s + # line meta return copy_chunks_rtol(dest, pos_d, pos_s, numbits) end + # line meta kd0, ld0 = get_chunks_id(pos_d) + # line meta kd1, ld1 = get_chunks_id(pos_d + numbits - 1) + # line meta ks0, ls0 = get_chunks_id(pos_s) + # line meta ks1, ls1 = get_chunks_id(pos_s + numbits - 1) + # line meta delta_kd = kd1 - kd0 + # line meta delta_ks = ks1 - ks0 + # line meta u = _msk64 + # line meta if delta_kd == 0 + # line meta msk_d0 = ~(u << ld0) | (u << ld1 << 1) else + # line meta msk_d0 = ~(u << ld0) + # line meta msk_d1 = (u << ld1 << 1) end + # line meta if delta_ks == 0 + # line meta msk_s0 = (u << ls0) & ~(u << ls1 << 1) else + # line meta msk_s0 = (u << ls0) end + # line meta chunk_s0 = glue_src_bitchunks(src, ks0, ks1, msk_s0, ls0) + # line meta dest[kd0] = (dest[kd0] & msk_d0) | ((chunk_s0 << ld0) & ~msk_d0) + # line meta if delta_kd == 0 + # line meta return end + # line meta for i = 1 : kd1 - kd0 - 1 + # line meta chunk_s1 = glue_src_bitchunks(src, ks0 + i, ks1, msk_s0, ls0) + # line meta chunk_s = (chunk_s0 >>> (63 - ld0) >>> 1) | (chunk_s1 << ld0) + # line meta dest[kd0 + i] = chunk_s + # line meta chunk_s0 = chunk_s1 end + # line meta if ks1 >= ks0 + delta_kd + # line meta chunk_s1 = glue_src_bitchunks(src, ks0 + delta_kd, ks1, msk_s0, ls0) else + # line meta chunk_s1 = UInt64(0) end + # line meta chunk_s = (chunk_s0 >>> (63 - ld0) >>> 1) | (chunk_s1 << ld0) + # line meta dest[kd1] = (dest[kd1] & msk_d1) | (chunk_s & ~msk_d1) + # line meta return end""" @@ -201,13 +255,21 @@ end""" @test_repr "[1 2 3; 4 5 6; 7 8 9]'" @test_repr "baremodule X +# line meta +# line meta importall ..A.b +# line meta import ...B.c +# line meta import D +# line meta import B.C.D.E.F.g end" @test_repr "baremodule Y +# line meta +# line meta export A, B, C +# line meta export D, E, F end" @@ -261,22 +323,22 @@ end @test string(:(-{x})) == "-{x}" # issue #11393 -@test_repr "@m(x,y) + z" -@test_repr "(@m(x,y),z)" -@test_repr "[@m(x,y),z]" -@test_repr "A[@m(x,y),z]" -@test_repr "T{@m(x,y),z}" +@test_repr "@m(x, y) + z" +@test_repr "(@m(x, y), z)" +@test_repr "[@m(x, y), z]" +@test_repr "A[@m(x, y), z]" +@test_repr "T{@m(x, y), z}" @test_repr "@m x @n(y) z" -@test_repr "f(@m(x,y);z=@n(a))" -@test_repr "@m(x,y).z" -@test_repr "::@m(x,y)+z" +@test_repr "f(@m(x, y); z=@n(a))" +@test_repr "@m(x, y).z" +@test_repr "::@m(x, y) + z" @test_repr "[@m(x) y z]" @test_repr "[@m(x) y; z]" @test_repr "let @m(x), y=z; end" -@test repr(:(@m x y)) == ":(@m x y)" -@test string(:(@m x y)) == "@m x y" -@test string(:(@m x y;)) == "begin \n @m x y\nend" +@test repr(:(@m x y)) == ":(#= $(@__FILE__):$(@__LINE__) =# @m x y)" +@test string(:(@m x y)) == "#= $(@__FILE__):$(@__LINE__) =# @m x y" +@test string(:(@m x y;)) == "begin\n #= $(@__FILE__):$(@__LINE__) =# @m x y\nend" # issue #11436 @test_repr "1 => 2 => 3" @@ -447,25 +509,33 @@ end # issue #15309 -l1, l2, l2n = Expr(:line,42), Expr(:line,42,:myfile), LineNumberNode(42) -@test string(l2n) == " # line 42:" -@test string(l2) == " # myfile, line 42:" -@test string(l1) == string(l2n) -ex = Expr(:block, l1, :x, l2, :y, l2n, :z) -@test replace(string(ex)," ","") == replace(""" -begin # line 42: - x # myfile, line 42: - y # line 42: - z -end""", " ", "") +let ex, + l1 = Expr(:line, 42), + l2 = Expr(:line, 42, :myfile), + l2n = LineNumberNode(42) + @test string(l2n) == "#= line 42 =#" + @test string(l2) == "#= myfile:42 =#" + @test string(l1) == string(l2n) + ex = Expr(:block, l1, :x, l2, :y, l2n, :z) + @test replace(string(ex)," ","") == replace(""" + begin + #= line 42 =# + x + #= myfile:42 =# + y + #= line 42 =# + z + end""", " ", "") +end # Test the printing of whatever form of line number representation # that is used in the arguments to a macro looks the same as for # regular quoting macro strquote(ex) - QuoteNode(string(ex)) + return QuoteNode(string(ex)) +end +let str_ex2a = @strquote(begin x end), str_ex2b = string(quote x end) + @test str_ex2a == str_ex2b end -str_ex2a, str_ex2b = @strquote(begin x end), string(quote x end) -@test str_ex2a == str_ex2b # test structured zero matrix printing for select structured types diff --git a/test/stacktraces.jl b/test/stacktraces.jl index ac1814354f12c..19e23aa0f18f1 100644 --- a/test/stacktraces.jl +++ b/test/stacktraces.jl @@ -6,7 +6,7 @@ let @noinline child() = stacktrace() @noinline parent() = child() @noinline grandparent() = parent() - line_numbers = @__LINE__ - [3, 2, 1] + line_numbers = @__LINE__() - [3, 2, 1] stack = grandparent() # Basic tests. @@ -68,7 +68,7 @@ let ct = current_task() return catch_stacktrace() end end - line_numbers = @__LINE__ .- [15, 10, 5] + line_numbers = @__LINE__() .- [15, 10, 5] # Test try...catch with stacktrace @test try_stacktrace()[1] == StackFrame(:try_stacktrace, @__FILE__, line_numbers[2]) diff --git a/test/worlds.jl b/test/worlds.jl index 13bb28597c090..1dfea7f6fe8d9 100644 --- a/test/worlds.jl +++ b/test/worlds.jl @@ -136,7 +136,7 @@ f265(::Int) = 1 # test for method errors h265() = true -loc_h265 = "$(Base.source_path()):$(@__LINE__ - 1)" +loc_h265 = "$(@__FILE__):$(@__LINE__() - 1)" @test h265() @test_throws MethodError put_n_take!(h265, ()) @test_throws MethodError wait(t265) From 6b05e37815448e351c34b35bb76909f22cae6980 Mon Sep 17 00:00:00 2001 From: Chris Foster Date: Tue, 7 Mar 2017 17:40:49 +1000 Subject: [PATCH 3/3] Add tests for new __LINE__ behaviour inside macros --- test/loading.jl | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/test/loading.jl b/test/loading.jl index b50ca7960966e..a39e094061859 100644 --- a/test/loading.jl +++ b/test/loading.jl @@ -2,7 +2,34 @@ using Base.Test -@test @__LINE__() == 5 +# Tests for @__LINE__ inside and outside of macros +@test (@__LINE__) == 6 + +macro macro_caller_lineno() + @test 9 == (@__LINE__) != __source__.line > 12 + return __source__.line +end + +@test @macro_caller_lineno() == (@__LINE__) > 12 + +# @__LINE__ in a macro expands to the location of the macrocall in the source +# while __source__.line is the location of the macro caller +macro nested_LINE_expansion() + return quote + return (@emit_LINE, $(__source__.line)) + end +end +macro nested_LINE_expansion2() + return :((@emit_LINE, $(__source__.line))) +end +macro emit_LINE() + return quote + (@__LINE__, $(__source__.line)) + end +end +@test (@emit_LINE) == ((@__LINE__) - 3, @__LINE__) +@test @nested_LINE_expansion() == ((@__LINE__() - 4, @__LINE__() - 12), @__LINE__()) +@test @nested_LINE_expansion2() == ((@__LINE__() - 5, @__LINE__() - 9), @__LINE__()) include("test_sourcepath.jl") thefname = "the fname!//\\&\1*"