Skip to content
Open
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
1 change: 1 addition & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,7 @@ export
isinteractive,
hasmethod,
methods,
instancemethods,
nameof,
parentmodule,
pathof,
Expand Down
86 changes: 80 additions & 6 deletions base/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1079,6 +1079,12 @@ function signature_type(@nospecialize(f), @nospecialize(argtypes))
return rewrap_unionall(Tuple{ft, u.parameters...}, argtypes)
end

function instance_signature_type(t::Type, @nospecialize(argtypes))
argtypes = to_tuple_type(argtypes)
u = unwrap_unionall(argtypes)::DataType
return rewrap_unionall(Tuple{t,u.parameters...}, argtypes)
end

"""
code_lowered(f, types; generated=true, debuginfo=:default)

Expand Down Expand Up @@ -1167,10 +1173,19 @@ function MethodList(mt::Core.MethodTable)
return MethodList(ms, mt)
end

function collect_methods(list, mod)
ms = Method[]
for m in list::Vector
m = m::Core.MethodMatch
(mod === nothing || parentmodule(m.method) ∈ mod) && push!(ms, m.method)
end
return ms
end

"""
methods(f, [types], [module])

Return the method table for `f`.
Return the method list for `f`.

If `types` is specified, return an array of methods whose types match.
If `module` is specified, return an array of methods defined in that module.
Expand All @@ -1186,11 +1201,8 @@ function methods(@nospecialize(f), @nospecialize(t),
world = get_world_counter()
world == typemax(UInt) && error("code reflection cannot be used from generated functions")
# Lack of specialization => a comprehension triggers too many invalidations via _collect, so collect the methods manually
ms = Method[]
for m in _methods(f, t, -1, world)::Vector
m = m::Core.MethodMatch
(mod === nothing || parentmodule(m.method) ∈ mod) && push!(ms, m.method)
end
mm = _methods(f, t, -1, world)
ms = collect_methods(mm, mod)
MethodList(ms, typeof(f).name.mt)
end
methods(@nospecialize(f), @nospecialize(t), mod::Module) = methods(f, t, (mod,))
Expand All @@ -1211,6 +1223,68 @@ function methods(@nospecialize(f),
return methods(f, Tuple{Vararg{Any}}, mod)
end

function _instancemethods(t::Type, @nospecialize(mt), lim::Int, world::UInt)
tt = instance_signature_type(t, mt)
return _methods_by_ftype(tt, lim, world)
end

"""
instancemethods(T::Type, [types], [module])

Return the method list for instances of type `T`. In particular, for any
object `x`, `methods(x)` is the same as `instancemethods(typeof(x))`.
The key difference is that `instancemethods` allows to retrieve the method
list for instances of a type without instantiating any object. Another
difference is that the type `T` need not be concrete.

If `types` is specified, return the methods whose types match.
If `module` is specified, only return the methods defined in that module.
Multiple modules can also be specified as an array.

# Example

```julia
julia> struct A{T}
x::T
end

# making the object callable
julia> (a::A{Float64})(x::Float64) = a.x + x
julia> (a::A{String})(x::String) = a.x * x

julia> a = A(1.0)
A{Float64}(1.0)

julia> methods(a) == instancemethods(A{Float64})
true

julia> length(instancemethods(A))
2
```

!!! compat "Julia 1.11"
This function requires at least Julia 1.11.

See also: [`methods`](@ref).
"""
function instancemethods(t::Type, @nospecialize(tmatch),
mod::Union{Tuple{Module},AbstractArray{Module},Nothing}=nothing)
world = get_world_counter()
mm = _instancemethods(t, tmatch, -1, world)
ms = collect_methods(mm, mod)
if t in (Any, Function, Core.Builtin)
t = DataType
end
return MethodList(ms, typename(t).mt)
end

instancemethods(t::Type, @nospecialize(tmatch), mod::Module) =
instancemethods(t, tmatch, (mod,))

instancemethods(t::Type,
mod::Union{Module,AbstractArray{Module},Nothing}=nothing) =
instancemethods(t, Tuple, mod)

function visit(f, mt::Core.MethodTable)
mt.defs !== nothing && visit(f, mt.defs)
nothing
Expand Down
1 change: 1 addition & 0 deletions doc/src/base/base.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Base.include_dependency
__init__
Base.which(::Any, ::Any)
Base.methods
Base.instancemethods
Base.@show
ans
err
Expand Down
67 changes: 67 additions & 0 deletions test/reflection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -959,6 +959,73 @@ end
@test length(methods(g, ())) == 1
end

module TestInstanceMethods

struct A{T}
x::T
end

(a::A)(y::Int) = a.x + y
(a::A{Float64})(y::Float64) = a.x + y
(a::A)(y::String) = "$y: $a.x"
(a::A)(y::Char) = "$y: $a.x"
(a::A{String})(y::Char) = "$y: $a.x"

struct B
x::Int
end

(b::B)(y) = b.x + y
(b::B)(y::Float64) = b.x + y
(b::B)(y::String) = string(b.x) * y

end

@testset "instancemethods" begin
using .TestInstanceMethods: TestInstanceMethods, A, B

for x in (1, 1.0, "1")
T = typeof(x)
@test instancemethods(A{T}) == methods(A(x))
@test instancemethods(A{T}, Tuple{String}) == methods(A(x), Tuple{String})
@test instancemethods(A{T}, TestInstanceMethods) ==
methods(A(x), TestInstanceMethods)
@test isempty(instancemethods(A{T}, [Base]))
@test instancemethods(A{T}, Tuple{Float64}, [TestInstanceMethods]) ==
methods(A(x), Tuple{Float64}, TestInstanceMethods)
@test isempty(instancemethods(A{T}, Tuple{String}, Base))
end

@test instancemethods(B) == methods(B(1))
@test instancemethods(B, Tuple{Float64}) == methods(B(1), Tuple{Float64})
@test instancemethods(B, [TestInstanceMethods]) ==
methods(B(1), TestInstanceMethods)
@test isempty(instancemethods(B, Base))
@test instancemethods(B, Tuple{Float64}, TestInstanceMethods) ==
methods(B(1), Tuple{Float64}, TestInstanceMethods)
@test isempty(instancemethods(B, Tuple{Float64}, [Base]))

@test length(instancemethods(A)) == 5
@test length(instancemethods(A, Tuple{Char})) == 2
@test length(instancemethods(A, TestInstanceMethods)) == 5
@test length(instancemethods(A, Tuple{Char}, [TestInstanceMethods])) == 2
@test length(instancemethods(A, [Base])) == 0
@test length(instancemethods(A, Tuple{Char}, Base)) == 0

@test length(instancemethods(B)) == 3
@test length(instancemethods(B, Tuple{Char})) == 1
@test length(instancemethods(B, [TestInstanceMethods])) == 3
@test length(instancemethods(B, Base)) == 0
@test length(instancemethods(B, Tuple{Char}, TestInstanceMethods)) == 1
@test length(instancemethods(B, Tuple{Char}, [Base])) == 0

@test instancemethods(typeof(+)) == methods(+)
@test instancemethods(typeof(+), Base) == methods(+, Base)
@test instancemethods(typeof(+), Tuple{Number,Number}) == methods(+, Tuple{Number,Number})
@test instancemethods(typeof(+), Tuple{Number,Number}, Base) ==
methods(+, Tuple{Number,Number}, Base)
end

module BodyFunctionLookup
f1(x, y; a=1) = error("oops")
f2(f::Function, args...; kwargs...) = f1(args...; kwargs...)
Expand Down