diff --git a/src/utils.jl b/src/utils.jl index 68f48b2..53af560 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -61,7 +61,15 @@ function is_func_expr(@nospecialize(ex), meth::Method) ex = get_func_expr(ex) is_func_expr(ex) || return false fname = nothing - if ex.head == :(->) + sigex = ex.args[1] + if ex.head == :function && get_call_expr(sigex) === nothing + # anonymous `function (args...) ... end`: match the argument tuple as for `->` + while isexpr(sigex, (:where, :(::))) + sigex = sigex.args[1] + end + isexpr(sigex, :tuple) || return false + exargs = sigex.args + elseif ex.head == :(->) exargs = ex.args[1] if isexpr(exargs, :tuple) exargs = exargs.args @@ -309,6 +317,18 @@ function kwmethod_basename(meth::Method) end false end + if idx === nothing + # The kwbody of an anonymous closure is named after neither the closure nor its type, + # but like every kwbody it has its own name in place of `#self#`, and the + # (unnamed) closure object follows the keyword arguments. + margs = Base.method_argnames(meth) + if margs[1] !== Symbol("#self#") + params = Base.unwrap_unionall(meth.sig).parameters + idx = findfirst(eachindex(margs)) do i + i > 1 && margs[i] === Symbol("") && i <= length(params) && params[i] isa Type && params[i] <: Function + end + end + end idx === nothing && return name_final, 0 return name_final, idx end diff --git a/test/runtests.jl b/test/runtests.jl index d546834..5917ff4 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -505,3 +505,20 @@ end CodeTracking.method_info[MethodInfoKey(method)] = [(lnn, ex)] @test CodeTracking.definition(Expr, method) == ex end + +@testset "anonymous closures" begin + for (make, src) in ((make_anon_function, "function (x::Vector{Float32}, i)"), + (make_anon_function_kw, "function (x::Vector{Float32}, i; k = a)"), + (make_arrow_kw, "(x::Vector{Float32}, i; k = a) -> x[i] + k")) + f = make(1.0f0) + m = only(methods(f)) + body, _ = CodeTracking.definition(String, m) + @test startswith(lstrip(body), src) + if !isempty(Base.kwarg_decl(m)) + # the keyword-body method; its function type is the closure's last type parameter + kwbody = only(methods(typeof(f).parameters[end].instance)) + body, _ = CodeTracking.definition(String, kwbody) + @test startswith(lstrip(body), src) + end + end +end diff --git a/test/script.jl b/test/script.jl index 8bde0ab..7f4d965 100644 --- a/test/script.jl +++ b/test/script.jl @@ -169,3 +169,18 @@ end # Single-line `eval(:(...))`: the method's line points at the `eval` call itself eval(:(eval_oneline(x) = 2x + 1)) + +# Anonymous closures, each passed as a call argument on its own line +make_anon_function(a) = identity( + function (x::Vector{Float32}, i) + return x[i] + a + end +) +make_anon_function_kw(a) = identity( + function (x::Vector{Float32}, i; k = a) + return x[i] + k + end +) +make_arrow_kw(a) = identity( + (x::Vector{Float32}, i; k = a) -> x[i] + k +)