diff --git a/base/boot.jl b/base/boot.jl index 43ced22c043d5..ec25fa2bc0b6d 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -533,11 +533,10 @@ import Core: CodeInfo, MethodInstance, CodeInstance, GotoNode, GotoIfNot, Return end # module IR # docsystem basics -const unescape = Symbol("hygienic-scope") macro doc(x...) docex = atdoc(__source__, __module__, x...) isa(docex, Expr) && docex.head === :escape && return docex - return Expr(:escape, Expr(unescape, docex, typeof(atdoc).name.module)) + return Expr(:escape, Expr(:var"hygienic-scope", docex, typeof(atdoc).name.module, __source__)) end macro __doc__(x) return Expr(:escape, Expr(:block, Expr(:meta, :doc), x)) diff --git a/base/docs/Docs.jl b/base/docs/Docs.jl index e0d21715c2147..e0733280e7c7d 100644 --- a/base/docs/Docs.jl +++ b/base/docs/Docs.jl @@ -286,12 +286,26 @@ catdoc(xs...) = vcat(xs...) const keywords = Dict{Symbol, DocStr}() function unblock(@nospecialize ex) + while isexpr(ex, :var"hygienic-scope") + isexpr(ex.args[1], :escape) || break + ex = ex.args[1].args[1] + end isexpr(ex, :block) || return ex exs = filter(ex -> !(isa(ex, LineNumberNode) || isexpr(ex, :line)), ex.args) length(exs) == 1 || return ex return unblock(exs[1]) end +# peek through ex to figure out what kind of expression it may eventually act like +# but ignoring scopes and line numbers +function unescape(@nospecialize ex) + ex = unblock(ex) + while isexpr(ex, :escape) || isexpr(ex, :var"hygienic-scope") + ex = unblock(ex.args[1]) + end + return ex +end + uncurly(@nospecialize ex) = isexpr(ex, :curly) ? ex.args[1] : ex namify(@nospecialize x) = astname(x, isexpr(x, :macro))::Union{Symbol,Expr,GlobalRef} @@ -351,18 +365,19 @@ function metadata(__source__, __module__, expr, ismodule) fields = P[] last_docstr = nothing for each in (expr.args[3]::Expr).args - if isa(each, Symbol) || isexpr(each, :(::)) + eachex = unescape(each) + if isa(eachex, Symbol) || isexpr(eachex, :(::)) # a field declaration if last_docstr !== nothing - push!(fields, P(namify(each::Union{Symbol,Expr}), last_docstr)) + push!(fields, P(namify(eachex::Union{Symbol,Expr}), last_docstr)) last_docstr = nothing end - elseif isexpr(each, :function) || isexpr(each, :(=)) + elseif isexpr(eachex, :function) || isexpr(eachex, :(=)) break - elseif isa(each, String) || isexpr(each, :string) || isexpr(each, :call) || - (isexpr(each, :macrocall) && each.args[1] === Symbol("@doc_str")) + elseif isa(eachex, String) || isexpr(eachex, :string) || isexpr(eachex, :call) || + (isexpr(eachex, :macrocall) && eachex.args[1] === Symbol("@doc_str")) # forms that might be doc strings - last_docstr = each::Union{String,Expr} + last_docstr = each end end dict = :($(Dict{Symbol,Any})($([(:($(P)($(quot(f)), $d)))::Expr for (f, d) in fields]...))) @@ -627,8 +642,9 @@ function loaddocs(docs::Vector{Core.SimpleVector}) for (mod, ex, str, file, line) in docs data = Dict{Symbol,Any}(:path => string(file), :linenumber => line) doc = docstr(str, data) - docstring = docm(LineNumberNode(line, file), mod, doc, ex, false) # expand the real @doc macro now - Core.eval(mod, Expr(Core.unescape, docstring, Docs)) + lno = LineNumberNode(line, file) + docstring = docm(lno, mod, doc, ex, false) # expand the real @doc macro now + Core.eval(mod, Expr(:var"hygienic-scope", docstring, Docs, lno)) end empty!(docs) nothing diff --git a/base/osutils.jl b/base/osutils.jl index 1f5a708d30c7a..95d0562540e5a 100644 --- a/base/osutils.jl +++ b/base/osutils.jl @@ -16,7 +16,7 @@ macro static(ex) @label loop hd = ex.head if hd ∈ (:if, :elseif, :&&, :||) - cond = Core.eval(__module__, ex.args[1]) + cond = Core.eval(__module__, ex.args[1])::Bool if xor(cond, hd === :||) return esc(ex.args[2]) elseif length(ex.args) == 3 diff --git a/base/threadcall.jl b/base/threadcall.jl index 45965fdbc6c65..7548c5063671f 100644 --- a/base/threadcall.jl +++ b/base/threadcall.jl @@ -47,7 +47,7 @@ macro threadcall(f, rettype, argtypes, argvals...) push!(body, :(return Int(Core.sizeof($rettype)))) # return code to generate wrapper function and send work request thread queue - wrapper = Expr(Symbol("hygienic-scope"), wrapper, @__MODULE__) + wrapper = Expr(:var"hygienic-scope", wrapper, @__MODULE__, __source__) return :(let fun_ptr = @cfunction($wrapper, Int, (Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid})) # use cglobal to look up the function on the calling thread do_threadcall(fun_ptr, cglobal($f), $rettype, Any[$(argtypes...)], Any[$(argvals...)]) diff --git a/base/util.jl b/base/util.jl index 6f424f80d13b6..ec99bc6f40c4f 100644 --- a/base/util.jl +++ b/base/util.jl @@ -604,7 +604,7 @@ macro kwdef(expr) kwdefs = nothing end return quote - Base.@__doc__ $(esc(expr)) + $(esc(:($Base.@__doc__ $expr))) $kwdefs end end diff --git a/src/ast.c b/src/ast.c index b1c69db2f0bc9..bd1ffee5b76b1 100644 --- a/src/ast.c +++ b/src/ast.c @@ -436,6 +436,8 @@ static jl_value_t *scm_to_julia(fl_context_t *fl_ctx, value_t e, jl_module_t *mo } JL_CATCH { // if expression cannot be converted, replace with error expr + //jl_(jl_current_exception()); + //jlbacktrace(); jl_expr_t *ex = jl_exprn(jl_error_sym, 1); v = (jl_value_t*)ex; jl_array_ptr_set(ex->args, 0, jl_cstr_to_string("invalid AST")); @@ -1000,7 +1002,59 @@ int jl_has_meta(jl_array_t *body, jl_sym_t *sym) JL_NOTSAFEPOINT return 0; } -static jl_value_t *jl_invoke_julia_macro(jl_array_t *args, jl_module_t *inmodule, jl_module_t **ctx, size_t world, int throw_load_error) +// Utility function to return whether `e` is any of the special AST types or +// will always evaluate to itself exactly unchanged. This corresponds to +// `is_self_quoting` in Core.Compiler utilities. +int jl_is_ast_node(jl_value_t *e) JL_NOTSAFEPOINT +{ + return jl_is_newvarnode(e) + || jl_is_code_info(e) + || jl_is_linenode(e) + || jl_is_gotonode(e) + || jl_is_gotoifnot(e) + || jl_is_returnnode(e) + || jl_is_ssavalue(e) + || jl_is_slotnumber(e) + || jl_is_argument(e) + || jl_is_quotenode(e) + || jl_is_globalref(e) + || jl_is_symbol(e) + || jl_is_pinode(e) + || jl_is_phinode(e) + || jl_is_phicnode(e) + || jl_is_upsilonnode(e) + || jl_is_expr(e); +} + +static int is_self_quoting_expr(jl_expr_t *e) JL_NOTSAFEPOINT +{ + return (e->head == jl_inert_sym || + e->head == jl_core_sym || + e->head == jl_line_sym || + e->head == jl_lineinfo_sym || + e->head == jl_meta_sym || + e->head == jl_boundscheck_sym || + e->head == jl_inline_sym || + e->head == jl_noinline_sym); +} + +// any AST, except those that cannot contain symbols +// and have no side effects +int need_esc_node(jl_value_t *e) JL_NOTSAFEPOINT +{ + if (jl_is_linenode(e) + || jl_is_ssavalue(e) + || jl_is_slotnumber(e) + || jl_is_argument(e) + || jl_is_quotenode(e)) + return 0; + if (jl_is_expr(e)) + return !is_self_quoting_expr((jl_expr_t*)e); + // note: jl_is_globalref(e) is not included here, since we care a little about about having a line number for it + return jl_is_ast_node(e); +} + +static jl_value_t *jl_invoke_julia_macro(jl_array_t *args, jl_module_t *inmodule, jl_module_t **ctx, jl_value_t **lineinfo, size_t world, int throw_load_error) { jl_task_t *ct = jl_current_task; JL_TIMING(MACRO_INVOCATION, MACRO_INVOCATION); @@ -1012,10 +1066,9 @@ static jl_value_t *jl_invoke_julia_macro(jl_array_t *args, jl_module_t *inmodule margs[0] = jl_array_ptr_ref(args, 0); // __source__ argument jl_value_t *lno = jl_array_ptr_ref(args, 1); + if (!jl_is_linenode(lno)) + lno = jl_new_struct(jl_linenumbernode_type, jl_box_long(0), jl_nothing); margs[1] = lno; - if (!jl_is_linenode(lno)) { - margs[1] = jl_new_struct(jl_linenumbernode_type, jl_box_long(0), jl_nothing); - } margs[2] = (jl_value_t*)inmodule; for (i = 3; i < nargs; i++) margs[i] = jl_array_ptr_ref(args, i - 1); @@ -1054,6 +1107,7 @@ static jl_value_t *jl_invoke_julia_macro(jl_array_t *args, jl_module_t *inmodule } } ct->world_age = last_age; + *lineinfo = margs[1]; JL_GC_POP(); return result; } @@ -1076,14 +1130,18 @@ static jl_value_t *jl_expand_macros(jl_value_t *expr, jl_module_t *inmodule, str JL_GC_POP(); return expr; } - if (e->head == jl_hygienicscope_sym && jl_expr_nargs(e) == 2) { + if (e->head == jl_hygienicscope_sym && jl_expr_nargs(e) >= 2) { struct macroctx_stack newctx; newctx.m = (jl_module_t*)jl_exprarg(e, 1); JL_TYPECHK(hygienic-scope, module, (jl_value_t*)newctx.m); newctx.parent = macroctx; jl_value_t *a = jl_exprarg(e, 0); jl_value_t *a2 = jl_expand_macros(a, inmodule, &newctx, onelevel, world, throw_load_error); - if (a != a2) + if (jl_is_expr(a2) && ((jl_expr_t*)a2)->head == jl_escape_sym && !need_esc_node(jl_exprarg(a2, 0))) + expr = jl_exprarg(a2, 0); + else if (!need_esc_node(a2)) + expr = a2; + else if (a != a2) jl_array_ptr_set(e->args, 0, a2); return expr; } @@ -1091,21 +1149,28 @@ static jl_value_t *jl_expand_macros(jl_value_t *expr, jl_module_t *inmodule, str struct macroctx_stack newctx; newctx.m = macroctx ? macroctx->m : inmodule; newctx.parent = macroctx; - jl_value_t *result = jl_invoke_julia_macro(e->args, inmodule, &newctx.m, world, throw_load_error); + jl_value_t *lineinfo = NULL; + jl_value_t *result = jl_invoke_julia_macro(e->args, inmodule, &newctx.m, &lineinfo, world, throw_load_error); + if (!need_esc_node(result)) + return result; jl_value_t *wrap = NULL; - JL_GC_PUSH3(&result, &wrap, &newctx.m); + JL_GC_PUSH4(&result, &wrap, &newctx.m, &lineinfo); // copy and wrap the result in `(hygienic-scope ,result ,newctx) if (jl_is_expr(result) && ((jl_expr_t*)result)->head == jl_escape_sym) result = jl_exprarg(result, 0); else - wrap = (jl_value_t*)jl_exprn(jl_hygienicscope_sym, 2); + wrap = (jl_value_t*)jl_exprn(jl_hygienicscope_sym, 3); result = jl_copy_ast(result); if (!onelevel) result = jl_expand_macros(result, inmodule, wrap ? &newctx : macroctx, onelevel, world, throw_load_error); - if (wrap) { + if (wrap && need_esc_node(result)) { jl_exprargset(wrap, 0, result); jl_exprargset(wrap, 1, newctx.m); - result = wrap; + jl_exprargset(wrap, 2, lineinfo); + if (jl_is_expr(result) && ((jl_expr_t*)result)->head == jl_escape_sym) + result = jl_exprarg(result, 0); + else + result = wrap; } JL_GC_POP(); return result; diff --git a/src/ast.scm b/src/ast.scm index 88220c03a7aa6..87db8449b3992 100644 --- a/src/ast.scm +++ b/src/ast.scm @@ -479,12 +479,13 @@ (define (eq-sym? a b) (or (eq? a b) (and (ssavalue? a) (ssavalue? b) (eqv? (cdr a) (cdr b))))) -(define (blockify e) +(define (blockify e (lno #f)) + (set! lno (if lno (list lno) '())) (if (and (pair? e) (eq? (car e) 'block)) (if (null? (cdr e)) - `(block (null)) - e) - `(block ,e))) + `(block ,@lno (null)) + (if (null? lno) e `(block ,@lno ,@(cdr e)))) + `(block ,@lno ,e))) (define (make-var-info name) (list name '(core Any) 0)) (define vinfo:name car) diff --git a/src/jlfrontend.scm b/src/jlfrontend.scm index aefac6d102aea..d376bc27085ab 100644 --- a/src/jlfrontend.scm +++ b/src/jlfrontend.scm @@ -93,18 +93,38 @@ ;; lowering entry points +; find the first line number in this expression, before we might eliminate them +(define (first-lineno blk) + (cond ((not (pair? blk)) #f) + ((eq? (car blk) 'line) blk) + ((and (eq? (car blk) 'hygienic-scope) (pair? (cdddr blk)) (pair? (cadddr blk)) (eq? (car (cadddr blk)) 'line)) + (cadddr blk)) + ((memq (car blk) '(escape hygienic-scope)) + (first-lineno (cadr blk))) + ((memq (car blk) '(toplevel block)) + (let loop ((xs (cdr blk))) + (and (pair? xs) + (let ((elt (first-lineno (car xs)))) + (or elt (loop (cdr xs))))))) + (else #f))) + ;; return a lambda expression representing a thunk for a top-level expression ;; note: expansion of stuff inside module is delayed, so the contents obey ;; toplevel expansion order (don't expand until stuff before is evaluated). (define (expand-toplevel-expr-- e file line) - (let ((ex0 (julia-expand-macroscope e))) + (let ((lno (first-lineno e)) + (ex0 (julia-expand-macroscope e))) + (if (and lno (or (not (length= lno 3)) (not (atom? (caddr lno))))) (set! lno #f)) (if (toplevel-only-expr? ex0) - ex0 - (let* ((ex (julia-expand0 ex0 file line)) + (if (and (pair? e) (memq (car ex0) '(error incomplete))) + ex0 + (if lno `(toplevel ,lno ,ex0) ex0)) + (let* ((linenode (if (and lno (or (= line 0) (eq? file 'none))) lno `(line ,line ,file))) + (ex (julia-expand0 ex0 linenode)) (th (julia-expand1 `(lambda () () (scope-block - ,(blockify ex))) + ,(blockify ex lno))) file line))) (if (and (null? (cdadr (caddr th))) (and (length= (lam:body th) 2) diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index cac8c7b5228b9..df4e791e1fa10 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -5090,8 +5090,8 @@ f(x) = yt(x) (define *current-desugar-loc* #f) -(define (julia-expand0 ex file line) - (with-bindings ((*current-desugar-loc* `(line ,line ,file))) +(define (julia-expand0 ex lno) + (with-bindings ((*current-desugar-loc* lno)) (trycatch (expand-forms ex) (lambda (e) (if (and (pair? e) (eq? (car e) 'error)) @@ -5106,4 +5106,4 @@ f(x) = yt(x) (define (julia-expand ex (file 'none) (line 0)) (julia-expand1 (julia-expand0 - (julia-expand-macroscope ex) file line) file line)) + (julia-expand-macroscope ex) `(line ,line ,file)) file line)) diff --git a/src/macroexpand.scm b/src/macroexpand.scm index 2933ca4888c4e..14d1fe1c5ab94 100644 --- a/src/macroexpand.scm +++ b/src/macroexpand.scm @@ -448,7 +448,8 @@ ((hygienic-scope) ; TODO: move this lowering to resolve-scopes, instead of reimplementing it here badly (let ((parent-scope (cons (list env m) parent-scope)) (body (cadr e)) - (m (caddr e))) + (m (caddr e)) + (lno (cdddr e))) (resolve-expansion-vars-with-new-env body env m parent-scope inarg #t))) ((tuple) (cons (car e) @@ -574,7 +575,8 @@ ((eq? (car e) 'module) e) ((eq? (car e) 'hygienic-scope) (let ((form (cadr e)) ;; form is the expression returned from expand-macros - (modu (caddr e))) ;; m is the macro's def module + (modu (caddr e)) ;; m is the macro's def module + (lno (cdddr e))) ;; lno is (optionally) the line number node (resolve-expansion-vars form modu))) (else (map julia-expand-macroscopes- e)))) @@ -585,8 +587,9 @@ ((eq? (car e) 'hygienic-scope) (let ((parent-scope (list relabels parent-scope)) (body (cadr e)) - (m (caddr e))) - `(hygienic-scope ,(rename-symbolic-labels- (cadr e) (table) parent-scope) ,m))) + (m (caddr e)) + (lno (cdddr e))) + `(hygienic-scope ,(rename-symbolic-labels- (cadr e) (table) parent-scope) ,m ,@lno))) ((and (eq? (car e) 'escape) (not (null? parent-scope))) `(escape ,(apply rename-symbolic-labels- (cadr e) parent-scope))) ((or (eq? (car e) 'symbolicgoto) (eq? (car e) 'symboliclabel)) diff --git a/stdlib/Test/src/Test.jl b/stdlib/Test/src/Test.jl index 392b736c09837..11bb6229ec0a1 100644 --- a/stdlib/Test/src/Test.jl +++ b/stdlib/Test/src/Test.jl @@ -27,7 +27,7 @@ export TestLogger, LogRecord using Random using Random: AbstractRNG, default_rng using InteractiveUtils: gen_call_with_extracted_types -using Base: typesplit +using Base: typesplit, remove_linenums! using Serialization: Serialization const DISPLAY_FAILED = ( @@ -500,19 +500,20 @@ macro test(ex, kws...) # Build the test expression test_expr!("@test", ex, kws...) - orig_ex = Expr(:inert, ex) result = get_test_result(ex, __source__) - return quote + ex = Expr(:inert, ex) + result = quote if $(length(skip) > 0 && esc(skip[1])) - record(get_testset(), Broken(:skipped, $orig_ex)) + record(get_testset(), Broken(:skipped, $ex)) else let _do = $(length(broken) > 0 && esc(broken[1])) ? do_broken_test : do_test - _do($result, $orig_ex) + _do($result, $ex) end end end + return result end """ @@ -540,10 +541,10 @@ Test Broken """ macro test_broken(ex, kws...) test_expr!("@test_broken", ex, kws...) - orig_ex = Expr(:inert, ex) result = get_test_result(ex, __source__) # code to call do_test with execution result and original expr - :(do_broken_test($result, $orig_ex)) + ex = Expr(:inert, ex) + return :(do_broken_test($result, $ex)) end """ @@ -570,9 +571,9 @@ Test Broken """ macro test_skip(ex, kws...) test_expr!("@test_skip", ex, kws...) - orig_ex = Expr(:inert, ex) - testres = :(Broken(:skipped, $orig_ex)) - :(record(get_testset(), $testres)) + ex = Expr(:inert, ex) + testres = :(Broken(:skipped, $ex)) + return :(record(get_testset(), $testres)) end # An internal function, called by the code generated by the @test @@ -660,7 +661,8 @@ function get_test_result(ex, source) $negate, )) else - testret = :(Returned($(esc(orig_ex)), nothing, $(QuoteNode(source)))) + ex = Expr(:block, source, esc(orig_ex)) + testret = :(Returned($ex, nothing, $(QuoteNode(source)))) end result = quote try @@ -670,7 +672,6 @@ function get_test_result(ex, source) Threw(_e, Base.current_exceptions(), $(QuoteNode(source))) end end - Base.remove_linenums!(result) result end @@ -759,9 +760,10 @@ In the final example, instead of matching a single string it could alternatively """ macro test_throws(extype, ex) orig_ex = Expr(:inert, ex) + ex = Expr(:block, __source__, esc(ex)) result = quote try - Returned($(esc(ex)), nothing, $(QuoteNode(__source__))) + Returned($ex, nothing, $(QuoteNode(__source__))) catch _e if $(esc(extype)) != InterruptException && _e isa InterruptException rethrow() @@ -769,8 +771,7 @@ macro test_throws(extype, ex) Threw(_e, nothing, $(QuoteNode(__source__))) end end - Base.remove_linenums!(result) - :(do_test_throws($result, $orig_ex, $(esc(extype)))) + return :(do_test_throws($result, $orig_ex, $(esc(extype)))) end const MACROEXPAND_LIKE = Symbol.(("@macroexpand", "@macroexpand1", "macroexpand")) @@ -1828,10 +1829,9 @@ function _inferred(ex, mod, allow = :(Union{})) ex = Expr(:call, GlobalRef(Test, :_materialize_broadcasted), farg, ex.args[2:end]...) end - Base.remove_linenums!(let ex = ex; + result = let ex = ex quote - let - allow = $(esc(allow)) + let allow = $(esc(allow)) allow isa Type || throw(ArgumentError("@inferred requires a type as second argument")) $(if any(a->(Meta.isexpr(a, :kw) || Meta.isexpr(a, :parameters)), ex.args) # Has keywords @@ -1855,7 +1855,8 @@ function _inferred(ex, mod, allow = :(Union{})) result end end - end) + end + return remove_linenums!(result) end function is_in_mods(m::Module, recursive::Bool, mods) diff --git a/test/compiler/inference.jl b/test/compiler/inference.jl index 21b5b66142fbd..da5772744607d 100644 --- a/test/compiler/inference.jl +++ b/test/compiler/inference.jl @@ -3804,7 +3804,7 @@ end end end end - @test occursin("thunk from $(@__MODULE__) starting at $(@__FILE__):$((@__LINE__) - 5)", string(timingmod.children)) + @test occursin("thunk from $(@__MODULE__) starting at $(@__FILE__):$((@__LINE__) - 6)", string(timingmod.children)) # END LINE NUMBER SENSITIVITY # Recursive function diff --git a/test/deprecation_exec.jl b/test/deprecation_exec.jl index 5b465e05f0a12..61ffcc2a59ac6 100644 --- a/test/deprecation_exec.jl +++ b/test/deprecation_exec.jl @@ -8,8 +8,6 @@ using Test using Logging -using Base: remove_linenums! - module DeprecationTests # to test @deprecate f() = true diff --git a/test/docs.jl b/test/docs.jl index 6707278c53847..7f6ece4e76ab4 100644 --- a/test/docs.jl +++ b/test/docs.jl @@ -642,7 +642,7 @@ macro m1_11993() end macro m2_11993() - Symbol("@m1_11993") + esc(Symbol("@m1_11993")) end @doc "This should document @m1... since its the result of expansion" @m2_11993 diff --git a/test/errorshow.jl b/test/errorshow.jl index 94722b803865f..5c6d8e3bea08c 100644 --- a/test/errorshow.jl +++ b/test/errorshow.jl @@ -531,7 +531,7 @@ end ex = :(@nest2b 42) @test _macroexpand1(ex) != macroexpand(M,ex) @test _macroexpand1(_macroexpand1(ex)) == macroexpand(M, ex) - @test (@macroexpand1 @nest2b 42) == _macroexpand1(ex) + @test (@macroexpand1 @nest2b 42) == _macroexpand1(:(@nest2b 42)) end foo_9965(x::Float64; w=false) = x diff --git a/test/goto.jl b/test/goto.jl index 011ec32a851bd..e069058f38d52 100644 --- a/test/goto.jl +++ b/test/goto.jl @@ -87,7 +87,7 @@ end @test goto_test5_3() -@test Expr(:error, "goto from a try/finally block is not permitted") == +@test Expr(:error, "goto from a try/finally block is not permitted around $(@__FILE__):$(3 + @__LINE__)") == Meta.lower(@__MODULE__, quote function goto_test6() try diff --git a/test/syntax.jl b/test/syntax.jl index 8bba5f9205613..aa854bfa0d19b 100644 --- a/test/syntax.jl +++ b/test/syntax.jl @@ -3,6 +3,7 @@ # tests for parser and syntax lowering using Random +using Base: remove_linenums! import Base.Meta.ParseError @@ -38,13 +39,8 @@ end # issue #9704 let a = :a - @test :(try - catch $a - end) == :(try - catch a - end) - @test :(module $a end) == :(module a - end) + @test :(try catch $a end) == :(try catch a end) + @test :(module $a end) == :(module a end) end # string literals @@ -706,7 +702,7 @@ m1_exprs = get_expr_list(Meta.lower(@__MODULE__, quote @m1 end)) let low3 = Meta.lower(@__MODULE__, quote @m3 end) m3_exprs = get_expr_list(low3) ci = low3.args[1]::Core.CodeInfo - @test ci.codelocs == [3, 1] + @test ci.codelocs == [4, 2] @test is_return_ssavalue(m3_exprs[end]) end @@ -1186,10 +1182,13 @@ end @test Meta.parse("@Mdl.foo [1] + [2]") == Meta.parse("@Mdl.foo([1] + [2])") # issue #24289 +module M24289 macro m24289() :(global $(esc(:x24289)) = 1) end -@test (@macroexpand @m24289) == :(global x24289 = 1) +end +M24289.@m24289 +@test x24289 === 1 # parsing numbers with _ and . @test Meta.parse("1_2.3_4") == 12.34 @@ -1664,10 +1663,12 @@ end macro foo28244(sym) x = :(bar()) push!(x.args, Expr(sym)) - x + esc(x) +end +@test @macroexpand(@foo28244(kw)) == Expr(:call, :bar, Expr(:kw)) +let x = @macroexpand @foo28244(var"let") + @test Meta.lower(@__MODULE__, x) == Expr(:error, "malformed expression") end -@test (@macroexpand @foo28244(kw)) == Expr(:call, GlobalRef(@__MODULE__,:bar), Expr(:kw)) -@test eval(:(@macroexpand @foo28244($(Symbol("let"))))) == Expr(:error, "malformed expression") # #16356 @test_throws ParseError Meta.parse("0xapi") @@ -1932,8 +1933,8 @@ macro id28992(x) x end @test Meta.@lower(.+(a,b) = 0) == Expr(:error, "invalid function name \".+\"") @test Meta.@lower((.+)(a,b) = 0) == Expr(:error, "invalid function name \"(.+)\"") let m = @__MODULE__ - @test Meta.lower(m, :($m.@id28992(.+(a,b) = 0))) == Expr(:error, "invalid function name \"$(nameof(m)).:.+\"") - @test Meta.lower(m, :($m.@id28992((.+)(a,b) = 0))) == Expr(:error, "invalid function name \"(.$(nameof(m)).+)\"") + @test Meta.lower(m, :($m.@id28992(.+(a,b) = 0))) == Expr(:error, "invalid function name \"$(nameof(m)).:.+\" around $(@__FILE__):$(@__LINE__)") + @test Meta.lower(m, :($m.@id28992((.+)(a,b) = 0))) == Expr(:error, "invalid function name \"(.$(nameof(m)).+)\" around $(@__FILE__):$(@__LINE__)") end @test @id28992([1] .< [2] .< [3]) == [true] @test @id28992(2 ^ -2) == 0.25 @@ -2639,10 +2640,10 @@ import .TestImportAs.Mod2 as M2 end @testset "issue #37393" begin - @test :(for outer i = 1:3; end) == Expr(:for, Expr(:(=), Expr(:outer, :i), :(1:3)), :(;;)) + @test remove_linenums!(:(for outer i = 1:3; end)) == Expr(:for, Expr(:(=), Expr(:outer, :i), :(1:3)), :(;;)) i = :i - @test :(for outer $i = 1:3; end) == Expr(:for, Expr(:(=), Expr(:outer, :i), :(1:3)), :(;;)) - @test :(for outer = 1:3; end) == Expr(:for, Expr(:(=), :outer, :(1:3)), :(;;)) + @test remove_linenums!(:(for outer $i = 1:3; end)) == Expr(:for, Expr(:(=), Expr(:outer, :i), :(1:3)), :(;;)) + @test remove_linenums!(:(for outer = 1:3; end)) == Expr(:for, Expr(:(=), :outer, :(1:3)), :(;;)) # TIL that this is possible for outer $ i = 1:3 @test 1 $ 2 in 1:3 @@ -2900,13 +2901,13 @@ macro m_underscore_hygiene() return :(_ = 1) end -@test @macroexpand(@m_underscore_hygiene()) == :(_ = 1) +@test Meta.@lower(@m_underscore_hygiene()) === 1 macro m_begin_hygiene(a) return :($(esc(a))[begin]) end -@test @m_begin_hygiene([1, 2, 3]) == 1 +@test @m_begin_hygiene([1, 2, 3]) === 1 # issue 40258 @test "a $("b $("c")")" == "a b c" @@ -3226,8 +3227,14 @@ end @test Meta.parseatom("@foo", 1; filename="foo", lineno=7) == (Expr(:macrocall, :var"@foo", LineNumberNode(7, :foo)), 5) @test Meta.parseall("@foo"; filename="foo", lineno=3) == Expr(:toplevel, LineNumberNode(3, :foo), Expr(:macrocall, :var"@foo", LineNumberNode(3, :foo))) -let ex = :(const $(esc(:x)) = 1; (::typeof(2))() = $(esc(:x))) - @test macroexpand(Main, Expr(:var"hygienic-scope", ex, Main)).args[3].args[1] == :((::$(GlobalRef(Main, :typeof))(2))()) +module M43993 +function foo43993 end +const typeof = error +end +let ex = :(const $(esc(:x)) = 1; (::typeof($(esc(:foo43993))))() = $(esc(:x))) + Core.eval(M43993, Expr(:var"hygienic-scope", ex, Core)) + @test M43993.x === 1 + @test invokelatest(M43993.foo43993) === 1 end struct Foo44013