From 88ad1d86f70676af573544bed326a10a39cbb3f6 Mon Sep 17 00:00:00 2001 From: Gennaro Prota Date: Wed, 22 Apr 2026 16:55:47 +0200 Subject: [PATCH 1/2] feat: render long noexcept specifications as noexcept(see-below) MrDocs used to render the full `noexcept` operand inline, so a declaration like void swap(reference, reference) noexcept( std::is_nothrow_move_constructible::value && std::is_nothrow_move_assignable::value && std::is_nothrow_move_constructible::value && std::is_nothrow_move_assignable::value); buried the `noexcept` condition in a mostly-unreadable slop. This replaces operands longer than 40 characters with an italic "see-below" placeholder in the declaration, and moves the actual condition to a dedicated "noexcept Specification" section of the exposition: void swap(reference, reference) noexcept(see-below); === noexcept Specification noexcept when `...long condition...`. The section is intentionally separate from the existing "Exceptions" section, which continues to cover `@throws` documentation. Closes issue #1103. --- include/mrdocs/Support/Handlebars.hpp | 12 + .../partials/symbol/signature/function.hbs | 8 +- .../partials/type/declarator-suffix.hbs | 6 +- src/lib/Support/Handlebars.cpp | 14 + src/test/lib/Metadata/NoexceptInfo.cpp | 118 +++++ .../symbols/function/noexcept.adoc | 159 +++++++ .../symbols/function/noexcept.cpp | 38 ++ .../symbols/function/noexcept.html | 194 ++++++++ .../symbols/function/noexcept.xml | 425 ++++++++++++++++++ 9 files changed, 971 insertions(+), 3 deletions(-) create mode 100644 src/test/lib/Metadata/NoexceptInfo.cpp create mode 100644 test-files/golden-tests/symbols/function/noexcept.adoc create mode 100644 test-files/golden-tests/symbols/function/noexcept.cpp create mode 100644 test-files/golden-tests/symbols/function/noexcept.html create mode 100644 test-files/golden-tests/symbols/function/noexcept.xml diff --git a/include/mrdocs/Support/Handlebars.hpp b/include/mrdocs/Support/Handlebars.hpp index fd1da66207..9773030146 100644 --- a/include/mrdocs/Support/Handlebars.hpp +++ b/include/mrdocs/Support/Handlebars.hpp @@ -1313,6 +1313,18 @@ MRDOCS_DECL bool ne_fn(dom::Array const& args); +/** "gt" helper function + + The "gt" helper returns true if the first argument compares + greater than the second, via @ref dom::Value's `operator<=>`. + + @param args The two values to compare. + @return True if the first value is greater than the second. +*/ +MRDOCS_DECL +bool +gt_fn(dom::Array const& args); + /** "not" helper function The "not" helper returns true if not all of the values are truthy. diff --git a/share/mrdocs/addons/generator/common/partials/symbol/signature/function.hbs b/share/mrdocs/addons/generator/common/partials/symbol/signature/function.hbs index 3b95a39675..ce00e009d3 100644 --- a/share/mrdocs/addons/generator/common/partials/symbol/signature/function.hbs +++ b/share/mrdocs/addons/generator/common/partials/symbol/signature/function.hbs @@ -31,7 +31,13 @@ {{~#if isConst}} const{{/if~}} {{#if isVolatile}} volatile{{/if~}} {{#if refQualifier}} {{refQualifier}}{{/if~}} -{{#if noexcept}} {{noexcept}}{{/if~}} +{{~#if noexcept~}} + {{~! Specs with operands longer than 40 characters (full string > 50, + accounting for the "noexcept(" and ")" wrappers) are rendered as + `noexcept(see-below)` plus a dedicated specification section; + shorter ones are shown inline. ~}} + {{~#if (gt (len noexcept) 50)}} noexcept({{#>markup/em}}see-below{{/markup/em}}){{else}} {{noexcept}}{{/if~}} +{{~/if~}} {{#if (eq funcClass "normal")}}{{>type/declarator-suffix returnType}}{{/if~}} {{#if requires}} diff --git a/share/mrdocs/addons/generator/common/partials/type/declarator-suffix.hbs b/share/mrdocs/addons/generator/common/partials/type/declarator-suffix.hbs index b01cc3d421..97b917f446 100644 --- a/share/mrdocs/addons/generator/common/partials/type/declarator-suffix.hbs +++ b/share/mrdocs/addons/generator/common/partials/type/declarator-suffix.hbs @@ -37,8 +37,10 @@ {{~#if cv-qualifiers}} {{cv-qualifiers}}{{/if~}} {{! Refqualifiers as "&" or "&&" ~}} {{#if (eq refQualifier "lvalue")}} &{{else if (eq refQualifier "rvalue")}} &&{{/if~}} - {{! Exception spec as literal string ~}} - {{#if exceptionSpec}} {{exceptionSpec}}{{/if~}} + {{! noexcept specification with "see-below" placeholder for long operands ~}} + {{~#if exceptionSpec~}} + {{~#if (gt (len exceptionSpec) 50)}} noexcept({{#>markup/em}}see-below{{/markup/em}}){{else}} {{exceptionSpec}}{{/if~}} + {{~/if~}} {{! Declarator suffix of the return type ~}} {{~>type/declarator-suffix returnType link-components-impl=link-components-impl~}} {{/if}} \ No newline at end of file diff --git a/src/lib/Support/Handlebars.cpp b/src/lib/Support/Handlebars.cpp index a9176adadb..88554d1d08 100644 --- a/src/lib/Support/Handlebars.cpp +++ b/src/lib/Support/Handlebars.cpp @@ -3828,6 +3828,7 @@ registerAntoraHelpers(Handlebars& hbs) hbs.registerHelper("and", dom::makeVariadicInvocable(and_fn)); hbs.registerHelper("detag", dom::makeInvocable(detag_fn)); hbs.registerHelper("eq", dom::makeVariadicInvocable(eq_fn)); + hbs.registerHelper("gt", dom::makeVariadicInvocable(gt_fn)); hbs.registerHelper("increment", dom::makeInvocable(increment_fn)); hbs.registerHelper("ne", dom::makeVariadicInvocable(ne_fn)); hbs.registerHelper("not", dom::makeVariadicInvocable(not_fn)); @@ -3840,6 +3841,7 @@ registerLogicalHelpers(Handlebars& hbs) { hbs.registerHelper("and", dom::makeVariadicInvocable(and_fn)); hbs.registerHelper("eq", dom::makeVariadicInvocable(eq_fn)); + hbs.registerHelper("gt", dom::makeVariadicInvocable(gt_fn)); hbs.registerHelper("ne", dom::makeVariadicInvocable(ne_fn)); hbs.registerHelper("not", dom::makeVariadicInvocable(not_fn)); hbs.registerHelper("or", dom::makeVariadicInvocable(or_fn)); @@ -3902,6 +3904,18 @@ ne_fn(dom::Array const& args) { return !eq_fn(args); } +// Greater-than comparison via `operator<=>` on `dom::Value`. +bool +gt_fn(dom::Array const& args) { + // args carries trailing options, so we need at least two real + // arguments plus that one. + if (args.size() < 3) + { + return false; + } + return args.get(0) > args.get(1); +} + bool not_fn(dom::Array const& args) { std::size_t const n = args.size(); diff --git a/src/test/lib/Metadata/NoexceptInfo.cpp b/src/test/lib/Metadata/NoexceptInfo.cpp new file mode 100644 index 0000000000..dc6885e1d5 --- /dev/null +++ b/src/test/lib/Metadata/NoexceptInfo.cpp @@ -0,0 +1,118 @@ +// +// Licensed under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// Copyright (c) 2026 Gennaro Prota (gennaro.prota@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdocs +// + +#include +#include +#include + +namespace mrdocs { +namespace { + +// Helpers to build the specs in one line per test. + +NoexceptInfo +makeNoexcept( + bool const implicit, + NoexceptKind const kind, + std::string operand = {}) +{ + NoexceptInfo info; + info.Implicit = implicit; + info.Kind = kind; + info.Operand = std::move(operand); + return info; +} + +// ------------------------------------------------------------------ + +struct NoexceptInfoTest +{ + // -- toString(NoexceptInfo) ---------------------------------- + + void test_noexcept_implicit_is_skipped() + { + NoexceptInfo const info = makeNoexcept(true, NoexceptKind::True); + BOOST_TEST(toString(info) == ""); + } + + void test_noexcept_implicit_shown_when_requested() + { + NoexceptInfo const info = makeNoexcept(true, NoexceptKind::True); + BOOST_TEST(toString(info, false, true) == "noexcept"); + } + + void test_noexcept_dependent_empty_operand() + { + NoexceptInfo const info = makeNoexcept(false, NoexceptKind::Dependent); + BOOST_TEST(toString(info) == ""); + } + + void test_noexcept_dependent_with_operand() + { + NoexceptInfo const info = makeNoexcept( + false, NoexceptKind::Dependent, "sizeof(T) > 4"); + BOOST_TEST(toString(info) == "noexcept(sizeof(T) > 4)"); + } + + void test_noexcept_false_resolved_drops_operand() + { + NoexceptInfo const info = makeNoexcept( + false, NoexceptKind::False, "false"); + BOOST_TEST(toString(info, true) == ""); + } + + void test_noexcept_false_with_operand() + { + NoexceptInfo const info = makeNoexcept( + false, NoexceptKind::False, "false"); + BOOST_TEST(toString(info) == "noexcept(false)"); + } + + void test_noexcept_true_resolved_drops_operand() + { + NoexceptInfo const info = makeNoexcept( + false, NoexceptKind::True, "true"); + BOOST_TEST(toString(info, true) == "noexcept"); + } + + void test_noexcept_true_empty_operand() + { + NoexceptInfo const info = makeNoexcept(false, NoexceptKind::True); + BOOST_TEST(toString(info) == "noexcept"); + } + + void test_noexcept_true_with_operand() + { + NoexceptInfo const info = makeNoexcept( + false, NoexceptKind::True, "true"); + BOOST_TEST(toString(info) == "noexcept(true)"); + } + + // -- runner -------------------------------------------------- + + void run() + { + test_noexcept_implicit_is_skipped(); + test_noexcept_implicit_shown_when_requested(); + test_noexcept_dependent_empty_operand(); + test_noexcept_dependent_with_operand(); + test_noexcept_false_resolved_drops_operand(); + test_noexcept_false_with_operand(); + test_noexcept_true_resolved_drops_operand(); + test_noexcept_true_empty_operand(); + test_noexcept_true_with_operand(); + } +}; + +} // (unnamed) + +TEST_SUITE(NoexceptInfoTest, "clang.mrdocs.Metadata.NoexceptInfo"); + +} // mrdocs diff --git a/test-files/golden-tests/symbols/function/noexcept.adoc b/test-files/golden-tests/symbols/function/noexcept.adoc new file mode 100644 index 0000000000..ce3c6267b7 --- /dev/null +++ b/test-files/golden-tests/symbols/function/noexcept.adoc @@ -0,0 +1,159 @@ += Reference +:mrdocs: + +[#index] +== Global namespace + +=== Functions + +[cols=1] +|=== +| Name +| link:#f1[`f1`] +| link:#f2[`f2`] +| link:#f3[`f3`] +| link:#f4[`f4`] +| link:#f5[`f5`] +| link:#f6[`f6`] +|=== + +=== Variables + +[cols=1] +|=== +| Name +| link:#is_nothrow_move_assignable_v[`is_nothrow_move_assignable_v`] +| link:#is_nothrow_move_constructible_v[`is_nothrow_move_constructible_v`] +| link:#is_nothrow_swappable_v[`is_nothrow_swappable_v`] +|=== + +[#f1] +== f1 + +=== Synopsis + +Declared in `<noexcept.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +void +f1() noexcept; +---- + +[#f2] +== f2 + +=== Synopsis + +Declared in `<noexcept.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +void +f2() noexcept(true); +---- + +[#f3] +== f3 + +=== Synopsis + +Declared in `<noexcept.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +void +f3() noexcept(false); +---- + +[#f4] +== f4 + +=== Synopsis + +Declared in `<noexcept.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +template<typename T> +void +f4(T&) noexcept(is_nothrow_swappable_v<T>); +---- + +[#f5] +== f5 + +=== Synopsis + +Declared in `<noexcept.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +template<typename T> +void +f5( + T a, + T b) noexcept(noexcept(a + b)); +---- + +[#f6] +== f6 + +=== Synopsis + +Declared in `<noexcept.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +template<typename T> +void +f6( + T&, + T&) noexcept(_see-below_); +---- + +=== `noexcept` Specification + +`noexcept` when `is_nothrow_move_constructible_v<T> && is_nothrow_move_assignable_v<T>`. + +[#is_nothrow_move_assignable_v] +== is_nothrow_move_assignable_v + +=== Synopsis + +Declared in `<noexcept.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +template<typename T> +inline constexpr bool is_nothrow_move_assignable_v = false; +---- + +[#is_nothrow_move_constructible_v] +== is_nothrow_move_constructible_v + +=== Synopsis + +Declared in `<noexcept.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +template<typename T> +inline constexpr bool is_nothrow_move_constructible_v = false; +---- + +[#is_nothrow_swappable_v] +== is_nothrow_swappable_v + +=== Synopsis + +Declared in `<noexcept.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +template<typename T> +inline constexpr bool is_nothrow_swappable_v = false; +---- + + +[.small]#Created with https://www.mrdocs.com[MrDocs]# diff --git a/test-files/golden-tests/symbols/function/noexcept.cpp b/test-files/golden-tests/symbols/function/noexcept.cpp new file mode 100644 index 0000000000..a3aae89704 --- /dev/null +++ b/test-files/golden-tests/symbols/function/noexcept.cpp @@ -0,0 +1,38 @@ +// A function with an unconditional noexcept-specifier. +void f1() noexcept; + +// A function with noexcept(true). +void f2() noexcept(true); + +// A function with noexcept(false). +void f3() noexcept(false); + +// Traits declared locally, so the fixture does not depend on +// . +template +inline constexpr bool is_nothrow_swappable_v = false; +template +inline constexpr bool is_nothrow_move_constructible_v = false; +template +inline constexpr bool is_nothrow_move_assignable_v = false; + +// A function template with a short conditional noexcept whose +// operand depends on a type trait. The operand is short enough +// to be rendered inline in the signature. +template +void f4(T&) noexcept(is_nothrow_swappable_v); + +// A function template with a simpler conditional noexcept: a +// dependent expression that is not a type trait. Also short +// enough to be inlined. +template +void f5(T a, T b) noexcept(noexcept(a + b)); + +// A function template whose noexcept operand is long enough to +// be rendered as `noexcept(see-below)` in the signature, with +// the actual condition shown in a dedicated "noexcept +// Specification" section. See issue #1103. +template +void f6(T&, T&) noexcept( + is_nothrow_move_constructible_v && + is_nothrow_move_assignable_v); diff --git a/test-files/golden-tests/symbols/function/noexcept.html b/test-files/golden-tests/symbols/function/noexcept.html new file mode 100644 index 0000000000..cd46f5c2ae --- /dev/null +++ b/test-files/golden-tests/symbols/function/noexcept.html @@ -0,0 +1,194 @@ + + +Reference + + + +
+

Reference

+
+
+

+Global Namespace

+
+

+Functions

+ + + + + + + + + + + + + + + +
Name
f1
f2
f3
f4
f5
f6
+ +

+Variables

+ + + + + + + + + + + + +
Name
is_nothrow_move_assignable_v
is_nothrow_move_constructible_v
is_nothrow_swappable_v
+ +
+
+
+

+f1

+
+
+

+Synopsis

+
+Declared in <noexcept.cpp>
+
void
+f1() noexcept;
+
+
+
+
+

+f2

+
+
+

+Synopsis

+
+Declared in <noexcept.cpp>
+
void
+f2() noexcept(true);
+
+
+
+
+

+f3

+
+
+

+Synopsis

+
+Declared in <noexcept.cpp>
+
void
+f3() noexcept(false);
+
+
+
+
+

+f4

+
+
+

+Synopsis

+
+Declared in <noexcept.cpp>
+
template<typename T>
+void
+f4(T&) noexcept(is_nothrow_swappable_v<T>);
+
+
+
+
+

+f5

+
+
+

+Synopsis

+
+Declared in <noexcept.cpp>
+
template<typename T>
+void
+f5(
+    T a,
+    T b) noexcept(noexcept(a + b));
+
+
+
+
+

+f6

+
+
+

+Synopsis

+
+Declared in <noexcept.cpp>
+
template<typename T>
+void
+f6(
+    T&,
+    T&) noexcept(see-below);
+
+
+

+noexcept Specification

+

noexcept when is_nothrow_move_constructible_v<T> && is_nothrow_move_assignable_v<T>.

+
+
+
+
+

+is_nothrow_move_assignable_v

+
+
+

+Synopsis

+
+Declared in <noexcept.cpp>
+
template<typename T>
+inline constexpr bool is_nothrow_move_assignable_v = false;
+
+
+
+
+

+is_nothrow_move_constructible_v

+
+
+

+Synopsis

+
+Declared in <noexcept.cpp>
+
template<typename T>
+inline constexpr bool is_nothrow_move_constructible_v = false;
+
+
+
+
+

+is_nothrow_swappable_v

+
+
+

+Synopsis

+
+Declared in <noexcept.cpp>
+
template<typename T>
+inline constexpr bool is_nothrow_swappable_v = false;
+
+
+ +
+ + + \ No newline at end of file diff --git a/test-files/golden-tests/symbols/function/noexcept.xml b/test-files/golden-tests/symbols/function/noexcept.xml new file mode 100644 index 0000000000..70bfe6f5d9 --- /dev/null +++ b/test-files/golden-tests/symbols/function/noexcept.xml @@ -0,0 +1,425 @@ + + + + namespace + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + regular + + 9SrB1UL3KesEDpt7G1mT9bvMnqC 3ugjxNbHHSuxBbaHt8ENMWRLxFAS 334bUXUdxG62ArqzF5WTRNS4vHSN 2M97oNTms6y7eS4KLHuYYbgXfFHf 4Mymm49w1T1EnA9ND8s2YLvTpSDJ mN2CbPQWgQQjMCijfe4Fh23X6Xv + 25sf877Kn8HYTr5vBPFx8eVVFniy rXgYX13niCt7zDjhpqVijgG11Pk 3gUTxinfev8CW73TFy2DZdHLysXc + + + + f1 + + + + noexcept.cpp + noexcept.cpp + 2 + 1 + + + + function + 9SrB1UL3KesEDpt7G1mT9bvMnqC + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + named + + + identifier + void + + + void + + + normal + + + f2 + + + + noexcept.cpp + noexcept.cpp + 5 + 1 + + + + function + 3ugjxNbHHSuxBbaHt8ENMWRLxFAS + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + named + + + identifier + void + + + void + + + normal + + + f3 + + + + noexcept.cpp + noexcept.cpp + 8 + 1 + + + + function + 334bUXUdxG62ArqzF5WTRNS4vHSN + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + named + + + identifier + void + + + void + + + normal + + + f4 + + + + noexcept.cpp + noexcept.cpp + 22 + 1 + + + + function + 2M97oNTms6y7eS4KLHuYYbgXfFHf + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + named + + + identifier + void + + + void + + + + + + + lvalue-reference + + + named + + + identifier + T + + + + + + + + + + normal + + + f5 + + + + noexcept.cpp + noexcept.cpp + 28 + 1 + + + + function + 4Mymm49w1T1EnA9ND8s2YLvTpSDJ + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + named + + + identifier + void + + + void + + + + + + + named + + + identifier + T + + + + + a + + + + + named + + + identifier + T + + + + + b + + + + normal + + + f6 + + + + noexcept.cpp + noexcept.cpp + 35 + 1 + + + + function + mN2CbPQWgQQjMCijfe4Fh23X6Xv + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + named + + + identifier + void + + + void + + + + + + + lvalue-reference + + + named + + + identifier + T + + + + + + + + + + + lvalue-reference + + + named + + + identifier + T + + + + + + + + + + normal + + + is_nothrow_move_assignable_v + + + noexcept.cpp + noexcept.cpp + 16 + 1 + + + variable + 25sf877Kn8HYTr5vBPFx8eVVFniy + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + named + + + identifier + bool + + + bool + + + + false + + + + + is_nothrow_move_constructible_v + + + noexcept.cpp + noexcept.cpp + 14 + 1 + + + variable + rXgYX13niCt7zDjhpqVijgG11Pk + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + named + + + identifier + bool + + + bool + + + + false + + + + + is_nothrow_swappable_v + + + noexcept.cpp + noexcept.cpp + 12 + 1 + + + variable + 3gUTxinfev8CW73TFy2DZdHLysXc + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + named + + + identifier + bool + + + bool + + + + false + + + + From 2593d40bc6419f4dc92c90b36b94d3c64c65dc05 Mon Sep 17 00:00:00 2001 From: Alan de Freitas Date: Fri, 24 Jul 2026 12:38:33 -0500 Subject: [PATCH 2/2] feat: configurable noexcept see-below threshold --- .../generator/common/partials/symbol.hbs | 1 + .../symbol/section/noexcept-specification.hbs | 16 ++ .../partials/symbol/signature/function.hbs | 11 +- .../signature/implementation-defined.hbs | 1 + .../partials/symbol/signature/record.hbs | 2 +- .../partials/symbol/signature/see-below.hbs | 1 + .../partials/type/declarator-suffix.hbs | 2 +- .../common/partials/type/name-infos.hbs | 2 +- src/lib/Gen/hbs/Builder.cpp | 26 ++ .../hbs/noexcept-see-below-limit/mrdocs.yml | 16 ++ .../noexcept-limit.adoc | 51 ++++ .../noexcept-limit.cpp | 7 + .../noexcept-limit.html | 62 +++++ .../golden-tests/snippets/commands/regex.adoc | 2 +- .../snippets/commands/see-below.adoc | 2 +- .../private-symbols/private-symbols.adoc | 2 +- .../snippets/options/see-below/see-below.adoc | 2 +- .../symbols/function/noexcept.adoc | 12 +- .../symbols/function/noexcept.html | 245 ++++++++---------- .../symbols/function/noexcept.yml | 1 + 20 files changed, 306 insertions(+), 158 deletions(-) create mode 100644 share/mrdocs/addons/generator/common/partials/symbol/section/noexcept-specification.hbs create mode 100644 share/mrdocs/addons/generator/common/partials/symbol/signature/implementation-defined.hbs create mode 100644 share/mrdocs/addons/generator/common/partials/symbol/signature/see-below.hbs create mode 100644 test-files/golden-tests/generator/hbs/noexcept-see-below-limit/mrdocs.yml create mode 100644 test-files/golden-tests/generator/hbs/noexcept-see-below-limit/noexcept-limit.adoc create mode 100644 test-files/golden-tests/generator/hbs/noexcept-see-below-limit/noexcept-limit.cpp create mode 100644 test-files/golden-tests/generator/hbs/noexcept-see-below-limit/noexcept-limit.html create mode 100644 test-files/golden-tests/symbols/function/noexcept.yml diff --git a/share/mrdocs/addons/generator/common/partials/symbol.hbs b/share/mrdocs/addons/generator/common/partials/symbol.hbs index 9ab4195a35..9f00494c50 100644 --- a/share/mrdocs/addons/generator/common/partials/symbol.hbs +++ b/share/mrdocs/addons/generator/common/partials/symbol.hbs @@ -28,6 +28,7 @@ {{> symbol/section/non-member-functions}} {{> symbol/section/derived-classes}} {{> symbol/section/introduced-symbols}} +{{> symbol/section/noexcept-specification}} {{> symbol/section/exceptions}} {{> symbol/section/return-value}} {{> symbol/section/template-parameters}} diff --git a/share/mrdocs/addons/generator/common/partials/symbol/section/noexcept-specification.hbs b/share/mrdocs/addons/generator/common/partials/symbol/section/noexcept-specification.hbs new file mode 100644 index 0000000000..f6984b7124 --- /dev/null +++ b/share/mrdocs/addons/generator/common/partials/symbol/section/noexcept-specification.hbs @@ -0,0 +1,16 @@ +{{! Exception specification: a dedicated section shown only when the noexcept + specifier is long enough that the signature elides it with the see-below + placeholder (see symbol/signature/function). Shorter specifiers are shown + inline and need no section here. The threshold is the noexcept-see-below-limit + option (the active generator's generator-options., exposed as + generatorConfig; default 50). symbol.noexcept is the whole "noexcept(...)" + string; its operand is that without the "noexcept(" prefix (9 chars) and + trailing ")". }} +{{#if symbol.noexcept}} +{{#if (gt (len symbol.noexcept) (or @root.generatorConfig.[noexcept-see-below-limit] 50))}} +{{#> markup/section name="noexcept-specification"}} +{{#> markup/dynamic-level-h }}Exception specification{{/markup/dynamic-level-h~}} +{{#> markup/p}}This function is potentially-throwing unless {{#>markup/code}}{{slice symbol.noexcept 9 (sub (len symbol.noexcept) 1)}}{{/markup/code}}.{{/markup/p}} +{{/markup/section}} +{{/if}} +{{/if}} diff --git a/share/mrdocs/addons/generator/common/partials/symbol/signature/function.hbs b/share/mrdocs/addons/generator/common/partials/symbol/signature/function.hbs index ce00e009d3..d35543a385 100644 --- a/share/mrdocs/addons/generator/common/partials/symbol/signature/function.hbs +++ b/share/mrdocs/addons/generator/common/partials/symbol/signature/function.hbs @@ -32,11 +32,12 @@ {{#if isVolatile}} volatile{{/if~}} {{#if refQualifier}} {{refQualifier}}{{/if~}} {{~#if noexcept~}} - {{~! Specs with operands longer than 40 characters (full string > 50, - accounting for the "noexcept(" and ")" wrappers) are rendered as - `noexcept(see-below)` plus a dedicated specification section; - shorter ones are shown inline. ~}} - {{~#if (gt (len noexcept) 50)}} noexcept({{#>markup/em}}see-below{{/markup/em}}){{else}} {{noexcept}}{{/if~}} + {{~! Specs whose full "noexcept(...)" string is longer than the + noexcept-see-below-limit option (the active generator's + generator-options., exposed as generatorConfig; default 50) are + rendered with the see-below placeholder plus a dedicated specification + section; shorter ones are shown inline. ~}} + {{~#if (gt (len noexcept) (or @root.generatorConfig.[noexcept-see-below-limit] 50))}} noexcept({{> symbol/signature/see-below}}){{else}} {{noexcept}}{{/if~}} {{~/if~}} {{#if (eq funcClass "normal")}}{{>type/declarator-suffix returnType}}{{/if~}} {{#if requires}} diff --git a/share/mrdocs/addons/generator/common/partials/symbol/signature/implementation-defined.hbs b/share/mrdocs/addons/generator/common/partials/symbol/signature/implementation-defined.hbs new file mode 100644 index 0000000000..6313e61cf2 --- /dev/null +++ b/share/mrdocs/addons/generator/common/partials/symbol/signature/implementation-defined.hbs @@ -0,0 +1 @@ +{{ str '/*' }} implementation-defined {{ str '*/' }} \ No newline at end of file diff --git a/share/mrdocs/addons/generator/common/partials/symbol/signature/record.hbs b/share/mrdocs/addons/generator/common/partials/symbol/signature/record.hbs index 987fa43c65..978d868699 100644 --- a/share/mrdocs/addons/generator/common/partials/symbol/signature/record.hbs +++ b/share/mrdocs/addons/generator/common/partials/symbol/signature/record.hbs @@ -24,5 +24,5 @@ final;` is ill-formed (`final` can't be used on a non-defining declaration). Similarly, don't show a semicolon if there are base classes. --}} -{{~#isSeeBelow}} { /* see-below */ }{{/isSeeBelow~}} +{{~#isSeeBelow}} { {{> symbol/signature/see-below}} }{{/isSeeBelow~}} {{#if (or isSeeBelow (and (not isFinal) (eq (len bases) 0)))}};{{/if}} \ No newline at end of file diff --git a/share/mrdocs/addons/generator/common/partials/symbol/signature/see-below.hbs b/share/mrdocs/addons/generator/common/partials/symbol/signature/see-below.hbs new file mode 100644 index 0000000000..15e5e07f27 --- /dev/null +++ b/share/mrdocs/addons/generator/common/partials/symbol/signature/see-below.hbs @@ -0,0 +1 @@ +{{ str '/*' }} see-below {{ str '*/' }} \ No newline at end of file diff --git a/share/mrdocs/addons/generator/common/partials/type/declarator-suffix.hbs b/share/mrdocs/addons/generator/common/partials/type/declarator-suffix.hbs index 97b917f446..04a65ac4f6 100644 --- a/share/mrdocs/addons/generator/common/partials/type/declarator-suffix.hbs +++ b/share/mrdocs/addons/generator/common/partials/type/declarator-suffix.hbs @@ -39,7 +39,7 @@ {{#if (eq refQualifier "lvalue")}} &{{else if (eq refQualifier "rvalue")}} &&{{/if~}} {{! noexcept specification with "see-below" placeholder for long operands ~}} {{~#if exceptionSpec~}} - {{~#if (gt (len exceptionSpec) 50)}} noexcept({{#>markup/em}}see-below{{/markup/em}}){{else}} {{exceptionSpec}}{{/if~}} + {{~#if (gt (len exceptionSpec) (or @root.generatorConfig.[noexcept-see-below-limit] 50))}} noexcept({{> symbol/signature/see-below}}){{else}} {{exceptionSpec}}{{/if~}} {{~/if~}} {{! Declarator suffix of the return type ~}} {{~>type/declarator-suffix returnType link-components-impl=link-components-impl~}} diff --git a/share/mrdocs/addons/generator/common/partials/type/name-infos.hbs b/share/mrdocs/addons/generator/common/partials/type/name-infos.hbs index b03bcaa975..9929575044 100644 --- a/share/mrdocs/addons/generator/common/partials/type/name-infos.hbs +++ b/share/mrdocs/addons/generator/common/partials/type/name-infos.hbs @@ -14,7 +14,7 @@ --}} {{#if id.isImplementationDefined~}} {{! The name refers to a symbol that's implementation defined. These are special names that should not be linked. ~}} - {{ str '/* '}}implementation-defined{{ str ' */'~}} + {{~> symbol/signature/implementation-defined ~}} {{else~}} {{#if prefix~}} {{> type/name-info prefix link-components-impl=link-components-impl~}}:: diff --git a/src/lib/Gen/hbs/Builder.cpp b/src/lib/Gen/hbs/Builder.cpp index 469a17430b..8deddf63b5 100644 --- a/src/lib/Gen/hbs/Builder.cpp +++ b/src/lib/Gen/hbs/Builder.cpp @@ -29,6 +29,24 @@ namespace hbs { namespace { +/** The active generator's `generator-options.` settings. + + Exposed to templates as `generatorConfig` so shared partials can read a + per-generator option directly as `@root.generatorConfig.`, avoiding a + costly generator-options lookup by generator name in the template and + letting common partials reach generator configuration without hardcoding + the generator. Returns an empty object when the generator has no entry. +*/ +dom::Value +generatorConfig(HandlebarsCorpus const& domCorpus) +{ + auto const& genOpts = domCorpus->config->generatorOptions; + auto const it = genOpts.find(domCorpus.fileExtension); + return it != genOpts.end() + ? dom::Value(it->second) + : dom::Value(dom::Object()); +} + /** Loads Handlebars partial templates from a directory. Recursively scans the specified directory for `.hbs` files and @@ -540,6 +558,10 @@ createContext(Symbol const& I) ctx.set("page", page); ctx.set("symbol", domCorpus.get(I.id)); ctx.set("config", domCorpus->config.object()); + // The active generator's id, so templates can index per-generator + // settings, e.g. lookup config.generator-options.. + ctx.set("generatorId", domCorpus.fileExtension); + ctx.set("generatorConfig", generatorConfig(domCorpus)); return ctx; } @@ -594,6 +616,10 @@ renderWrapped( page.set("hasDefaultStyles", domCorpus.hasDefaultStyles); ctx.set("page", page); ctx.set("config", domCorpus->config.object()); + // The active generator's id, so templates can index per-generator + // settings, e.g. lookup config.generator-options.. + ctx.set("generatorId", domCorpus.fileExtension); + ctx.set("generatorConfig", generatorConfig(domCorpus)); ctx.set("contents", dom::makeInvocable([&](dom::Value const &) -> Expected { MRDOCS_TRY(contentsCb()); diff --git a/test-files/golden-tests/generator/hbs/noexcept-see-below-limit/mrdocs.yml b/test-files/golden-tests/generator/hbs/noexcept-see-below-limit/mrdocs.yml new file mode 100644 index 0000000000..9bbb2706ea --- /dev/null +++ b/test-files/golden-tests/generator/hbs/noexcept-see-below-limit/mrdocs.yml @@ -0,0 +1,16 @@ +generator: [adoc, html] +multipage: false +no-default-styles: true +warn-if-undocumented: false +source-root: . +# noexcept-see-below-limit set for each generator to a small value, so the +# specifier (longer than 20, but shorter than the default 50) is elided to +# noexcept(/* see-below */) with its own section. Both generators set it, so +# the elision is driven by the option and its rendering is checked in adoc and +# html. The inline case (specifier at or below the limit) is the default and is +# already exercised throughout the suite, so it is not repeated here. +generator-options: + adoc: + noexcept-see-below-limit: 20 + html: + noexcept-see-below-limit: 20 diff --git a/test-files/golden-tests/generator/hbs/noexcept-see-below-limit/noexcept-limit.adoc b/test-files/golden-tests/generator/hbs/noexcept-see-below-limit/noexcept-limit.adoc new file mode 100644 index 0000000000..734444d58d --- /dev/null +++ b/test-files/golden-tests/generator/hbs/noexcept-see-below-limit/noexcept-limit.adoc @@ -0,0 +1,51 @@ += Reference +:mrdocs: + +[#index] +== Global namespace + +=== Functions + +[cols="1,4"] +|=== +| Name| Description +| link:#swap[`swap`] +| Swaps two values, conditionally noexcept. +|=== + + +[#swap] +== swap + +Swaps two values, conditionally noexcept. + +=== Synopsis + +Declared in `<noexcept‐limit.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +template<class T> +void +swap( + T& a, + T& b) noexcept(/* see-below */); +---- + +=== Exception specification + +This function is potentially-throwing unless `sizeof(T) > sizeof(int)`. + +=== Parameters + +[cols="1,4"] +|=== +| Name| Description +| *a* +| first value +| *b* +| second value +|=== + + +[.small]#Created with https://www.mrdocs.com[MrDocs]# diff --git a/test-files/golden-tests/generator/hbs/noexcept-see-below-limit/noexcept-limit.cpp b/test-files/golden-tests/generator/hbs/noexcept-see-below-limit/noexcept-limit.cpp new file mode 100644 index 0000000000..197e492053 --- /dev/null +++ b/test-files/golden-tests/generator/hbs/noexcept-see-below-limit/noexcept-limit.cpp @@ -0,0 +1,7 @@ +/** Swaps two values, conditionally noexcept. + + @param a first value + @param b second value + */ +template +void swap(T& a, T& b) noexcept(sizeof(T) > sizeof(int)); diff --git a/test-files/golden-tests/generator/hbs/noexcept-see-below-limit/noexcept-limit.html b/test-files/golden-tests/generator/hbs/noexcept-see-below-limit/noexcept-limit.html new file mode 100644 index 0000000000..2042a4ef9c --- /dev/null +++ b/test-files/golden-tests/generator/hbs/noexcept-see-below-limit/noexcept-limit.html @@ -0,0 +1,62 @@ + + +Reference + + + +
+

Reference

+
+
+

Global namespace

+
+

Functions

+ + + + + + + +
NameDescription
swap Swaps two values, conditionally noexcept.
+ +
+
+
+

swap

+
+

Swaps two values, conditionally noexcept.

+
+
+

Synopsis

+

Declared in <noexcept-limit.cpp>

+
template<class T>
+void
+swap(
+    T& a,
+    T& b) noexcept(/* see-below */);
+
+
+

Exception specification

+

This function is potentially-throwing unless sizeof(T) > sizeof(int).

+
+
+

Parameters

+ + + + + + + + +
NameDescription
afirst value
bsecond value
+
+
+ +
+ + + \ No newline at end of file diff --git a/test-files/golden-tests/snippets/commands/regex.adoc b/test-files/golden-tests/snippets/commands/regex.adoc index f5b59dcc2a..cce6cc5a2f 100644 --- a/test-files/golden-tests/snippets/commands/regex.adoc +++ b/test-files/golden-tests/snippets/commands/regex.adoc @@ -12,7 +12,7 @@ Declared in `<regex.cpp>` [source,cpp,subs="verbatim,replacements,macros,-callouts"] ---- -class regex { /* see-below */ }; +class regex { /* see-below */ }; ---- === Description diff --git a/test-files/golden-tests/snippets/commands/see-below.adoc b/test-files/golden-tests/snippets/commands/see-below.adoc index 61bd369771..be8a67d78e 100644 --- a/test-files/golden-tests/snippets/commands/see-below.adoc +++ b/test-files/golden-tests/snippets/commands/see-below.adoc @@ -12,7 +12,7 @@ Declared in `<see‐below.cpp>` [source,cpp,subs="verbatim,replacements,macros,-callouts"] ---- -class handle { /* see-below */ }; +class handle { /* see-below */ }; ---- diff --git a/test-files/golden-tests/snippets/configuration/private-symbols/private-symbols.adoc b/test-files/golden-tests/snippets/configuration/private-symbols/private-symbols.adoc index a0cb01d832..4b06e5b2e3 100644 --- a/test-files/golden-tests/snippets/configuration/private-symbols/private-symbols.adoc +++ b/test-files/golden-tests/snippets/configuration/private-symbols/private-symbols.adoc @@ -12,7 +12,7 @@ Declared in `<logr/log.hpp>` [source,cpp,subs="verbatim,replacements,macros,-callouts"] ---- -class log_record { /* see-below */ }; +class log_record { /* see-below */ }; ---- === Description diff --git a/test-files/golden-tests/snippets/options/see-below/see-below.adoc b/test-files/golden-tests/snippets/options/see-below/see-below.adoc index 862e7c75f1..9cd61ad013 100644 --- a/test-files/golden-tests/snippets/options/see-below/see-below.adoc +++ b/test-files/golden-tests/snippets/options/see-below/see-below.adoc @@ -12,7 +12,7 @@ Declared in `<netz/buffer_view.hpp>` [source,cpp,subs="verbatim,replacements,macros,-callouts"] ---- -class buffer_view { /* see-below */ }; +class buffer_view { /* see-below */ }; ---- === Description diff --git a/test-files/golden-tests/symbols/function/noexcept.adoc b/test-files/golden-tests/symbols/function/noexcept.adoc index ce3c6267b7..b3c05afb5f 100644 --- a/test-files/golden-tests/symbols/function/noexcept.adoc +++ b/test-files/golden-tests/symbols/function/noexcept.adoc @@ -6,7 +6,7 @@ === Functions -[cols=1] +[cols="1"] |=== | Name | link:#f1[`f1`] @@ -17,9 +17,10 @@ | link:#f6[`f6`] |=== + === Variables -[cols=1] +[cols="1"] |=== | Name | link:#is_nothrow_move_assignable_v[`is_nothrow_move_assignable_v`] @@ -27,6 +28,7 @@ | link:#is_nothrow_swappable_v[`is_nothrow_swappable_v`] |=== + [#f1] == f1 @@ -109,12 +111,12 @@ template<typename T> void f6( T&, - T&) noexcept(_see-below_); + T&) noexcept(/* see-below */); ---- -=== `noexcept` Specification +=== Exception specification -`noexcept` when `is_nothrow_move_constructible_v<T> && is_nothrow_move_assignable_v<T>`. +This function is potentially-throwing unless `is_nothrow_move_constructible_v<T> && is_nothrow_move_assignable_v<T>`. [#is_nothrow_move_assignable_v] == is_nothrow_move_assignable_v diff --git a/test-files/golden-tests/symbols/function/noexcept.html b/test-files/golden-tests/symbols/function/noexcept.html index cd46f5c2ae..f56b355685 100644 --- a/test-files/golden-tests/symbols/function/noexcept.html +++ b/test-files/golden-tests/symbols/function/noexcept.html @@ -6,185 +6,148 @@

Reference

-
-
-

-Global Namespace

-
-

-Functions

+
+
+

Global namespace

+
+

Functions

- - - + - - - - - - - + + + + + +
Name
Name
f1
f2
f3
f4
f5
f6
f1
f2
f3
f4
f5
f6
-

-Variables

+

Variables

- - - + - - - - + + +
Name
Name
is_nothrow_move_assignable_v
is_nothrow_move_constructible_v
is_nothrow_swappable_v
is_nothrow_move_assignable_v
is_nothrow_move_constructible_v
is_nothrow_swappable_v
-
-
-
-

-f1

-
-
-

-Synopsis

-
-Declared in <noexcept.cpp>
+
+
+
+

f1

+
+
+

Synopsis

+

Declared in <noexcept.cpp>

void
 f1() noexcept;
-
-
-
-
-

-f2

-
-
-

-Synopsis

-
-Declared in <noexcept.cpp>
+
+
+
+
+

f2

+
+
+

Synopsis

+

Declared in <noexcept.cpp>

void
 f2() noexcept(true);
-
-
-
-
-

-f3

-
-
-

-Synopsis

-
-Declared in <noexcept.cpp>
+
+
+
+
+

f3

+
+
+

Synopsis

+

Declared in <noexcept.cpp>

void
 f3() noexcept(false);
-
-
-
-
-

-f4

-
-
-

-Synopsis

-
-Declared in <noexcept.cpp>
+
+
+
+
+

f4

+
+
+

Synopsis

+

Declared in <noexcept.cpp>

template<typename T>
 void
 f4(T&) noexcept(is_nothrow_swappable_v<T>);
-
-
-
-
-

-f5

-
-
-

-Synopsis

-
-Declared in <noexcept.cpp>
+
+
+
+
+

f5

+
+
+

Synopsis

+

Declared in <noexcept.cpp>

template<typename T>
 void
 f5(
     T a,
     T b) noexcept(noexcept(a + b));
-
-
-
-
-

-f6

-
-
-

-Synopsis

-
-Declared in <noexcept.cpp>
+
+
+
+
+

f6

+
+
+

Synopsis

+

Declared in <noexcept.cpp>

template<typename T>
 void
 f6(
     T&,
-    T&) noexcept(see-below);
-
-
-

-noexcept Specification

-

noexcept when is_nothrow_move_constructible_v<T> && is_nothrow_move_assignable_v<T>.

-
-
-
-
-

-is_nothrow_move_assignable_v

-
-
-

-Synopsis

-
-Declared in <noexcept.cpp>
+ T&) noexcept(/* see-below */); +
+
+

Exception specification

+

This function is potentially-throwing unless is_nothrow_move_constructible_v<T> && is_nothrow_move_assignable_v<T>.

+
+
+
+
+

is_nothrow_move_assignable_v

+
+
+

Synopsis

+

Declared in <noexcept.cpp>

template<typename T>
 inline constexpr bool is_nothrow_move_assignable_v = false;
-
-
-
-
-

-is_nothrow_move_constructible_v

-
-
-

-Synopsis

-
-Declared in <noexcept.cpp>
+
+
+
+
+

is_nothrow_move_constructible_v

+
+
+

Synopsis

+

Declared in <noexcept.cpp>

template<typename T>
 inline constexpr bool is_nothrow_move_constructible_v = false;
-
-
-
-
-

-is_nothrow_swappable_v

-
-
-

-Synopsis

-
-Declared in <noexcept.cpp>
+
+
+
+
+

is_nothrow_swappable_v

+
+
+

Synopsis

+

Declared in <noexcept.cpp>

template<typename T>
 inline constexpr bool is_nothrow_swappable_v = false;
-
-
+
+