Skip to content

Commit d969649

Browse files
committed
feat: add function object support
Variables with a type that is a record whose only public non-special members are operator() overloads are now documented as functions. For each operator() overload, the finalizer creates a synthetic FunctionSymbol that carries the variable name and parent but the operator signature, return type, and source location. The original variable and its type are hidden as implementation-defined. Detection triggers: - Auto-detection (on by default, controlled by `auto-function-objects`) for records whose only public non-special members are operator() overloads, including class templates when the variable lives in an enclosing scope of the type. - The `@functionobject` (or `@functor`) doc command for cases that fail auto-detection (e.g. types with extra public members) or when auto-detection is off. Key implementation details: - FunctionObjectFinalizer runs early in the pipeline (before OverloadsFinalizer). It creates synthetic FunctionSymbol entries, hides the variable and its type, and appends a fixed disclaimer note about ADL and address-taking. - FunctionSymbol gains an optional FunctionObjectImpl field that links each synthetic function back to the operator() it models. An example of this feature is added to the landing page. Closes #564.
1 parent a1f9a82 commit d969649

37 files changed

+5671
-85
lines changed

docs/mrdocs.schema.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@
3636
"title": "Automatically provide missing documentation for special functions and trivial metadata",
3737
"type": "boolean"
3838
},
39+
"auto-function-objects": {
40+
"default": true,
41+
"description": "When true, MrDocs automatically detects function objects: constexpr variables whose type is a struct or class whose only public non-special members are operator() overloads. Set to false to disable auto-detection and rely solely on the @functionobject doc command.",
42+
"enum": [
43+
true,
44+
false
45+
],
46+
"title": "Automatically detect function objects",
47+
"type": "boolean"
48+
},
3949
"auto-relates": {
4050
"default": true,
4151
"description": "When set to `true`, Mr.Docs automatically finds non-member functions that are related to the current class.",

docs/website/data.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,12 @@
106106
"source": "sqrt.cpp",
107107
"imageClass": "writing",
108108
"boxClass": "box3 box-shape3 box270"
109+
},
110+
{
111+
"description": "Function objects are documented as callable entities—operator() signatures appear directly on the variable.",
112+
"source": "function_object.cpp",
113+
"imageClass": "pointing",
114+
"boxClass": "box3 box-shape1 box0"
109115
}
110116
]
111117
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
struct abs_fn
2+
{
3+
/** Compute the absolute value.
4+
5+
@param x The input value.
6+
@return The absolute value of x.
7+
*/
8+
double
9+
operator()(double x) const noexcept;
10+
};
11+
12+
/** Return the absolute value of a number. */
13+
constexpr abs_fn abs = {};

include/mrdocs/Metadata/DocComment.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//
77
// Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com)
88
// Copyright (c) 2023 Krystian Stasiowski (sdkrystian@gmail.com)
9+
// Copyright (c) 2026 Gennaro Prota (gennaro.prota@gmail.com)
910
//
1011
// Official repository: https://github.com/cppalliance/mrdocs
1112
//
@@ -106,6 +107,9 @@ struct MRDOCS_DECL DocComment {
106107
*/
107108
std::vector<doc::ReferenceInline> related;
108109

110+
/// True if the @functionobject (or @functor) command was used on this symbol.
111+
bool IsFunctionObject = false;
112+
109113
/** Constructor.
110114
*/
111115
MRDOCS_DECL
@@ -171,6 +175,10 @@ struct MRDOCS_DECL DocComment {
171175
inline
172176
void merge(DocComment& I, DocComment&& other)
173177
{
178+
// Merge the function-object flag before comparing so
179+
// the flag alone doesn't cause spurious inequality.
180+
I.IsFunctionObject |= other.IsFunctionObject;
181+
174182
// FIXME: this doesn't merge parameter information;
175183
// parameters with the same name but different directions
176184
// or descriptions end up being duplicated

include/mrdocs/Metadata/Symbol/Function.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,14 @@ struct FunctionSymbol final
128128
*/
129129
ExplicitInfo Explicit;
130130

131+
/** Back-reference to the function object implementation type.
132+
133+
When set, this function was synthesized from a function
134+
object variable: the function does not participate in ADL
135+
and taking its address is undefined behavior.
136+
*/
137+
Optional<SymbolID> FunctionObjectImpl;
138+
131139
//--------------------------------------------
132140

133141
/** Construct a function symbol with its ID.

include/mrdocs/Metadata/Symbol/SymbolID.hpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,26 @@ tag_invoke(
207207
SymbolID const& id,
208208
DomCorpus const* domCorpus);
209209

210+
/** Convert an optional SymbolID to dom::Value or null.
211+
*/
212+
inline
213+
void
214+
tag_invoke(
215+
dom::ValueFromTag,
216+
dom::Value& v,
217+
Optional<SymbolID> const& id,
218+
DomCorpus const* domCorpus)
219+
{
220+
if (!id)
221+
{
222+
v = nullptr;
223+
}
224+
else
225+
{
226+
tag_invoke(dom::ValueFromTag{}, v, *id, domCorpus);
227+
}
228+
}
229+
210230
/** Convert SymbolID pointers to dom::Value or null.
211231
212232
@param v The output parameter to receive the dom::Value.

include/mrdocs/Metadata/Symbol/Variable.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
//
66
// Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com)
77
// Copyright (c) 2023 Krystian Stasiowski (sdkrystian@gmail.com)
8+
// Copyright (c) 2026 Gennaro Prota (gennaro.prota@gmail.com)
89
//
910
// Official repository: https://github.com/cppalliance/mrdocs
1011
//

src/lib/AST/ASTVisitor.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
// Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com)
88
// Copyright (c) 2023 Krystian Stasiowski (sdkrystian@gmail.com)
99
// Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com)
10+
// Copyright (c) 2026 Gennaro Prota (gennaro.prota@gmail.com)
1011
//
1112
// Official repository: https://github.com/cppalliance/mrdocs
1213
//
@@ -22,6 +23,7 @@
2223
#include <lib/Support/Radix.hpp>
2324
#include <mrdocs/Metadata.hpp>
2425
#include <mrdocs/Support/Algorithm.hpp>
26+
#include <mrdocs/Support/Assert.hpp>
2527
#include <mrdocs/Support/ScopeExit.hpp>
2628
#include <clang/AST/AST.h>
2729
#include <clang/AST/Attr.h>

src/lib/AST/ASTVisitor.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
// Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com)
88
// Copyright (c) 2023 Krystian Stasiowski (sdkrystian@gmail.com)
99
// Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com)
10+
// Copyright (c) 2026 Gennaro Prota (gennaro.prota@gmail.com)
1011
//
1112
// Official repository: https://github.com/cppalliance/mrdocs
1213
//

src/lib/AST/ExtractDocComment.cpp

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,28 @@ dumpCommentContent(
9898
namespace mrdocs {
9999
namespace {
100100

101+
// -------- Custom doc commands that set a boolean flag
102+
103+
/** Entry for a custom block command that sets a boolean flag
104+
on DocComment and processes its paragraph content.
105+
106+
To add a new custom flag command, add an entry to the
107+
customFlagCommands array below: both registration and
108+
handling are driven from this single table.
109+
*/
110+
struct CustomFlagCommand
111+
{
112+
char const* name;
113+
bool DocComment::* flag;
114+
};
115+
116+
constexpr CustomFlagCommand customFlagCommands[] = {
117+
{"functionobject", &DocComment::IsFunctionObject},
118+
// "functionobject", without an underscore, isn't particularly
119+
// readable, so provide "functor" as an alternative.
120+
{"functor", &DocComment::IsFunctionObject},
121+
};
122+
101123
// -------- Small helpers
102124

103125
static std::string
@@ -1004,6 +1026,23 @@ class DocCommentVisitor
10041026
MRDOCS_UNREACHABLE();
10051027

10061028
default:
1029+
// Custom flag commands registered at runtime:
1030+
// set the corresponding flag and preserve any
1031+
// paragraph text in the doc comment.
1032+
for (auto const& custom : customFlagCommands)
1033+
{
1034+
if (llvm::StringRef(cmd->Name) == custom.name)
1035+
{
1036+
jd_.*(custom.flag) = true;
1037+
auto p = parseBlock(
1038+
std::in_place_type<doc::ParagraphBlock>);
1039+
if (!p.children.empty())
1040+
{
1041+
jd_.Document.emplace_back(std::move(p));
1042+
}
1043+
return;
1044+
}
1045+
}
10071046
// unsupported → ignore
10081047
return;
10091048
}
@@ -1157,8 +1196,11 @@ class DocCommentVisitor
11571196
void
11581197
initCustomCommentCommands(clang::ASTContext& context)
11591198
{
1160-
(void) context;
1161-
// Reserved for future custom commands registration.
1199+
auto& traits = context.getCommentCommandTraits();
1200+
for (auto const& cmd : customFlagCommands)
1201+
{
1202+
traits.registerBlockCommand(cmd.name);
1203+
}
11621204
}
11631205

11641206
void

0 commit comments

Comments
 (0)