Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions include/mrdocs/Support/Handlebars.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
10 changes: 10 additions & 0 deletions share/mrdocs/addons/generator/adoc/partials/symbol.adoc.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
11 changes: 11 additions & 0 deletions share/mrdocs/addons/generator/html/partials/symbol.html.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,17 @@
{{/if}}
</div>

{{/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)}}
<div>
{{#> markup/dynamic-level-h level=2 }}<code>noexcept</code> Specification{{/markup/dynamic-level-h~}}
<p><code>noexcept</code> when <code>{{slice symbol.noexcept 9 (sub (len symbol.noexcept) 1)}}</code>.</p>
</div>
{{/if}}
{{/if}}
{{! Exceptions }}
{{#if symbol.doc.exceptions}}
Expand Down
14 changes: 14 additions & 0 deletions src/lib/Support/Handlebars.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand All @@ -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));
Expand Down Expand Up @@ -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();
Expand Down
118 changes: 118 additions & 0 deletions src/test/lib/Metadata/NoexceptInfo.cpp
Original file line number Diff line number Diff line change
@@ -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 <mrdocs/Dom/String.hpp>
#include <mrdocs/Metadata/Specifiers/NoexceptInfo.hpp>
#include <test_suite/test_suite.hpp>

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
159 changes: 159 additions & 0 deletions test-files/golden-tests/symbols/function/noexcept.adoc
Original file line number Diff line number Diff line change
@@ -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&lowbar;nothrow&lowbar;move&lowbar;assignable&lowbar;v`]
| link:#is_nothrow_move_constructible_v[`is&lowbar;nothrow&lowbar;move&lowbar;constructible&lowbar;v`]
| link:#is_nothrow_swappable_v[`is&lowbar;nothrow&lowbar;swappable&lowbar;v`]
|===

[#f1]
== f1

=== Synopsis

Declared in `&lt;noexcept&period;cpp&gt;`

[source,cpp,subs="verbatim,replacements,macros,-callouts"]
----
void
f1() noexcept;
----

[#f2]
== f2

=== Synopsis

Declared in `&lt;noexcept&period;cpp&gt;`

[source,cpp,subs="verbatim,replacements,macros,-callouts"]
----
void
f2() noexcept(true);
----

[#f3]
== f3

=== Synopsis

Declared in `&lt;noexcept&period;cpp&gt;`

[source,cpp,subs="verbatim,replacements,macros,-callouts"]
----
void
f3() noexcept(false);
----

[#f4]
== f4

=== Synopsis

Declared in `&lt;noexcept&period;cpp&gt;`

[source,cpp,subs="verbatim,replacements,macros,-callouts"]
----
template&lt;typename T&gt;
void
f4(T&) noexcept(is&lowbar;nothrow&lowbar;swappable&lowbar;v&lt;T&gt;);
----

[#f5]
== f5

=== Synopsis

Declared in `&lt;noexcept&period;cpp&gt;`

[source,cpp,subs="verbatim,replacements,macros,-callouts"]
----
template&lt;typename T&gt;
void
f5(
T a,
T b) noexcept(noexcept(a &plus; b));
----

[#f6]
== f6

=== Synopsis

Declared in `&lt;noexcept&period;cpp&gt;`

[source,cpp,subs="verbatim,replacements,macros,-callouts"]
----
template&lt;typename T&gt;
void
f6(
T&,
T&) noexcept(_see-below_);
----

=== `noexcept` Specification

`noexcept` when `is&lowbar;nothrow&lowbar;move&lowbar;constructible&lowbar;v&lt;T&gt; &amp;&amp; is&lowbar;nothrow&lowbar;move&lowbar;assignable&lowbar;v&lt;T&gt;`.

[#is_nothrow_move_assignable_v]
== is&lowbar;nothrow&lowbar;move&lowbar;assignable&lowbar;v

=== Synopsis

Declared in `&lt;noexcept&period;cpp&gt;`

[source,cpp,subs="verbatim,replacements,macros,-callouts"]
----
template&lt;typename T&gt;
inline constexpr bool is&lowbar;nothrow&lowbar;move&lowbar;assignable&lowbar;v = false;
----

[#is_nothrow_move_constructible_v]
== is&lowbar;nothrow&lowbar;move&lowbar;constructible&lowbar;v

=== Synopsis

Declared in `&lt;noexcept&period;cpp&gt;`

[source,cpp,subs="verbatim,replacements,macros,-callouts"]
----
template&lt;typename T&gt;
inline constexpr bool is&lowbar;nothrow&lowbar;move&lowbar;constructible&lowbar;v = false;
----

[#is_nothrow_swappable_v]
== is&lowbar;nothrow&lowbar;swappable&lowbar;v

=== Synopsis

Declared in `&lt;noexcept&period;cpp&gt;`

[source,cpp,subs="verbatim,replacements,macros,-callouts"]
----
template&lt;typename T&gt;
inline constexpr bool is&lowbar;nothrow&lowbar;swappable&lowbar;v = false;
----


[.small]#Created with https://www.mrdocs.com[MrDocs]#
Loading
Loading