diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc12ba9..af909e2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,6 +16,7 @@ jobs: matrix: version: - '1.0' + - '1.6' - '1' - 'nightly' os: diff --git a/src/MethodAnalysis.jl b/src/MethodAnalysis.jl index 64ca23d..1ba8517 100644 --- a/src/MethodAnalysis.jl +++ b/src/MethodAnalysis.jl @@ -24,8 +24,8 @@ Split a signature type like `Tuple{typeof(f),ArgTypes...}` back out to `(f, Tupl function call_type(tt) ft = tt.parameters[1] argt = Tuple{tt.parameters[2:end]...} - name = Symbol(String(ft.name.name)[2:end]) # strip off leading '#' - return (getfield(ft.name.module, name), argt) + name = Symbol(String(ft.name.name)) + return (getfield(ft.name.module, name).instance, argt) end """ @@ -87,34 +87,69 @@ julia> methodinstance(Tuple{typeof(f), Int, String}) MethodInstance for f(::Int64, ::String) ``` """ -function methodinstance(@nospecialize(f), @nospecialize(types)) +methodinstance(@nospecialize(f), @nospecialize(types)) = + _methodinstance(f, types, false) + +function methodinstance(@nospecialize(types)) + f, argt = call_type(types) + return methodinstance(f, types) +end + +function _methodinstance(@nospecialize(f), @nospecialize(types), multi::Bool) if types isa Tuple tt = Tuple{typeof(f), types...} - return methodinstance(f, tt) + return _methodinstance(f, tt, multi) end - inst = nothing + kept = MethodInstance[] visit(f) do mi if isa(mi, MethodInstance) - if mi.specTypes === types - inst = mi + if multi ? (mi.specTypes <: types) : (mi.specTypes === types) + push!(kept, mi) end return false end true end - return inst -end -function methodinstance(@nospecialize(types)) - f, argt = call_type(types) - return methodinstance(f, types) + multi && return kept + length(kept) == 1 && return kept[1] + length(kept) == 0 && return nothing + error(length(kept), " MethodInstances matched the specified types") end """ methodinstances() - methodinstances(mod::Module) - methodinstances(f) + methodinstances(top) Collect all `MethodInstance`s, optionally restricting them to a particular module, function, method, or methodlist. + +# Examples + +``` +julia> sin(π/2) +1.0 + +julia> sin(0.8f0) +0.7173561f0 + +julia> methodinstances(sin) +2-element Vector{Core.MethodInstance}: + MethodInstance for sin(::Float64) + MethodInstance for sin(::Float32) + +julia> m = which(convert, (Type{Bool}, Real)) +convert(::Type{T}, x::Number) where T<:Number in Base at number.jl:7 + +julia> methodinstances(m) +68-element Vector{Core.MethodInstance}: + MethodInstance for convert(::Type{UInt128}, ::Int64) + MethodInstance for convert(::Type{Int128}, ::Int64) + MethodInstance for convert(::Type{Int64}, ::Int32) + MethodInstance for convert(::Type{UInt64}, ::Int64) + ⋮ +``` + +Note the method `m` was broader than the signature we queried with, and the returned `MethodInstance`s reflect that breadth. +See [`methodinstances`](@ref) for a more restrictive subset. """ function methodinstances(top=()) if isa(top, Module) || isa(top, Function) || isa(top, Type) || isa(top, Method) || isa(top, Base.MethodList) @@ -129,6 +164,32 @@ function methodinstances(top=()) return mis end +""" + methodinstances(f, types) + methodinstances(tt::Type{<:Tuple}) + +Return all MethodInstances whose signature is a subtype of `types`. + +# Example + +``` +julia> methodinstances(convert, (Type{Bool}, Real)) +2-element Vector{Core.MethodInstance}: + MethodInstance for convert(::Type{Bool}, ::Bool) + MethodInstance for convert(::Type{Bool}, ::Int64) +``` + +Compare this to the result from [`methodinstance`](@ref). +""" +methodinstances(@nospecialize(f), @nospecialize(types)) = + _methodinstance(f, types, true) + +function methodinstances(@nospecialize(types::Type)) + f, argt = call_type(types) + return methodinstances(f, types) +end + + if isdefined(Core, :MethodMatch) include("findcallers.jl") end diff --git a/test/runtests.jl b/test/runtests.jl index 9241dd5..63f4365 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -87,6 +87,19 @@ end mis = methodinstances(which(convert, (Type{String}, AbstractString))) @test length(mis) > 2 @test mi ∉ mis # that's covered by a different Method + + f(x) = 1 + f(1) + f(1.0) + @test methodinstance(f, (Real,)) === nothing + mi = methodinstance(f, (Float64,)) + @test mi.specTypes == Tuple{typeof(f),Float64} + for mis in (methodinstances(f, (Real,)), methodinstances(Tuple{typeof(f),Real})) + @test length(mis) == 2 + @test all(mis) do mi + mi.specTypes ∈ (Tuple{typeof(f),Float64}, Tuple{typeof(f),Int}) + end + end end @testset "Backedges" begin @@ -189,7 +202,7 @@ module Callers f(x) = rand() function g() - x = sin(π/2) + x = sin(rand()) y = f(x) z = round(Int, x) y = f(z)