From 7ca380674eed0007cbb17db8e4ce15163e53dc04 Mon Sep 17 00:00:00 2001 From: Markus Kurtz Date: Thu, 19 May 2022 10:04:43 +0200 Subject: [PATCH] Fix documenting parametric functor calls Differentiate signature of constructors, functors and normal function calls, thus improving upon PR #25626 and fixing #44889. --- base/docs/Docs.jl | 65 +++++++++++++++++++++++++---------------------- test/docs.jl | 47 ++++++++++++++++++++++++++++++---- 2 files changed, 77 insertions(+), 35 deletions(-) diff --git a/base/docs/Docs.jl b/base/docs/Docs.jl index b84b3ee8d55f4..8c742c003cec5 100644 --- a/base/docs/Docs.jl +++ b/base/docs/Docs.jl @@ -88,38 +88,44 @@ function initmeta(m::Module) nothing end -function signature!(tv::Vector{Any}, expr::Expr) - is_macrocall = isexpr(expr, :macrocall) - if is_macrocall || isexpr(expr, :call) - sig = :(Union{Tuple{}}) - first_arg = is_macrocall ? 3 : 2 # skip function arguments - for arg in expr.args[first_arg:end] - isexpr(arg, :parameters) && continue - if isexpr(arg, :kw) # optional arg - push!(sig.args, :(Tuple{$((sig.args[end]::Expr).args[2:end]...)})) - end - push!((sig.args[end]::Expr).args, argtype(arg)) - end - if isexpr(expr.args[1], :curly) && isempty(tv) - append!(tv, mapany(tvar, (expr.args[1]::Expr).args[2:end])) +function callsignature(args::Vector{Any}) + sig = :(Union{Tuple{}}) + for arg in args + isexpr(arg, :parameters) && continue + if isexpr(arg, :kw) # optional arg + push!(sig.args, :(Tuple{$((sig.args[end]::Expr).args[2:end]...)})) end - for i = length(tv):-1:1 - push!(sig.args, :(Tuple{$((tv[i]::Expr).args[1])})) - end - for i = length(tv):-1:1 - sig = Expr(:where, sig, tv[i]) + push!((sig.args[end]::Expr).args, argtype(arg)) + end + sig +end + +function signature(expr::Expr) + typevars = Expr[] + while isexpr(expr, :where) + append!(typevars, tvar.(expr.args[2:end])) + expr = expr.args[1] + end + if isexpr(expr, :macrocall) + callsignature(expr.args[3:end]) + elseif isexpr(expr, :call) + sig = callsignature(expr.args[2:end]) + if expr.args[1] isa Expr && expr.args[1].head !== :. + sig = (expr.args[1]::Expr).head === :(::) ? + :(Base.Docs.Functor{<:$((expr.args[1]::Expr).args[end]), <:$sig}) : + :(Base.Docs.ParametricConstructor{$(expr.args[1]), <:$sig}) end - return sig - elseif isexpr(expr, :where) - append!(tv, mapany(tvar, expr.args[2:end])) - return signature!(tv, expr.args[1]) + isempty(typevars) ? sig : Expr(:where, sig, typevars...) + elseif isexpr(expr, :quote) && isexpr(expr.args[1], :macrocall) + :(Union{}) else - return signature!(tv, expr.args[1]) + signature(expr.args[1]) end end -signature!(tv::Vector{Any}, @nospecialize(other)) = :(Union{}) -signature(expr::Expr) = signature!([], expr) -signature(@nospecialize other) = signature!([], other) +signature(@nospecialize other) = :(Union{}) + +struct ParametricConstructor{S, T} end +struct Functor{S, T} end function argtype(expr::Expr) isexpr(expr, :(::)) && return expr.args[end] @@ -297,9 +303,8 @@ function astname(x::Expr, ismacro::Bool) head = x.head if head === :. ismacro ? macroname(x) : x - # Call overloading, e.g. `(a::A)(b) = b` or `function (a::A)(b) b end` should document `A(b)` - elseif (head === :function || head === :(=)) && isexpr(x.args[1], :call) && isexpr((x.args[1]::Expr).args[1], :(::)) - return astname(((x.args[1]::Expr).args[1]::Expr).args[end], ismacro) + elseif head === :call && isexpr(x.args[1], :(::)) + return astname((x.args[1]::Expr).args[end], ismacro) else n = isexpr(x, (:module, :struct)) ? 2 : 1 astname(x.args[n], ismacro) diff --git a/test/docs.jl b/test/docs.jl index 762a481ee4801..f3412176aab24 100644 --- a/test/docs.jl +++ b/test/docs.jl @@ -177,7 +177,7 @@ t(::AbstractString) "t-2" t(::Int, ::Any) "t-3" -t{S <: Integer}(::S) +t(::S) where {S <: Integer} # Docstrings to parametric methods after definition using where syntax (#32960): tw(x::T) where T = nothing @@ -357,7 +357,7 @@ let d1 = @doc(DocsTest.t(::Int, ::Any)), @test docstrings_equal(d1,d2) end -let d1 = @doc(DocsTest.t{S <: Integer}(::S)), +let d1 = @doc(DocsTest.t(::S) where {S <: Integer}), d2 = doc"t-3" @test docstrings_equal(d1,d2) end @@ -655,7 +655,7 @@ end @doc "This should document @m1... since its the result of expansion" @m2_11993 @test (@doc @m1_11993) !== nothing let d = (@doc :@m2_11993), - macro_doc = Markdown.parse("`$(curmod_prefix)@m2_11993` is a macro.") + macro_doc = Markdown.parse("`$(curmod_prefix == "Main." ? "" : curmod_prefix)@m2_11993` is a macro.") @test docstring_startswith(d, doc""" No documentation found. @@ -704,7 +704,7 @@ Base.collect(::Type{EmptyType{T}}) where {T} = "borked" end let fd = meta(I12515)[@var(Base.collect)] - @test fd.order[1] == (Union{Tuple{Type{I12515.EmptyType{T}}}, Tuple{T}} where T) + @test fd.order[1] == (Union{Tuple{Type{I12515.EmptyType{T}}}} where T) end # PR #12593 @@ -1457,7 +1457,7 @@ function (f::MyFunc)(x) return f end -@test docstrings_equal(@doc(MyFunc(2)), +@test docstrings_equal(@doc((::MyFunc)(2)), doc""" Docs for calling `f::MyFunc`. """) @@ -1513,3 +1513,40 @@ struct S41727 end @test S41727(1) isa S41727 @test string(@repl S41727.x) == "x is 4\n" + +module FunctorCalls + +struct A{T, U} + x +end + +"A()" +A() = nothing + +"A{Int, Int}()" +A{Int, Int}() = nothing + +"A{S, T}(::S, ::T) where {S, T}" +(A{S, T}(::S, ::T) where {S, T}) = nothing + +"(::A)()" +(::A)() = nothing + +"(::A{S, T})(::S, ::T) where {S, T}" +((a::A{S, T})(b::S, c::T) where {S, T}) = nothing + +"(::A{T, T})() where T" +(::A{T, T})() where T = nothing + +#"A(x)" +#eval(:($A() = nothing)) + +end + +debug = true +@test docstrings_equal(@doc(FunctorCalls.A()), doc"A()"; debug) +@test docstrings_equal(@doc(FunctorCalls.A{Int, Int}()), doc"A{Int, Int}()"; debug) +@test docstrings_equal(@doc(FunctorCalls.A{S, T}(::S, ::T) where T where S), doc"A{S, T}(::S, ::T) where {S, T}"; debug) +@test docstrings_equal(@doc((::FunctorCalls.A)()), doc"(::A)()"; debug) +@test docstrings_equal(@doc((::FunctorCalls.A{S, T})(::S, ::T) where {S, T}), doc"(::A{S, T})(::S, ::T) where {S, T}"; debug) +@test docstrings_equal(@doc((a::(FunctorCalls.A::UnionAll){T::TypeVar, T::TypeVar})() where T), Docs.catdoc(doc"(::A)()", doc"(::A{T, T})() where T"); debug)