Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
fail-fast: false
matrix:
version:
- '1.6' # latest LTS
- 'lts'
- '1'
- 'pre'
- 'nightly'
Expand Down
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
name = "CodeTracking"
uuid = "da1fd8a2-8d9e-5ec2-8556-3022fb5608a2"
authors = ["Tim Holy <tim.holy@gmail.com>"]
version = "1.3.9"
version = "2.0.0-DEV"

[deps]
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[compat]
julia = "1.6"
julia = "1.10"

[extras]
ColorTypes = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"
Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,16 @@ You can also 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}
1-element Vector{Pair{Union{Nothing, Core.MethodTable}, Type}}:
nothing => 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}
1-element Vector{Pair{Union{Nothing, Core.MethodTable}, Type}}:
nothing => Tuple{typeof(red),AbstractRGB}
```

with the first element being the method table for which the method has been defined (a value of `nothing` denotes the default method table).

CodeTracking also helps correcting for [Julia issue #26314](https://github.com/JuliaLang/julia/issues/26314):

```julia
Expand Down
22 changes: 14 additions & 8 deletions src/CodeTracking.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ CodeTracking can be thought of as an extension of InteractiveUtils, and pairs we
module CodeTracking

using Base: PkgId
using Core: LineInfoNode
using Core: LineInfoNode, MethodTable
using Base.Meta: isexpr
using UUIDs
using InteractiveUtils
Expand All @@ -29,12 +29,15 @@ include("utils.jl")

# These values get populated by Revise

# `method_info[sig]` is either:
# `method_info[mt => sig]` is either:
# - `missing`, to indicate that the method cannot be located
# - a list of `(lnn,ex)` pairs. In almost all cases there will be just one of these,
# but "mistakes" in moving methods from one file to another can result in more than
# definition. The last pair in the list is the currently-active definition.
const method_info = IdDict{Type,Union{Missing,Vector{Tuple{LineNumberNode,Expr}}}}()
# one definition. The last pair in the list is the currently-active definition.

const MethodInfoKey = Pair{Union{Nothing, MethodTable}, Type}

const method_info = IdDict{MethodInfoKey,Union{Missing,Vector{Tuple{LineNumberNode,Expr}}}}()

const _pkgfiles = Dict{PkgId,PkgFiles}()

Expand All @@ -58,6 +61,9 @@ const expressions_callback = Ref{Any}(nothing)
const juliabase = joinpath("julia", "base")
const juliastdlib = joinpath("julia", "stdlib", "v$(VERSION.major).$(VERSION.minor)")

method_table(method::Method) = isdefined(method, :external_mt) ? method.external_mt::MethodTable : nothing
MethodInfoKey(method::Method) = MethodInfoKey(method_table(method), method.sig)

### Public API

"""
Expand All @@ -70,13 +76,13 @@ the method declaration, otherwise it is the first line of the method's body.
function whereis(method::Method)
file, line = String(method.file), method.line
startswith(file, "REPL[") && return file, line
lin = get(method_info, method.sig, nothing)
lin = get(method_info, MethodInfoKey(method), nothing)
if lin === nothing
f = method_lookup_callback[]
if f !== nothing
try
Base.invokelatest(f, method)
lin = get(method_info, method.sig, nothing)
lin = get(method_info, MethodInfoKey(method), nothing)
catch
end
end
Expand Down Expand Up @@ -298,13 +304,13 @@ See also [`code_expr`](@ref).
"""
function definition(::Type{Expr}, method::Method)
file = String(method.file)
def = startswith(file, "REPL[") ? nothing : get(method_info, method.sig, nothing)
def = startswith(file, "REPL[") ? nothing : get(method_info, MethodInfoKey(method), nothing)
if def === nothing
f = method_lookup_callback[]
if f !== nothing
try
Base.invokelatest(f, method)
def = get(method_info, method.sig, nothing)
def = get(method_info, MethodInfoKey(method), nothing)
catch
end
end
Expand Down
40 changes: 29 additions & 11 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ using CodeTracking
using Test, InteractiveUtils, REPL, LinearAlgebra, SparseArrays
# Note: ColorTypes needs to be installed, but note the intentional absence of `using ColorTypes`

using CodeTracking: line_is_decl
using CodeTracking: line_is_decl, MethodInfoKey

if !isempty(ARGS) && "revise" ∈ ARGS
# For running tests with and without Revise
Expand Down Expand Up @@ -101,7 +101,7 @@ isdefined(Main, :Revise) ? Main.Revise.includet("script.jl") : include("script.j

# Test a method marked as missing
m = @which sum(1:5)
CodeTracking.method_info[m.sig] = missing
CodeTracking.method_info[MethodInfoKey(nothing, m.sig)] = missing
@test whereis(m) == (CodeTracking.maybe_fix_path(String(m.file)), m.line)
@test definition(m) === nothing

Expand Down Expand Up @@ -297,8 +297,8 @@ end
@testset "With Revise" begin
if isdefined(Main, :Revise)
m = @which gcd(10, 20)
sigs = signatures_at(Base.find_source_file(String(m.file)), m.line)
@test !isempty(sigs)
mt_sigs = signatures_at(Base.find_source_file(String(m.file)), m.line)
@test !isempty(mt_sigs)
ex = @code_expr(gcd(10, 20))
@test ex isa Expr
body = ex.args[2]
Expand All @@ -308,10 +308,10 @@ end

if Base.VERSION < v"1.11.0-0"
m = first(methods(edit))
sigs = signatures_at(String(m.file), m.line)
@test !isempty(sigs)
sigs = signatures_at(Base.find_source_file(String(m.file)), m.line)
@test !isempty(sigs)
mt_sigs = signatures_at(String(m.file), m.line)
@test !isempty(mt_sigs)
mt_sigs = signatures_at(Base.find_source_file(String(m.file)), m.line)
@test !isempty(mt_sigs)
end

# issue #23
Expand All @@ -321,9 +321,10 @@ end

if isdefined(Revise, :add_revise_deps)
Revise.add_revise_deps()
sigs = signatures_at(CodeTracking, "src/utils.jl", 5)
@test length(sigs) == 1 # only isn't available on julia 1.0
@test first(sigs) == Tuple{typeof(CodeTracking.checkname), Expr, Any}
mt_sigs = signatures_at(CodeTracking, "src/utils.jl", 5)
@test length(mt_sigs) == 1 # only isn't available on julia 1.0
(mt, sig) = first(mt_sigs)
@test sig == Tuple{typeof(CodeTracking.checkname), Expr, Any}
@test pkgfiles(CodeTracking).id == Base.PkgId(CodeTracking)
end

Expand Down Expand Up @@ -454,3 +455,20 @@ end
@test CodeTracking.strip_gensym("#𝓔′#90") == :𝓔′
@test CodeTracking.strip_gensym("𝓔′##kw") == :𝓔′
end

@testset "External method tables" begin
mod = @eval module $(gensym(:ExternalMT))
Base.Experimental.@MethodTable method_table
end
ex = :(Base.Experimental.@overlay method_table +(x::String, y::String) = x * y)
if VERSION ≥ v"1.13-"
method = Core.eval(mod, ex)
else
Core.eval(mod, ex)
method = only(Base.MethodList(mod.method_table).ms)
end
lnn = LineNumberNode(Int(method.line), method.file)
@test CodeTracking.definition(Expr, method) === nothing
CodeTracking.method_info[MethodInfoKey(method)] = [(lnn, ex)]
@test CodeTracking.definition(Expr, method) == ex
end
Loading