Skip to content

Commit

Permalink
This fixes JuliaLang#3377. It allows for writing methods(:@time), `…
Browse files Browse the repository at this point in the history
…edit(:@time)`  and `@edit @time 1+1` plus the equivalent for `which` and `less`. `methods` can also check for macro methods as  `methods(:@time, Tuple{Any})`. The `@edit`, `@less` and `@which` do also check for method specialization of the macro.
  • Loading branch information
dhoegh authored and dhoegh committed Feb 28, 2016
1 parent 636916e commit a4bf4ae
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 3 deletions.
10 changes: 8 additions & 2 deletions base/interactiveutil.jl
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,14 @@ function gen_call_with_extracted_types(fcn, ex0)
Expr(:call, typesof, map(esc, args[2:end])...))
end
exret = Expr(:none)
is_macro = false
ex = expand(ex0)
if !isa(ex, Expr)
if fcn in [:which, :less, :edit] && ex0.head == :macrocall
# special case @edit @time 1+2 to edit the macro
is_macro = true
exret = Expr(:call, fcn, esc(:($(ex0.args[1]))),
Expr(:call, typesof, map(esc, ex0.args[2:end])...))
elseif !isa(ex, Expr)
exret = Expr(:call, :error, "expression is not a function call or symbol")
elseif ex.head == :call
if any(e->(isa(e, Expr) && e.head==:(...)), ex0.args) &&
Expand All @@ -278,7 +284,7 @@ function gen_call_with_extracted_types(fcn, ex0)
end
end
end
if ex.head == :thunk || exret.head == :none
if (!is_macro && 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")
Expand Down
24 changes: 23 additions & 1 deletion base/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,21 @@ end

tt_cons(t::ANY, tup::ANY) = (@_pure_meta; Tuple{t, (isa(tup, Type) ? tup.parameters : tup)...})

# Checks if a expresion is an empty macrocall. The empty macrocall is gennerated as: `:@time`
# This is used in `which` and `methods` to allow: `methods(:@time)`/`edit(:@time)`
_is_empty_macrocall(f) = isa(f, Expr) && f.head==:macrocall && length(f.args)==1
# Returns the macro from a getfield expresion as: :(Base.@time).
# The functions is similar to get_value in REPLCompletion
function _get_macro(fn, sym::Expr)
#Should only be called for sym.head == :.
for ex in sym.args
fn = _get_macro(fn, ex)
end
fn
end
_get_macro(fn, sym::Symbol) = fn.(sym)
_get_macro(fn, sym::QuoteNode) = fn.(sym.value)

code_lowered(f, t::ANY=Tuple) = map(m->uncompressed_ast(m.func), methods(f, t))
function methods(f::ANY,t::ANY)
if isa(f,Builtin)
Expand All @@ -179,6 +194,10 @@ function methods(f::ANY,t::ANY)
Any[m[3] for m in _methods(f,t,-1)]
end
function _methods(f::ANY,t::ANY,lim)
#special case Expr to allow methods(@time,Tuple{Any})
if _is_empty_macrocall(f)
f = _get_macro(current_module(),f.args[1])
end
ft = isa(f,Type) ? Type{f} : typeof(f)
if isa(t,Type)
_methods_by_ftype(Tuple{ft, t.parameters...}, lim)
Expand Down Expand Up @@ -220,7 +239,7 @@ end

function methods(f::ANY)
ft = typeof(f)
if ft <: Type || !isempty(ft.parameters)
if ft <: Type || !isempty(ft.parameters) || _is_empty_macrocall(f)
# for these types of `f`, not every method in the table will necessarily
# match, so we need to filter based on its type.
methods(f, Tuple{Vararg{Any}})
Expand Down Expand Up @@ -320,6 +339,9 @@ function which(f::ANY, t::ANY)
if isa(f,Builtin)
throw(ArgumentError("argument is not a generic function"))
end
if _is_empty_macrocall(f)
f = _get_macro(current_module(),f.args[1])
end
t = to_tuple_type(t)
if isleaftype(t)
ms = methods(f, t)
Expand Down
21 changes: 21 additions & 0 deletions test/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -237,3 +237,24 @@ let rts = return_types(TLayout)
@test length(rts) >= 3 # general constructor, specific constructor, and call-to-convert adapter(s)
@test all(rts .== TLayout)
end

module MacroTest
export @macrotest
macro macrotest(x::Int, y::AbstractString) end
macro macrotest(x::Int, y::Int)
nothing
end
end

let
using MacroTest
@test which(:@macrotest, Tuple{Int,UTF8String})==@which @macrotest 1 "test"
@test which(:@macrotest, Tuple{Int,Int})==@which @macrotest 1 1
@test length(methods(:@macrotest)) == 2
@test length(methods(:@macrotest, Tuple{Int,UTF8String})) == 1

@test methods(:(MacroTest.@macrotest),Tuple{Int, Int})[1]==@which MacroTest.@macrotest 1 1
@test basename(functionloc(@which @macrotest 1 1)[1]) == "reflection.jl"
#Uncomment when #15280 is solved
#@test basename(functionloc(@which @macrotest 1 "")[1]) == "reflection.jl"
end

0 comments on commit a4bf4ae

Please sign in to comment.