Skip to content

Commit 8e1ca1e

Browse files
committed
Add method table as key for method_info (#140)
1 parent 332c21f commit 8e1ca1e

File tree

5 files changed

+66
-44
lines changed

5 files changed

+66
-44
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
fail-fast: false
1616
matrix:
1717
version:
18-
- '1.6' # latest LTS
18+
- 'lts'
1919
- '1'
2020
- 'pre'
2121
- 'nightly'

Project.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
name = "CodeTracking"
22
uuid = "da1fd8a2-8d9e-5ec2-8556-3022fb5608a2"
33
authors = ["Tim Holy <[email protected]>"]
4-
version = "1.3.9"
4+
version = "2.0.0-DEV"
55

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

1010
[compat]
11-
julia = "1.6"
11+
julia = "1.10"
1212

1313
[extras]
1414
ColorTypes = "3da002f7-5984-5a60-b8a6-cbb66c0b333f"

README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ CodeTracking can be thought of as an extension of Julia's
88
It provides an interface for obtaining:
99

1010
- the strings and expressions of method definitions
11-
- the method signatures at a specific file & line number
11+
- the method signatures (and their corresponding method table) at a specific file & line number
1212
- location information for "dynamic" code that might have moved since it was first loaded
1313
- a list of files that comprise a particular package.
1414

@@ -112,18 +112,20 @@ PkgFiles(ColorTypes [3da002f7-5984-5a60-b8a6-cbb66c0b333f]):
112112
```
113113
114114
115-
You can also find the method-signatures at a particular location:
115+
You can also find the method signatures at a particular location:
116116
117117
```julia
118118
julia> signatures_at(ColorTypes, "src/traits.jl", 14)
119-
1-element Array{Any,1}:
120-
Tuple{typeof(red),AbstractRGB}
119+
1-element Vector{Pair{Union{Nothing, Core.MethodTable}, Type}}:
120+
nothing => Tuple{typeof(red),AbstractRGB}
121121

122122
julia> signatures_at("/home/tim/.julia/packages/ColorTypes/BsAWO/src/traits.jl", 14)
123-
1-element Array{Any,1}:
124-
Tuple{typeof(red),AbstractRGB}
123+
1-element Vector{Pair{Union{Nothing, Core.MethodTable}, Type}}:
124+
nothing => Tuple{typeof(red),AbstractRGB}
125125
```
126126
127+
with the first element being the method table for which the method was added (a value of `nothing` denotes the default method table).
128+
127129
CodeTracking also helps correcting for [Julia issue #26314](https://github.com/JuliaLang/julia/issues/26314):
128130
129131
```julia

src/CodeTracking.jl

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ CodeTracking can be thought of as an extension of InteractiveUtils, and pairs we
66
- `definition`: a lower-level variant of the above
77
- `pkgfiles`: return information about the source files that define a package
88
- `whereis`: Return location information about methods (with Revise, it updates as you edit files)
9-
- `signatures_at`: return the signatures of all methods whose definition spans the specified location
9+
- `signatures_at`: return the signatures (and the corresponding method table) of all methods whose definition spans the specified location
1010
"""
1111
module CodeTracking
1212

1313
using Base: PkgId
14-
using Core: LineInfoNode
14+
using Core: LineInfoNode, MethodTable
1515
using Base.Meta: isexpr
1616
using UUIDs
1717
using InteractiveUtils
@@ -29,12 +29,15 @@ include("utils.jl")
2929

3030
# These values get populated by Revise
3131

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

3942
const _pkgfiles = Dict{PkgId,PkgFiles}()
4043

@@ -51,32 +54,33 @@ const method_lookup_callback = Ref{Any}(nothing)
5154
# id is the PkgId of the corresponding package
5255
# relpath is the path of the file from the basedir of `id`
5356
# mod is the "active" module at that point in the source
54-
# exsigs is a ex=>sigs dictionary, where `ex` is the source expression and `sigs`
55-
# a list of method-signatures defined by that expression.
57+
# exsigs is a `ex => mt_sigs` dictionary, where `ex` is the source expression and `mt_sigs`
58+
# a list of `method_table => signature` pairs defined by that expression.
5659
const expressions_callback = Ref{Any}(nothing)
5760

5861
const juliabase = joinpath("julia", "base")
5962
const juliastdlib = joinpath("julia", "stdlib", "v$(VERSION.major).$(VERSION.minor)")
6063

64+
method_table(method::Method) = isdefined(method, :external_mt) ? method.external_mt::MethodTable : nothing
65+
MethodInfoKey(method::Method) = MethodInfoKey(method_table(method), method.sig)
66+
6167
### Public API
6268

6369
"""
6470
filepath, line = whereis(method::Method)
6571
66-
Return the file and line of the definition of `method`. The meaning of `line`
67-
depends on the Julia version: on Julia 1.5 and higher it is the line number of
68-
the method declaration, otherwise it is the first line of the method's body.
72+
Return the file and line of the definition of `method`.
6973
"""
7074
function whereis(method::Method)
7175
file, line = String(method.file), method.line
7276
startswith(file, "REPL[") && return file, line
73-
lin = get(method_info, method.sig, nothing)
77+
lin = get(method_info, MethodInfoKey(method), nothing)
7478
if lin === nothing
7579
f = method_lookup_callback[]
7680
if f !== nothing
7781
try
7882
Base.invokelatest(f, method)
79-
lin = get(method_info, method.sig, nothing)
83+
lin = get(method_info, MethodInfoKey(method), nothing)
8084
catch
8185
end
8286
end
@@ -134,11 +138,9 @@ function whereis(lineinfo::Core.LineInfoNode, method::Method)
134138
end
135139

136140
"""
137-
sigs = signatures_at(filename, line)
141+
mt_sigs = signatures_at(filename, line)
138142
139-
Return the signatures of all methods whose definition spans the specified location.
140-
Prior to Julia 1.5, `line` must correspond to a line in the method body
141-
(not the signature or final `end`).
143+
Return the `method_table => signature` pairs of all methods whose definition spans the specified location.
142144
143145
Returns `nothing` if there are no methods at that location.
144146
"""
@@ -175,11 +177,11 @@ function signatures_at(filename::AbstractString, line::Integer)
175177
end
176178

177179
"""
178-
sigs = signatures_at(mod::Module, relativepath, line)
180+
mt_sigs = signatures_at(mod::Module, relativepath, line)
179181
180-
For a package that defines module `mod`, return the signatures of all methods whose definition
181-
spans the specified location. `relativepath` indicates the path of the file relative to
182-
the packages top-level directory, e.g., `"src/utils.jl"`.
182+
For a package that defines module `mod`, return the `method_table => signature` pairs of
183+
all methods whose definition spans the specified location. `relativepath` indicates the
184+
path of the file relative to the packages top-level directory, e.g., `"src/utils.jl"`.
183185
`line` must correspond to a line in the method body (not the signature or final `end`).
184186
185187
Returns `nothing` if there are no methods at that location.
@@ -194,10 +196,10 @@ function signatures_at(id::PkgId, relpath::AbstractString, line::Integer)
194196
expressions === nothing && error("cannot look up methods by line number, try `using Revise` before loading other packages")
195197
try
196198
for (mod, exsigs) in Base.invokelatest(expressions, id, relpath)
197-
for (ex, sigs) in exsigs
199+
for (ex, mt_sigs) in exsigs
198200
lr = linerange(ex)
199201
lr === nothing && continue
200-
line lr && return sigs
202+
line lr && return mt_sigs
201203
end
202204
end
203205
catch
@@ -298,13 +300,13 @@ See also [`code_expr`](@ref).
298300
"""
299301
function definition(::Type{Expr}, method::Method)
300302
file = String(method.file)
301-
def = startswith(file, "REPL[") ? nothing : get(method_info, method.sig, nothing)
303+
def = startswith(file, "REPL[") ? nothing : get(method_info, MethodInfoKey(method), nothing)
302304
if def === nothing
303305
f = method_lookup_callback[]
304306
if f !== nothing
305307
try
306308
Base.invokelatest(f, method)
307-
def = get(method_info, method.sig, nothing)
309+
def = get(method_info, MethodInfoKey(method), nothing)
308310
catch
309311
end
310312
end

test/runtests.jl

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ using CodeTracking
44
using Test, InteractiveUtils, REPL, LinearAlgebra, SparseArrays
55
# Note: ColorTypes needs to be installed, but note the intentional absence of `using ColorTypes`
66

7-
using CodeTracking: line_is_decl
7+
using CodeTracking: line_is_decl, MethodInfoKey
88

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

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

@@ -297,8 +297,8 @@ end
297297
@testset "With Revise" begin
298298
if isdefined(Main, :Revise)
299299
m = @which gcd(10, 20)
300-
sigs = signatures_at(Base.find_source_file(String(m.file)), m.line)
301-
@test !isempty(sigs)
300+
mt_sigs = signatures_at(Base.find_source_file(String(m.file)), m.line)
301+
@test !isempty(mt_sigs)
302302
ex = @code_expr(gcd(10, 20))
303303
@test ex isa Expr
304304
body = ex.args[2]
@@ -308,10 +308,10 @@ end
308308

309309
if Base.VERSION < v"1.11.0-0"
310310
m = first(methods(edit))
311-
sigs = signatures_at(String(m.file), m.line)
312-
@test !isempty(sigs)
313-
sigs = signatures_at(Base.find_source_file(String(m.file)), m.line)
314-
@test !isempty(sigs)
311+
mt_sigs = signatures_at(String(m.file), m.line)
312+
@test !isempty(mt_sigs)
313+
mt_sigs = signatures_at(Base.find_source_file(String(m.file)), m.line)
314+
@test !isempty(mt_sigs)
315315
end
316316

317317
# issue #23
@@ -321,9 +321,10 @@ end
321321

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

@@ -454,3 +455,20 @@ end
454455
@test CodeTracking.strip_gensym("#𝓔′#90") == :𝓔′
455456
@test CodeTracking.strip_gensym("𝓔′##kw") == :𝓔′
456457
end
458+
459+
@testset "External method tables" begin
460+
mod = @eval module $(gensym(:ExternalMT))
461+
Base.Experimental.@MethodTable method_table
462+
end
463+
ex = :(Base.Experimental.@overlay method_table +(x::String, y::String) = x * y)
464+
if VERSION v"1.13-"
465+
method = Core.eval(mod, ex)
466+
else
467+
Core.eval(mod, ex)
468+
method = only(Base.MethodList(mod.method_table).ms)
469+
end
470+
lnn = LineNumberNode(Int(method.line), method.file)
471+
@test CodeTracking.definition(Expr, method) === nothing
472+
CodeTracking.method_info[MethodInfoKey(method)] = [(lnn, ex)]
473+
@test CodeTracking.definition(Expr, method) == ex
474+
end

0 commit comments

Comments
 (0)