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
+
+
+
+Variables
+
+
+
+
+
+
+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
+
+
+
+
+
+
+ type
+ T
+ typename
+
+
+ normal
+
+
+ f5
+
+
+ noexcept.cpp
+ noexcept.cpp
+ 28
+ 1
+
+
+ function
+ 8UyaUNUP7e7Lw8EqXP3My1yLQ6U=
+ regular
+ //////////////////////////8=
+
+
+ identifier
+ void
+
+
+
+
+
+ identifier
+ T
+
+
+ a
+
+
+
+
+ identifier
+ T
+
+
+ b
+
+
+
+ type
+ T
+ typename
+
+
+ normal
+
+
+ f6
+
+
+ noexcept.cpp
+ noexcept.cpp
+ 35
+ 1
+
+
+ function
+ NubFG5T9/Beym2c/ShoR//0YUyU=
+ regular
+ //////////////////////////8=
+
+
+ identifier
+ void
+
+
+
+
+
+
+ identifier
+ T
+
+
+
+
+
+
+
+
+ identifier
+ T
+
+
+
+
+
+
+ type
+ T
+ typename
+
+
+ normal
+
+
+ is_nothrow_move_assignable_v
+
+
+ noexcept.cpp
+ noexcept.cpp
+ 16
+ 1
+
+
+ variable
+ Tc9ci3FD59/jcGuSU0poHg/W/gY=
+ regular
+ //////////////////////////8=
+
+
+ identifier
+ bool
+
+
+
+
+ type
+ T
+ typename
+
+
+ false
+ 1
+ 1
+
+
+ is_nothrow_move_constructible_v
+
+
+ noexcept.cpp
+ noexcept.cpp
+ 14
+ 1
+
+
+ variable
+ PUuglSr4gxhfPzz7VJftFjC3IXc=
+ regular
+ //////////////////////////8=
+
+
+ identifier
+ bool
+
+
+
+
+ type
+ T
+ typename
+
+
+ false
+ 1
+ 1
+
+
+ is_nothrow_swappable_v
+
+
+ noexcept.cpp
+ noexcept.cpp
+ 12
+ 1
+
+
+ variable
+ wGin1cWt/dOHtf7KP/24V/5GKec=
+ regular
+ //////////////////////////8=
+
+
+ identifier
+ bool
+
+
+
+
+ type
+ T
+ typename
+
+
+ false
+ 1
+ 1
+
+