Skip to content

Commit 261d527

Browse files
author
dhoegh
committed
This fixes JuliaLang#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.
1 parent 636916e commit 261d527

File tree

3 files changed

+52
-3
lines changed

3 files changed

+52
-3
lines changed

base/interactiveutil.jl

+8-2
Original file line numberDiff line numberDiff line change
@@ -254,8 +254,14 @@ function gen_call_with_extracted_types(fcn, ex0)
254254
Expr(:call, typesof, map(esc, args[2:end])...))
255255
end
256256
exret = Expr(:none)
257+
is_macro = false
257258
ex = expand(ex0)
258-
if !isa(ex, Expr)
259+
if fcn in [:which, :less, :edit] && ex0.head == :macrocall
260+
# special case @edit @time 1+2 to edit the macro
261+
is_macro = true
262+
exret = Expr(:call, fcn, esc(:($(ex0.args[1]))),
263+
Expr(:call, typesof, map(esc, ex0.args[2:end])...))
264+
elseif !isa(ex, Expr)
259265
exret = Expr(:call, :error, "expression is not a function call or symbol")
260266
elseif ex.head == :call
261267
if any(e->(isa(e, Expr) && e.head==:(...)), ex0.args) &&
@@ -278,7 +284,7 @@ function gen_call_with_extracted_types(fcn, ex0)
278284
end
279285
end
280286
end
281-
if ex.head == :thunk || exret.head == :none
287+
if (!is_macro && ex.head == :thunk) || exret.head == :none
282288
exret = Expr(:call, :error, "expression is not a function call, "
283289
* "or is too complex for @$fcn to analyze; "
284290
* "break it down to simpler parts if possible")

base/reflection.jl

+23-1
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,21 @@ end
170170

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

173+
# Checks if a expresion is an empty macrocall. The empty macrocall is gennerated as: `:@time`
174+
# This is used in `which` and `methods` to allow: `methods(:@time)`/`edit(:@time)`
175+
_is_empty_macrocall(f) = isa(f, Expr) && f.head==:macrocall && length(f.args)==1
176+
# Returns the macro from a getfield expresion as: :(Base.@time).
177+
# The functions is similar to get_value in REPLCompletion
178+
function _get_macro(fn, sym::Expr)
179+
#Should only be called for sym.head == :.
180+
for ex in sym.args
181+
fn = _get_macro(fn, ex)
182+
end
183+
fn
184+
end
185+
_get_macro(fn, sym::Symbol) = fn.(sym)
186+
_get_macro(fn, sym::QuoteNode) = fn.(sym.value)
187+
173188
code_lowered(f, t::ANY=Tuple) = map(m->uncompressed_ast(m.func), methods(f, t))
174189
function methods(f::ANY,t::ANY)
175190
if isa(f,Builtin)
@@ -179,6 +194,10 @@ function methods(f::ANY,t::ANY)
179194
Any[m[3] for m in _methods(f,t,-1)]
180195
end
181196
function _methods(f::ANY,t::ANY,lim)
197+
#special case Expr to allow methods(@time,Tuple{Any})
198+
if _is_empty_macrocall(f)
199+
f = _get_macro(current_module(),f.args[1])
200+
end
182201
ft = isa(f,Type) ? Type{f} : typeof(f)
183202
if isa(t,Type)
184203
_methods_by_ftype(Tuple{ft, t.parameters...}, lim)
@@ -220,7 +239,7 @@ end
220239

221240
function methods(f::ANY)
222241
ft = typeof(f)
223-
if ft <: Type || !isempty(ft.parameters)
242+
if ft <: Type || !isempty(ft.parameters) || _is_empty_macrocall(f)
224243
# for these types of `f`, not every method in the table will necessarily
225244
# match, so we need to filter based on its type.
226245
methods(f, Tuple{Vararg{Any}})
@@ -320,6 +339,9 @@ function which(f::ANY, t::ANY)
320339
if isa(f,Builtin)
321340
throw(ArgumentError("argument is not a generic function"))
322341
end
342+
if _is_empty_macrocall(f)
343+
f = _get_macro(current_module(),f.args[1])
344+
end
323345
t = to_tuple_type(t)
324346
if isleaftype(t)
325347
ms = methods(f, t)

test/reflection.jl

+21
Original file line numberDiff line numberDiff line change
@@ -237,3 +237,24 @@ let rts = return_types(TLayout)
237237
@test length(rts) >= 3 # general constructor, specific constructor, and call-to-convert adapter(s)
238238
@test all(rts .== TLayout)
239239
end
240+
241+
module MacroTest
242+
export @macrotest
243+
macro macrotest(x::Int, y::AbstractString) end
244+
macro macrotest(x::Int, y::Int)
245+
nothing
246+
end
247+
end
248+
249+
let
250+
using MacroTest
251+
@test which(:@macrotest, Tuple{Int,UTF8String})==@which @macrotest 1 "test"
252+
@test which(:@macrotest, Tuple{Int,Int})==@which @macrotest 1 1
253+
@test length(methods(:@macrotest)) == 2
254+
@test length(methods(:@macrotest, Tuple{Int,UTF8String})) == 1
255+
256+
@test methods(:(MacroTest.@macrotest),Tuple{Int, Int})[1]==@which MacroTest.@macrotest 1 1
257+
@test basename(functionloc(@which @macrotest 1 1)[1]) == "reflection.jl"
258+
#Uncomment when #15280 is solved
259+
#@test basename(functionloc(@which @macrotest 1 "")[1]) == "reflection.jl"
260+
end

0 commit comments

Comments
 (0)