# Documenter.jl: How to avoid repetitions of shared docstrings?

**URL:** <https://discourse.julialang.org/t/documenter-jl-how-to-avoid-repetitions-of-shared-docstrings/128885>\
**Category:** General Usage\
**Tags:** documenter\
**Created:** [May 9, 2025, 11:16pm UTC](https://discourse.julialang.org/t/documenter-jl-how-to-avoid-repetitions-of-shared-docstrings/128885 "2025-05-09T23:16:33Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)\
**Post date:** [May 9, 2025, 11:16pm UTC](https://discourse.julialang.org/t/documenter-jl-how-to-avoid-repetitions-of-shared-docstrings/128885/1 "2025-05-09T23:16:33Z")

</div>

Suppose I have two functions that share a common docstring:

```julia
f() = 1
g() = 2

"docstring for `f` and `g`"
f, g

```

I only want to the docstring to appear once in the documentation, not twice. However, if I only write

````julia
```@docs
f
```

````

somewhere in `docs/src`, then I get an error,

```julia
These are docstrings in the checked modules (configured with the modules keyword)
that are not included in canonical @docs or @autodocs blocks.

```

Strictly speaking, the message is wrong because the docstring _is_ included. Anyway, with `warnonly` I can turn the error message into a warning.

However, if I have a couple of such docstrings, then I get a long list of warnings that is hard to keep track of. As a result, I won’t notice anymore if there is some docstring indeed missing from the documentation.

I guess I could create an additional file in the `docs/src` directory where I list the omitted functions. But then I have to remember not to put that file up on the web site. I also have to manually create the page structure for the documentation for otherwise the new file will appear in the navigation bar.

Does anybody know a better solution?

---

<div class="post-metadata">

**Author:** ![gdalle](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gdalle/32/27854_2.png) [@gdalle](https://discourse.julialang.org/u/gdalle)\
**Post date:** [May 10, 2025, 5:52am UTC](https://discourse.julialang.org/t/documenter-jl-how-to-avoid-repetitions-of-shared-docstrings/128885/2 "2025-05-10T05:52:45Z")

</div>

Why is it a problem that both docstrings appear on the documentation? After all, if the user looks for the docstring of `g`, it’s better that they find it right away?  
If it is about space, you can collapse docstrings by default so that they don’t take too much room.

---

<div class="post-metadata">

**Author:** ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)\
**Post date:** [May 10, 2025, 10:47am UTC](https://discourse.julialang.org/t/documenter-jl-how-to-avoid-repetitions-of-shared-docstrings/128885/3 "2025-05-10T10:47:11Z")

</div>

> [@gdalle](#):
>
> Why is it a problem

I’m not thinking of a user who looks up a specific docstring (which I would rather do in the REPL), but of someone who reads a good part of the documentation to learn about the package. As a user, I would find it quite annoying to be repeatedly confronted with the same text. I would also find it annoying to click all the time to open collapsed parts.

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [May 10, 2025, 2:43pm UTC](https://discourse.julialang.org/t/documenter-jl-how-to-avoid-repetitions-of-shared-docstrings/128885/4 "2025-05-10T14:43:43Z")

</div>

It’d probably be good to find a mainstream example of this. I really can’t recall multiple-expressions docstrings, even the mutating/nonmutating versions suggestion isn’t done in practice because the arguments are often different enough to warrant explanation. Your `f` and `g` examples are also different enough to warrant separate docstrings. Enums have a type for the shared part of the documentation, and the members usually reference that after specific information.

Something that has a _similar_ effect though are `const` aliases for definitions (and not other things it seems). The aliases don’t get their own docstring in the source code so they don’t show up in references either, but help mode and `@doc` both retrieve the definition’s docstring via any alias.

---

<div class="post-metadata">

**Author:** ![kellertuer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kellertuer/32/220707_2.png) [@kellertuer](https://discourse.julialang.org/u/kellertuer)\
**Post date:** [May 10, 2025, 3:23pm UTC](https://discourse.julialang.org/t/documenter-jl-how-to-avoid-repetitions-of-shared-docstrings/128885/5 "2025-05-10T15:23:27Z")

</div>

I started doing that regularly, that there is one doc string mentioning both signatures, see e.g.

- [exp](https://juliamanifolds.github.io/LieGroups.jl/stable/groups/circle_group/#Base.exp-Tuple%7BLieGroup%7B%E2%84%9D,%20AdditionGroupOperation,%20%3C:Manifolds.Circle%7B%E2%84%9D%7D%7D,%20Number%7D) and
- [`exp!`](https://juliamanifolds.github.io/LieGroups.jl/stable/groups/circle_group/#ManifoldsBase.exp!-Tuple%7BLieGroup%7B%E2%84%9D,%20AdditionGroupOperation,%20%3C:Manifolds.Circle%7B%E2%84%9D%7D%7D,%20Any,%20Any%7D)

That way asking for either of the docs in REPL one gets aware of the other method easily, both compute the same anyways and the last line explains the in-place variable (in the JuliaManifolds ecosystem quite often the second variable).  
In the docs we even define a string that is then attached to both methods.

Sure in there rendered docs that is a bit redundant, see e.g. the first two doc strings on [Gradient Descent · Manopt.jl](https://manoptjl.org/stable/solvers/gradient_descent/),  
maybe one could collapse the second by default somewhen in Documenter, that would be neat.

Whether these two examples are mainstream, I leave to others to decide though.

---

<div class="post-metadata">

**Author:** ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)\
**Post date:** [May 10, 2025, 4:43pm UTC](https://discourse.julialang.org/t/documenter-jl-how-to-avoid-repetitions-of-shared-docstrings/128885/6 "2025-05-10T16:43:15Z")

</div>

Here are two examples from SmallCollections.jl that I have in mind:

- [MapStyle](https://matthias314.github.io/SmallCollections.jl/stable/nonexported/#SmallCollections.MapStyle) – I find it more convenient to discuss it together with all four subtypes in a single entry.
- [any](https://matthias314.github.io/SmallCollections.jl/stable/smallbitset/#Base.any-Tuple%7BFunction,%20SmallBitSet%7D) – I want to mention a common keyword argument added to some functions from `Base`. Again, I find it more convenient to present all four functions in one place.

Collapsing only some of the docstrings listed on a page (as suggested by @kellertuer) would be nice. Or listing the shared docstring with several functions in the headline, by saying somethig like

````julia
```@docs
f, g
```

````

But all these would be changes to Documenter.jl.

---

<div class="post-metadata">

**Author:** ![goerz](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/goerz/32/3269_2.png) [@goerz](https://discourse.julialang.org/u/goerz)\
**Post date:** [May 10, 2025, 10:53pm UTC](https://discourse.julialang.org/t/documenter-jl-how-to-avoid-repetitions-of-shared-docstrings/128885/7 "2025-05-10T22:53:09Z")

</div>

```julia
"docstring for `f` and `g`"
f() = 1

"See [`f`](@ref)"
g() = 2

```

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [May 10, 2025, 11:12pm UTC](https://discourse.julialang.org/t/documenter-jl-how-to-avoid-repetitions-of-shared-docstrings/128885/8 "2025-05-10T23:12:51Z")

</div>

> [@matthias314](#):
>
> [MapStyle](https://matthias314.github.io/SmallCollections.jl/stable/nonexported/#SmallCollections.MapStyle) – I find it more convenient to discuss it together with all four subtypes in a single entry.

This I think is pretty justifiable. Earlier I mentioned enums, and it’d also be nice sometimes if we could see the fixed number of instances and the type all described in one place without having to go through a reference.

> [@matthias314](#):
>
> [any](https://matthias314.github.io/SmallCollections.jl/stable/smallbitset/#Base.any-Tuple%7BFunction,%20SmallBitSet%7D) – I want to mention a common keyword argument added to some functions from `Base`.

Not a fan of how this involves several different functions. If I `using SmallCollections` then try help mode for the function or specific method for `any`, `all`, etc, I see method signatures for several functions I didn’t ask for. Also confused why I’m seeing `Base`’s docstring when I query the `SmallBitSet` method,

> **I thought method-wise docstrings weren't supposed to show other methods' or the wider function's in help mode so that we aren't given irrelevant call examples.**
>
> ```julia-auto
> julia> begin
> foo(::String)=1
> bar(::String)=1
> "foobarstring"
> foo(::String), bar(::String)
> 
> "barint"
> bar(::Int)=2
> 
> "bar"
> bar
> 
> "foo"
> foo
> end
> foo
> 
> help?> bar(::String)
> foobarstring
> 
> help?> bar(::Int)
> barint
> 
> help?> bar
> search: bar Pair Char mark
> 
> foobarstring
> 
> ───────────────────────────────────────────────────────────────
> 
> barint
> 
> ───────────────────────────────────────────────────────────────
> 
> bar
> 
> ```

```julia-auto

```

> [@kellertuer](#):
>
> - [exp](https://juliamanifolds.github.io/LieGroups.jl/stable/groups/circle_group/#Base.exp-Tuple%7BLieGroup%7B%E2%84%9D,%20AdditionGroupOperation,%20%3C:Manifolds.Circle%7B%E2%84%9D%7D%7D,%20Number%7D) and
> - [`exp!`](https://juliamanifolds.github.io/LieGroups.jl/stable/groups/circle_group/#ManifoldsBase.exp!-Tuple%7BLieGroup%7B%E2%84%9D,%20AdditionGroupOperation,%20%3C:Manifolds.Circle%7B%E2%84%9D%7D%7D,%20Any,%20Any%7D)

It appears that [Base.exp === ManifoldsBase.exp](https://github.com/JuliaManifolds/ManifoldsBase.jl/blob/5cdee8b5901ea72ac84e3d5b1a03679030b55f3b/src/ManifoldsBase.jl#L5), and the docstrings for those method signatures in circle\_group\_real.jl [interpolate a shared string](https://github.com/JuliaManifolds/LieGroups.jl/blob/c15f28fa97f4a1126f5bcc520ff6ea13d6c4d710/src/groups/circle_group_real.jl#L100) instead of using the multiple expressions to one docstring syntax. That should technically be the same docstring, but that may end up having some effect on how Documenter works or will work.

---

<div class="post-metadata">

**Author:** ![gdalle](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gdalle/32/27854_2.png) [@gdalle](https://discourse.julialang.org/u/gdalle)\
**Post date:** [May 11, 2025, 4:46am UTC](https://discourse.julialang.org/t/documenter-jl-how-to-avoid-repetitions-of-shared-docstrings/128885/9 "2025-05-11T04:46:47Z")

</div>

> [@matthias314](#):
>
> Collapsing only some of the docstrings listed on a page (as suggested by @kellertuer) would be nice. Or listing the shared docstring with several functions in the headline, by saying somethig like

An alternative would be to have discursive docs pages where only one docstring from each category is displayed (a kind of tutorial), and then one exhaustive API reference where they are all listed. Documenter provides the option to add duplicated docstrings that way by specifying that only one can be [canonical](https://documenter.juliadocs.org/stable/man/syntax/#noncanonical-block).

---

<div class="post-metadata">

**Author:** ![kellertuer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kellertuer/32/220707_2.png) [@kellertuer](https://discourse.julialang.org/u/kellertuer)\
**Post date:** [May 11, 2025, 8:46am UTC](https://discourse.julialang.org/t/documenter-jl-how-to-avoid-repetitions-of-shared-docstrings/128885/10 "2025-05-11T08:46:09Z")

</div>

> [@Benny](#):
>
> {…} instead of using the multiple expressions to one docstring syntax. That should technically be the same docstring, but that may end up having some effect on how Documenter works or will work.

Sure, technically that is different, mainly because I was not aware of the multiple expressions thing; nevertheless, this was more about the effect, and that is in practice currently the same – though I like the `f,g` approach!

---

<div class="post-metadata">

**Author:** ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)\
**Post date:** [May 11, 2025, 7:08pm UTC](https://discourse.julialang.org/t/documenter-jl-how-to-avoid-repetitions-of-shared-docstrings/128885/11 "2025-05-11T19:08:42Z")

</div>

Even besides how Documenter internally tracks docstrings, I don’t know how docstring identity is actually intended to work. Equal `String`s are identical by definition, but equal docstrings instantiated and attached separately to different expressions are treated and displayed as different. But does the multiple expressions syntax imply a shared docstring or multiple duplicate docstrings as the documented “equivalent” separate expressions do?

---

<div class="post-metadata">

**Author:** ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)\
**Post date:** [May 11, 2025, 8:30pm UTC](https://discourse.julialang.org/t/documenter-jl-how-to-avoid-repetitions-of-shared-docstrings/128885/12 "2025-05-11T20:30:35Z")

</div>

> [@Benny](#):
>
> Also confused why I’m seeing `Base`’s docstring when I query the `SmallBitSet` method

I guess this is because of the type parameter in the `SmallBitSet` methods, see the PR below. I would say that docstring search by signature is broken in the presence of type parameters. I haven’t got any reaction from the developers so far.

> <https://github.com/JuliaLang/julia/pull/53824>
>
> \## The problem
> 
> I think the signatures generated for docstrings create problem…s when type parameters are involved. This can be seen when searching docstrings based on signatures. My understanding of the intended behavior is that only docstrings matching the given signature should be displayed or, if none exists, all docstrings for the given function. This works well without type parameters, but not with them. I reported this already in #52669, but I didn’t get any response. The present PR intends to fix this issue. The underlying problem is somewhat subtle, so I try to explain it in detail.
> 
> EDIT: The builds fail because a change in \`doc/src/stdlib/SparseArrays.md\` is needed, see below.
> 
> \## Examples
> \`\`\`
> help?\> filter(iszero)
> \`\`\`
> displays the two docstrings
> \`\`\`
> filter(f, a)
> filter(f)
> \`\`\`
> although the first one doesn't match the signature.
> \`\`\`
> help?\> NamedTuple(\[:a =\> 1, :b =\> 2\])
> \`\`\`
> displays
> \`\`\`
> NamedTuple{names}(args::Tuple)
> NamedTuple{names,T}(args::Tuple)
> NamedTuple{names}(nt::NamedTuple)
> NamedTuple(itr)
> \`\`\`
> although only the last one matches.
> 
> \## What is going wrong?
> 
> Here are the signatures used by the help system for the examples above:
> \`\`\`
> julia\> using Base.Docs: META, @var, signature
> 
> julia\> function docsigs(f, M::Module)
> @eval keys(getfield($M, META)\[@var $f\].docs)
> end;
> 
> julia\> docsigs(:filter, Base)
> KeySet for a IdDict{Any, Any} with 4 entries. Keys:
> Tuple{Any, Base.SkipMissing{\<:AbstractArray}}
> Tuple{Any, AbstractDict}
> Union{Tuple{N}, Tuple{T}, Tuple{Any, Array{T, N}}} where {T, N}
> Tuple{Any}
> 
> julia\> docsigs(:NamedTuple, Base.BaseDocs)
> KeySet for a IdDict{Any, Any} with 4 entries. Keys:
> Union{Tuple{Tuple}, Tuple{T}, Tuple{names}} where {names, T}
> Union{Tuple{Tuple}, Tuple{names}} where names
> Tuple{Any}
> Union{Tuple{NamedTuple}, Tuple{names}} where names
> \`\`\`
> The third entry for \`filter\` comes from
> https://github.com/JuliaLang/julia/blob/d68a04ee9cc9f5479cf729b1bda17d950d4951ba/base/array.jl#L2875
> as can be seen via
> \`\`\`
> julia\> signature(:( filter(f, a::Array{T, N}) where {T, N} ))
> :((Union{Tuple{Any, Array{T, N}}, Tuple{N}, Tuple{T}} where N \<: Any) where T \<: Any)
> \`\`\`
> The type parameters \`T\` and \`N\` have been added as signatures, which leads to wrong search results.
> 
> The first entry for \`NamedTuple\` comes from
> https://github.com/JuliaLang/julia/blob/d68a04ee9cc9f5479cf729b1bda17d950d4951ba/base/docs/basedocs.jl#L3306
> Here the same happens with the parameters \`names\` and \`T\`, even in the absence of a \`where\` clause:
> \`\`\`
> julia\> signature(:( NamedTuple{names,T}(args::Tuple) ))
> :((Union{Tuple{Tuple}, Tuple{T}, Tuple{names}} where T \<: Any) where names \<: Any)
> \`\`\`
> 
> \## The problematic code
> 
> The signatures attached to docstrings are generated by the function \`Base.Docs.signature!\`. PR #21036 added among other things the following statements:
> https://github.com/JuliaLang/julia/blob/f65b8aba9abb9f0bd0f2da5fba8cc3eb1475cef1/base/docs/Docs.jl#L91-L96
> The \`for\` loop adds type parameters from \`where\` clauses as additional signatures, which was the problem for \`filter\`. In the absence of \`where\` clauses, the \`if\` statement does the same with \`:curly\` expressions. We have seen this for \`NamedTuple\`.
> 
> \## Proposed solution
> 
> The PR deletes these two loops. As far as I can tell, searching docstrings by signatures then works as expected. However, docstrings attached to constructors like
> \`\`\`
> NamedTuple{names}(args::Tuple)
> NamedTuple{names,T}(args::Tuple)
> \`\`\`
> cannot be distinguished anymore because the signatures of the arguments are the same. Attaching a docstring to the second call replaces the first one. At present they can be distinguished, and this may have been the motivation for #21036.
> 
> That having different docstrings for constructors differing only by a type parameter could have been the original motivation is also indicated by two tests in \`test/docs.jl\`. One checks that the signatures have the (now problematic) form, and the other one checks that different docstrings can be attached to constructors like the \`NamedTuple\` example above. I’ve modified the former test and marked the other one as \`@test\_broken\`. \`NamedTuple\` is the only case where I have seen this problem. (I've compiled Julia and loaded all standard libs.) I've merged the corresponding docstrings into one. I have also changed two lines in the documentation.
> 
> Also, docstrings attached to constructor calls with parameters must now use a \`where\` clause if the parameter is used for the arguments. For example,
> \`\`\`
> "doc" A{T}(x::T)
> \`\`\`
> is currently allowed, but would have to be written
> \`\`\`
> "doc" A{T}(x::T) where T
> \`\`\`
> For this reason, one needs the following additional change to build the documentation. The file is from a different git repository, see \[here\](https://github.com/JuliaSparse/SparseArrays.jl/blob/main/docs/src/index.md).
> \`\`\`
> $ diff doc/src/stdlib/SparseArrays.md.orig doc/src/stdlib/SparseArrays.md
> 240c240
> \< permute!{Tv, Ti, Tp \<: Integer, Tq \<: Integer}(::SparseMatrixCSC{Tv,Ti}, ::SparseMatrixCSC{Tv,Ti}, ::AbstractArray{Tp,1}, ::AbstractArray{Tq,1})
> \---
> \> permute!{Tv, Ti, Tp, Tq}(::SparseMatrixCSC{Tv,Ti}, ::SparseMatrixCSC{Tv,Ti}, ::AbstractArray{Tp,1}, ::AbstractArray{Tq,1}) where {Tv, Ti, Tp \<: Integer, Tq \<: Integer}
> \`\`\`
> With this change the documentation builds without problems.
> 
> \## Changing less?
> 
> (ADDED) One could avoid the changes just mentioned if one kept part of the code that I delete, namely the lines that treat \`:curly\` expressions like \`where\` clauses. This is problematic, however, because on the level of expressions free parameters cannot be distinguished from constant parameters. Consider the following example:
> \`\`\`
> struct A{T} end
> "doc Int" A{Int}(x::Int) = A{Int}()
> "doc T" A{T}(x) where T = A{T}()
> \`\`\`
> The signature for the first docstring would be converted to \`Tuple{Int} where Int\`, which is the same as \`Tuple{Any}\`. Hence \`help?\> A{Int}(1.0)\` would display both docstrings although the first one doesn't match. (This is also the current behavior.)
> 
> \## Conclusion
> 
> I think that the current situation is not desirable and should be changed. Whether the proposed PR is the right way to go is up for debate. That docstrings can be attached to function calls (instead of method definitions) is not documented, I believe. In this sense one could say that the change made by this PR would be non-breaking. In any case, let me know what you think.
