Docstring for `Method`

Hey everyone,

I’m trying to do something that I thought would be easy but is actually rather tricky.
I simply want to get the docstring of a Method.

For example:

"""
my f
"""
f(x::Int) = 2x


"""
with strings
"""
f(x::String) = print(x)

"""
and keyword args
"""
f(; y=1) = print(y)

methods

gives:

# 3 methods for generic function "f":
[1] f(; y) in Main at /Users/federicoclaudi/Documents/Github/Term.jl/workspace.jl:16
[2] f(x::Int64) in Main at /Users/federicoclaudi/Documents/Github/Term.jl/workspace.jl:5
[3] f(x::String) in Main at /Users/federicoclaudi/Documents/Github/Term.jl/workspace.jl:11

if, say, I wanted to get the docstring of the second method, what should I do?
I know about Docs.Binding and Docs.meta to get multidocs like it’s done, for example, in the REPL but I was hoping there was a more direct way to get at this.
Specifically, when the methods are constructors for a DataType there might be multiple methods that differ only in their keyword arguments and the approach above doesn’t distinguish them since they share the same signature.

Thank you,
Federico

1 Like

Is this what you are asking for?

help?> f(::String)
  with strings

julia> @doc f(::String)
  with strings
1 Like

Hey thanks for getting back to me so quickly.

I’m afraid not. I need the docstring for a specific method, not for all of them. There’s way to use the signature, but it still doesn’t always cover all cases. Isn’t there a way, given an object of type Method to get the actual docstring that belongs to ?

I don’t think that’s possible, you can’t have two methods with the same type signature that only differ by keyword arguments, because the keyword arguments don’t count for dispatch.

1 Like

That;s fair, I think you’re right.

This is the solution I landed on, it seems to get the job done, leaving it here in case folks find it useful:

using Base.Docs: meta, Binding, doc
import Markdown

"""
    get_methods_with_docstrings(obj::Union{Union, DataType, Function})

Get the docstring for each method for an object (function/datatype).
"""
function get_methods_with_docstrings(obj::Union{Union, DataType, Function})::Tuple{Vector, Vector}
    # get the parent module and the methods list for the object
    mod = parentmodule(obj)
    mm = methods(obj)

    # get the module's multidoc
    binding = Binding(mod, Symbol(obj))    
    dict = meta(mod)
    multidoc = dict[binding]
    
    # for each module, attempt to get the docstring as markdown
    docstrings = []
    for m in mm
        # cleanup signature
        sig = length(m.sig.types) == 1 ? Tuple{} : Tuple{m.sig.types[2:end]...}
        
        haskey(multidoc.docs, sig) || begin
            push!(docstrings, nothing)
        end
        docs = multidoc.docs[sig].text[1] |> Markdown.parse
        push!(docstrings, docs)
    end

    return mm, docstrings
end


This is quite a useful snippet, but for relatively complex cases, e.g., Core.TypeVar would appear in sig (DataType).

A real-world case did happen to me where the above code didn’t work due to inconsistent handling of DataType with “non-normalized” Core.TypeVars:

julia> Tuple{Union{Number, AbstractVecOrMat{<:Number}}}.parameters[1].b
AbstractVecOrMat{<:Number} (alias for Union{AbstractArray{var"#s6", 1}, AbstractArray{var"#s6", 2}} where var"#s6"<:Number)

julia> t.parameters[1].b
AbstractVecOrMat{<:Number} (alias for Union{AbstractArray{var"#s10", 1}, AbstractArray{var"#s10", 2}} where var"#s10"<:Number)

It seems that using strings to lookup the type might satisfy more common cases, and this is a revised version:

using Base.Docs: meta, Binding, doc
import Markdown

"""
    get_methods_with_docstrings(obj::Union{Union, DataType, Function})

Get the docstring for each method for an object (function/datatype).
"""
function get_methods_with_docstrings(obj::Union{Union, DataType, Function})
    # get the parent module and the methods list for the object
    mod = parentmodule(obj)
    mm = methods(obj)

    # get the module's multidoc
    binding = Binding(mod, Symbol(obj))    
    dict = meta(mod)
    multidoc = Dict{String, Any}(string(k) => v for (k, v) in dict[binding].docs)
    
    # for each module, attempt to get the docstring as markdown
    docstrings = Any[]
    for m in mm
        # cleanup signature
        sig = length(m.sig.types) == 1 ? Tuple{} : Tuple{m.sig.types[2:end]...}
        sig_as_str = string(sig)

        haskey(multidoc, sig_as_str) || continue

        docs = multidoc[sig_as_str].text[1] |> Markdown.parse
        push!(docstrings, m => docs)
    end

    return docstrings
end

Besides, I guess there are some internal utility do the process of transforming m.sig to a stable sig used in multidoc, which shall be a complete solution, but I cannot find such transformation in Base or Core.