diff --git a/base/Base.jl b/base/Base.jl index a9d458cb5e9ee..234a771e2d63d 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..41ac48b4e3b14 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,14 +54,16 @@ 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) - preexpr = length(args) > 1 ? inlineanonymous(args[1], dim) : (:(nothing)) - postexpr = length(args) > 2 ? inlineanonymous(args[2], dim) : (:(nothing)) + esc_rng && (rng = esc(rng)) + preexpr = length(args) > 1 ? esc(inlineanonymous(args[1], dim)) : nothing + postexpr = length(args) > 2 ? esc(inlineanonymous(args[2], dim)) : nothing ex = quote - for $(esc(itervar)) = $(esc(rng)) - $(esc(preexpr)) + for $itervar = $rng + $preexpr $ex - $(esc(postexpr)) + $postexpr end end end @@ -290,14 +291,15 @@ struct LReplace{S<:AbstractString} end LReplace(sym::Symbol, val::Integer) = LReplace(sym, string(sym), val) -lreplace(ex::Expr, sym::Symbol, val) = lreplace!(copy(ex), LReplace(sym, val)) +lreplace(ex::Expr, sym::Symbol, val) = lreplace!(copy(ex), LReplace(sym, val), false, 0) -function lreplace!(sym::Symbol, r::LReplace) +function lreplace!(sym::Symbol, r::LReplace, in_quote_context::Bool, escs::Int) + escs == 0 || return sym sym == r.pat_sym && return r.val - Symbol(lreplace!(string(sym), r)) + Symbol(lreplace_string!(string(sym), r)) end -function lreplace!(str::AbstractString, r::LReplace) +function lreplace_string!(str::String, r::LReplace) i = firstindex(str) pat = r.pat_str j = firstindex(pat) @@ -329,7 +331,7 @@ function lreplace!(str::AbstractString, r::LReplace) if matching && j > lastindex(pat) if i > lastindex(str) || str[i] == '_' # We have a match - return string(str[1:prevind(str, istart)], r.val, lreplace!(str[i:end], r)) + return string(str[1:prevind(str, istart)], r.val, lreplace_string!(str[i:end], r)) end matching = false j = firstindex(pat) @@ -339,24 +341,42 @@ function lreplace!(str::AbstractString, r::LReplace) str end -function lreplace!(ex::Expr, r::LReplace) +function lreplace!(ex::Expr, r::LReplace, in_quote_context::Bool, escs::Int) # Curly-brace notation, which acts like parentheses - if ex.head === :curly && length(ex.args) == 2 && isa(ex.args[1], Symbol) && endswith(string(ex.args[1]::Symbol), "_") - excurly = exprresolve(lreplace!(ex.args[2], r)) + if !in_quote_context && ex.head === :curly && length(ex.args) == 2 && isa(ex.args[1], Symbol) && endswith(string(ex.args[1]::Symbol), "_") + excurly = exprresolve(lreplace!(ex.args[2], r, in_quote_context, escs)) if isa(excurly, Int) return Symbol(ex.args[1]::Symbol, excurly) else ex.args[2] = excurly return ex end + elseif ex.head === :meta || ex.head === :inert + return ex + elseif ex.head === :$ + # no longer an executable expression (handle all equivalent forms of :inert, :quote, and QuoteNode the same way) + in_quote_context = false + elseif ex.head === :quote + # executable again + in_quote_context = true + elseif ex.head === :var"hygienic-scope" + # no longer our expression + escs += 1 + elseif ex.head === :escape + # our expression again once zero + escs == 0 && return ex + escs -= 1 + elseif ex.head === :macrocall + # n.b. blithely go about altering arguments to macros also, assuming that is at all what the user intended + # it is probably the user's fault if they put a macro inside here and didn't mean for it to get rewritten end for i in 1:length(ex.args) - ex.args[i] = lreplace!(ex.args[i], r) + ex.args[i] = lreplace!(ex.args[i], r, in_quote_context, escs) end ex end -lreplace!(arg, r::LReplace) = arg +lreplace!(@nospecialize(arg), r::LReplace, in_quote_context::Bool, escs::Int) = arg poplinenum(arg) = arg 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..e679693b368a6 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -1273,22 +1273,22 @@ It also supports the following syntax: ```jldoctest julia> @macroexpand @invoke f(x::T, y) -:(Core.invoke(f, Tuple{T, Core.Typeof(y)}, x, y)) +:(Core.invoke(f, Base.Tuple{T, Core.Typeof(y)}, x, y)) julia> @invoke 420::Integer % Unsigned 0x00000000000001a4 julia> @macroexpand @invoke (x::X).f -:(Core.invoke(Base.getproperty, Tuple{X, Core.Typeof(:f)}, x, :f)) +:(Core.invoke(Base.getproperty, Base.Tuple{X, Core.Typeof(:f)}, x, :f)) julia> @macroexpand @invoke (x::X).f = v::V -:(Core.invoke(Base.setproperty!, Tuple{X, Core.Typeof(:f), V}, x, :f, v)) +:(Core.invoke(Base.setproperty!, Base.Tuple{X, Core.Typeof(:f), V}, x, :f, v)) julia> @macroexpand @invoke (xs::Xs)[i::I] -:(Core.invoke(Base.getindex, Tuple{Xs, I}, xs, i)) +:(Core.invoke(Base.getindex, Base.Tuple{Xs, I}, xs, i)) julia> @macroexpand @invoke (xs::Xs)[i::I] = v::V -:(Core.invoke(Base.setindex!, Tuple{Xs, V, I}, xs, v, i)) +:(Core.invoke(Base.setindex!, Base.Tuple{Xs, V, I}, xs, v, i)) ``` !!! compat "Julia 1.7" @@ -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..380006bfcf050 100644 --- a/base/show.jl +++ b/base/show.jl @@ -1,6 +1,9 @@ # 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, + is_unary_and_binary_operator function show(io::IO, ::MIME"text/plain", u::UndefInitializer) show(io, u) @@ -1542,104 +1545,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 +1683,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/task.jl b/base/task.jl index 4f17330bb4455..1d234e8784ad9 100644 --- a/base/task.jl +++ b/base/task.jl @@ -139,7 +139,7 @@ true ``` """ macro task(ex) - thunk = Base.replace_linenums!(:(()->$(esc(ex))), __source__) + thunk = replace_linenums!(:(()->$(esc(ex))), __source__) :(Task($thunk)) end @@ -495,7 +495,7 @@ function _wait_multiple(waiting_tasks, throwexc=false, all=false, failfast=false for i in findall(remaining_mask) waiter = waiter_tasks[i] donenotify = tasks[i].donenotify::ThreadSynchronizer - @lock donenotify Base.list_deletefirst!(donenotify.waitq, waiter) + @lock donenotify list_deletefirst!(donenotify.waitq, waiter) end done_tasks = tasks[done_mask] if throwexc && exception @@ -660,15 +660,15 @@ isolating the asynchronous code from changes to the variable's value in the curr Interpolating values via `\$` is available as of Julia 1.4. """ macro async(expr) - do_async_macro(expr, __source__) + do_async_macro(expr, __source__, identity) end # generate the code for @async, possibly wrapping the task in something before # pushing it to the wait queue. -function do_async_macro(expr, linenums; wrap=identity) - letargs = Base._lift_one_interp!(expr) +function do_async_macro(expr, linenums, wrap) + letargs = _lift_one_interp!(expr) - thunk = Base.replace_linenums!(:(()->($(esc(expr)))), linenums) + thunk = replace_linenums!(:(()->($(esc(expr)))), linenums) var = esc(sync_varname) quote let $(letargs...) @@ -708,7 +708,7 @@ fetch(t::UnwrapTaskFailedException) = unwrap_task_failed(fetch, t) # macro for running async code that doesn't throw wrapped exceptions macro async_unwrap(expr) - do_async_macro(expr, __source__, wrap=task->:(Base.UnwrapTaskFailedException($task))) + do_async_macro(expr, __source__, taskvar->:(UnwrapTaskFailedException($taskvar))) end """ @@ -758,29 +758,37 @@ function errormonitor(t::Task) end # Capture interpolated variables in $() and move them to let-block -function _lift_one_interp!(e) +function _lift_one_interp!(@nospecialize e) letargs = Any[] # store the new gensymed arguments - _lift_one_interp_helper(e, false, letargs) # Start out _not_ in a quote context (false) - letargs + _lift_one_interp_helper(e, false, 0, letargs) # Start out _not_ in a quote context (false) and not needing escapes + return letargs end -_lift_one_interp_helper(v, _, _) = v -function _lift_one_interp_helper(expr::Expr, in_quote_context, letargs) +_lift_one_interp_helper(@nospecialize(v), _::Bool, _::Int, _::Vector{Any}) = v +function _lift_one_interp_helper(expr::Expr, in_quote_context::Bool, escs::Int, letargs::Vector{Any}) if expr.head === :$ if in_quote_context # This $ is simply interpolating out of the quote # Now, we're out of the quote, so any _further_ $ is ours. in_quote_context = false - else + elseif escs == 0 + # if escs is non-zero, then we cannot hoist expr.args without violating hygiene rules newarg = gensym() push!(letargs, :($(esc(newarg)) = $(esc(expr.args[1])))) return newarg # Don't recurse into the lifted $() exprs end + elseif expr.head === :meta || expr.head === :inert + return expr elseif expr.head === :quote in_quote_context = true # Don't try to lift $ directly out of quotes elseif expr.head === :macrocall return expr # Don't recur into macro calls, since some other macros use $ + elseif expr.head === :var"hygienic-scope" + escs += 1 + elseif expr.head === :escape + escs == 0 && return expr + escs -= 1 end for (i,e) in enumerate(expr.args) - expr.args[i] = _lift_one_interp_helper(e, in_quote_context, letargs) + expr.args[i] = _lift_one_interp_helper(e, in_quote_context, escs, letargs) end expr end @@ -1007,7 +1015,7 @@ function schedule(t::Task, @nospecialize(arg); error=false) # schedule a task to be (re)started with the given value or exception t._state === task_state_runnable || Base.error("schedule: Task not runnable") if error - q = t.queue; q === nothing || Base.list_deletefirst!(q::IntrusiveLinkedList{Task}, t) + q = t.queue; q === nothing || list_deletefirst!(q::IntrusiveLinkedList{Task}, t) setfield!(t, :result, arg) setfield!(t, :_isexception, true) else @@ -1033,7 +1041,7 @@ function yield() try wait() catch - q = ct.queue; q === nothing || Base.list_deletefirst!(q::IntrusiveLinkedList{Task}, ct) + q = ct.queue; q === nothing || list_deletefirst!(q::IntrusiveLinkedList{Task}, ct) rethrow() end end diff --git a/base/views.jl b/base/views.jl index 6898abdda1471..10fb3b0dc5e1b 100644 --- a/base/views.jl +++ b/base/views.jl @@ -14,22 +14,41 @@ should transform to A[B[lastindex(B)]] """ -replace_ref_begin_end!(ex) = replace_ref_begin_end_!(ex, nothing)[1] -# replace_ref_begin_end_!(ex,withex) returns (new ex, whether withex was used) -function replace_ref_begin_end_!(ex, withex) +replace_ref_begin_end!(__module__::Module, @nospecialize ex) = replace_ref_begin_end_!(__module__, ex, nothing, false, 0)[1] +# replace_ref_begin_end_!(...) returns (new ex, whether withex was used) +function replace_ref_begin_end_!(__module__::Module, ex, withex, in_quote_context::Bool, escs::Int) + @nospecialize used_withex = false + function escapes(@nospecialize(ex), escs::Int) + for i = 1:escs + ex = esc(ex) + end + return ex + end + if ex isa Expr && ex.head === :macrocall + # Blithly modifying the arguments to another macro is unwise, so call + # macroexpand first on it. + # Unfortunately, macroexpand itself corrupts the scope of variables in + # the result by calling macroexpand.scm before returning which cannot be + # avoided since `jl_expand_macros` is private and somewhat difficult to + # reimplement correctly. + ex = macroexpand(__module__, ex) + end if isa(ex,Symbol) - if ex === :begin - withex === nothing && error("Invalid use of begin") - return withex[1], true - elseif ex === :end - withex === nothing && error("Invalid use of end") - return withex[2], true + if !in_quote_context + if ex === :begin + withex === nothing && error("Invalid use of begin outside []") + return escapes((withex::NTuple{2,Expr})[1], escs), true + elseif ex === :end + withex === nothing && error("Invalid use of end outside []") + return escapes((withex::NTuple{2,Expr})[2], escs), true + end end 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 + if !in_quote_context && ex.head === :ref # n.b. macroexpand.scm design is incapable of tracking :begin and :end scope, so emulate that here too and ignore escs + ex.args[1], used_withex = replace_ref_begin_end_!(__module__, ex.args[1], withex, in_quote_context, escs) + 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 @@ -37,41 +56,90 @@ function replace_ref_begin_end_!(ex, withex) return ex, used_withex elseif nargs == 1 # replace with lastindex(S) - ex.args[2], used_S = replace_ref_begin_end_!(ex.args[2], (:($firstindex($S)),:($lastindex($S)))) + ex.args[2], used_S = replace_ref_begin_end_!(__module__, ex.args[2], (:($firstindex($S)),:($lastindex($S))), in_quote_context, escs) 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 - exj, used = replace_ref_begin_end_!(ex.args[j], (:($firstindex($S,$n)),:($lastindex($S,$n)))) + n = nx === 0 ? ni : :($nx + $ni) + exj, used = replace_ref_begin_end_!(__module__, ex.args[j], (:($firstindex($S,$n)),:($lastindex($S,$n))), in_quote_context, escs) 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] + S = escapes(S, escs) ex.args[1] = S - ex = Expr(:let, :($S = $S0), ex) - end - else - # recursive search - for i = eachindex(ex.args) - ex.args[i], used = replace_ref_begin_end_!(ex.args[i], withex) - used_withex |= used + ex = :(local $S = $S0; $(assignments...); $ex) end + return ex, used_withex + elseif ex.head === :$ + # no longer an executable expression (handle all equivalent forms of :inert, :quote, and QuoteNode the same way) + in_quote_context = false + elseif ex.head === :quote + # executable again + in_quote_context = true + elseif ex.head === :var"hygienic-scope" + # no longer our expression + escs += 1 + elseif ex.head === :escape + # our expression again once zero + escs == 0 && return ex, used_withex + escs -= 1 + elseif ex.head === :meta || ex.head === :inert + return ex, used_withex + end + # recursive search + for i = eachindex(ex.args) + ex.args[i], used = replace_ref_begin_end_!(__module__, ex.args[i], withex, in_quote_context, escs) + used_withex |= used end end - ex, used_withex + return ex, used_withex end """ @@ -125,17 +193,19 @@ julia> A macro view(ex) Meta.isexpr(ex, :ref) || throw(ArgumentError( "Invalid use of @view macro: argument must be a reference expression A[...].")) - ex = replace_ref_begin_end!(ex) + ex = replace_ref_begin_end!(__module__, ex) # NOTE We embed `view` as a function object itself directly into the AST. # By doing this, we prevent the creation of function definitions like # `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 +246,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 +261,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 @@ -245,5 +313,5 @@ julia> A ``` """ macro views(x) - esc(_views(replace_ref_begin_end!(x))) + esc(_views(replace_ref_begin_end!(__module__, x))) 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..8825b4802f990 100644 --- a/stdlib/InteractiveUtils/src/macros.jl +++ b/stdlib/InteractiveUtils/src/macros.jl @@ -2,14 +2,14 @@ # macro wrappers for various reflection functions -using Base: insert!, replace_ref_begin_end!, +using Base: insert!, replace_ref_begin_end_!, infer_return_type, infer_exception_type, infer_effects, code_ircode, isexpr # defined in Base so it's possible to time all imports, including InteractiveUtils and its deps # via. `Base.@time_imports` etc. import Base: @time_imports, @trace_compile, @trace_dispatch -typesof_expr(args::Vector{Any}, where_params::Union{Nothing, Vector{Any}} = nothing) = rewrap_where(:(Tuple{$(get_typeof.(args)...)}), where_params) +typesof_expr(args::Vector{Any}, where_params::Union{Nothing, Vector{Any}} = nothing) = rewrap_where(:(Tuple{$(Any[get_typeof(a) for a in args]...)}), where_params) function extract_where_parameters(ex::Expr) isexpr(ex, :where) || return ex, nothing @@ -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 @@ -196,9 +212,6 @@ end is_code_macro(fcn) = startswith(string(fcn), "code_") function gen_call_with_extracted_types(__module__, fcn, ex0, kws = Expr[]; is_source_reflection = !is_code_macro(fcn), supports_binding_reflection = false) - if isexpr(ex0, :ref) - ex0 = replace_ref_begin_end!(ex0) - end # assignments get bypassed: @edit a = f(x) <=> @edit f(x) if isa(ex0, Expr) && ex0.head == :(=) && isa(ex0.args[1], Symbol) && isempty(kws) return gen_call_with_extracted_types(__module__, fcn, ex0.args[2], kws; is_source_reflection, supports_binding_reflection) @@ -282,7 +295,7 @@ function gen_call_with_extracted_types(__module__, fcn, ex0, kws = Expr[]; is_so tt = rewrap_where(:(Tuple{$nt, $(get_typeof.(args)...)}), where_params) return :($(fcn)(Core.kwcall, $tt; $(kws...))) elseif ex0.head === :call - argtypes = Any[get_typeof(arg) for arg in ex0.args[2:end]] + argtypes = Any[get_typeof(ex0.args[i]) for i in 2:length(ex0.args)] if ex0.args[1] === :^ && length(ex0.args) >= 3 && isa(ex0.args[3], Int) farg = :(Base.literal_pow) pushfirst!(argtypes, :(typeof(^))) @@ -299,8 +312,21 @@ function gen_call_with_extracted_types(__module__, fcn, ex0, kws = Expr[]; is_so return Expr(:call, fcn, Base.setproperty!, typesof_expr(Any[lhs.args..., rhs], where_params), kws...) elseif lhs.head === :ref - return Expr(:call, fcn, Base.setindex!, - typesof_expr(Any[lhs.args[1], rhs, lhs.args[2:end]...], where_params), kws...) + arr = lhs.args[1] + lhs.args = Any[get_typeof(a) for a in lhs.args] + arrex = lhs.args[1] + if isexpr(arr, :(::), 1) || isexpr(arr, :(::), 2) || isexpr(arr, :..., 1) + lhs.args[1] = Expr(:call, :error, "array expression with begin/end cannot also use ::") + else + lhs.args[1] = esc(arr) + end + ex, _ = replace_ref_begin_end_!(__module__, lhs, nothing, false, 1) + ## since replace_ref_begin_end! mutates lhs in place, we can mutate inplace also then return ex + lhs.head = :call + lhs.args[1] = get_typeof(rhs) + pushfirst!(lhs.args, arrex) + lhs.args = Any[fcn, Base.setindex!, rewrap_where(:(Tuple{$(lhs.args...)}), where_params), kws...] + return ex end end elseif ex0.head === :vcat || ex0.head === :typed_vcat @@ -320,8 +346,29 @@ function gen_call_with_extracted_types(__module__, fcn, ex0, kws = Expr[]; is_so else return Expr(:call, fcn, f, typesof_expr(ex0.args, where_params), kws...) end + elseif ex0.head === :ref + arr = ex0.args[1] + ex0.args = Any[get_typeof(a) for a in ex0.args] + arrex = ex0.args[1] + if isexpr(arr, :(::), 1) || isexpr(arr, :(::), 2) || isexpr(arr, :..., 1) + ex0.args[1] = Expr(:call, :error, "array expression with begin/end cannot also use ::") + else + ex0.args[1] = esc(arr) + end + ex, _ = replace_ref_begin_end_!(__module__, ex0, nothing, false, 1) + ## since replace_ref_begin_end! mutates ex0 in place, we can mutate inplace also then return ex + ex0.head = :call + ex0.args[1] = arrex + ex0.args = Any[fcn, Base.getindex, rewrap_where(:(Tuple{$(ex0.args...)}), where_params), kws...] + return ex else - for (head, f) in (:ref => Base.getindex, :hcat => Base.hcat, :(.) => Base.getproperty, :vect => Base.vect, Symbol("'") => Base.adjoint, :typed_hcat => Base.typed_hcat, :string => string) + PairSymAny = Pair{Symbol, Any} + for (head, f) in (PairSymAny(:hcat, Base.hcat), + PairSymAny(:(.), Base.getproperty), + PairSymAny(:vect, Base.vect), + PairSymAny(Symbol("'"), Base.adjoint), + PairSymAny(:typed_hcat, Base.typed_hcat), + PairSymAny(:string, string)) if ex0.head === head return Expr(:call, fcn, f, typesof_expr(ex0.args, where_params), kws...) end @@ -329,7 +376,7 @@ function gen_call_with_extracted_types(__module__, fcn, ex0, kws = Expr[]; is_so end end if isa(ex0, Expr) && ex0.head === :macrocall # Make @edit @time 1+2 edit the macro by using the types of the *expressions* - return Expr(:call, fcn, esc(ex0.args[1]), Tuple{#=__source__=#LineNumberNode, #=__module__=#Module, Any[ Core.Typeof(a) for a in ex0.args[3:end] ]...}, kws...) + return Expr(:call, fcn, esc(ex0.args[1]), Tuple{#=__source__=#LineNumberNode, #=__module__=#Module, Any[ Core.Typeof(ex0.args[i]) for i in 3:length(ex0.args) ]...}, kws...) end ex = Meta.lower(__module__, ex0) @@ -350,10 +397,10 @@ function gen_call_with_extracted_types(__module__, fcn, ex0, kws = Expr[]; is_so end end if ex.head === :thunk || exret.head === :none - exret = Expr(:call, :error, "expression is not a function call, " - * "or is too complex for @$fcn to analyze; " - * "break it down to simpler parts if possible. " - * "In some cases, you may want to use Meta.@lower.") + exret = Expr(:call, :error, "expression is not a function call, \ + or is too complex for @$fcn to analyze; \ + break it down to simpler parts if possible. \ + In some cases, you may want to use Meta.@lower.") end return exret end @@ -648,14 +695,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 diff --git a/test/arrayops.jl b/test/arrayops.jl index 84b9c8e7b2f6a..f9c592e672973 100644 --- a/test/arrayops.jl +++ b/test/arrayops.jl @@ -2714,6 +2714,7 @@ function f15894(d) s end @test f15894(fill(1, 100)) == 100 +@test (@nexprs 2 i -> "_i_: $i") == "_i_: 2" end @testset "sign, conj[!], ~" begin diff --git a/test/cartesian.jl b/test/cartesian.jl index 6097d4ca3770a..e2c064ec0c55f 100644 --- a/test/cartesian.jl +++ b/test/cartesian.jl @@ -4,7 +4,7 @@ ex = Base.Cartesian.exprresolve(:(if 5 > 4; :x; else :y; end)) @test ex.args[2] == QuoteNode(:x) -@test Base.Cartesian.lreplace!("val_col", Base.Cartesian.LReplace{String}(:col, "col", 1)) == "val_1" +@test Base.Cartesian.lreplace_string!("val_col", Base.Cartesian.LReplace{String}(:col, "col", 1)) == "val_1" @test Base.setindex(CartesianIndex(1,5,4),3,2) == CartesianIndex(1, 3, 4) @testset "Expression Resolve" begin @test Base.Cartesian.exprresolve(:(1 + 3)) == 4 diff --git a/test/subarray.jl b/test/subarray.jl index a462224e7643a..45ad4851b3b56 100644 --- a/test/subarray.jl +++ b/test/subarray.jl @@ -1098,3 +1098,6 @@ end @test Base.mightalias(permutedims(V1), V1) @test Base.mightalias(permutedims(V1), permutedims(V1)) end + + +@test @views quote var"begin" + var"end" end isa Expr