diff --git a/base/reflection.jl b/base/reflection.jl index 4dd26806382f1..75c9a492f2c81 100644 --- a/base/reflection.jl +++ b/base/reflection.jl @@ -1280,6 +1280,51 @@ function hasmethod(@nospecialize(f), @nospecialize(t), kwnames::Tuple{Vararg{Sym return issubset(kwnames, kws) end +""" + fbody = bodyfunction(basemethod::Method) + +Find the keyword "body function" (the function that contains the body of the method +as written, called after all missing keyword-arguments have been assigned default values). +`basemethod` is the method you obtain via [`which`](@ref) or [`methods`](@ref). +""" +function bodyfunction(basemethod::Method) + function getsym(arg) + isa(arg, Symbol) && return arg + isa(arg, GlobalRef) && return arg.name + return nothing + end + + fmod = basemethod.module + # The lowered code for `basemethod` should look like + # %1 = mkw(kwvalues..., #self#, args...) + # return %1 + # where `mkw` is the name of the "active" keyword body-function. + ast = Base.uncompressed_ast(basemethod) + f = nothing + if isa(ast, Core.CodeInfo) && length(ast.code) >= 2 + callexpr = ast.code[end-1] + if isa(callexpr, Expr) && callexpr.head == :call + fsym = callexpr.args[1] + if isa(fsym, Symbol) + f = getfield(fmod, fsym) + elseif isa(fsym, GlobalRef) + newsym = nothing + if fsym.mod === Core && fsym.name === :_apply + newsym = getsym(callexpr.args[2]) + elseif fsym.mod === Core && fsym.name === :_apply_iterate + newsym = getsym(callexpr.args[3]) + end + if isa(newsym, Symbol) + f = getfield(basemethod.module, newsym)::Function + else + f = getfield(fsym.mod, fsym.name)::Function + end + end + end + end + return f +end + """ Base.isambiguous(m1, m2; ambiguous_bottom=false) -> Bool diff --git a/test/reflection.jl b/test/reflection.jl index 0889511610148..0dee5dc53bcb7 100644 --- a/test/reflection.jl +++ b/test/reflection.jl @@ -909,3 +909,19 @@ end @test length(methods(g, ())) == 1 end + +module BodyFunctionLookup +f1(x, y; a=1) = error("oops") +f2(f::Function, args...; kwargs...) = f1(args...; kwargs...) +end + +@testset "bodyfunction" begin + m = first(methods(BodyFunctionLookup.f1)) + f = Base.bodyfunction(m) + @test occursin("f1#", String(nameof(f))) + m = first(methods(BodyFunctionLookup.f2)) + f = Base.bodyfunction(m) + @test f !== Core._apply_iterate + @test f !== Core._apply + @test occursin("f2#", String(nameof(f))) +end