diff --git a/Project.toml b/Project.toml index bad33c1..a4a71a8 100644 --- a/Project.toml +++ b/Project.toml @@ -6,12 +6,16 @@ version = "0.9.3" LibGit2 = "76f85450-5226-5b5a-8eaa-529ad045b433" [compat] +CodeTracking = "1" +REPL = "1" julia = "1" [extras] +CodeTracking = "da1fd8a2-8d9e-5ec2-8556-3022fb5608a2" Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Markdown", "Pkg", "Test"] +test = ["Markdown", "Pkg", "Test", "REPL", "CodeTracking"] diff --git a/src/DocStringExtensions.jl b/src/DocStringExtensions.jl index 1e1a1ce..b1f5606 100644 --- a/src/DocStringExtensions.jl +++ b/src/DocStringExtensions.jl @@ -86,6 +86,7 @@ export interpolation # Includes. +include("parsing.jl") include("utilities.jl") include("abbreviations.jl") include("templates.jl") diff --git a/src/abbreviations.jl b/src/abbreviations.jl index a40a9de..a7ab924 100644 --- a/src/abbreviations.jl +++ b/src/abbreviations.jl @@ -290,7 +290,12 @@ The singleton type for [`SIGNATURES`](@ref) abbreviations. $(:FIELDS) """ -struct MethodSignatures <: Abbreviation end +struct MethodSignatures <: Abbreviation + expr::Union{Nothing, Expr} + print_types::Bool +end + +interpolation(ms::MethodSignatures, expr) = MethodSignatures(expr, ms.print_types) """ An [`Abbreviation`](@ref) for including a simplified representation of all the method @@ -308,39 +313,7 @@ f(x, y; a, b...) ``` ```` """ -const SIGNATURES = MethodSignatures() - -function format(::MethodSignatures, buf, doc) - 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) - - if !isempty(groups) - println(buf) - println(buf, "```julia") - for group in groups - for method in group - printmethod(buf, binding, func, method) - println(buf) - end - end - println(buf, "\n```\n") - end -end - - -# -# `TypedMethodSignatures` -# - -""" -The singleton type for [`TYPEDSIGNATURES`](@ref) abbreviations. - -$(:FIELDS) -""" -struct TypedMethodSignatures <: Abbreviation end +const SIGNATURES = MethodSignatures(nothing, false) """ An [`Abbreviation`](@ref) for including a simplified representation of all the method @@ -358,21 +331,25 @@ f(x::Int, y::Int; a, b...) ``` ```` """ -const TYPEDSIGNATURES = TypedMethodSignatures() +const TYPEDSIGNATURES = MethodSignatures(nothing, true) + +function format(ms::MethodSignatures, buf, doc) + binding = doc.data[:binding] + typesig = doc.data[:typesig] + modname = doc.data[:module] + func = Docs.resolve(binding) -function format(::TypedMethodSignatures, buf, doc) - local binding = doc.data[:binding] - local typesig = doc.data[:typesig] - local modname = doc.data[:module] - local func = Docs.resolve(binding) # TODO: why is methodgroups returning invalid methods? # the methodgroups always appears to return a Vector and the size depends on whether parametric types are used # and whether default arguments are used - local groups = methodgroups(func, typesig, modname) + groups = methodgroups(func, typesig, modname) if !isempty(groups) group = groups[end] + ast_info = isnothing(ms.expr) ? nothing : parse_call(ms.expr) + println(buf) println(buf, "```julia") + for (i, method) in enumerate(group) N = length(arguments(method)) # return a list of tuples that represent type signatures @@ -395,9 +372,11 @@ function format(::TypedMethodSignatures, buf, doc) else t = tuples[findfirst(f, tuples)] end - printmethod(buf, binding, func, method, t) + + printmethod(buf, binding, func, method, ast_info, t, ms.print_types) println(buf) end + println(buf, "\n```\n") end end diff --git a/src/parsing.jl b/src/parsing.jl new file mode 100644 index 0000000..bf107cb --- /dev/null +++ b/src/parsing.jl @@ -0,0 +1,126 @@ +Base.@kwdef struct ASTArg + name::Union{Symbol, Nothing} = nothing + type = nothing + default = nothing + variadic::Bool = false +end + +# Parse an argument with a type annotation. +# Example input: `x::Int` +function parse_arg_with_type(arg_expr::Expr) + if !Meta.isexpr(arg_expr, :(::)) + throw(ArgumentError("Argument is not a :(::) expr")) + end + + n_expr_args = length(arg_expr.args) + return if n_expr_args == 1 + # '::Int' + ASTArg(; type=arg_expr.args[1]) + elseif n_expr_args == 2 + # 'x::Int' + ASTArg(; name=arg_expr.args[1], type=arg_expr.args[2]) + else + Meta.dump(arg_expr) + error("Couldn't parse typed argument (printed above)") + end +end + +# Parse an argument with a default value. +# Example input: `x=5` +function parse_arg_with_default(arg_expr::Expr) + if !Meta.isexpr(arg_expr, :kw) + throw(ArgumentError("Argument is not a :kw expr")) + end + + if arg_expr.args[1] isa Symbol + # This is an argument without a type annotation + ASTArg(; name=arg_expr.args[1], default=arg_expr.args[2]) + else + # This is an argument with a type annotation + tmp = parse_arg_with_type(arg_expr.args[1]) + ASTArg(; name=tmp.name, type=tmp.type, default=arg_expr.args[2]) + end +end + +# Parse a list of expressions, assuming the list is an argument list containing +# positional/keyword arguments. +# Example input: `(x, y::Int; z=5, kwargs...)` +function parse_arglist!(exprs, args, kwargs, is_kwarg_list=false) + list = is_kwarg_list ? kwargs : args + + for arg_expr in exprs + if arg_expr isa Symbol + # Plain argument name with no type or default value + push!(list, ASTArg(; name=arg_expr)) + elseif Meta.isexpr(arg_expr, :(::)) + # With a type annotation + push!(list, parse_arg_with_type(arg_expr)) + elseif Meta.isexpr(arg_expr, :kw) + # With a default value (and possibly a type annotation) + push!(list, parse_arg_with_default(arg_expr)) + elseif Meta.isexpr(arg_expr, :parameters) + # Keyword arguments + parse_arglist!(arg_expr.args, args, kwargs, true) + elseif Meta.isexpr(arg_expr, :...) + # Variadic argument + if arg_expr.args[1] isa Symbol + # Without a type annotation + push!(list, ASTArg(; name=arg_expr.args[1], variadic=true)) + elseif Meta.isexpr(arg_expr.args[1], :(::)) + # With a type annotation + arg_expr = arg_expr.args[1] + push!(list, ASTArg(; name=arg_expr.args[1], type=arg_expr.args[2], variadic=true)) + else + Meta.dump(arg_expr) + error("Couldn't parse variadic Expr in arg list (printed above)") + end + else + Meta.dump(arg_expr) + error("Couldn't parse Expr in arg list (printed above)") + end + end +end + +# Find a :call expression within an Expr. This will take care of ignoring other +# tokens like `where` clauses. It will return `nothing` if a :call expression +# wasn't found. +function find_call_expr(obj) + if Meta.isexpr(obj, :call) + # Base case: we've found the :call expression + return obj + elseif !(obj isa Expr) || isempty(obj.args) + # Base case: this is the end of a branch in the expression tree + return nothing + end + + # Recursive case: recurse over all the Expr arguments + for arg in obj.args + if arg isa Expr + result = find_call_expr(arg) + if !isnothing(result) + return result + end + end + end + + return nothing +end + +# Parse an expression to find a :call expr, and return as much information as +# possible about the arguments. +# Example input: `foo(x) = x^2` +function parse_call(expr::Expr) + Base.remove_linenums!(expr) + expr = find_call_expr(expr) + + if !Meta.isexpr(expr, :call) + throw(ArgumentError("Couldn't find a :call Expr, are you documenting a function? If so this may be a bug in DocStringExtensions.jl, please open an issue and include the function being documented.")) + end + + args = ASTArg[] + kwargs = ASTArg[] + # Skip the first argument because that's just the function name + parse_arglist!(expr.args[2:end], args, kwargs) + + return (; args, kwargs) +end diff --git a/src/templates.jl b/src/templates.jl index 83fb988..8390727 100644 --- a/src/templates.jl +++ b/src/templates.jl @@ -38,6 +38,12 @@ replacement docstring generated from the template. \""" ``` +Note that a significant limitation of docstring templates is that the +abbreviations used will be declared separately from the bindings that they +operate on, which means that they will not have access to the bindings +`Expr`'s. That will disable `TYPEDSIGNATURES` and `SIGNATURES` from showing +default [keyword ]argument values in docstrings. + `DEFAULT` is the default template that is applied to a docstring if no other template definitions match the documented expression. The `DOCSTRING` abbreviation is used to mark the location in the template where the actual docstring body will be spliced into each @@ -119,11 +125,8 @@ function template_hook(source::LineNumberNode, mod::Module, docstr, expr::Expr) return (source, mod, docstr, expr) end -function template_hook(docstr, expr::Expr) - source, mod, docstr, expr::Expr = template_hook(LineNumberNode(0), current_module(), docstr, expr) - docstr, expr -end - +# This definition looks a bit weird, but in combination with hook!() the effect +# is that template_hook() will fall back to calling the default expander(). template_hook(args...) = args get_template(t::Dict, k::Symbol) = haskey(t, k) ? t[k] : get(t, :DEFAULT, Any[DOCSTRING]) diff --git a/src/utilities.jl b/src/utilities.jl index 0180699..6f7a4d4 100644 --- a/src/utilities.jl +++ b/src/utilities.jl @@ -321,28 +321,7 @@ function find_tuples(typesig) end end -""" -$(:TYPEDSIGNATURES) - -Print a simplified representation of a method signature to `buffer`. Some of these -simplifications include: - - * no `TypeVar`s; - * no types; - * no keyword default values; - -# Examples - -```julia -f(x::Int; a = 1, b...) = x -sig = printmethod(Docs.Binding(Main, :f), f, first(methods(f))) -``` -""" -function printmethod(buffer::IOBuffer, binding::Docs.Binding, func, method::Method, typesig) - # TODO: print qualified? - local args = string.(arguments(method)) - local kws = string.(keywords(func, method)) - +function format_args(args::Vector{ASTArg}, typesig, print_types) # find inner tuple type function find_inner_tuple_type(t) # t is always either a UnionAll which represents a generic type or a Tuple where each parameter is the argument @@ -383,25 +362,86 @@ function printmethod(buffer::IOBuffer, binding::Docs.Binding, func, method::Meth collect(typesig.types) args = map(args, argtypes) do arg,t + name = "" type = "" suffix = "" + default_value = "" + + if !isnothing(arg.name) + name = arg.name + elseif isnothing(arg.name) && (t === Any || !print_types) + name = "_" + end if isvarargtype(t) t = vararg_eltype(t) suffix = "..." + elseif arg.variadic + # This extra branch is here for kwargs, where we don't have type + # information. + suffix = "..." end - if t!==Any + if print_types && t !== Any type = "::$t" end + if !isnothing(arg.default) + default_value = "=$(arg.default)" + end + + "$name$type$suffix$default_value" + end + + return args +end + +""" +$(:TYPEDSIGNATURES) - "$arg$type$suffix" +Print a simplified representation of a method signature to `buffer`. Some of these +simplifications include: + + * no `TypeVar`s; + * no types; + * no keyword default values; + +# Examples + +```julia +f(x::Int; a = 1, b...) = x +sig = printmethod(Docs.Binding(Main, :f), f, first(methods(f))) +``` +""" +function printmethod(buffer::IOBuffer, binding::Docs.Binding, func, method::Method, + ast_info, typesig, print_types::Bool) + local formatted_args + local formatted_kws + + if isnothing(ast_info) + formatted_args = string.(arguments(method)) + formatted_kws = string.(keywords(func, method)) + else + formatted_args = format_args(ast_info.args, typesig, print_types) + + # We don't have proper type information for keyword arguments like we do + # with `typesig` for positional arguments, so we assume they're all Any. An + # alternative would be to use the types extracted from the AST, but that + # might not exactly match the types of positional arguments (e.g. an alias + # type would be printed as the underlying type for positional arguments but + # under the alias for keyword arguments). + kws = ast_info.kwargs + formatted_kws = format_args(kws, NTuple{length(kws), Any}, print_types) end - rt = Base.return_types(func, typesig) + rt = try + # We wrap this in a try-catch block because Base.return_types() is + # documented to fail on generated functions. + Base.return_types(func, typesig) + catch + nothing + end + can_print_rt = print_types && !isnothing(rt) && length(rt) >= 1 && rt[1] !== Nothing && rt[1] !== Union{} - return printmethod_format(buffer, string(binding.var), args, string.(kws); - return_type = - length(rt) >= 1 && rt[1] !== Nothing && rt[1] !== Union{} ? - " -> $(rt[1])" : "") + return printmethod_format(buffer, string(binding.var), formatted_args, formatted_kws; + return_type = can_print_rt ? " -> $(rt[1])" : "") end printmethod(b, f, m) = String(take!(printmethod(IOBuffer(), b, f, m))) diff --git a/test/TestModule/M.jl b/test/TestModule/M.jl index 01093bd..ce94d10 100644 --- a/test/TestModule/M.jl +++ b/test/TestModule/M.jl @@ -49,6 +49,11 @@ struct K K(; a = 1) = new() end +macro m1(expr) expr end +macro m2(expr) expr end + +@m1 @m2 l(x::Int, y=1) = x + y + abstract type AbstractType1 <: Integer end abstract type AbstractType2{S, T <: Integer} <: Integer end diff --git a/test/runtests.jl b/test/runtests.jl index 79ec958..5b31f2f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,4 +1,5 @@ using DocStringExtensions + using Test import Markdown import LibGit2 diff --git a/test/tests.jl b/test/tests.jl index 560613c..5ef04bb 100644 --- a/test/tests.jl +++ b/test/tests.jl @@ -1,9 +1,37 @@ +using REPL # Hack to get around: https://github.com/JuliaLang/julia/issues/52986 +import CodeTracking: code_string +import DocStringExtensions: MethodSignatures const DSE = DocStringExtensions include("templates.jl") include("interpolation.jl") include("TestModule/M.jl") +if !isdefined(Base, :default_tt) + # This function isn't available in early Julia versions so we vendor it in + function default_tt(@nospecialize(f)) + ms = methods(f).ms + if length(ms) == 1 + return Base.tuple_type_tail(ms[1].sig) + else + return Tuple + end + end +else + import Base: default_tt +end + + +# Helper function to get the Expr of a function. In some cases the argument +# types will need to be explicitly given. +function get_expr(f::Function, arg_types...) + if isempty(arg_types) + arg_types = default_tt(f) + end + + Meta.parse(code_string(f, arg_types)) +end + # initialize a test repo in test/TestModule which is needed for some tests function with_test_repo(f) repo = LibGit2.init(joinpath(@__DIR__, "TestModule")) @@ -132,34 +160,31 @@ end end @testset "method signatures" begin + UntypedSignatures(x) = MethodSignatures(x, false) + doc.data = Dict( :binding => Docs.Binding(M, :f), :typesig => Tuple{Any}, :module => M, ) - DSE.format(SIGNATURES, buf, doc) + + DSE.format(UntypedSignatures(get_expr(M.f)), buf, doc) str = String(take!(buf)) @test occursin("\n```julia\n", str) @test occursin("\nf(x)\n", str) @test occursin("\n```\n", str) + g_expr = get_expr(M.g, Int, Int, Int) doc.data = Dict( :binding => Docs.Binding(M, :g), :typesig => Union{Tuple{}, Tuple{Any}}, :module => M, ) - DSE.format(SIGNATURES, buf, doc) + DSE.format(UntypedSignatures(g_expr), buf, doc) str = String(take!(buf)) @test occursin("\n```julia\n", str) - # On 1.10+, automatically generated methods have keywords in the metadata, - # hence the display difference between Julia versions. - if VERSION >= v"1.10" - @test occursin("\ng(; ...)\n", str) - @test occursin("\ng(x; ...)\n", str) - else - @test occursin("\ng()\n", str) - @test occursin("\ng()\n", str) - end + @test occursin("\ng(; kwargs...)\n", str) + @test occursin("\ng(x=1; kwargs...)\n", str) @test occursin("\n```\n", str) doc.data = Dict( @@ -167,21 +192,13 @@ end :typesig => Union{Tuple{}, Tuple{Any}, Tuple{Any, Any}, Tuple{Any, Any, Any}}, :module => M, ) - DSE.format(SIGNATURES, buf, doc) + DSE.format(UntypedSignatures(g_expr), buf, doc) str = String(take!(buf)) @test occursin("\n```julia\n", str) - # On 1.10+, automatically generated methods have keywords in the metadata, - # hence the display difference between Julia versions. - if VERSION >= v"1.10" - @test occursin("\ng(; ...)\n", str) - @test occursin("\ng(x; ...)\n", str) - @test occursin("\ng(x, y; ...)\n", str) - else - @test occursin("\ng()\n", str) - @test occursin("\ng(x)\n", str) - @test occursin("\ng(x, y)\n", str) - end - @test occursin("\ng(x, y, z; kwargs...)\n", str) + @test occursin("\ng(; kwargs...)\n", str) + @test occursin("\ng(x=1; kwargs...)\n", str) + @test occursin("\ng(x=1, y=2; kwargs...)\n", str) + @test occursin("\ng(x=1, y=2, z=3; kwargs...)\n", str) @test occursin("\n```\n", str) doc.data = Dict( @@ -189,7 +206,7 @@ end :typesig => Tuple{Any}, :module => M, ) - DSE.format(SIGNATURES, buf, doc) + DSE.format(UntypedSignatures(get_expr(M.g_1)), buf, doc) str = String(take!(buf)) @test occursin("\n```julia\n", str) @test occursin("\ng_1(x)\n", str) @@ -200,7 +217,7 @@ end :typesig => Union{Tuple{Any, Int, Any}}, :module => M, ) - DSE.format(SIGNATURES, buf, doc) + DSE.format(UntypedSignatures(get_expr(M.h_4)), buf, doc) str = String(take!(buf)) @test occursin("\n```julia\n", str) @test occursin("\nh_4(x, _, z)\n", str) @@ -213,7 +230,10 @@ end :typesig => Tuple{M.A}, :module => M, ) - DSE.format(DSE.TYPEDSIGNATURES, buf, doc) + + TypedSignatures(x) = MethodSignatures(x, true) + + DSE.format(TypedSignatures(get_expr(M.h_1)), buf, doc) str = String(take!(buf)) @test occursin("\n```julia\n", str) f = str -> replace(str, " " => "") @@ -230,24 +250,25 @@ end :typesig => Tuple{String}, :module => M, ) - DSE.format(TYPEDSIGNATURES, buf, doc) + DSE.format(TypedSignatures(get_expr(M.g_2)), buf, doc) str = String(take!(buf)) @test occursin("\n```julia\n", str) @test occursin("\ng_2(x::String)", str) @test occursin("\n```\n", str) + h_expr = get_expr(M.h, Int, Int, Int) doc.data = Dict( :binding => Docs.Binding(M, :h), :typesig => Tuple{Int, Int, Int}, :module => M, ) - DSE.format(DSE.TYPEDSIGNATURES, buf, doc) + DSE.format(TypedSignatures(h_expr), buf, doc) str = String(take!(buf)) @test occursin("\n```julia\n", str) if typeof(1) === Int64 - @test occursin("\nh(x::Int64, y::Int64, z::Int64; kwargs...) -> Int64\n", str) + @test occursin("\nh(x::Int64, y::Int64=2, z::Int64=3; kwargs...) -> Int64\n", str) else - @test occursin("\nh(x::Int32, y::Int32, z::Int32; kwargs...) -> Int32\n", str) + @test occursin("\nh(x::Int32, y::Int32=2, z::Int32=3; kwargs...) -> Int32\n", str) end @test occursin("\n```\n", str) @@ -256,25 +277,13 @@ end :typesig => Tuple{Int}, :module => M, ) - DSE.format(DSE.TYPEDSIGNATURES, buf, doc) + DSE.format(TypedSignatures(h_expr), buf, doc) str = String(take!(buf)) @test occursin("\n```julia\n", str) if typeof(1) === Int64 - # On 1.10+, automatically generated methods have keywords in the metadata, - # hence the display difference between Julia versions. - if VERSION >= v"1.10" - @test occursin("\nh(x::Int64; ...) -> Int64\n", str) - else - @test occursin("\nh(x::Int64) -> Int64\n", str) - end + @test occursin("\nh(x::Int64; kwargs...) -> Int64\n", str) else - # On 1.10+, automatically generated methods have keywords in the metadata, - # hence the display difference between Julia versions. - if VERSION >= v"1.10" - @test occursin("\nh(x::Int32; ...) -> Int32\n", str) - else - @test occursin("\nh(x::Int32) -> Int32\n", str) - end + @test occursin("\nh(x::Int32; ...) -> Int32\n", str) end @test occursin("\n```\n", str) @@ -283,7 +292,7 @@ end :typesig => Tuple{T} where T, :module => M, ) - DSE.format(DSE.TYPEDSIGNATURES, buf, doc) + DSE.format(TypedSignatures(get_expr(M.k_0)), buf, doc) str = String(take!(buf)) @test occursin("\n```julia\n", str) @test occursin("\nk_0(x) -> Any\n", str) @@ -294,12 +303,13 @@ end :typesig => Union{Tuple{String}, Tuple{String, T}, Tuple{String, T, T}, Tuple{T}} where T <: Number, :module => M, ) - DSE.format(DSE.TYPEDSIGNATURES, buf, doc) + k_1_expr = get_expr(M.k_1, String, Int, Int) + DSE.format(TypedSignatures(k_1_expr), buf, doc) str = String(take!(buf)) @test occursin("\n```julia\n", str) @test occursin("\nk_1(x::String) -> String\n", str) - @test occursin("\nk_1(x::String, y::Number) -> String\n", str) - @test occursin("\nk_1(x::String, y::Number, z::Number) -> String\n", str) + @test occursin("\nk_1(x::String, y::Number=0) -> String\n", str) + @test occursin("\nk_1(x::String, y::Number=0, z::Number=zero(T)) -> String\n", str) @test occursin("\n```\n", str) doc.data = Dict( @@ -307,8 +317,7 @@ end :typesig => (Union{Tuple{String, U, T}, Tuple{T}, Tuple{U}} where T <: Number) where U <: Complex, :module => M, ) - - DSE.format(DSE.TYPEDSIGNATURES, buf, doc) + DSE.format(TypedSignatures(get_expr(M.k_2)), buf, doc) str = String(take!(buf)) @test occursin("\n```julia\n", str) @test occursin("k_2(x::String, y::Complex, z::Number) -> String", str) @@ -319,7 +328,7 @@ end :typesig => (Union{Tuple{Any, T, U}, Tuple{U}, Tuple{T}} where U <: Any) where T <: Any, :module => M, ) - DSE.format(DSE.TYPEDSIGNATURES, buf, doc) + DSE.format(TypedSignatures(get_expr(M.k_3)), buf, doc) str = String(take!(buf)) @test occursin("\n```julia\n", str) @test occursin("\nk_3(x, y, z) -> Any\n", str) @@ -330,15 +339,15 @@ end :typesig => Union{Tuple{String}, Tuple{String, Int}}, :module => M, ) - DSE.format(DSE.TYPEDSIGNATURES, buf, doc) + DSE.format(TypedSignatures(get_expr(M.k_4, String, Int)), buf, doc) str = String(take!(buf)) @test occursin("\n```julia\n", str) if VERSION > v"1.3.0" @test occursin("\nk_4(::String)\n", str) if typeof(1) === Int64 - @test occursin("\nk_4(::String, ::Int64)\n", str) + @test occursin("\nk_4(::String, ::Int64=0)\n", str) else - @test occursin("\nk_4(::String, ::Int32)\n", str) + @test occursin("\nk_4(::String, ::Int32=0)\n", str) end else # TODO: remove this test when julia 1.0.0 support is dropped. @@ -353,12 +362,12 @@ end :typesig => Union{Tuple{Type{T}, String}, Tuple{Type{T}, String, Union{Nothing, Function}}, Tuple{T}} where T <: Number, :module => M, ) - DSE.format(DSE.TYPEDSIGNATURES, buf, doc) + DSE.format(TypedSignatures(get_expr(M.k_5, Type{Int}, String, Nothing)), buf, doc) str = String(take!(buf)) @test occursin("\n```julia\n", str) if VERSION > v"1.3.0" @test occursin("\nk_5(::Type{T<:Number}, x::String) -> String\n", str) - @test occursin("\nk_5(\n ::Type{T<:Number},\n x::String,\n func::Union{Nothing, Function}\n) -> String\n", str) + @test occursin("\nk_5(\n ::Type{T<:Number},\n x::String,\n func::Union{Nothing, Function}=nothing\n) -> String\n", str) @test occursin("\n```\n", str) else # TODO: remove this test when julia 1.0.0 support is dropped. @@ -372,7 +381,7 @@ end :typesig => Union{Tuple{Vector{T}}, Tuple{T}} where T <: Number, :module => M, ) - DSE.format(DSE.TYPEDSIGNATURES, buf, doc) + DSE.format(TypedSignatures(get_expr(M.k_6)), buf, doc) f = str -> replace(str, " " => "") str = String(take!(buf)) str = f(str) @@ -390,16 +399,15 @@ end :typesig => Union{Tuple{Union{Nothing, T}}, Tuple{T}, Tuple{Union{Nothing, T}, T}} where T<:Integer, :module => M, ) - DSE.format(DSE.TYPEDSIGNATURES, buf, doc) + DSE.format(TypedSignatures(get_expr(M.k_7, Nothing, Int)), buf, doc) str = String(take!(buf)) @test occursin("\n```julia\n", str) if VERSION >= v"1.6" && VERSION < v"1.7" @test occursin("\nk_7(\n x::Union{Nothing, T} where T<:Integer\n) -> Union{Nothing, Integer}\n", str) - @test occursin("\nk_7(\n x::Union{Nothing, T} where T<:Integer,\n y::Integer\n) -> Union{Nothing, Integer}\n", str) else @test occursin("\nk_7(\n x::Union{Nothing, T} where T<:Integer\n) -> Union{Nothing, T} where T<:Integer\n", str) - @test occursin("\nk_7(\n x::Union{Nothing, T} where T<:Integer,\n y::Integer\n) -> Union{Nothing, T} where T<:Integer\n", str) end + @test occursin("\nk_7(\n x::Union{Nothing, T} where T<:Integer,\n y::Integer=zero(T)\n) -> Union{Nothing, T} where T<:Integer\n", str) @test occursin("\n```\n", str) doc.data = Dict( @@ -407,7 +415,7 @@ end :typesig => Union{Tuple{Any}}, :module => M, ) - DSE.format(DSE.TYPEDSIGNATURES, buf, doc) + DSE.format(TypedSignatures(get_expr(M.k_8)), buf, doc) str = String(take!(buf)) @test occursin("\n```julia\n", str) @test occursin("\nk_8(x) -> Any\n", str) @@ -418,7 +426,7 @@ end :typesig => Union{Tuple{T where T}}, :module => M, ) - DSE.format(DSE.TYPEDSIGNATURES, buf, doc) + DSE.format(TypedSignatures(get_expr(M.k_9)), buf, doc) str = String(take!(buf)) @test occursin("\n```julia\n", str) @test occursin("\nk_9(x) -> Any\n", str) @@ -431,7 +439,7 @@ end :typesig => Union{Tuple{Int, Vararg{Any}}}, :module => M, ) - DSE.format(DSE.TYPEDSIGNATURES, buf, doc) + DSE.format(TypedSignatures(get_expr(M.k_11)), buf, doc) str = String(take!(buf)) @test occursin("\n```julia\n", str) @test occursin("\nk_11(x::Int64, xs...) -> Int64\n", str) @@ -442,7 +450,7 @@ end :typesig => Union{Tuple{Int, Vararg{Real}}}, :module => M, ) - DSE.format(DSE.TYPEDSIGNATURES, buf, doc) + DSE.format(TypedSignatures(get_expr(M.k_12)), buf, doc) str = String(take!(buf)) @test occursin("\n```julia\n", str) @test occursin("\nk_12(x::Int64, xs::Real...) -> Int64\n", str) @@ -450,7 +458,15 @@ end end - + doc.data = Dict( + :binding => Docs.Binding(M, :l), + :typesig => Union{Tuple{Int}, Tuple{Int, Any}}, + :module => M, + ) + DSE.format(TypedSignatures(get_expr(M.l, Int, Int)), buf, doc) + str = String(take!(buf)) + @test occursin("\nl(x::Int64) -> Int64\n", str) + @test occursin("\nl(x::Int64, y=1) -> Any\n", str) end @testset "function names" begin