diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 243b45c..627ee17 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: fail-fast: false matrix: version: - - '1.6' # latest LTS + - 'lts' - '1' - 'pre' - 'nightly' diff --git a/Project.toml b/Project.toml index 1d77231..20e8d75 100644 --- a/Project.toml +++ b/Project.toml @@ -1,14 +1,14 @@ name = "CodeTracking" uuid = "da1fd8a2-8d9e-5ec2-8556-3022fb5608a2" authors = ["Tim Holy "] -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" diff --git a/README.md b/README.md index a599614..c3b9fd6 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/CodeTracking.jl b/src/CodeTracking.jl index 585c2ec..0e11566 100644 --- a/src/CodeTracking.jl +++ b/src/CodeTracking.jl @@ -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 @@ -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}() @@ -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 """ @@ -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 @@ -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 diff --git a/test/runtests.jl b/test/runtests.jl index c448802..d3d3d04 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -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 @@ -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 @@ -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] @@ -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 @@ -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 @@ -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