Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: WIP: Make macro hygiene easier to use and less error-prone #10940

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions base/Enums.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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;]
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion base/REPL.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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 #############################
Expand Down
2 changes: 1 addition & 1 deletion base/REPLCompletions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
21 changes: 16 additions & 5 deletions base/base.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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}) =
Expand Down
3 changes: 2 additions & 1 deletion base/client.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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, [])
Expand Down
14 changes: 7 additions & 7 deletions base/docs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand All @@ -153,7 +153,7 @@ function mdify(ex)
end
end

function namedoc(meta, def, name)
@hygienic function namedoc(meta, def, name)
quote
@init
$(esc(def))
Expand All @@ -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))
Expand All @@ -171,7 +171,7 @@ function funcdoc(meta, def)
end
end

function objdoc(meta, def)
@hygienic function objdoc(meta, def)
quote
@init
f = $(esc(def))
Expand All @@ -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]))
Expand All @@ -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))))
Expand Down
7 changes: 7 additions & 0 deletions base/expr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 4 additions & 4 deletions base/help.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
48 changes: 28 additions & 20 deletions base/interactiveutil.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 7 additions & 7 deletions base/math.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand All @@ -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
Expand Down
Loading