diff --git a/base/Enums.jl b/base/Enums.jl index b5fd6a7a1b163..4cae01826a031 100644 --- a/base/Enums.jl +++ b/base/Enums.jl @@ -10,7 +10,7 @@ Base.start{T<:Enum}(::Type{T}) = 1 # next, done defined per Enum # generate code to test whether expr is in the given set of values -function membershiptest(expr, values) +@hygienic function membershiptest(expr, values) lo, hi = extrema(values) sv = sort(values) if sv == [lo:hi;] @@ -89,10 +89,10 @@ macro enum(T,syms...) blk = quote # enum definition immutable $(esc(T)) <: Enum - val::$enumT + $(:val)::$enumT function $(esc(typename))(x::Integer) - $(membershiptest(:x, values)) || enum_argument_error($(Meta.quot(typename)), x) - new(x) + $(membershiptest(quote x end, values)) || enum_argument_error($(Meta.quot(typename)), x) + $:new(x) end end Base.typemin{E<:$(esc(typename))}(x::Type{E}) = E($lo) diff --git a/base/REPL.jl b/base/REPL.jl index e0331d51e3ed3..7bfa9f45838ab 100644 --- a/base/REPL.jl +++ b/base/REPL.jl @@ -700,7 +700,8 @@ function setup_interface(repl::LineEditREPL; hascolor = repl.hascolor, extra_rep # and pass into Base.repl_cmd for processing (handles `ls` and `cd` # special) on_done = respond(repl, julia_prompt) do line - Expr(:call, :(Base.repl_cmd), macroexpand(Expr(:macrocall, symbol("@cmd"),line)), outstream(repl)) + cmdmac = Expr(:., :Base, Expr(:quote, symbol("@cmd"))) + Expr(:call, :(Base.repl_cmd), macroexpand(Expr(:macrocall, cmdmac, line)), outstream(repl)) end) ################################# Stage II ############################# diff --git a/base/REPLCompletions.jl b/base/REPLCompletions.jl index bdb579be4053d..e1b380aff744e 100644 --- a/base/REPLCompletions.jl +++ b/base/REPLCompletions.jl @@ -386,7 +386,7 @@ function shell_completions(string, pos) if all(map(s -> isa(s, AbstractString), args.args[end].args)) # Treat this as a path (perhaps give a list of commands in the future as well?) return complete_path(join(args.args[end].args), pos) - elseif isexpr(arg, :escape) && (isexpr(arg.args[1], :incomplete) || isexpr(arg.args[1], :error)) + elseif isexpr(arg, :incomplete) || isexpr(arg, :error) r = first(last_parse):prevind(last_parse, last(last_parse)) partial = scs[r] ret, range = completions(partial, endof(partial)) diff --git a/base/base.jl b/base/base.jl index c7c94077f8dc1..8876819313a9d 100644 --- a/base/base.jl +++ b/base/base.jl @@ -251,7 +251,8 @@ function precompile(f::ANY, args::Tuple) end end -esc(e::ANY) = Expr(:escape, e) +# Neuter esc but for convenience don't remove it yet - it's deprecated +esc(e::ANY) = e macro boundscheck(yesno,blk) # hack: use this syntax since it avoids introducing line numbers @@ -264,12 +265,22 @@ macro inbounds(blk) :(@boundscheck false $(esc(blk))) end -macro label(name::Symbol) - Expr(:symboliclabel, name) +macro label(name) + isa(name, Expr) && + (name.head === :.) && + isa(name.args[2], Expr) && + (name.args[2].head === :quote) && + (name = name.args[2].args[1]) # remove any macro hygiene + Expr(:symboliclabel, name::Symbol) end -macro goto(name::Symbol) - Expr(:symbolicgoto, name) +macro goto(name) + isa(name, Expr) && + (name.head === :.) && + isa(name.args[2], Expr) && + (name.args[2].head === :quote) && + (name = name.args[2].args[1]) # remove any macro hygiene + Expr(:symbolicgoto, name::Symbol) end call{T,N}(::Type{Array{T}}, d::NTuple{N,Int}) = diff --git a/base/client.jl b/base/client.jl index 4d28d03ba92bd..c056ea4689f6c 100644 --- a/base/client.jl +++ b/base/client.jl @@ -75,8 +75,9 @@ function repl_cmd(cmd, out) end function repl_hook(input::AbstractString) + cmdmac = Expr(:., :Base, Expr(:quote, symbol("@cmd"))) Expr(:call, :(Base.repl_cmd), - macroexpand(Expr(:macrocall,symbol("@cmd"),input))) + macroexpand(Expr(:macrocall,cmdmac,input))) end display_error(er) = display_error(er, []) diff --git a/base/docs.jl b/base/docs.jl index 1355b394cdc84..65980cd25e597 100644 --- a/base/docs.jl +++ b/base/docs.jl @@ -51,7 +51,7 @@ function newmethod(funcs, f) return newmethod(applicable) end -function trackmethod(def) +@hygienic function trackmethod(def) name = uncurly(unblock(def).args[1].args[1]) f = esc(name) quote @@ -143,7 +143,7 @@ uncurly(ex) = isexpr(ex, :curly) ? ex.args[1] : ex namify(ex::Expr) = namify(ex.args[1]) namify(sy::Symbol) = sy -function mdify(ex) +@hygienic function mdify(ex) if isa(ex, AbstractString) :(@doc_str $ex) elseif isexpr(ex, :macrocall) && namify(ex) == symbol("@mstr") @@ -153,7 +153,7 @@ function mdify(ex) end end -function namedoc(meta, def, name) +@hygienic function namedoc(meta, def, name) quote @init $(esc(def)) @@ -162,7 +162,7 @@ function namedoc(meta, def, name) end end -function funcdoc(meta, def) +@hygienic function funcdoc(meta, def) quote @init f, m = $(trackmethod(def)) @@ -171,7 +171,7 @@ function funcdoc(meta, def) end end -function objdoc(meta, def) +@hygienic function objdoc(meta, def) quote @init f = $(esc(def)) @@ -182,7 +182,7 @@ end fexpr(ex) = isexpr(ex, :function) || (isexpr(ex, :(=)) && isexpr(ex.args[1], :call)) -function docm(meta, def) +@hygienic function docm(meta, def) def′ = unblock(def) isexpr(def′, :macro) && return namedoc(meta, def, symbol("@", namify(def′))) isexpr(def′, :type) && return namedoc(meta, def, namify(def′.args[2])) @@ -192,7 +192,7 @@ function docm(meta, def) return objdoc(meta, def) end -function docm(ex) +@hygienic function docm(ex) isa(ex,Symbol) && haskey(keywords, ex) && return keywords[ex] isexpr(ex, :->) && return docm(ex.args...) isexpr(ex, :call) && return :(doc($(esc(ex.args[1])), @which $(esc(ex)))) diff --git a/base/expr.jl b/base/expr.jl index 98556b11307b1..0ce69e3dd6f3f 100644 --- a/base/expr.jl +++ b/base/expr.jl @@ -159,3 +159,10 @@ function popmeta!(body::Expr, sym::Symbol) return (false, []) end popmeta!(arg, sym) = (false, []) + +# Enable hygiene in all quotes inside this annotation +# This is useful for subroutines of macro expanders +export @hygienic +macro hygienic(exp) + Expr(:with_hygiene, exp) +end diff --git a/base/help.jl b/base/help.jl index 0cbaf76d567dc..6a88fbdd7a737 100644 --- a/base/help.jl +++ b/base/help.jl @@ -189,14 +189,14 @@ isname(ex::Expr) = ((ex.head == :. && isname(ex.args[1]) && isname(ex.args[2])) macro help_(ex) if ex === :? || ex === :help - return Expr(:call, :help) + return :(help()) elseif !isa(ex, Expr) || isname(ex) - return Expr(:call, :help, esc(ex)) + return :(help($ex)) elseif ex.head == :macrocall && length(ex.args) == 1 # e.g., "julia> @help @printf" - return Expr(:call, :help, string(ex.args[1])) + return :(help($(string(ex.args[1])))) else - return Expr(:macrocall, symbol("@which"), esc(ex)) + return :(@which $ex) end end diff --git a/base/interactiveutil.jl b/base/interactiveutil.jl index d0c65c91ba099..dc195fd34ae3d 100644 --- a/base/interactiveutil.jl +++ b/base/interactiveutil.jl @@ -221,57 +221,65 @@ code_warntype(f, t::ANY) = code_warntype(STDOUT, f, t) typesof(args...) = Tuple{map(a->(isa(a,Type) ? Type{a} : typeof(a)), args)...} -function gen_call_with_extracted_types(fcn, ex0) - if isa(ex0, Symbol) - return Expr(:call, fcn, Meta.quot(ex0)) - end +@hygienic function gen_call_with_extracted_types(fcn, ex0) + # Make calls to fcn hygienic + hfcn = Expr(:hygienic, fcn) + if isa(ex0, Symbol) + return quote ($hfcn)($(Meta.quot(ex0))) end + end if isa(ex0, Expr) && any(a->(Meta.isexpr(a, :kw) || Meta.isexpr(a, :parameters)), ex0.args) # keyword args not used in dispatch, so just remove them args = filter(a->!(Meta.isexpr(a, :kw) || Meta.isexpr(a, :parameters)), ex0.args) - return Expr(:call, fcn, esc(args[1]), - Expr(:call, :typesof, map(esc, args[2:end])...)) + return quote ($hfcn)($(args[1]), typesof($(args[2:end]...))) end end if isa(ex0, Expr) && ex0.head == :call - return Expr(:call, fcn, esc(ex0.args[1]), - Expr(:call, :typesof, map(esc, ex0.args[2:end])...)) + return quote ($hfcn)($(ex0.args[1]), typesof($(ex0.args[2:end]...))) end + end + # "@which a[i]=x" works but "@which(a[i]=x)" is misparsed as a keyword arg + if isa(ex0, Expr) && ex0.head == :kw && length(ex0.args) == 2 + ex0 = Expr(:(=), ex0.args...) end ex = expand(ex0) - exret = Expr(:call, :error, "expression is not a function call or symbol") + exret = quote error("expression is not a function call or symbol") end if !isa(ex, Expr) # do nothing -> error elseif ex.head == :call if any(e->(isa(e, Expr) && e.head==:(...)), ex0.args) && isa(ex.args[1], TopNode) && ex.args[1].name == :apply - exret = Expr(:call, ex.args[1], fcn, - Expr(:tuple, esc(ex.args[2])), - Expr(:call, :typesof, map(esc, ex.args[3:end])...)) + exret = quote ($(ex.args[1]))($hfcn, + $(Expr(:tuple, ex.args[2])), + typesof($(ex.args[3:end]...))) + end else - exret = Expr(:call, fcn, esc(ex.args[1]), - Expr(:call, :typesof, map(esc, ex.args[2:end])...)) + exret = quote ($hfcn)($(ex.args[1]), typesof($(ex.args[2:end]...))) end end elseif ex.head == :body a1 = ex.args[1] if isa(a1, Expr) && a1.head == :call a11 = a1.args[1] if a11 == :setindex! - exret = Expr(:call, fcn, a11, - Expr(:call, :typesof, map(esc, a1.args[2:end])...)) + exret = quote ($hfcn)($a11, typesof($(a1.args[2:end]...))) end end end elseif ex.head == :thunk - exret = Expr(:call, :error, "expression is not a function call, " - * "or is too complex for @which to analyze; " - * "break it down to simpler parts if possible") + msg = "expression is not a function call, " * + "or is too complex for @$fcn to analyze; " * + "break it down to simpler parts if possible" + exret = quote error($msg) end end exret end for fname in [:which, :less, :edit, :code_typed, :code_warntype, :code_lowered, :code_llvm, :code_llvm_raw, :code_native] + # Pass the symbol :which etc, not the value of the variable which, to gen... + # Unfortunately there isn't any way to do this with quote because putting + # quote $fname end inside the macro evaluates fname at the wrong time + qfname = Expr(:quote, fname) @eval begin macro ($fname)(ex0) - gen_call_with_extracted_types($(Expr(:quote,fname)), ex0) + gen_call_with_extracted_types($qfname, ex0) end end end diff --git a/base/math.jl b/base/math.jl index 78599da82d371..dcc4cd1ae989a 100644 --- a/base/math.jl +++ b/base/math.jl @@ -58,16 +58,16 @@ end # efficient algorithm described in Knuth, TAOCP vol. 2, section 4.6.4, # equation (3). macro evalpoly(z, p...) - a = :($(esc(p[end]))) - b = :($(esc(p[end-1]))) + a = :($(p[end])) + b = :($(p[end-1])) as = [] for i = length(p)-2:-1:1 - ai = symbol("a", i) + ai = Expr(:hygienic, symbol("a", i)) push!(as, :($ai = $a)) a = :(muladd(r, $ai, $b)) - b = :(muladd(-s, $ai, $(esc(p[i])))) + b = :(muladd(-s, $ai, $(p[i]))) end - ai = :a0 + ai = Expr(:hygienic, :a0) push!(as, :($ai = $a)) C = Expr(:block, :(x = real(tt)), @@ -76,8 +76,8 @@ macro evalpoly(z, p...) :(s = x*x + y*y), as..., :(muladd($ai, tt, $b))) - R = Expr(:macrocall, symbol("@horner"), :tt, map(esc, p)...) - :(let tt = $(esc(z)) + R = quote @horner(tt, $(p...)) end + :(let tt = $z isa(tt, Complex) ? $C : $R end) end diff --git a/base/printf.jl b/base/printf.jl index 8a7f34e827979..24e0ec7e724db 100644 --- a/base/printf.jl +++ b/base/printf.jl @@ -6,7 +6,7 @@ export @printf, @sprintf const SmallFloatingPoint = Union(Float64,Float32,Float16) const SmallNumber = Union(SmallFloatingPoint,Base.Signed64,Base.Unsigned64,UInt128,Int128) -function gen(s::AbstractString) +@hygienic function gen(s::AbstractString) args = [] blk = Expr(:block, :(local neg, pt, len, exp, do_out, args)) for x in parse(s) @@ -133,7 +133,7 @@ end ### printf formatter generation ### -function special_handler(flags::ASCIIString, width::Int) +@hygienic function special_handler(flags::ASCIIString, width::Int) @gensym x blk = Expr(:block) pad = '-' in flags ? rpad : lpad @@ -148,7 +148,7 @@ function special_handler(flags::ASCIIString, width::Int) x, ex, blk end -function pad(m::Int, n, c::Char) +@hygienic function pad(m::Int, n, c::Char) if m <= 1 :($n > 0 && write(out,$c)) else @@ -221,7 +221,7 @@ function print_exp_a(out, exp::Integer) end -function gen_d(flags::ASCIIString, width::Int, precision::Int, c::Char) +@hygienic function gen_d(flags::ASCIIString, width::Int, precision::Int, c::Char) # print integer: # [dDiu]: print decimal digits # [o]: print octal digits @@ -239,18 +239,18 @@ function gen_d(flags::ASCIIString, width::Int, precision::Int, c::Char) # interpret the number prefix = "" if lowercase(c)=='o' - fn = '#' in flags ? :decode_0ct : :decode_oct + fn = '#' in flags ? quote decode_0ct end : quote decode_oct end elseif c=='x' '#' in flags && (prefix = "0x") - fn = :decode_hex + fn = quote decode_hex end elseif c=='X' '#' in flags && (prefix = "0X") - fn = :decode_HEX + fn = quote decode_HEX end else - fn = :decode_dec + fn = quote decode_dec end end push!(blk.args, :((do_out, args) = $fn(out, $x, $flags, $width, $precision, $c))) - ifblk = Expr(:if, :do_out, Expr(:block)) + ifblk = Expr(:if, quote do_out end, Expr(:block)) push!(blk.args, ifblk) blk = ifblk.args[2] push!(blk.args, :((len, pt, neg) = args)) @@ -302,7 +302,7 @@ function gen_d(flags::ASCIIString, width::Int, precision::Int, c::Char) :(($x)::Real), ex end -function gen_f(flags::ASCIIString, width::Int, precision::Int, c::Char) +@hygienic function gen_f(flags::ASCIIString, width::Int, precision::Int, c::Char) # print to fixed trailing precision # [fF]: the only choice # @@ -317,7 +317,7 @@ function gen_f(flags::ASCIIString, width::Int, precision::Int, c::Char) # interpret the number if precision < 0; precision = 6; end push!(blk.args, :((do_out, args) = fix_dec(out, $x, $flags, $width, $precision, $c))) - ifblk = Expr(:if, :do_out, Expr(:block)) + ifblk = Expr(:if, quote do_out end, Expr(:block)) push!(blk.args, ifblk) blk = ifblk.args[2] push!(blk.args, :((len, pt, neg) = args)) @@ -364,7 +364,7 @@ function gen_f(flags::ASCIIString, width::Int, precision::Int, c::Char) :(($x)::Real), ex end -function gen_e(flags::ASCIIString, width::Int, precision::Int, c::Char) +@hygienic function gen_e(flags::ASCIIString, width::Int, precision::Int, c::Char) # print float in scientific form: # [e]: use 'e' to introduce exponent # [E]: use 'E' to introduce exponent @@ -381,7 +381,7 @@ function gen_e(flags::ASCIIString, width::Int, precision::Int, c::Char) if precision < 0; precision = 6; end ndigits = min(precision+1,length(DIGITS)-1) push!(blk.args, :((do_out, args) = ini_dec(out,$x,$ndigits, $flags, $width, $precision, $c))) - ifblk = Expr(:if, :do_out, Expr(:block)) + ifblk = Expr(:if, quote do_out end, Expr(:block)) push!(blk.args, ifblk) blk = ifblk.args[2] push!(blk.args, :((len, pt, neg) = args)) @@ -458,7 +458,7 @@ function gen_e(flags::ASCIIString, width::Int, precision::Int, c::Char) :(($x)::Real), ex end -function gen_a(flags::ASCIIString, width::Int, precision::Int, c::Char) +@hygienic function gen_a(flags::ASCIIString, width::Int, precision::Int, c::Char) # print float in hexadecimal format # [a]: lowercase hex float, e.g. -0x1.cfp-2 # [A]: uppercase hex float, e.g. -0X1.CFP-2 @@ -473,10 +473,10 @@ function gen_a(flags::ASCIIString, width::Int, precision::Int, c::Char) x, ex, blk = special_handler(flags,width) if c == 'A' hexmark, expmark = "0X", "P" - fn = :ini_HEX + fn = quote ini_HEX end else hexmark, expmark = "0x", "p" - fn = :ini_hex + fn = quote ini_hex end end # if no precision, print max non-zero if precision < 0 @@ -485,7 +485,7 @@ function gen_a(flags::ASCIIString, width::Int, precision::Int, c::Char) ndigits = min(precision+1,length(DIGITS)-1) push!(blk.args, :((do_out, args) = $fn(out,$x,$ndigits, $flags, $width, $precision, $c))) end - ifblk = Expr(:if, :do_out, Expr(:block)) + ifblk = Expr(:if, quote do_out end, Expr(:block)) push!(blk.args, ifblk) blk = ifblk.args[2] push!(blk.args, :((len, exp, neg) = args)) @@ -566,7 +566,7 @@ function gen_a(flags::ASCIIString, width::Int, precision::Int, c::Char) :(($x)::Real), ex end -function gen_c(flags::ASCIIString, width::Int, precision::Int, c::Char) +@hygienic function gen_c(flags::ASCIIString, width::Int, precision::Int, c::Char) # print a character: # [cC]: both the same for us (Unicode) # @@ -587,7 +587,7 @@ function gen_c(flags::ASCIIString, width::Int, precision::Int, c::Char) :(($x)::Integer), blk end -function gen_s(flags::ASCIIString, width::Int, precision::Int, c::Char) +@hygienic function gen_s(flags::ASCIIString, width::Int, precision::Int, c::Char) # print a string: # [sS]: both the same for us (Unicode) # @@ -622,7 +622,7 @@ end # TODO: faster pointer printing. -function gen_p(flags::ASCIIString, width::Int, precision::Int, c::Char) +@hygienic function gen_p(flags::ASCIIString, width::Int, precision::Int, c::Char) # print pointer: # [p]: the only option # @@ -642,7 +642,7 @@ function gen_p(flags::ASCIIString, width::Int, precision::Int, c::Char) :(($x)::Ptr), blk end -function gen_g(flags::ASCIIString, width::Int, precision::Int, c::Char) +@hygienic function gen_g(flags::ASCIIString, width::Int, precision::Int, c::Char) error("printf \"%g\" format specifier not implemented") end @@ -994,7 +994,7 @@ is_str_expr(ex) = isa(ex,Expr) && (ex.head == :string || (ex.head == :macrocall && isa(ex.args[1],Symbol) && endswith(string(ex.args[1]),"str"))) -function _printf(macroname, io, fmt, args) +@hygienic function _printf(macroname, io, fmt, args) isa(fmt, AbstractString) || throw(ArgumentError("$macroname: format must be a plain static string (no interpolation or prefix)")) sym_args, blk = gen(fmt) @@ -1045,7 +1045,7 @@ end macro printf(args...) !isempty(args) || throw(ArgumentError("@printf: called with no arguments")) if isa(args[1], AbstractString) || is_str_expr(args[1]) - _printf("@printf", :STDOUT, args[1], args[2:end]) + _printf("@printf", quote STDOUT end, args[1], args[2:end]) else (length(args) >= 2 && (isa(args[2], AbstractString) || is_str_expr(args[2]))) || throw(ArgumentError("@printf: first or second argument must be a format string")) diff --git a/base/process.jl b/base/process.jl index ce8311a05972c..6ea1042052a41 100644 --- a/base/process.jl +++ b/base/process.jl @@ -288,50 +288,54 @@ function spawn(pc::ProcessChainOrNot, cmds::ErrOrCmds, stdios::StdIOSet, exitcb: end +# Generates non-hygienic bindings of in, out, err +# and close_in, close_out, close_err macro setup_stdio() esc( quote - close_in,close_out,close_err = false,false,false - in,out,err = stdios + $:close_in, $:close_out, $:close_err = false, false, false + $:in, $:out, $:err = stdios if isa(stdios[1], Pipe) if stdios[1].handle == C_NULL error("pipes passed to spawn must be initialized") end elseif isa(stdios[1], FileRedirect) - in = FS.open(stdios[1].filename, JL_O_RDONLY) - close_in = true + $:in = FS.open(stdios[1].filename, JL_O_RDONLY) + $:close_in = true elseif isa(stdios[1], IOStream) - in = FS.File(RawFD(fd(stdios[1]))) + $:in = FS.File(RawFD(fd(stdios[1]))) end if isa(stdios[2], Pipe) if stdios[2].handle == C_NULL error("pipes passed to spawn must be initialized") end elseif isa(stdios[2], FileRedirect) - out = FS.open(stdios[2].filename, JL_O_WRONLY | JL_O_CREAT | (stdios[2].append?JL_O_APPEND:JL_O_TRUNC), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) - close_out = true + $:out = FS.open(stdios[2].filename, JL_O_WRONLY | JL_O_CREAT | (stdios[2].append?JL_O_APPEND:JL_O_TRUNC), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) + $:close_out = true elseif isa(stdios[2], IOStream) - out = FS.File(RawFD(fd(stdios[2]))) + $:out = FS.File(RawFD(fd(stdios[2]))) end if isa(stdios[3], Pipe) if stdios[3].handle == C_NULL error("pipes passed to spawn must be initialized") end elseif isa(stdios[3], FileRedirect) - err = FS.open(stdios[3].filename, JL_O_WRONLY | JL_O_CREAT | (stdios[3].append?JL_O_APPEND:JL_O_TRUNC), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) - close_err = true + $:err = FS.open(stdios[3].filename, JL_O_WRONLY | JL_O_CREAT | (stdios[3].append?JL_O_APPEND:JL_O_TRUNC), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) + $:close_err = true elseif isa(stdios[3], IOStream) - err = FS.File(RawFD(fd(stdios[3]))) + $:err = FS.File(RawFD(fd(stdios[3]))) end end) end +# Depends on non-hygienic bindings of in, out, err +# and close_in, close_out, close_err macro cleanup_stdio() esc( quote - close_in && close(in) - close_out && close(out) - close_err && close(err) + $:close_in && close($:in) + $:close_out && close($:out) + $:close_err && close($:err) end) end diff --git a/doc/stdlib/base.rst b/doc/stdlib/base.rst index b12c06bf2e4e9..3c0f968183846 100644 --- a/doc/stdlib/base.rst +++ b/doc/stdlib/base.rst @@ -566,8 +566,7 @@ Syntax .. function:: esc(e::ANY) - Only valid in the context of an Expr returned from a macro. Prevents the macro hygiene pass from turning embedded variables into gensym variables. See the :ref:`man-macros` - section of the Metaprogramming chapter of the manual for more details and examples. + Deprecated. Does nothing and returns its argument. .. function:: gensym([tag]) @@ -577,6 +576,13 @@ Syntax Generates a gensym symbol for a variable. For example, ``@gensym x y`` is transformed into ``x = gensym("x"); y = gensym("y")``. +.. function:: @hygienic + + Causes any ``quote`` expressions inside the following expression to + be treated as hygienic. Use this in functions that are used as helpers + to macros. See the :ref:`man-macros` section of the Metaprogramming + chapter of the manual for more details and examples. + .. function:: parse(str, start; greedy=true, raise=true) Parse the expression string and return an expression (which could later be passed to eval for execution). Start is the index of the first character to start parsing. If ``greedy`` is true (default), ``parse`` will try to consume as much input as it can; otherwise, it will stop as soon as it has parsed a valid expression. Incomplete but otherwise syntactically valid expressions will return ``Expr(:incomplete, "(error message)")``. If ``raise`` is true (default), syntax errors other than incomplete expressions will raise an error. If ``raise`` is false, ``parse`` will return an expression that will raise an error upon evaluation. diff --git a/src/jlfrontend.scm b/src/jlfrontend.scm index f565a6f298afd..a53be0918f65b 100644 --- a/src/jlfrontend.scm +++ b/src/jlfrontend.scm @@ -80,7 +80,7 @@ ((and (pair? e) (eq? (car e) 'global) (every symbol? (cdr e))) e) (else - (let ((ex0 (julia-expand-macros e))) + (let ((ex0 (julia-expand-macros e #f))) (if (and (pair? ex0) (eq? (car ex0) 'toplevel)) `(toplevel ,@(map expand-toplevel-expr (cdr ex0))) (let* ((ex (julia-expand01 ex0)) @@ -229,7 +229,7 @@ (define (jl-macroexpand expr) (reset-gensyms) (parser-wrap (lambda () - (julia-expand-macros expr)))) + (julia-expand-macros expr #f)))) ; run whole frontend on a string. useful for testing. (define (fe str) diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index d08559f0ce360..327d18f485a2c 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -75,6 +75,10 @@ (define (bad-formal-argument v) (error (string #\" (deparse v) #\" " is not a valid function argument name"))) +(define (hygienic-symbol? s) + (or (symbol? s) + (and (pair? s) (eq (car s) 'hygienic)))) + (define (arg-name v) (cond ((and (symbol? v) (not (eq? v 'true)) (not (eq? v 'false))) v) @@ -84,9 +88,10 @@ (case (car v) ((... kw) (decl-var (cadr v))) ((|::|) - (if (not (symbol? (cadr v))) + (if (not (hygienic-symbol? (cadr v))) (bad-formal-argument (cadr v))) (decl-var v)) + ((hygienic) v) (else (bad-formal-argument v)))))) ; convert a lambda list into a list of just symbols @@ -413,11 +418,11 @@ (map (lambda (x ub) `(call (top TypeVar) ',x ,ub ,@bnd)) sl upperbounds)))) (define (sparam-name sp) - (cond ((symbol? sp) + (cond ((hygienic-symbol? sp) sp) ((and (length= sp 3) (eq? (car sp) '|<:|) - (symbol? (cadr sp))) + (hygienic-symbol? (cadr sp))) (cadr sp)) (else (error "malformed type parameter list")))) @@ -452,10 +457,10 @@ (define (dots->vararg a) (if (null? a) a (let ((head (butlast a)) - (las (last a))) - (if (vararg? las) - `(,@head (curly Vararg ,(cadr las))) - `(,@head ,las))))) + (las (last a))) + (if (vararg? las) + `(,@head (curly Vararg ,(cadr las))) + `(,@head ,las))))) (define (method-def-expr- name sparams argl body isstaged) (receive @@ -476,7 +481,7 @@ (body (method-lambda-expr argl body))) (if (null? sparams) `(method ,name (call (top svec) (curly Tuple ,@(dots->vararg types)) (call (top svec))) - ,body ,isstaged) + ,body ,isstaged) `(method ,name (call (lambda ,names (call (top svec) (curly Tuple ,@(dots->vararg types)) (call (top svec) ,@names))) @@ -1010,7 +1015,7 @@ `(block ,.(reverse! stmts) (call (top ccall) ,name ,RT (call (top svec) ,@(dots->vararg atypes)) - ,.(reverse! C) + ,.(reverse! C) ,@A)) (let* ((a (car A)) (isseq (and (pair? (car F)) (eq? (caar F) '...))) @@ -1323,7 +1328,8 @@ (if (null? binds) (cons 'varlist vars) (cond - ((or (symbol? (car binds)) (decl? (car binds))) + ((or (hygienic-symbol? (car binds)) + (decl? (car binds))) ;; just symbol -> add local (loop (cdr binds) (cons (decl-var (car binds)) vars))) @@ -1331,7 +1337,7 @@ (eq? (caar binds) '=)) ;; some kind of assignment (cond - ((or (symbol? (cadar binds)) + ((or (hygienic-symbol? (cadar binds)) (decl? (cadar binds))) ;; a=b -> add argument (loop (cdr binds) @@ -1811,10 +1817,10 @@ 'tuple (lambda (e) (for-each (lambda (x) - ;; assignment inside tuple looks like a keyword argument - (if (assignment? x) - (error "assignment not allowed inside tuple"))) - (cdr e)) + ;; assignment inside tuple looks like a keyword argument + (if (assignment? x) + (error "assignment not allowed inside tuple"))) + (cdr e)) (expand-forms `(call (top tuple) ,@(cdr e)))) 'dict @@ -2020,8 +2026,8 @@ (begin (if (not (and (pair? argtypes) (eq? (car argtypes) 'tuple))) - (if (and (pair? RT) - (eq? (car RT) 'tuple)) + (if (and (pair? RT) + (eq? (car RT) 'tuple)) (error "ccall argument types must be a tuple; try \"(T,)\" and check if you specified a correct return type") (error "ccall argument types must be a tuple; try \"(T,)\""))) (expand-forms @@ -3238,59 +3244,70 @@ So far only the second case can actually occur. (define (wrap-with-splice x) `(call (top _expr) (inert $) - (call (top _expr) (inert tuple) - (call (top _expr) (inert |...|) ,x)))) + (call (top _expr) (inert tuple) + (call (top _expr) (inert |...|) ,x)))) -(define (julia-bq-bracket x d) +(define (julia-bq-bracket x d hygienic) (if (splice-expr? x) (if (= d 0) - (cadr (cadr (cadr x))) - (list 'cell1d - (wrap-with-splice (julia-bq-expand (cadr (cadr (cadr x))) (- d 1))))) - (list 'cell1d (julia-bq-expand x d)))) + (cadr (cadr (cadr x))) + (list 'cell1d + (wrap-with-splice (julia-bq-expand (cadr (cadr (cadr x))) (- d 1) hygienic)))) + (list 'cell1d (julia-bq-expand x d hygienic)))) -(define (julia-bq-expand x d) +(define (julia-bq-expand x d hygienic) (cond ((or (eq? x 'true) (eq? x 'false)) x) - ((or (symbol? x) (jlgensym? x)) (list 'inert x)) + ((jlgensym? x) (list 'inert x)) + ((symbol? x) (if hygienic + `(call (top _expr) (inert hygienic) (inert ,x)) + (list 'inert x))) ((atom? x) x) ((eq? (car x) 'quote) - `(call (top _expr) (inert quote) ,(julia-bq-expand (cadr x) (+ d 1)))) + `(call (top _expr) + (inert quote) + ,(if (atom? (cadr x)) + (list 'inert (cadr x)) ;; (quote 'a) => (inert 'a) ensures that :a does not become hygienic + (julia-bq-expand (cadr x) (+ d 1) hygienic)))) ((eq? (car x) '$) - (if (and (= d 0) (length= x 2)) - (cadr x) - (if (splice-expr? (cadr x)) - `(call (top splicedexpr) (inert $) - (call (top append_any) ,(julia-bq-bracket (cadr x) (- d 1)))) - `(call (top _expr) (inert $) ,(julia-bq-expand (cadr x) (- d 1)))))) - ((not (contains (lambda (e) (and (pair? e) (eq? (car e) '$))) x)) + (if (and (= d 0) (length= x 2)) + (cadr x) + (if (splice-expr? (cadr x)) + `(call (top splicedexpr) (inert $) + (call (top append_any) ,(julia-bq-bracket (cadr x) (- d 1) hygienic))) + `(call (top _expr) (inert $) ,(julia-bq-expand (cadr x) (- d 1) hygienic))))) + ((and (not (contains (lambda (e) (and (pair? e) (eq? (car e) '$))) x)) + (or (not hygienic) (eq? (car x) 'inert) (eq? (car x) 'line))) `(copyast (inert ,x))) - ((not (any splice-expr? x)) - `(call (top _expr) ,.(map (lambda (ex) (julia-bq-expand ex d)) x))) - (else - (let loop ((p (cdr x)) (q '())) - (if (null? p) - (let ((forms (reverse q))) - `(call (top splicedexpr) ,(julia-bq-expand (car x) d) - (call (top append_any) ,@forms))) - (loop (cdr p) (cons (julia-bq-bracket (car p) d) q))))))) - -(define (julia-expand-macros e) + ((not (any splice-expr? x)) + `(call (top _expr) (quote ,(car x)) + ,.(map (lambda (ex) (julia-bq-expand ex d hygienic)) + (cdr x)))) + (else + (let loop ((p (cdr x)) (q '())) + (if (null? p) + (let ((forms (reverse q))) + `(call (top splicedexpr) (quote ,(car x)) + (call (top append_any) ,@forms))) + (loop (cdr p) (cons (julia-bq-bracket (car p) d hygienic) q))))))) + +(define (julia-expand-macros e with-hygiene) (cond ((not (pair? e)) e) - ((eq? (car e) 'quote) - ;; backquote is essentially a built-in macro at the moment - (julia-expand-macros (julia-bq-expand (cadr e) 0))) - ((eq? (car e) 'inert) - e) + ((eq? (car e) 'inert) e) + ((eq? (car e) 'quote) ;; backquote is essentially a built-in macro at the moment + (if (atom? (cadr e)) + (list 'inert (cadr e)) ;; (quote 'a) => (inert 'a) ensures that :a does not become hygienic + (julia-expand-macros (julia-bq-expand (cadr e) 0 with-hygiene) + with-hygiene))) ((eq? (car e) 'macrocall) ;; expand macro (let ((form - (if (and (length> e 2) (pair? (caddr e)) (eq? (caaddr e) 'triple_quoted_string)) - ;; for a custom triple-quoted string literal, first invoke mstr - ;; to handle unindenting - (apply invoke-julia-macro (cadr e) - (julia-expand-macros `(macrocall @mstr ,(cadr (caddr e)))) - (cdddr e)) - (apply invoke-julia-macro (cadr e) (cddr e))))) + (if (and (length> e 2) (pair? (caddr e)) (eq? (caaddr e) 'triple_quoted_string)) + ;; for a custom triple-quoted string literal, first invoke mstr + ;; to handle unindenting + (apply invoke-julia-macro (cadr e) + (julia-expand-macros `(macrocall @mstr ,(cadr (caddr e))) with-hygiene) + (cdddr e)) + (apply invoke-julia-macro (cadr e) (cddr e))))) (if (not form) (error (string "macro \"" (cadr e) "\" not defined"))) (if (and (pair? form) (eq? (car form) 'error)) @@ -3299,34 +3316,46 @@ So far only the second case can actually occur. (m (cdr form))) ;; m is the macro's def module, or #f if def env === use env (rename-symbolic-labels - (julia-expand-macros - (resolve-expansion-vars form m)))))) + (julia-expand-macros (resolve-expansion-vars form m) + with-hygiene))))) + ((eq? (car e) 'macro) + (map (lambda (se) (julia-expand-macros se #t)) e)) + ((eq? (car e) 'with_hygiene) + (julia-expand-macros (cadr e) #t)) (else - (map julia-expand-macros e)))) + (map (lambda (se) (julia-expand-macros se with-hygiene)) e)))) (define (pair-with-gensyms v) (map (lambda (s) - (if (pair? s) - s - (cons s (named-gensy s)))) + (cond ((not (pair? s)) + (cons s (named-gensy s))) + ((eq? (car s) 'hygienic) + (cons s (named-gensy (cadr s)))) + (else s))) v)) +;TODO: Remove this after all uses of Expr(:escape ... have been eliminated (define (unescape e) (if (and (pair? e) (eq? (car e) 'escape)) (cadr e) e)) +(define (strip-hygiene e) + (if (and (pair? e) (eq? (car e) 'hygienic)) + (cadr e) + e)) + (define (typevar-expr-name e) - (if (symbol? e) e + (if (hygienic-symbol? e) e (cadr e))) (define (new-expansion-env-for x env) (append! (filter (lambda (v) - (not (assq (car v) env))) + (not (assoc (car v) env))) (append! (pair-with-gensyms (vars-introduced-by x)) - (map (lambda (v) (cons v v)) + (map (lambda (v) (cons v (strip-hygiene v))) (keywords-introduced-by x)))) env)) @@ -3343,23 +3372,27 @@ So far only the second case can actually occur. (define (resolve-expansion-vars- e env m inarg) (cond ((or (eq? e 'true) (eq? e 'false) (eq? e 'end)) e) - ((symbol? e) - (let ((a (assq e env))) - (if a (cdr a) - (if m `(|.| ,m (quote ,e)) - e)))) ((or (not (pair? e)) (quoted? e)) e) (else (case (car e) - ((jlgensym) e) + ((hygienic) + ;; Have to use assoc instead of assq because schema to julia + ;; conversion defeats any attempt to make (hygienic ) + ;; objects. env is small enough that it shouldn't matter. + (let ((a (assoc e env))) + (if a (cdr a) + (if m `(|.| ,m (quote ,(cadr e))) + (cadr e))))) + ;TODO: Remove this after all uses of Expr(:escape ... have been eliminated ((escape) (cadr e)) + ((jlgensym) e) ((using import importall export) (map unescape e)) ((macrocall) - (if (or (eq? (cadr e) '@label) (eq? (cadr e) '@goto)) e - `(macrocall ,.(map (lambda (x) - (resolve-expansion-vars- x env m inarg)) - (cdr e))))) + (if (or (eq? (cadr e) '@label) (eq? (cadr e) '@goto)) e + `(macrocall ,.(map (lambda (x) + (resolve-expansion-vars- x env m inarg)) + (cdr e))))) ((symboliclabel) e) ((symbolicgoto) e) ((type) @@ -3400,12 +3433,12 @@ So far only the second case can actually occur. ,(if inarg (resolve-expansion-vars- (cadr (cadr e)) env m inarg) ;; in keyword arg A=B, don't transform "A" - (cadr (cadr e))) + (strip-hygiene (cadr (cadr e)))) ,(resolve-expansion-vars- (caddr (cadr e)) env m inarg)) ,(resolve-expansion-vars- (caddr e) env m inarg)) `(kw ,(if inarg (resolve-expansion-vars- (cadr e) env m inarg) - (cadr e)) + (strip-hygiene (cadr e))) ,(resolve-expansion-vars- (caddr e) env m inarg)))) ((let) @@ -3421,7 +3454,7 @@ So far only the second case can actually occur. newenv m inarg)) ;; expand initial values in old env (resolve-expansion-vars- (caddr bind) env m inarg)) - bind)) + (resolve-expansion-vars- bind newenv m inarg))) (cddr e))))) ;; todo: trycatch @@ -3457,7 +3490,7 @@ So far only the second case can actually occur. ((escape) '()) ((= function) (append! (filter - symbol? + (lambda (x) (and (pair? x) (eq? (car x) 'hygienic))) (if (and (pair? (cadr e)) (eq? (car (cadr e)) 'tuple)) (map decl-var* (cdr (cadr e))) (list (decl-var* (cadr e))))) @@ -3479,11 +3512,12 @@ So far only the second case can actually occur. (define (env-for-expansion e) (let ((globals (find-declared-vars-in-expansion e 'global))) - (let ((v (diff (delete-duplicates + (let ((v (difference (delete-duplicates (append! (find-declared-vars-in-expansion e 'local) (find-assigned-vars-in-expansion e) (map (lambda (x) - (if (pair? x) (car x) x)) + (if (hygienic-symbol? x) x + (car x))) (vars-introduced-by e)))) globals))) (append! @@ -3512,7 +3546,7 @@ So far only the second case can actually occur. (expand-binding-forms ex)))) (define (julia-expand0 ex) - (let ((e (julia-expand-macros ex))) + (let ((e (julia-expand-macros ex #f))) (if (and (pair? e) (eq? (car e) 'toplevel)) `(toplevel ,.(map julia-expand01 (cdr e))) (julia-expand01 e)))) diff --git a/src/utils.scm b/src/utils.scm index 08121e8eebf04..8d2307c72eef8 100644 --- a/src/utils.scm +++ b/src/utils.scm @@ -15,6 +15,13 @@ ((memq (car s1) s2) (diff (cdr s1) s2)) (else (cons (car s1) (diff (cdr s1) s2))))) +(define (difference s1 s2) + (cond ((null? s1) '()) + ((member (car s1) s2) (diff (cdr s1) s2)) + (else (cons (car s1) (diff (cdr s1) s2))))) + +(define (unique lst) (delete-duplicates lst)) + (define (has-dups lst) (if (null? lst) #f diff --git a/test/choosetests.jl b/test/choosetests.jl index dfd229d3eb2cd..9812113ee6ec6 100644 --- a/test/choosetests.jl +++ b/test/choosetests.jl @@ -18,7 +18,7 @@ function choosetests(choices = []) "arrayops", "tuple", "subarray", "reduce", "reducedim", "random", "intfuncs", "simdloop", "blas", "sparse", "bitarray", "copy", "math", "fastmath", "functional", - "operators", "path", "ccall", + "operators", "path", "ccall", "macros", "bigint", "sorting", "statistics", "spawn", "backtrace", "priorityqueue", "file", "version", "resolve", "pollfd", "mpfr", "broadcast", "complex", "socket", diff --git a/test/macros.jl b/test/macros.jl new file mode 100644 index 0000000000000..79689d56c975e --- /dev/null +++ b/test/macros.jl @@ -0,0 +1,115 @@ +# Test some macro behavior that is not tested elsewhere + +# The complex @printf macro is thoroughly tested elsewhere + +# Test proper hygiene in the complex @which macro +# @less, @edit, @code_typed, @code_lowered, @code_llvm, @code_native are +# defined in the same way as @which so they don't need independent testing + +module Which_test + type UnexpectedException <: Exception + which::Int + end + function Base.showerror(io::IO, e::UnexpectedException) + print(io, "should not be called ", e.which) + end + + # Error if hygiene fails and the wrong function is called + typesof(x...) = throw(UnexpectedException(1)) + error(x...) = throw(UnexpectedException(2)) + which(x...) = throw(UnexpectedException(3)) + + f1(x::Int) = "int" + f1(x::Real) = "real" + + function test1(n) + # Error if hygiene fails and the wrong function is called + typesof(x...) = throw(UnexpectedException(4)) + error(x...) = throw(UnexpectedException(5)) + which(x...) = throw(UnexpectedException(6)) + + return @which(f1(n)).sig + end + + f2(x::Int; k=1) = "int" + f2(x::Real; k=2) = "real" + + function test2(n) + typesof(x...) = throw(UnexpectedException(7)) + error(x...) = throw(UnexpectedException(8)) + which(x...) = throw(UnexpectedException(9)) + + return @which(f2(n, k=5)).sig + end + + f3(x::Int, y::Real...) = x .+ y + + function test3(nx, ny) + typesof(x...) = throw(UnexpectedException(10)) + error(x...) = throw(UnexpectedException(11)) + which(x...) = throw(UnexpectedException(12)) + + return @which(f3(nx, ny...)).sig + end + + function test4(a, i) + typesof(x...) = throw(UnexpectedException(13)) + error(x...) = throw(UnexpectedException(14)) + which(x...) = throw(UnexpectedException(15)) + + # Have to make a string because type parameters in the method signature + # are output but cannot be input + return string(@which(a[i]).sig) + end + + function test5(a, i) + typesof(x...) = throw(UnexpectedException(16)) + error(x...) = throw(UnexpectedException(17)) + which(x...) = throw(UnexpectedException(18)) + + return string((@which a[i]).sig) + end + + function test6(a, i, x) + typesof(x...) = throw(UnexpectedException(19)) + error(x...) = throw(UnexpectedException(20)) + which(x...) = throw(UnexpectedException(21)) + + return string(@which(a[i] = x).sig) + end + + function test7(a, i, x) + typesof(x...) = throw(UnexpectedException(22)) + error(x...) = throw(UnexpectedException(23)) + which(x...) = throw(UnexpectedException(24)) + + return string((@which a[i] = x).sig) + end +end + +# Test the simplest and most common case first +@test Which_test.test1(1.0) == Tuple{Real,} +@test Which_test.test1(123) == Tuple{Int} + +# Should get error("no method found for the specified argument types") +# in which(), not UnexpectedException +@test_throws ErrorException Which_test.test1("foo") + +# Test gen_call_with_extracted_types's alternate path for keyword arguments +@test Which_test.test2(1.0) == Tuple{Real} +@test Which_test.test2(123) == Tuple{Int} + +# Test gen_call_with_extracted_types's alternate path for splatted arguments +@test Which_test.test3(1, Any[1.0, 2.0]) == Tuple{Int, Vararg{Real}} + +# Test gen_call_with_extracted_types's alternate path for subscripting +t4 = (1, 2, 3, 4) +a4 = Any[1, 2, 3, 4] +i1 = 1 +x7 = 7 +@test Which_test.test4(t4, i1) == "Tuple{Tuple,$Int}" +@test Which_test.test5(t4, i1) == "Tuple{Tuple,$Int}" +@test Which_test.test4(a4, i1) == "Tuple{Array{T,N},Real}" # yes, Real. Why? +@test Which_test.test5(a4, i1) == "Tuple{Array{T,N},Real}" +@test Which_test.test6(a4, i1, x7) == "Tuple{Array{Any,N},ANY,Real}" +@test Which_test.test7(a4, i1, x7) == "Tuple{Array{Any,N},ANY,Real}"