diff --git a/base/Base.jl b/base/Base.jl index 9d510b5c5d47c..1a8f25dc2501e 100644 --- a/base/Base.jl +++ b/base/Base.jl @@ -30,6 +30,9 @@ let os = ccall(:jl_get_UNAME, Any, ()) end end +# metaprogramming +include("meta.jl") + # subarrays include("subarray.jl") include("views.jl") @@ -157,9 +160,6 @@ include("weakkeydict.jl") # ScopedValues include("scopedvalues.jl") -# metaprogramming -include("meta.jl") - # Logging include("logging/logging.jl") using .CoreLogging diff --git a/base/boot.jl b/base/boot.jl index d055c47516f91..bbf3fc0faa227 100644 --- a/base/boot.jl +++ b/base/boot.jl @@ -746,7 +746,7 @@ end # module providing the IR object model # excluding types already exported by Core (GlobalRef, QuoteNode, Expr, LineNumberNode) -# any type beyond these is self-quoting (see also Base.is_ast_node) +# any type beyond these is self-quoting (see also Base.isa_ast_node) module IR export CodeInfo, MethodInstance, CodeInstance, GotoNode, GotoIfNot, ReturnNode, diff --git a/base/cartesian.jl b/base/cartesian.jl index ca0fc0aac0cfc..9af9485b653de 100644 --- a/base/cartesian.jl +++ b/base/cartesian.jl @@ -36,15 +36,14 @@ If you want just a post-expression, supply [`nothing`](@ref) for the pre-express parentheses and semicolons, you can supply multi-statement expressions. """ macro nloops(N, itersym, rangeexpr, args...) - _nloops(N, itersym, rangeexpr, args...) + _nloops(N, itersym, true, rangeexpr, args...) end -function _nloops(N::Int, itersym::Symbol, arraysym::Symbol, args::Expr...) - @gensym d - _nloops(N, itersym, :($d->Base.axes($arraysym, $d)), args...) +function _nloops(N::Int, itersym::Symbol, esc_rng::Bool, arraysym::Symbol, args::Expr...) + _nloops(N, itersym, false, :(d->axes($(esc(arraysym)), d)), args...) end -function _nloops(N::Int, itersym::Symbol, rangeexpr::Expr, args::Expr...) +function _nloops(N::Int, itersym::Symbol, esc_rng::Bool, rangeexpr::Expr, args::Expr...) if rangeexpr.head !== :-> throw(ArgumentError("second argument must be an anonymous function expression to compute the range")) end @@ -55,11 +54,13 @@ function _nloops(N::Int, itersym::Symbol, rangeexpr::Expr, args::Expr...) ex = Expr(:escape, body) for dim = 1:N itervar = inlineanonymous(itersym, dim) + itervar = esc(itervar) rng = inlineanonymous(rangeexpr, dim) + esc_rng && (rng = esc(rng)) preexpr = length(args) > 1 ? inlineanonymous(args[1], dim) : (:(nothing)) postexpr = length(args) > 2 ? inlineanonymous(args[2], dim) : (:(nothing)) ex = quote - for $(esc(itervar)) = $(esc(rng)) + for $itervar = $rng $(esc(preexpr)) $ex $(esc(postexpr)) diff --git a/base/experimental.jl b/base/experimental.jl index 31cc12f1ab796..23e434c3c208d 100644 --- a/base/experimental.jl +++ b/base/experimental.jl @@ -41,7 +41,7 @@ within this scope, even if the compiler can't prove this to be the case. Experimental API. Subject to change without deprecation. """ macro aliasscope(body) - sym = gensym() + sym = :aliasscope_result quote $(Expr(:aliasscope)) $sym = $(esc(body)) diff --git a/base/meta.jl b/base/meta.jl index 5d880a7442b3e..54e37869568d7 100644 --- a/base/meta.jl +++ b/base/meta.jl @@ -18,9 +18,121 @@ export quot, public parse -using Base: isidentifier, isoperator, isunaryoperator, isbinaryoperator, ispostfixoperator import Base: isexpr +## AST decoding helpers ## + +is_id_start_char(c::AbstractChar) = ccall(:jl_id_start_char, Cint, (UInt32,), c) != 0 +is_id_char(c::AbstractChar) = ccall(:jl_id_char, Cint, (UInt32,), c) != 0 + +""" + isidentifier(s) -> Bool + +Return whether the symbol or string `s` contains characters that are parsed as +a valid ordinary identifier (not a binary/unary operator) in Julia code; +see also [`Base.isoperator`](@ref). + +Internally Julia allows any sequence of characters in a `Symbol` (except `\\0`s), +and macros automatically use variable names containing `#` in order to avoid +naming collision with the surrounding code. In order for the parser to +recognize a variable, it uses a limited set of characters (greatly extended by +Unicode). `isidentifier()` makes it possible to query the parser directly +whether a symbol contains valid characters. + +# Examples +```jldoctest +julia> Meta.isidentifier(:x), Meta.isidentifier("1x") +(true, false) +``` +""" +function isidentifier(s::AbstractString) + x = Iterators.peel(s) + isnothing(x) && return false + (s == "true" || s == "false") && return false + c, rest = x + is_id_start_char(c) || return false + return all(is_id_char, rest) +end +isidentifier(s::Symbol) = isidentifier(string(s)) + +is_op_suffix_char(c::AbstractChar) = ccall(:jl_op_suffix_char, Cint, (UInt32,), c) != 0 + +_isoperator(s) = ccall(:jl_is_operator, Cint, (Cstring,), s) != 0 + +""" + isoperator(s::Symbol) + +Return `true` if the symbol can be used as an operator, `false` otherwise. + +# Examples +```jldoctest +julia> Meta.isoperator(:+), Meta.isoperator(:f) +(true, false) +``` +""" +isoperator(s::Union{Symbol,AbstractString}) = _isoperator(s) || ispostfixoperator(s) + +""" + isunaryoperator(s::Symbol) + +Return `true` if the symbol can be used as a unary (prefix) operator, `false` otherwise. + +# Examples +```jldoctest +julia> Meta.isunaryoperator(:-), Meta.isunaryoperator(:√), Meta.isunaryoperator(:f) +(true, true, false) +``` +""" +isunaryoperator(s::Symbol) = ccall(:jl_is_unary_operator, Cint, (Cstring,), s) != 0 +is_unary_and_binary_operator(s::Symbol) = ccall(:jl_is_unary_and_binary_operator, Cint, (Cstring,), s) != 0 +is_syntactic_operator(s::Symbol) = ccall(:jl_is_syntactic_operator, Cint, (Cstring,), s) != 0 + +""" + isbinaryoperator(s::Symbol) + +Return `true` if the symbol can be used as a binary (infix) operator, `false` otherwise. + +# Examples +```jldoctest +julia> Meta.isbinaryoperator(:-), Meta.isbinaryoperator(:√), Meta.isbinaryoperator(:f) +(true, false, false) +``` +""" +function isbinaryoperator(s::Symbol) + return _isoperator(s) && (!isunaryoperator(s) || is_unary_and_binary_operator(s)) && + s !== Symbol("'") +end + +""" + ispostfixoperator(s::Union{Symbol,AbstractString}) + +Return `true` if the symbol can be used as a postfix operator, `false` otherwise. + +# Examples +```jldoctest +julia> Meta.ispostfixoperator(Symbol("'")), Meta.ispostfixoperator(Symbol("'ᵀ")), Meta.ispostfixoperator(:-) +(true, true, false) +``` +""" +function ispostfixoperator(s::Union{Symbol,AbstractString}) + s = String(s)::String + return startswith(s, '\'') && all(is_op_suffix_char, SubString(s, 2)) +end + +const keyword_syms = IdSet{Symbol}([ + :baremodule, :begin, :break, :catch, :const, :continue, :do, :else, :elseif, + :end, :export, :var"false", :finally, :for, :function, :global, :if, :import, + :let, :local, :macro, :module, :public, :quote, :return, :struct, :var"true", + :try, :using, :while ]) + +function is_valid_identifier(sym) + return (isidentifier(sym) && !(sym in keyword_syms)) || + (_isoperator(sym) && + !(sym in (Symbol("'"), :(::), :?)) && + !is_syntactic_operator(sym) + ) +end + """ Meta.quot(ex)::Expr @@ -516,6 +628,21 @@ function unescape(@nospecialize ex) return ex end +""" + Meta.reescape(unescaped_expr, original_expr) + +Re-wrap `unescaped_expr` with the same level of escaping as `original_expr` had. +This is the inverse operation of [`unescape`](@ref) - if the original expression +was escaped, the unescaped expression is wrapped in `:escape` again. +""" +function reescape(@nospecialize(unescaped_expr), @nospecialize(original_expr)) + if isexpr(original_expr, :escape) || isexpr(original_expr, :var"hygienic-scope") + return reescape(Expr(:escape, unescaped_expr), original_expr.args[1]) + else + return unescaped_expr + end +end + """ Meta.uncurly(expr) diff --git a/base/reflection.jl b/base/reflection.jl index 5ad05b615ddfd..bd81788d9a3b3 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -1305,19 +1305,19 @@ macro invoke(ex) f, args, kwargs = destructure_callex(topmod, ex) types = Expr(:curly, :Tuple) out = Expr(:call, GlobalRef(Core, :invoke)) - isempty(kwargs) || push!(out.args, Expr(:parameters, kwargs...)) - push!(out.args, f) + isempty(kwargs) || push!(out.args, Expr(:parameters, Any[esc(kw) for kw in kwargs]...)) + push!(out.args, esc(f)) push!(out.args, types) for arg in args if isexpr(arg, :(::)) - push!(out.args, arg.args[1]) - push!(types.args, arg.args[2]) + push!(out.args, esc(arg.args[1])) + push!(types.args, esc(arg.args[2])) else - push!(out.args, arg) - push!(types.args, Expr(:call, GlobalRef(Core, :Typeof), arg)) + push!(out.args, esc(arg)) + push!(types.args, Expr(:call, GlobalRef(Core, :Typeof), esc(arg))) end end - return esc(out) + return out end getglobalref(gr::GlobalRef, world::UInt) = ccall(:jl_eval_globalref, Any, (Any, UInt), gr, world) @@ -1367,42 +1367,42 @@ macro invokelatest(ex) if !isa(f, GlobalRef) out_f = Expr(:call, GlobalRef(Base, :invokelatest)) - isempty(kwargs) || push!(out_f.args, Expr(:parameters, kwargs...)) + isempty(kwargs) || push!(out_f.args, Expr(:parameters, Any[esc(kw) for kw in kwargs]...)) if isexpr(f, :(.)) - s = gensym() + s = :s check = quote - $s = $(f.args[1]) + $s = $(esc(f.args[1])) isa($s, Module) end - push!(out_f.args, Expr(:(.), s, f.args[2])) + push!(out_f.args, Expr(:(.), s, esc(f.args[2]))) else - push!(out_f.args, f) + push!(out_f.args, esc(f)) end - append!(out_f.args, args) + append!(out_f.args, Any[esc(arg) for arg in args]) if @isdefined(s) - f = :(GlobalRef($s, $(f.args[2]))) - elseif !isa(f, Symbol) - return esc(out_f) + f = :(GlobalRef($s, $(esc(f.args[2])))) + elseif isa(f, Symbol) + check = esc(:($(Expr(:isglobal, f)))) else - check = :($(Expr(:isglobal, f))) + return out_f end end out_gr = Expr(:call, GlobalRef(Base, :invokelatest_gr)) - isempty(kwargs) || push!(out_gr.args, Expr(:parameters, kwargs...)) + isempty(kwargs) || push!(out_gr.args, Expr(:parameters, Any[esc(kw) for kw in kwargs]...)) push!(out_gr.args, isa(f, GlobalRef) ? QuoteNode(f) : isa(f, Symbol) ? QuoteNode(GlobalRef(__module__, f)) : f) - append!(out_gr.args, args) + append!(out_gr.args, Any[esc(arg) for arg in args]) if isa(f, GlobalRef) - return esc(out_gr) + return out_gr end # f::Symbol - return esc(:($check ? $out_gr : $out_f)) + return :($check ? $out_gr : $out_f) end function destructure_callex(topmod::Module, @nospecialize(ex)) diff --git a/base/show.jl b/base/show.jl index 870b2f47fe4a6..2b4f514d4b0d7 100644 --- a/base/show.jl +++ b/base/show.jl @@ -1,6 +1,8 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license using .Compiler: has_typevar +using .Meta: isidentifier, isoperator, isunaryoperator, isbinaryoperator, ispostfixoperator, + is_id_start_char, is_id_char, _isoperator, is_syntactic_operator, is_valid_identifier function show(io::IO, ::MIME"text/plain", u::UndefInitializer) show(io, u) @@ -1542,104 +1544,6 @@ const expr_parens = Dict(:tuple=>('(',')'), :vcat=>('[',']'), :ncat =>('[',']'), :nrow =>('[',']'), :braces=>('{','}'), :bracescat=>('{','}')) -## AST decoding helpers ## - -is_id_start_char(c::AbstractChar) = ccall(:jl_id_start_char, Cint, (UInt32,), c) != 0 -is_id_char(c::AbstractChar) = ccall(:jl_id_char, Cint, (UInt32,), c) != 0 - -""" - isidentifier(s) -> Bool - -Return whether the symbol or string `s` contains characters that are parsed as -a valid ordinary identifier (not a binary/unary operator) in Julia code; -see also [`Base.isoperator`](@ref). - -Internally Julia allows any sequence of characters in a `Symbol` (except `\\0`s), -and macros automatically use variable names containing `#` in order to avoid -naming collision with the surrounding code. In order for the parser to -recognize a variable, it uses a limited set of characters (greatly extended by -Unicode). `isidentifier()` makes it possible to query the parser directly -whether a symbol contains valid characters. - -# Examples -```jldoctest -julia> Meta.isidentifier(:x), Meta.isidentifier("1x") -(true, false) -``` -""" -function isidentifier(s::AbstractString) - x = Iterators.peel(s) - isnothing(x) && return false - (s == "true" || s == "false") && return false - c, rest = x - is_id_start_char(c) || return false - return all(is_id_char, rest) -end -isidentifier(s::Symbol) = isidentifier(string(s)) - -is_op_suffix_char(c::AbstractChar) = ccall(:jl_op_suffix_char, Cint, (UInt32,), c) != 0 - -_isoperator(s) = ccall(:jl_is_operator, Cint, (Cstring,), s) != 0 - -""" - isoperator(s::Symbol) - -Return `true` if the symbol can be used as an operator, `false` otherwise. - -# Examples -```jldoctest -julia> Meta.isoperator(:+), Meta.isoperator(:f) -(true, false) -``` -""" -isoperator(s::Union{Symbol,AbstractString}) = _isoperator(s) || ispostfixoperator(s) - -""" - isunaryoperator(s::Symbol) - -Return `true` if the symbol can be used as a unary (prefix) operator, `false` otherwise. - -# Examples -```jldoctest -julia> Meta.isunaryoperator(:-), Meta.isunaryoperator(:√), Meta.isunaryoperator(:f) -(true, true, false) -``` -""" -isunaryoperator(s::Symbol) = ccall(:jl_is_unary_operator, Cint, (Cstring,), s) != 0 -is_unary_and_binary_operator(s::Symbol) = ccall(:jl_is_unary_and_binary_operator, Cint, (Cstring,), s) != 0 -is_syntactic_operator(s::Symbol) = ccall(:jl_is_syntactic_operator, Cint, (Cstring,), s) != 0 - -""" - isbinaryoperator(s::Symbol) - -Return `true` if the symbol can be used as a binary (infix) operator, `false` otherwise. - -# Examples -```jldoctest -julia> Meta.isbinaryoperator(:-), Meta.isbinaryoperator(:√), Meta.isbinaryoperator(:f) -(true, false, false) -``` -""" -function isbinaryoperator(s::Symbol) - return _isoperator(s) && (!isunaryoperator(s) || is_unary_and_binary_operator(s)) && - s !== Symbol("'") -end - -""" - ispostfixoperator(s::Union{Symbol,AbstractString}) - -Return `true` if the symbol can be used as a postfix operator, `false` otherwise. - -# Examples -```jldoctest -julia> Meta.ispostfixoperator(Symbol("'")), Meta.ispostfixoperator(Symbol("'ᵀ")), Meta.ispostfixoperator(:-) -(true, true, false) -``` -""" -function ispostfixoperator(s::Union{Symbol,AbstractString}) - s = String(s)::String - return startswith(s, '\'') && all(is_op_suffix_char, SubString(s, 2)) -end """ operator_precedence(s::Symbol) @@ -1778,19 +1682,6 @@ function show_enclosed_list(io::IO, op, items, sep, cl, indent, prec=0, quote_le print(io, cl) end -const keyword_syms = Set([ - :baremodule, :begin, :break, :catch, :const, :continue, :do, :else, :elseif, - :end, :export, :var"false", :finally, :for, :function, :global, :if, :import, - :let, :local, :macro, :module, :public, :quote, :return, :struct, :var"true", - :try, :using, :while ]) - -function is_valid_identifier(sym) - return (isidentifier(sym) && !(sym in keyword_syms)) || - (_isoperator(sym) && - !(sym in (Symbol("'"), :(::), :?)) && - !is_syntactic_operator(sym) - ) -end # show a normal (non-operator) function call, e.g. f(x, y) or A[z] # kw: `=` expressions are parsed with head `kw` in this context diff --git a/base/simdloop.jl b/base/simdloop.jl index 797b77ed75a99..40bd52f0fce37 100644 --- a/base/simdloop.jl +++ b/base/simdloop.jl @@ -60,22 +60,22 @@ function compile(x, ivdep) check_body!(x) var,range = parse_iteration_space(x.args[1]) - r = gensym("r") # Range value - j = gensym("i") # Iteration variable for outer loop - n = gensym("n") # Trip count for inner loop - i = gensym("i") # Trip index for inner loop - quote + # r: Range value + # j: Iteration variable for outer loop + # n: Trip count for inner loop + # i: Trip index for inner loop + return quote # Evaluate range value once, to enhance type and data flow analysis by optimizers. - let $r = $range - for $j in Base.simd_outer_range($r) - let $n = Base.simd_inner_length($r,$j) - if zero($n) < $n + let r = $(esc(range)) + for j in Base.simd_outer_range(r) + let n = Base.simd_inner_length(r,j) + if zero(n) < n # Lower loop in way that seems to work best for LLVM 3.3 vectorizer. - let $i = zero($n) - while $i < $n - local $var = Base.simd_index($r,$j,$i) - $(x.args[2]) # Body of loop - $i += 1 + let i = zero(n) + while i < n + local $(esc(var)) = Base.simd_index(r,j,i) + $(esc(x.args[2])) # Body of loop + i += 1 $(Expr(:loopinfo, Symbol("julia.simdloop"), ivdep)) # Mark loop as SIMD loop end end @@ -125,12 +125,12 @@ either case, your inner loop should have the following properties to allow vecto * No iteration ever waits on a previous iteration to make forward progress. """ macro simd(forloop) - esc(compile(forloop, nothing)) + compile(forloop, nothing) end macro simd(ivdep, forloop) if ivdep === :ivdep - esc(compile(forloop, Symbol("julia.ivdep"))) + compile(forloop, Symbol("julia.ivdep")) else throw(SimdError("Only ivdep is valid as the first argument to @simd")) end diff --git a/base/some.jl b/base/some.jl index 4269b2d78aedd..7a1508ae11d3f 100644 --- a/base/some.jl +++ b/base/some.jl @@ -152,8 +152,9 @@ macro something(args...) which is why we need the last argument first when building the final expression. =# - for arg in reverse(args) - val = gensym() + for i in reverse(eachindex(args)) + arg = args[i] + val = Cartesian.inlineanonymous(:val, i) expr = quote $val = $(esc(arg)) if !isnothing($val) diff --git a/base/views.jl b/base/views.jl index 6898abdda1471..4a3e21de99bfd 100644 --- a/base/views.jl +++ b/base/views.jl @@ -29,7 +29,8 @@ function replace_ref_begin_end_!(ex, withex) elseif isa(ex,Expr) if ex.head === :ref ex.args[1], used_withex = replace_ref_begin_end_!(ex.args[1], withex) - S = isa(ex.args[1],Symbol) ? ex.args[1]::Symbol : gensym(:S) # temp var to cache ex.args[1] if needed + S = gensym(:S) # temp var to cache ex.args[1] if needed. if S is a global or expression, then it has side effects to use + assignments = [] used_S = false # whether we actually need S # new :ref, so redefine withex nargs = length(ex.args)-1 @@ -39,29 +40,62 @@ function replace_ref_begin_end_!(ex, withex) # replace with lastindex(S) ex.args[2], used_S = replace_ref_begin_end_!(ex.args[2], (:($firstindex($S)),:($lastindex($S)))) else - n = 1 + ni = 1 + nx = 0 J = lastindex(ex.args) + need_temps = false # whether any arg needs temporaries + + # First pass: determine if any argument will needs temporaries + for j = 2:J + exj = ex.args[j] + if isexpr(exj, :...) + need_temps = true + break + end + end + + # Second pass: if any need temps, create temps for all args + temp_vars = Tuple{Int,Symbol}[] for j = 2:J + n = nx === 0 ? ni : :($nx + $ni) exj, used = replace_ref_begin_end_!(ex.args[j], (:($firstindex($S,$n)),:($lastindex($S,$n)))) used_S |= used ex.args[j] = exj - if isa(exj,Expr) && exj.head === :... - # splatted object - exjs = exj.args[1] - n = :($n + length($exjs)) - elseif isa(n, Expr) - # previous expression splatted - n = :($n + 1) - else - # an integer - n += 1 + ni += 1 + if need_temps + isva = isexpr(exj, :...) # implied need_temps + if isva + exj = exj.args[1] + end + if isa_ast_node(exj) # create temp to preserve evaluation order and count in case `used` gets set later + exj = gensym(:arg) + push!(temp_vars, (j, exj)) + end + if isva + ni -= 1 + nx = nx === 0 ? :(length($exj)) : :($nx + length($exj)) + end + end + end + + # Third pass: if `used`, need to actually make those temp assignments now + if used_S + for (j, temp_var) in temp_vars + exj = ex.args[j] + isva = isexpr(exj, :...) # implied need_temps + if isva + exj = exj.args[1] + end + push!(assignments, :(local $temp_var = $exj)) + ex.args[j] = isva ? Expr(:..., temp_var) : temp_var end end end - if used_S && S !== ex.args[1] + + if used_S S0 = ex.args[1] ex.args[1] = S - ex = Expr(:let, :($S = $S0), ex) + ex = :(local $S = $S0; $(assignments...); $ex) end else # recursive search @@ -131,11 +165,13 @@ macro view(ex) # `view(A, idx) = xxx` in cases such as `@view(A[idx]) = xxx.` if Meta.isexpr(ex, :ref) ex = Expr(:call, view, ex.args...) - elseif Meta.isexpr(ex, :let) && (arg2 = ex.args[2]; Meta.isexpr(arg2, :ref)) + elseif Meta.isexpr(ex, :block) + arg2 = ex.args[end] + Meta.isexpr(arg2, :ref) || error("unsupported replace_ref_begin_end result") # ex replaced by let ...; foo[...]; end - ex.args[2] = Expr(:call, view, arg2.args...) + ex.args[end] = Expr(:call, view, arg2.args...) else - error("invalid expression") + error("unsupported replace_ref_begin_end result") end return esc(ex) end @@ -176,10 +212,7 @@ function _views(ex::Expr) # temp vars to avoid recomputing a and i, # which will be assigned in a let block: - a = gensym(:a) - i = let lhs=lhs # #15276 - [gensym(:i) for k = 1:length(lhs.args)-1] - end + i = Symbol[Symbol(:i, k) for k = 1:length(lhs.args)-1] # for splatted indices like a[i, j...], we need to # splat the corresponding temp var. @@ -194,14 +227,15 @@ function _views(ex::Expr) end end - Expr(:let, - Expr(:block, - :($a = $(_views(lhs.args[1]))), - Any[:($(i[k]) = $(_views(lhs.args[k+1]))) for k=1:length(i)]...), - Expr(first(h) == '.' ? :(.=) : :(=), :($a[$(I...)]), - Expr(:call, Symbol(h[1:end-1]), - :($maybeview($a, $(I...))), - mapany(_views, ex.args[2:end])...))) + Expr(:var"hygienic-scope", # assign a and i to the macro's scope + Expr(:let, + Expr(:block, + :(a = $(esc(_views(lhs.args[1])))), + Any[:($(i[k]) = $(esc(_views(lhs.args[k+1])))) for k=1:length(i)]...), + Expr(first(h) == '.' ? :(.=) : :(=), :(a[$(I...)]), + Expr(:call, esc(Symbol(h[1:end-1])), + :($maybeview(a, $(I...))), + mapany(e -> esc(_views(e)), ex.args[2:end])...))), Base) else exprarray(ex.head, mapany(_views, ex.args)) end diff --git a/doc/src/devdocs/ast.md b/doc/src/devdocs/ast.md index 9a5be35aed706..52f9b98849c77 100644 --- a/doc/src/devdocs/ast.md +++ b/doc/src/devdocs/ast.md @@ -236,9 +236,9 @@ These expressions are represented as `LineNumberNode`s in Julia. ### Macros Macro hygiene is represented through the expression head pair `escape` and `hygienic-scope`. -The result of a macro expansion is automatically wrapped in `(hygienic-scope block module)`, +The result of a macro expansion is automatically wrapped in `(hygienic-scope block module [lno])`, to represent the result of the new scope. The user can insert `(escape block)` inside -to interpolate code from the caller. +to interpolate code from the caller. The lno is the `__source__` argument of the macro, if included. ## Lowered form diff --git a/stdlib/InteractiveUtils/src/macros.jl b/stdlib/InteractiveUtils/src/macros.jl index 753e84beda06e..b8195eef3ab9b 100644 --- a/stdlib/InteractiveUtils/src/macros.jl +++ b/stdlib/InteractiveUtils/src/macros.jl @@ -22,14 +22,24 @@ function rewrap_where(ex::Expr, where_params::Union{Nothing, Vector{Any}}) end function get_typeof(@nospecialize ex) - isexpr(ex, :(::), 1) && return esc(ex.args[1]) - isexpr(ex, :(::), 2) && return esc(ex.args[2]) + # Always unescape to get the core expression, then reescape and esc + original_ex = ex + ex = Meta.unescape(ex) + + if isexpr(ex, :(::), 1) + return esc(Meta.reescape(ex.args[1], original_ex)) + end + if isexpr(ex, :(::), 2) + return esc(Meta.reescape(ex.args[2], original_ex)) + end if isexpr(ex, :..., 1) splatted = ex.args[1] - isexpr(splatted, :(::), 1) && return Expr(:curly, :Vararg, esc(splatted.args[1])) - return :(Any[Core.Typeof(x) for x in $(esc(splatted))]...) + if isexpr(splatted, :(::), 1) + return Expr(:curly, :Vararg, esc(Meta.reescape(splatted.args[1], original_ex))) + end + return :(Any[Core.Typeof(x) for x in $(esc(Meta.reescape(splatted, original_ex)))]...) end - return :(Core.Typeof($(esc(ex)))) + return :(Core.Typeof($(esc(Meta.reescape(ex, original_ex))))) end function is_broadcasting_call(ex) @@ -94,8 +104,14 @@ function recursive_dotcalls!(ex, args, i=1) end function extract_farg(@nospecialize arg) - !isexpr(arg, :(::), 1) && return esc(arg) - fT = esc(arg.args[1]) + # Always unescape to get the core expression, then reescape and esc + original_arg = arg + arg = Meta.unescape(arg) + + if !isexpr(arg, :(::), 1) + return esc(Meta.reescape(arg, original_arg)) + end + fT = esc(Meta.reescape(arg.args[1], original_arg)) :($construct_callable($fT)) end @@ -648,14 +664,11 @@ macro activate(what) if !(Component in allowed_components) error("Usage Error: Component $Component is not recognized. Expected one of $allowed_components") end - s = gensym() if Component === :Compiler && isempty(options) push!(options, :reflection) end options = map(options) do opt Expr(:kw, opt, true) end - Expr(:toplevel, - esc(:(import $Component as $s)), - esc(:($s.activate!(;$(options...))))) + return :(Base.require($__module__, $(QuoteNode(Component))).activate!(; $(options...))) end diff --git a/stdlib/Test/src/Test.jl b/stdlib/Test/src/Test.jl index 7f73662c87938..4c9dbaec79973 100644 --- a/stdlib/Test/src/Test.jl +++ b/stdlib/Test/src/Test.jl @@ -2229,11 +2229,12 @@ function _inferred(ex, mod, allow = :(Union{})) allow isa Type || throw(ArgumentError("@inferred requires a type as second argument")) $(if any(@nospecialize(a)->(Meta.isexpr(a, :kw) || Meta.isexpr(a, :parameters)), ex.args) # Has keywords - args = gensym() - kwargs = gensym() + # Create the call expression with escaped user expressions + call_expr = :($(esc(ex.args[1]))(args...; kwargs...)) quote - $(esc(args)), $(esc(kwargs)), result = $(esc(Expr(:call, _args_and_call, ex.args[2:end]..., ex.args[1]))) - inftype = $(gen_call_with_extracted_types(mod, Base.infer_return_type, :($(ex.args[1])($(args)...; $(kwargs)...)); is_source_reflection = false)) + args, kwargs, result = $(esc(Expr(:call, _args_and_call, ex.args[2:end]..., ex.args[1]))) + # wrap in dummy hygienic-scope to work around scoping issues with `call_expr` already having `esc` on the necessary parts + inftype = $(Expr(:var"hygienic-scope", gen_call_with_extracted_types(mod, Base.infer_return_type, call_expr; is_source_reflection = false), Test)) end else # No keywords