From 008cd1060b9cabf5a279836a3ef161a1ae422504 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sun, 3 Mar 2019 07:50:58 -0600 Subject: [PATCH] Implement signatures_at Closes #3 --- README.md | 12 ++++++++++++ src/CodeTracking.jl | 46 ++++++++++++++++++++++++++++++++++++++++++++- src/utils.jl | 20 ++++++++++++++++++++ test/runtests.jl | 2 ++ 4 files changed, 79 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 665b7e1..382404b 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,18 @@ julia> definition(m, String) "red(c::AbstractRGB ) = c.r\n" ``` +or to find the method-signatures at a particular location: + +```julia +julia> signatures_at(ColorTypes, "src/traits.jl", 14) +1-element Array{Any,1}: + Tuple{typeof(red),AbstractRGB} + +julia> signatures_at("/home/tim/.julia/packages/ColorTypes/BsAWO/src/traits.jl", 14) +1-element Array{Any,1}: + Tuple{typeof(red),AbstractRGB} +``` + ## A few details CodeTracking won't do anything *useful* unless the user is also running Revise, diff --git a/src/CodeTracking.jl b/src/CodeTracking.jl index 2730744..dcb7378 100644 --- a/src/CodeTracking.jl +++ b/src/CodeTracking.jl @@ -4,7 +4,7 @@ using Base: PkgId using Core: LineInfoNode using UUIDs -export whereis, definition, pkgfiles +export whereis, definition, pkgfiles, signatures_at include("pkgfiles.jl") include("utils.jl") @@ -18,6 +18,7 @@ const method_info = IdDict{Type,Tuple{LineNumberNode,Expr}}() const _pkgfiles = Dict{PkgId,PkgFiles}() const method_lookup_callback = Ref{Any}(nothing) +const expressions_callback = Ref{Any}(nothing) ### Public API @@ -68,7 +69,50 @@ function whereis(lineinfo, method::Method) return file, lineinfo.line-method.line+line1 end +""" + sigs = signatures_at(filename, line) + +Return the signatures of all methods whose definition spans the specified location. +`line` must correspond to a line in the method body (not the signature or final `end`). + +Returns `nothing` if there are no methods at that location. +""" +function signatures_at(filename::AbstractString, line::Integer) + for (id, pkgfls) in _pkgfiles + if startswith(filename, basedir(pkgfls)) + return signatures_at(id, relpath(filename, basedir(pkgfls)), line) + end + end + error("$filename not found, perhaps the package is not loaded") +end + +""" + sigs = signatures_at(mod::Module, relativepath, line) + +For a package that defines module `mod`, return the signatures of all methods whose definition +spans the specified location. `relativepath` indicates the path of the file relative to +the packages top-level directory, e.g., `"src/utils.jl"`. +`line` must correspond to a line in the method body (not the signature or final `end`). + +Returns `nothing` if there are no methods at that location. +""" +function signatures_at(mod::Module, relpath::AbstractString, line::Integer) + id = PkgId(mod) + return signatures_at(id, relpath, line) +end +function signatures_at(id::PkgId, relpath::AbstractString, line::Integer) + expressions = expressions_callback[] + expressions === nothing && error("cannot look up methods by line number, try `using Revise` before loading other packages") + for (mod, exsigs) in Base.invokelatest(expressions, id, relpath) + for (ex, sigs) in exsigs + lr = linerange(ex) + lr === nothing && continue + line ∈ lr && return sigs + end + end + return nothing +end """ src = definition(method::Method, String) diff --git a/src/utils.jl b/src/utils.jl index f340f20..79c5369 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -13,6 +13,26 @@ function isfuncexpr(ex) return false end +function linerange(def::Expr) + start, haslinestart = findline(def, identity) + stop, haslinestop = findline(def, Iterators.reverse) + (haslinestart & haslinestop) && return start:stop + return nothing +end +linerange(arg) = linerange(convert(Expr, arg)) # Handle Revise's RelocatableExpr + +function findline(ex, order) + ex.head == :line && return ex.args[1], true + for a in order(ex.args) + a isa LineNumberNode && return a.line, true + if a isa Expr + ln, hasline = findline(a, order) + hasline && return ln, true + end + end + return 0, false +end + fileline(lin::LineInfoNode) = String(lin.file), lin.line fileline(lnn::LineNumberNode) = String(lnn.file), lnn.line diff --git a/test/runtests.jl b/test/runtests.jl index 11fb78b..0badd71 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,3 +1,5 @@ +# Note: some of CodeTracking's functionality can only be tested by Revise + using CodeTracking using Test # Note: ColorTypes needs to be installed, but note the intentional absence of `using ColorTypes`