diff --git a/include/mrdocs/Support/Handlebars.hpp b/include/mrdocs/Support/Handlebars.hpp index ccdc506423..5197515706 100644 --- a/include/mrdocs/Support/Handlebars.hpp +++ b/include/mrdocs/Support/Handlebars.hpp @@ -1300,6 +1300,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/adoc/partials/symbol.adoc.hbs b/share/mrdocs/addons/generator/adoc/partials/symbol.adoc.hbs index 1f3290f23f..a65db9f85d 100644 --- a/share/mrdocs/addons/generator/adoc/partials/symbol.adoc.hbs +++ b/share/mrdocs/addons/generator/adoc/partials/symbol.adoc.hbs @@ -167,6 +167,16 @@ | {{> symbol/qualified-name . }} {{/each}} |=== +{{/if}} +{{/if}} +{{! noexcept specification - only when the operand is long enough to + be rendered as noexcept(see-below) in the signature. Short + operands are shown inline and need no separate section. }} +{{#if symbol.noexcept}} +{{#if (gt (len symbol.noexcept) 50)}} +{{#> markup/dynamic-level-h }}`noexcept` Specification{{/markup/dynamic-level-h~}} +`noexcept` when `{{slice symbol.noexcept 9 (sub (len symbol.noexcept) 1)}}`. + {{/if}} {{/if}} {{! Exceptions }} 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 4ffab163fa..aaa300aea7 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/share/mrdocs/addons/generator/html/partials/symbol.html.hbs b/share/mrdocs/addons/generator/html/partials/symbol.html.hbs index acab90eb12..978f15c0ac 100644 --- a/share/mrdocs/addons/generator/html/partials/symbol.html.hbs +++ b/share/mrdocs/addons/generator/html/partials/symbol.html.hbs @@ -231,6 +231,17 @@ {{/if}} +{{/if}} +{{! noexcept specification - only when the operand is long enough to + be rendered as noexcept(see-below) in the signature. Short + operands are shown inline and need no separate section. }} +{{#if symbol.noexcept}} +{{#if (gt (len symbol.noexcept) 50)}} +
+{{#> markup/dynamic-level-h level=2 }}noexcept Specification{{/markup/dynamic-level-h~}} +

noexcept when {{slice symbol.noexcept 9 (sub (len symbol.noexcept) 1)}}.

+
+{{/if}} {{/if}} {{! Exceptions }} {{#if symbol.doc.exceptions}} diff --git a/src/lib/Support/Handlebars.cpp b/src/lib/Support/Handlebars.cpp index 41ccb9bf72..6616edfc06 100644 --- a/src/lib/Support/Handlebars.cpp +++ b/src/lib/Support/Handlebars.cpp @@ -3835,6 +3835,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)); @@ -3847,6 +3848,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)); @@ -3909,6 +3911,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..c019fe2e13 --- /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).get() == ""); + } + + void test_noexcept_implicit_shown_when_requested() + { + NoexceptInfo const info = makeNoexcept(true, NoexceptKind::True); + BOOST_TEST(toString(info, false, true).get() == "noexcept"); + } + + void test_noexcept_dependent_empty_operand() + { + NoexceptInfo const info = makeNoexcept(false, NoexceptKind::Dependent); + BOOST_TEST(toString(info).get() == ""); + } + + void test_noexcept_dependent_with_operand() + { + NoexceptInfo const info = makeNoexcept( + false, NoexceptKind::Dependent, "sizeof(T) > 4"); + BOOST_TEST(toString(info).get() == "noexcept(sizeof(T) > 4)"); + } + + void test_noexcept_false_resolved_drops_operand() + { + NoexceptInfo const info = makeNoexcept( + false, NoexceptKind::False, "false"); + BOOST_TEST(toString(info, true).get() == ""); + } + + void test_noexcept_false_with_operand() + { + NoexceptInfo const info = makeNoexcept( + false, NoexceptKind::False, "false"); + BOOST_TEST(toString(info).get() == "noexcept(false)"); + } + + void test_noexcept_true_resolved_drops_operand() + { + NoexceptInfo const info = makeNoexcept( + false, NoexceptKind::True, "true"); + BOOST_TEST(toString(info, true).get() == "noexcept"); + } + + void test_noexcept_true_empty_operand() + { + NoexceptInfo const info = makeNoexcept(false, NoexceptKind::True); + BOOST_TEST(toString(info).get() == "noexcept"); + } + + void test_noexcept_true_with_operand() + { + NoexceptInfo const info = makeNoexcept( + false, NoexceptKind::True, "true"); + BOOST_TEST(toString(info).get() == "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..67887b42b1 --- /dev/null +++ b/test-files/golden-tests/symbols/function/noexcept.xml @@ -0,0 +1,316 @@ + + + + + + namespace + //////////////////////////8= + regular + + CnO51rIKTzfiVKHkR3TdPa0eo+8= + 0MJUv5yGFR9nXWFLeYc+rjOY+iM= + khwJweIqd5FuWAg8T+l+GEljQVc= + YLQQaYyVExJgVdKmDUI8g3kUSe4= + 8UyaUNUP7e7Lw8EqXP3My1yLQ6U= + NubFG5T9/Beym2c/ShoR//0YUyU= + Tc9ci3FD59/jcGuSU0poHg/W/gY= + PUuglSr4gxhfPzz7VJftFjC3IXc= + wGin1cWt/dOHtf7KP/24V/5GKec= + + + + f1 + + + noexcept.cpp + noexcept.cpp + 2 + 1 + + + function + CnO51rIKTzfiVKHkR3TdPa0eo+8= + regular + //////////////////////////8= + + + identifier + void + + + normal + + + f2 + + + noexcept.cpp + noexcept.cpp + 5 + 1 + + + function + 0MJUv5yGFR9nXWFLeYc+rjOY+iM= + regular + //////////////////////////8= + + + identifier + void + + + normal + + + f3 + + + noexcept.cpp + noexcept.cpp + 8 + 1 + + + function + khwJweIqd5FuWAg8T+l+GEljQVc= + regular + //////////////////////////8= + + + identifier + void + + + normal + + + f4 + + + noexcept.cpp + noexcept.cpp + 22 + 1 + + + function + YLQQaYyVExJgVdKmDUI8g3kUSe4= + regular + //////////////////////////8= + + + identifier + void + + + + + + + identifier + T + + + + + + normal + + + f5 + + + noexcept.cpp + noexcept.cpp + 28 + 1 + + + function + 8UyaUNUP7e7Lw8EqXP3My1yLQ6U= + regular + //////////////////////////8= + + + identifier + void + + + + + + identifier + T + + + a + + + + + identifier + T + + + b + + + normal + + + f6 + + + noexcept.cpp + noexcept.cpp + 35 + 1 + + + function + NubFG5T9/Beym2c/ShoR//0YUyU= + regular + //////////////////////////8= + + + identifier + void + + + + + + + identifier + T + + + + + + + + + identifier + T + + + + + + normal + + + is_nothrow_move_assignable_v + + + noexcept.cpp + noexcept.cpp + 16 + 1 + + + variable + Tc9ci3FD59/jcGuSU0poHg/W/gY= + regular + //////////////////////////8= + + + identifier + bool + + + + false + 1 + 1 + + + is_nothrow_move_constructible_v + + + noexcept.cpp + noexcept.cpp + 14 + 1 + + + variable + PUuglSr4gxhfPzz7VJftFjC3IXc= + regular + //////////////////////////8= + + + identifier + bool + + + + false + 1 + 1 + + + is_nothrow_swappable_v + + + noexcept.cpp + noexcept.cpp + 12 + 1 + + + variable + wGin1cWt/dOHtf7KP/24V/5GKec= + regular + //////////////////////////8= + + + identifier + bool + + + + false + 1 + 1 + +