Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TYPEDSIGNATURES gets confused with more than one type parameter #84

Closed
j-fu opened this issue Nov 30, 2019 · 6 comments · Fixed by #88
Closed

TYPEDSIGNATURES gets confused with more than one type parameter #84

j-fu opened this issue Nov 30, 2019 · 6 comments · Fixed by #88

Comments

@j-fu
Copy link

j-fu commented Nov 30, 2019

Hi,
documenting functions with more the one type parameter with TYPEDSIGNATURES leads to UnionAll error.

Here is the MWE:

module XDoc
using DocStringExtensions

"""
$(SIGNATURES)

Function with two type parameters.
"""
function foo(u::Tu, v::Tv) where{Tu,Tv}
    println("Tu: $(Tu), v:$(Tv)")
    nothing
end

"""
$(TYPEDSIGNATURES)

Another function with two type parameters.
"""
function bar(u::Tu, v::Tv) where{Tu,Tv}
    println("Tu: $(Tu), v:$(Tv)")
    nothing
end
end
julia> include("XDoc.jl")
help?> XDoc.bar
ERROR: type UnionAll has no field a
Stacktrace:
 [1] getproperty(::Type, ::Symbol) at ./Base.jl:15
 [2] printmethod(::Base.GenericIOBuffer{Array{UInt8,1}}, ::Base.Docs.Binding, ::Function, ::Method, ::Type) at /home/fuhrmann/.julia/packages/DocStringExtensions/BEyNH/src/utilities.jl:250
 [3] format(::DocStringExtensions.TypedMethodSignatures, ::Base.GenericIOBuffer{Array{UInt8,1}}, ::Base.Docs.DocStr) at /home/fuhrmann/.julia/packages/DocStringExtensions/BEyNH/src/abbreviations.jl:378
 [4] formatdoc(::Base.GenericIOBuffer{Array{UInt8,1}}, ::Base.Docs.DocStr, ::DocStringExtensions.TypedMethodSignatures) at /home/fuhrmann/.julia/packages/DocStringExtensions/BEyNH/src/abbreviations.jl:25
 [5] formatdoc(::Base.Docs.DocStr) at /home/abuild/rpmbuild/BUILD/julia-1.2.0/usr/share/julia/stdlib/v1.2/REPL/src/docview.jl:60
 [6] parsedoc(::Base.Docs.DocStr) at /home/abuild/rpmbuild/BUILD/julia-1.2.0/usr/share/julia/stdlib/v1.2/REPL/src/docview.jl:68
 [7] iterate at ./generator.jl:47 [inlined]
 [8] _collect(::Array{Base.Docs.DocStr,1}, ::Base.Generator{Array{Base.Docs.DocStr,1},typeof(Base.Docs.parsedoc)}, ::Base.EltypeUnknown, ::Base.HasShape{1}) at ./array.jl:619
 [9] collect_similar at ./array.jl:548 [inlined]
 [10] map at ./abstractarray.jl:2073 [inlined]
 [11] doc(::Base.Docs.Binding, ::Type) at /home/abuild/rpmbuild/BUILD/julia-1.2.0/usr/share/julia/stdlib/v1.2/REPL/src/docview.jl:117
 [12] doc(::Base.Docs.Binding) at /home/abuild/rpmbuild/BUILD/julia-1.2.0/usr/share/julia/stdlib/v1.2/REPL/src/docview.jl:85
 [13] top-level scope at /home/abuild/rpmbuild/BUILD/julia-1.2.0/usr/share/julia/stdlib/v1.2/REPL/src/docview.jl:355
help?> XDoc.foo
  foo(u, v)
  

  Function with two type parameters.

The current workaround is of course to use SIGNATURES, but I like the possibility to generate as much information as possible from the code.

Jürgen

@mortenpi
Copy link
Member

It's probably due to the assumption that typesig will always have a field called :a:

for (i, method) in enumerate(group)
if i == length(group)
t = typesig
else
t = typesig.a
typesig = typesig.b
end
printmethod(buf, binding, func, method, t)
println(buf)
end

I won't have time to dig into this deeper though, but would happily take a PR.

@kdheepak
Copy link
Contributor

@mortenpi I'm looking into this and will be happy to submit a PR.

I do have a question though. I have the following file:

cat test.jl
───────┬─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
       │ File: test.jl
───────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   1   │ using DocStringExtensions
   2   │
   3   │ """
   4   │ $TYPEDSIGNATURES
   5   │
   6   │ hello_world is a function
   7   │ """
   8   │ function hello_world(::Type{T}, s::AbstractString) where {T <: Number}
   9   │     @show T, s
  10   │     nothing
  11   │ end
  12   │
  13   │ println(@doc hello_world(Number, "hi"))
───────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

And I have this minimal example, and when I run it in DocStringExtensions, I get the following

binding = hello_world
typesig = Union{Tuple{T}, Tuple{Type{T},AbstractString}} where T<:Number
modname = Main
func = hello_world

```julia
hello_world(?::T<:Number, s::T<:Number)

```

hello_world is a function

I put @show calls in the following function for the above output:

local binding = doc.data[:binding]
local typesig = doc.data[:typesig]
local modname = doc.data[:module]
local func = Docs.resolve(binding)
local groups = methodgroups(func, typesig, modname)

Why does the typesig have Tuple{Type{T},AbstractString}? Shouldn't it just be AbstractString? Is this a bug? Is DocStringExtensions generating typesig or is that happening in base julia?

If I don't use a generic typeof as the first argument, this works as expected.

───────┬─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
       │ File: test.jl
───────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   1   │ using DocStringExtensions
   2   │
   3   │ """
   4   │ $TYPEDSIGNATURES
   5   │
   6   │ hello_world is a function
   7   │ """
   8   │ function hello_world(::Type{Number}, s::AbstractString)
   9   │     @show Number, s
  10   │     nothing
  11   │ end
  12   │
  13   │ println(@doc hello_world(Number, "hi"))
───────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
binding = hello_world
typesig = Tuple{Type{Number},AbstractString}
modname = Main
func = hello_world

```julia
hello_world(?::Type{Number}, s::AbstractString)

```

hello_world is a function

@kdheepak
Copy link
Contributor

I think I figured out the answer to part of my question

struct __CUSTOM__ end

function Docs.formatdoc(buffer, docstr, ::Type{__CUSTOM__})
    @show docstr.data[:typesig]
end


"""
hello_world is a function

$__CUSTOM__
"""
function hello_world(::Type{T}, s::AbstractString) where T <: Number
    @show Number, s
    nothing
end

println(@doc hello_world(Number, "hi"))

This returns

docstr.data[:typesig] = Union{Tuple{T}, Tuple{Type{T},AbstractString}} where T<:Number
hello_world is a function

So this is generated by Julia. I'm still not sure if Tuple{Type{T},AbstractString} is a bug or not.

@kdheepak
Copy link
Contributor

Okay, it's seems that it is not a bug, but intended. Sorry for spamming this thread. I'm still trying to figure out why the typesig is the way it is, and I have more questions than answers.

@mortenpi
Copy link
Member

Thanks for tackling this @kdheepak!

To answer your question about the type signature: yep, it's something that comes from Julia itself. It's created by the Base.Docs.signature function:

julia> Base.Docs.signature(:(function foo end))
:(Union{})

julia> Base.Docs.signature(:(function foo(::Int) end))
:(Union{Tuple{Int}})

julia> Base.Docs.signature(:(function foo(::Vector{T}) where T <: Number end))
:(Union{Tuple{Vector{T}}, Tuple{T}} where T <: Number)

The weirdness about the parametric signature, I believe, is a bug in Docs.signature (JuliaLang/julia#29437). The second Tuple{T} shouldn't be there, which you can see if you look up the signature for the method in the method table:

julia> function foo(::Int) end
foo (generic function with 1 method)

julia> function foo(::Vector{T}) where T <: Number end
foo (generic function with 2 methods)

julia> methods(foo)
# 2 methods for generic function "foo":
[1] foo(::Int64) in Main at REPL[4]:1
[2] foo(::Array{T,1}) where T<:Number in Main at REPL[10]:1

julia> methods(foo).ms[1].sig
Tuple{typeof(foo),Int64}

julia> methods(foo).ms[2].sig
Tuple{typeof(foo),Array{T,1}} where T<:Number

So, coming back to the original example, the typesig for function hello_world(::Type{Number}, s::AbstractString) end should be the following UnionAll: Tuple{Type{T},AbstractString} where T<:Number. However, that's not what the docsystem stores, so in DocStringExtensions we'd need some sort of a workaround for that bug.

@kdheepak
Copy link
Contributor

Thanks for the comment! This is exactly what I wanted to know. 1) it is a bug with the signature function, and 2) how to generate the signature with the Base.Docs module and what the Julia type system thinks the signature is. I'll post more information in the PR.

@mortenpi mortenpi added this to the 0.8.2 milestone May 12, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants