From a4bf4ae432c2eee6cd769b27b5761e022285c018 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8egh?= Date: Sun, 28 Feb 2016 21:06:27 +0100 Subject: [PATCH] This fixes #3377. It allows for writing `methods(:@time)`, `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. --- base/interactiveutil.jl | 10 ++++++++-- base/reflection.jl | 24 +++++++++++++++++++++++- test/reflection.jl | 21 +++++++++++++++++++++ 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/base/interactiveutil.jl b/base/interactiveutil.jl index 9dcc89e07aef60..19f8a852d045e2 100644 --- a/base/interactiveutil.jl +++ b/base/interactiveutil.jl @@ -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) && @@ -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") diff --git a/base/reflection.jl b/base/reflection.jl index 6cb18c4e027ade..b868096fcc5a5e 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -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) @@ -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) @@ -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}}) @@ -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) diff --git a/test/reflection.jl b/test/reflection.jl index 969050dfedfd87..b70e8d09fd0932 100644 --- a/test/reflection.jl +++ b/test/reflection.jl @@ -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