Skip to content

Commit 668d480

Browse files
committed
Add bodymethod
This allows you to find the body method for any input method.
1 parent 75be0fb commit 668d480

File tree

2 files changed

+84
-1
lines changed

2 files changed

+84
-1
lines changed

src/LoweredCodeUtils.jl

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ using JuliaInterpreter
66
using JuliaInterpreter: @lookup, moduleof, pc_expr, step_expr!, is_global_ref, whichtt,
77
next_until!, finish_and_return!, nstatements, codelocation
88

9-
export signature, methoddef!, methoddefs!
9+
export signature, methoddef!, methoddefs!, bodymethod
1010

1111
"""
1212
iscallto(stmt, name)
@@ -441,6 +441,68 @@ function _methoddefs!(@nospecialize(recurse), signatures, frame::Frame, pc; defi
441441
return pc
442442
end
443443

444+
"""
445+
mbody = bodymethod(m::Method)
446+
447+
Return the "body method" for a method `m`. `mbody` contains the code of the function body
448+
when `m` was defined.
449+
"""
450+
function bodymethod(mkw::Method)
451+
function is_self_call(stmt, slotnames, argno=1)
452+
if isa(stmt, Expr)
453+
if stmt.head == :call
454+
a = stmt.args[argno]
455+
if isa(a, Core.SlotNumber)
456+
if slotnames[a.id] == Symbol("#self#")
457+
return true
458+
end
459+
end
460+
end
461+
end
462+
return false
463+
end
464+
m = mkw
465+
local src
466+
while true
467+
framecode = JuliaInterpreter.get_framecode(m)
468+
fakeargs = Any[nothing for i = 1:length(framecode.scope.nargs)]
469+
frame = JuliaInterpreter.prepare_frame(framecode, fakeargs, isa(m.sig, UnionAll) ? sparam_ub(m) : Core.svec())
470+
src = framecode.src
471+
(length(src.code) > 1 && is_self_call(src.code[end-1], src.slotnames)) || break
472+
# Build the optional arg, so we can get its type
473+
pc = frame.pc
474+
while pc < length(src.code) - 1
475+
pc = step_expr!(frame)
476+
end
477+
val = pc > 1 ? frame.framedata.ssavalues[pc-1] : src.code[1].args[end]
478+
sig = Tuple{Base.unwrap_unionall(m.sig).parameters..., typeof(val)}
479+
m = whichtt(sig)
480+
end
481+
length(src.code) > 1 || return m
482+
stmt = src.code[end-1]
483+
if isexpr(stmt, :call) && (f = stmt.args[1]; isa(f, QuoteNode))
484+
# Check that it has a #self# call
485+
hasself = any(i->is_self_call(stmt, src.slotnames, i), 1:length(stmt.args))
486+
hasself || return m
487+
f = f.value
488+
mths = methods(f)
489+
if length(mths) == 1
490+
return first(mths)
491+
end
492+
end
493+
return m
494+
end
495+
496+
function sparam_ub(meth::Method)
497+
typs = []
498+
sig = meth.sig
499+
while sig isa UnionAll
500+
push!(typs, Symbol(sig.var.ub))
501+
sig = sig.body
502+
end
503+
return Core.svec(typs...)
504+
end
505+
444506
# precompilation
445507

446508
if ccall(:jl_generating_output, Cint, ()) == 1

test/runtests.jl

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,18 @@ struct Caller end
1111
struct Gen{T} end
1212
end
1313

14+
bodymethtest0(x) = 0
15+
function bodymethtest0(x)
16+
y = 2x
17+
y + x
18+
end
19+
bodymethtest1(x, y=1; z="hello") = 1
20+
bodymethtest2(x, y=Dict(1=>2); z="hello") = 2
21+
bodymethtest3(x::T, y=Dict(1=>2); z="hello") where T<:AbstractFloat = 3
22+
# No kw but has optional args
23+
bodymethtest4(x, y=1) = 4
24+
bodymethtest5(x, y=Dict(1=>2)) = 5
25+
1426
@testset "LoweredCodeUtils.jl" begin
1527
signatures = Set{Any}()
1628
newcode = CodeInfo[]
@@ -198,4 +210,13 @@ end
198210
empty!(signatures)
199211
methoddefs!(signatures, frame; define=false)
200212
@test !isempty(signatures)
213+
214+
for m in methods(bodymethtest0)
215+
@test bodymethod(m) === m
216+
end
217+
@test startswith(String(bodymethod(first(methods(bodymethtest1))).name), "#")
218+
@test startswith(String(bodymethod(first(methods(bodymethtest2))).name), "#")
219+
@test startswith(String(bodymethod(first(methods(bodymethtest3))).name), "#")
220+
@test bodymethod(first(methods(bodymethtest4))).nargs == 3 # one extra for #self#
221+
@test bodymethod(first(methods(bodymethtest5))).nargs == 3
201222
end

0 commit comments

Comments
 (0)