Skip to content

Commit f9098fe

Browse files
authored
Add visit_withmodule (#32)
This allows the operation to know the module in which the current item was found. Note that this is distinct from module ownership: module A f(x) = 1 end module B using ..A A.f(x::Int) = 2 end In this case both methods are found while traversing A, even though `which(A.f, (Int,)).module == B`.
1 parent 81f05f1 commit f9098fe

File tree

4 files changed

+78
-3
lines changed

4 files changed

+78
-3
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "MethodAnalysis"
22
uuid = "85b6ec6f-f7df-4429-9514-a64bcd9ee824"
33
authors = ["Tim Holy <tim.holy@gmail.com>"]
4-
version = "0.4.7"
4+
version = "0.4.8"
55

66
[deps]
77
AbstractTrees = "1520ce14-60c1-5f80-bbc7-55ef81b5835c"

src/MethodAnalysis.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ using Base: Callable, IdSet
66
using Core: MethodInstance, CodeInfo, SimpleVector, MethodTable
77
using Base.Meta: isexpr
88

9-
export visit, call_type, methodinstance, methodinstances, worlds # findcallers is exported from its own file
9+
export visit, visit_withmodule
10+
export call_type, methodinstance, methodinstances, worlds # findcallers is exported from its own file
1011
export visit_backedges, all_backedges, with_all_backedges, terminal_backedges, direct_backedges
1112
export child_modules, methodinstances_owned_by
1213
export hasbox

src/visit.jl

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,17 +68,52 @@ function visit(@nospecialize(operation); print::Bool=false)
6868
return nothing
6969
end
7070

71+
struct ModuleWrapper
72+
operation
73+
parent::Union{Module,Nothing}
74+
end
75+
(w::ModuleWrapper)(@nospecialize(x)) = w.operation(x, w.parent)
76+
77+
rewrap_operation(@nospecialize(operation), ::Module) = operation
78+
rewrap_operation(w::ModuleWrapper, mod::Module) = ModuleWrapper(w.operation, mod)
79+
80+
"""
81+
visit_withmodule(operation; print::Bool=false)
82+
visit_withmodule(operation, obj, mod; print::Bool=false)
83+
84+
Similar to [`visit`](@ref), except that `operation` should have signature `operation(x, mod)` where `mod` is either:
85+
86+
- the module in which `x` was found, or
87+
- `nothing` if `x` is itself a top-level module.
88+
89+
If you're visiting underneath a specific object `obj`, you must supply `mod`, the module (or `nothing`) in which
90+
`obj` would be found.
91+
"""
92+
function visit_withmodule(@nospecialize(operation); print::Bool=false)
93+
visited = IdSet{Any}()
94+
wrapped = ModuleWrapper(operation, nothing)
95+
for mod in Base.loaded_modules_array()
96+
_visit(wrapped, mod, visited, print)
97+
end
98+
return nothing
99+
end
100+
101+
function visit_withmodule(@nospecialize(operation), @nospecialize(obj), mod::Union{Module,Nothing}; print::Bool=false)
102+
return _visit(ModuleWrapper(operation, mod), obj, IdSet{Any}(), print)
103+
end
104+
71105
# These are non-keyword functions due to https://github.com/JuliaLang/julia/issues/34516
72106

73107
function _visit(@nospecialize(operation), mod::Module, visited::IdSet{Any}, print::Bool)
74108
mod visited && return nothing
75109
push!(visited, mod)
76110
print && println("Module ", mod)
77111
if operation(mod)
112+
newop = rewrap_operation(operation, mod)
78113
for nm in names(mod; all=true)
79114
if isdefined(mod, nm)
80115
obj = getfield(mod, nm)
81-
_visit(operation, obj, visited, print)
116+
_visit(newop, obj, visited, print)
82117
end
83118
end
84119
end

test/runtests.jl

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ module Outer
2121
f2(x, y::Number) = x + y
2222
end
2323

24+
module One
25+
f(x) = 1
26+
end
27+
module Two
28+
using ..One
29+
One.f(x::Int) = x + 2
30+
end
31+
2432

2533
@testset "visit" begin
2634
@test Outer.Inner.g("hi") == 0
@@ -72,6 +80,37 @@ end
7280
end
7381
end
7482

83+
@testset "visit_withmodule" begin
84+
found = Dict{Module,Set{Method}}()
85+
visit_withmodule(Main, nothing) do item, mod
86+
if item === Main
87+
@test mod === nothing
88+
return true
89+
end
90+
if mod == Main && item isa Module
91+
return item (Outer, Outer.Inner, Outer.OtherInner, One, Two)
92+
end
93+
item isa Method || return true
94+
push!(get!(Set{Any}, found, mod), item)
95+
return false
96+
end
97+
s = found[One]
98+
@test methods(One.f) s
99+
@test any(m -> m.module == One, s)
100+
@test any(m -> m.module == Two, s)
101+
@test methods(Outer.f2) found[Outer]
102+
@test methods(Outer.Inner.h) found[Outer.Inner]
103+
104+
found = Dict{Union{Module,Nothing}, Set{Module}}()
105+
visit_withmodule() do item, mod
106+
item isa Module || return false
107+
push!(get!(Set{Module}, found, mod), item)
108+
return true
109+
end
110+
@test Main union(found[nothing], found[Core])
111+
@test One found[Main]
112+
end
113+
75114
@testset "child_modules" begin
76115
m = Module()
77116
Base.eval(m, :(

0 commit comments

Comments
 (0)