Skip to content

Commit 2bbb63b

Browse files
authored
Fix typed signatures parametric types (#88)
1 parent 8adca63 commit 2bbb63b

File tree

6 files changed

+290
-27
lines changed

6 files changed

+290
-27
lines changed

docs/Showcase/src/Showcase.jl

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,60 @@ $(TYPEDSIGNATURES)
7070
foo(x::AbstractString) = nothing
7171

7272

73+
## Methods with type parameters
74+
75+
"""
76+
A method for [`$(FUNCTIONNAME)`](@ref), with type parameters. Original declaration:
77+
78+
```julia
79+
bar(x::AbstractArray{T}, y::T) where {T <: Integer} = nothing
80+
```
81+
82+
And the result from [`TYPEDSIGNATURES`](@ref) abbreviation:
83+
84+
$(TYPEDSIGNATURES)
85+
86+
For comparison, [`SIGNATURES`](@ref) abbreviation:
87+
88+
$(SIGNATURES)
89+
"""
90+
bar(x::AbstractArray{T}, y::T) where {T <: Integer} = nothing
91+
92+
"""
93+
A method for [`$(FUNCTIONNAME)`](@ref), with type parameters. Original declaration:
94+
95+
```julia
96+
bar(x::AbstractArray{T}, ::String) where {T <: Integer} = x
97+
```
98+
99+
And the result from [`TYPEDSIGNATURES`](@ref) abbreviation:
100+
101+
$(TYPEDSIGNATURES)
102+
103+
For comparison, [`SIGNATURES`](@ref) abbreviation:
104+
105+
$(SIGNATURES)
106+
"""
107+
bar(x::AbstractArray{T}, ::String) where {T <: Integer} = x
108+
109+
"""
110+
A method for [`$(FUNCTIONNAME)`](@ref), with type parameters. Original declaration:
111+
112+
```julia
113+
bar(x::AbstractArray{T}, y::U) where {T <: Integer, U <: AbstractString} = 0
114+
```
115+
116+
And the result from [`TYPEDSIGNATURES`](@ref) abbreviation:
117+
118+
$(TYPEDSIGNATURES)
119+
120+
For comparison, [`SIGNATURES`](@ref) abbreviation:
121+
122+
$(SIGNATURES)
123+
"""
124+
bar(x::AbstractArray{T}, y::U) where {T <: Integer, U <: AbstractString} = 0
125+
126+
73127
"""
74128
The [`TYPEDEF`](@ref) abbreviation includes the type signature:
75129

docs/src/showcase.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,18 @@ foo(::Int)
2121
foo(::String)
2222
```
2323

24+
### Type parameters
25+
26+
[`TYPEDSIGNATURES`](@ref) can also handle type parameters. However, the resulting signatures
27+
may not be as clean as in the code since they have to be reconstructed from Julia's internal
28+
representation:
29+
30+
```@docs
31+
bar(x::AbstractArray{T}, y::T) where {T <: Integer}
32+
bar(x::AbstractArray{T}, ::String) where {T <: Integer}
33+
bar(x::AbstractArray{T}, y::U) where {T <: Integer, U <: AbstractString}
34+
```
35+
2436
## Types
2537

2638
```@docs

src/abbreviations.jl

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -368,28 +368,28 @@ function format(::TypedMethodSignatures, buf, doc)
368368
local typesig = doc.data[:typesig]
369369
local modname = doc.data[:module]
370370
local func = Docs.resolve(binding)
371+
# TODO: why is methodgroups returning invalid methods?
372+
# the methodgroups always appears to return a Vector and the size depends on whether parametric types are used
373+
# and whether default arguments are used
371374
local groups = methodgroups(func, typesig, modname)
372375
if !isempty(groups)
376+
group = groups[end]
373377
println(buf)
374378
println(buf, "```julia")
375-
for group in groups
376-
if length(group) == 1
377-
for method in group
378-
printmethod(buf, binding, func, method, typesig)
379-
println(buf)
380-
end
379+
for (i, method) in enumerate(group)
380+
N = length(arguments(method))
381+
# return a list of tuples that represent type signatures
382+
tuples = find_tuples(typesig)
383+
# The following will find the tuple that matches the number of arguments in the function
384+
# ideally we would check that the method signature matches the Tuple{...} signature
385+
# but that is not straightforward because of how expressive Julia can be
386+
if Sys.iswindows()
387+
t = tuples[findlast(t -> t isa DataType && string(t.name) == "Tuple" && length(t.types) == N, tuples)]
381388
else
382-
for (i, method) in enumerate(group)
383-
if i == length(group)
384-
t = typesig
385-
else
386-
t = typesig.a
387-
typesig = typesig.b
388-
end
389-
printmethod(buf, binding, func, method, t)
390-
println(buf)
391-
end
389+
t = tuples[findfirst(t -> t isa DataType && string(t.name) == "Tuple" && length(t.types) == N, tuples)]
392390
end
391+
printmethod(buf, binding, func, method, t)
392+
println(buf)
393393
end
394394
println(buf, "\n```\n")
395395
end

src/utilities.jl

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,53 @@ function printmethod(buffer::IOBuffer, binding::Docs.Binding, func, method::Meth
222222
return buffer
223223
end
224224

225+
"""
226+
$(:SIGNATURES)
227+
228+
Converts a method signature (or a union of several signatures) in a vector of (single)
229+
signatures.
230+
231+
This is used for decoding the method signature that a docstring is paired with. In the case
232+
when the docstring applies to multiple methods (e.g. when default positional argument values
233+
are used and define multiple methods at once), they are combined together as union of `Tuple`
234+
types.
235+
236+
```jldoctest; setup = :(using DocStringExtensions)
237+
julia> DocStringExtensions.find_tuples(Tuple{String,Number,Int})
238+
1-element Array{DataType,1}:
239+
Tuple{String,Number,Int64}
240+
241+
julia> DocStringExtensions.find_tuples(Tuple{T} where T <: Integer)
242+
1-element Array{DataType,1}:
243+
Tuple{T<:Integer}
244+
245+
julia> s = Union{
246+
Tuple{Int64},
247+
Tuple{U},
248+
Tuple{T},
249+
Tuple{Int64,T},
250+
Tuple{Int64,T,U}
251+
} where U where T;
252+
253+
julia> DocStringExtensions.find_tuples(s)
254+
5-element Array{DataType,1}:
255+
Tuple{Int64}
256+
Tuple{U}
257+
Tuple{T}
258+
Tuple{Int64,T}
259+
Tuple{Int64,T,U}
260+
```
261+
"""
262+
function find_tuples(typesig)
263+
if typesig isa UnionAll
264+
return find_tuples(typesig.body)
265+
elseif typesig isa Union
266+
return [typesig.a, find_tuples(typesig.b)...]
267+
else
268+
return [typesig,]
269+
end
270+
end
271+
225272
"""
226273
$(:TYPEDSIGNATURES)
227274
@@ -245,12 +292,9 @@ function printmethod(buffer::IOBuffer, binding::Docs.Binding, func, method::Meth
245292
print(buffer, binding.var)
246293
print(buffer, "(")
247294
local args = arguments(method)
295+
local where_syntax = []
248296
for (i, sym) in enumerate(args)
249-
if typesig isa UnionAll
250-
t = typesig.body.a.types[1]
251-
else
252-
t = typesig.types[i]
253-
end
297+
t = typesig.types[i]
254298
print(buffer, "$sym::$t")
255299
if i != length(args)
256300
print(buffer, ", ")
@@ -262,12 +306,7 @@ function printmethod(buffer::IOBuffer, binding::Docs.Binding, func, method::Meth
262306
join(buffer, kws, ", ")
263307
end
264308
print(buffer, ")")
265-
if typesig isa UnionAll
266-
t = typesig.body.a
267-
else
268-
t = typesig
269-
end
270-
rt = Base.return_types(func, t)
309+
rt = Base.return_types(func, typesig)
271310
if length(rt) >= 1 && rt[1] !== Nothing && rt[1] !== Union{}
272311
print(buffer, " -> $(rt[1])")
273312
end

test/TestModule/M.jl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const A{T} = Union{Vector{T}, Matrix{T}}
1313
h_1(x::A) = x
1414
h_2(x::A{Int}) = x
1515
h_3(x::A{T}) where {T} = x
16+
h_4(x, ::Int, z) = x
1617

1718
i_1(x; y = x) = x * y
1819
i_2(x::Int; y = x) = x * y
@@ -22,6 +23,17 @@ i_4(x; y::T = zero(T), z::U = zero(U)) where {T, U} = x + y + z
2223
j_1(x, y) = x * y # two arguments, no keyword arguments
2324
j_1(x; y = x) = x * y # one argument, one keyword argument
2425

26+
k_1(x::String, y::T = 0, z::T = zero(T)) where T <: Number = x
27+
k_2(x::String, y::U, z::T) where T <: Number where U <: Complex = x
28+
k_3(x, y::T, z::U) where {T, U} = x + y + z
29+
k_4(::String, ::Int = 0) = nothing
30+
k_5(::Type{T}, x::String, func::Union{Nothing, Function} = nothing) where T <: Number = x
31+
k_6(x::Vector{T}) where T <: Number = x
32+
k_7(x::Union{T,Nothing}, y::T = zero(T)) where {T <: Number} = x
33+
k_8(x) = x
34+
k_9(x::T where T<:Any) = x
35+
k_10(x::T) where T = x
36+
2537
mutable struct T
2638
a
2739
b

test/tests.jl

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,17 @@ end
165165
@test occursin("\ng(x, y)\n", str)
166166
@test occursin("\ng(x, y, z; kwargs...)\n", str)
167167
@test occursin("\n```\n", str)
168+
169+
doc.data = Dict(
170+
:binding => Docs.Binding(M, :h_4),
171+
:typesig => Union{Tuple{Any, Int, Any}},
172+
:module => M,
173+
)
174+
DSE.format(SIGNATURES, buf, doc)
175+
str = String(take!(buf))
176+
@test occursin("\n```julia\n", str)
177+
@test occursin("\nh_4(x, ?, z)\n", str)
178+
@test occursin("\n```\n", str)
168179
end
169180

170181
@testset "method signatures with types" begin
@@ -212,6 +223,141 @@ end
212223
@test occursin("\nh(x::Int32) -> Int32\n", str)
213224
end
214225
@test occursin("\n```\n", str)
226+
227+
doc.data = Dict(
228+
:binding => Docs.Binding(M, :k_1),
229+
:typesig => Union{Tuple{String}, Tuple{String, T}, Tuple{String, T, T}, Tuple{T}} where T <: Number,
230+
:module => M,
231+
)
232+
DSE.format(DSE.TYPEDSIGNATURES, buf, doc)
233+
str = String(take!(buf))
234+
@test occursin("\n```julia\n", str)
235+
@test occursin("\nk_1(x::String) -> String\n", str)
236+
@test occursin("\nk_1(x::String, y::T<:Number) -> String\n", str)
237+
@test occursin("\nk_1(x::String, y::T<:Number, z::T<:Number) -> String\n", str)
238+
@test occursin("\n```\n", str)
239+
240+
doc.data = Dict(
241+
:binding => Docs.Binding(M, :k_2),
242+
:typesig => (Union{Tuple{String, U, T}, Tuple{T}, Tuple{U}} where T <: Number) where U <: Complex,
243+
:module => M,
244+
)
245+
246+
DSE.format(DSE.TYPEDSIGNATURES, buf, doc)
247+
str = String(take!(buf))
248+
@test occursin("\n```julia\n", str)
249+
@test occursin("k_2(x::String, y::U<:Complex, z::T<:Number) -> String", str)
250+
@test occursin("\n```\n", str)
251+
252+
doc.data = Dict(
253+
:binding => Docs.Binding(M, :k_3),
254+
:typesig => (Union{Tuple{Any, T, U}, Tuple{U}, Tuple{T}} where U <: Any) where T <: Any,
255+
:module => M,
256+
)
257+
DSE.format(DSE.TYPEDSIGNATURES, buf, doc)
258+
str = String(take!(buf))
259+
@test occursin("\n```julia\n", str)
260+
@test occursin("\nk_3(x::Any, y::T, z::U) -> Any\n", str)
261+
@test occursin("\n```\n", str)
262+
263+
doc.data = Dict(
264+
:binding => Docs.Binding(M, :k_4),
265+
:typesig => Union{Tuple{String}, Tuple{String, Int}},
266+
:module => M,
267+
)
268+
DSE.format(DSE.TYPEDSIGNATURES, buf, doc)
269+
str = String(take!(buf))
270+
@test occursin("\n```julia\n", str)
271+
if VERSION > v"1.3.0"
272+
@test occursin("\nk_4(::String)\n", str)
273+
if typeof(1) === Int64
274+
@test occursin("\nk_4(::String, ::Int64)\n", str)
275+
else
276+
@test occursin("\nk_4(::String, ::Int32)\n", str)
277+
end
278+
else
279+
# TODO: remove this test when julia 1.0.0 support is dropped.
280+
# older versions of julia seem to return this
281+
# str = "\n```julia\nk_4(#temp#::String)\nk_4(#temp#::String, #temp#::Int64)\n\n```\n\n"
282+
@test occursin("\nk_4", str)
283+
end
284+
@test occursin("\n```\n", str)
285+
286+
287+
doc.data = Dict(
288+
:binding => Docs.Binding(M, :k_5),
289+
:typesig => Union{Tuple{Type{T}, String}, Tuple{Type{T}, String, Union{Nothing, Function}}, Tuple{T}} where T <: Number,
290+
:module => M,
291+
)
292+
DSE.format(DSE.TYPEDSIGNATURES, buf, doc)
293+
str = String(take!(buf))
294+
@test occursin("\n```julia\n", str)
295+
if VERSION > v"1.3.0"
296+
@test occursin("\nk_5(::Type{T<:Number}, x::String) -> String\n", str)
297+
@test occursin("\nk_5(::Type{T<:Number}, x::String, func::Union{Nothing, Function}) -> String\n", str)
298+
@test occursin("\n```\n", str)
299+
else
300+
# TODO: remove this test when julia 1.0.0 support is dropped.
301+
# older versions of julia seem to return this
302+
# str = "\n```julia\nk_5(#temp#::Type{T<:Number}, x::String) -> String\nk_5(#temp#::Type{T<:Number}, x::String, func::Union{Nothing, Function}) -> String\n\n```\n\n"
303+
@test occursin("\nk_5", str)
304+
end
305+
306+
doc.data = Dict(
307+
:binding => Docs.Binding(M, :k_6),
308+
:typesig => Union{Tuple{Vector{T}}, Tuple{T}} where T <: Number,
309+
:module => M,
310+
)
311+
DSE.format(DSE.TYPEDSIGNATURES, buf, doc)
312+
str = String(take!(buf))
313+
@test occursin("\n```julia\n", str)
314+
@test occursin("\nk_6(x::Array{T<:Number,1}) -> Array{T<:Number,1}\n", str)
315+
@test occursin("\n```\n", str)
316+
317+
doc.data = Dict(
318+
:binding => Docs.Binding(M, :k_7),
319+
:typesig => Union{Tuple{Union{T, Nothing}}, Tuple{Union{T, Nothing}, T}, Tuple{T}} where T <: Number,
320+
:module => M,
321+
)
322+
DSE.format(DSE.TYPEDSIGNATURES, buf, doc)
323+
str = String(take!(buf))
324+
@test occursin("\n```julia\n", str)
325+
@test occursin("\nk_7(x::Union{Nothing, T<:Number}) -> Union{Nothing, Number}\n", str)
326+
@test occursin("\nk_7(x::Union{Nothing, T<:Number}, y::T<:Number) -> Union{Nothing, T<:Number}\n", str)
327+
@test occursin("\n```\n", str)
328+
329+
doc.data = Dict(
330+
:binding => Docs.Binding(M, :k_8),
331+
:typesig => Union{Tuple{Any}},
332+
:module => M,
333+
)
334+
DSE.format(DSE.TYPEDSIGNATURES, buf, doc)
335+
str = String(take!(buf))
336+
@test occursin("\n```julia\n", str)
337+
@test occursin("\nk_8(x::Any) -> Any\n", str)
338+
@test occursin("\n```\n", str)
339+
340+
doc.data = Dict(
341+
:binding => Docs.Binding(M, :k_9),
342+
:typesig => Union{Tuple{T where T}},
343+
:module => M,
344+
)
345+
DSE.format(DSE.TYPEDSIGNATURES, buf, doc)
346+
str = String(take!(buf))
347+
@test occursin("\n```julia\n", str)
348+
@test occursin("\nk_9(x::Any) -> Any\n", str)
349+
@test occursin("\n```\n", str)
350+
351+
doc.data = Dict(
352+
:binding => Docs.Binding(M, :k_10),
353+
:typesig => Union{Tuple{T}, Tuple{T}} where T,
354+
:module => M,
355+
)
356+
DSE.format(DSE.TYPEDSIGNATURES, buf, doc)
357+
str = String(take!(buf))
358+
@test_broken occursin("\n```julia\n", str)
359+
@test_broken occursin("\nk_10(x::T) -> Any\n", str)
360+
@test_broken occursin("\n```\n", str)
215361
end
216362

217363
@testset "function names" begin

0 commit comments

Comments
 (0)