Skip to content

Commit c4dd89a

Browse files
committed
feat: add $meta.type and $meta.bases to all DOM objects
This enables the Handlebars templates to identify object types and base classes at runtime. The '$' prefix follows the JSON Schema conventions for system metadata. Closes issue #1148.
1 parent 086becc commit c4dd89a

7 files changed

Lines changed: 196 additions & 36 deletions

File tree

share/mrdocs/addons/generator/common/partials/symbol/members-table.hbs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
See: https://mrdocs.com/docs/mrdocs/develop/generators.html#dom_reference
1616
--}}
1717
{{#if members}}
18-
{{#if (or (eq members[0].class "name") (any_of_by members "isRegular" "isSeeBelow"))}}
18+
{{#if (or (eq members.[0].$meta.type "Name") (any_of_by members "isRegular" "isSeeBelow"))}}
1919
{{#>markup/h level=(select @root.config.multipage (select traversing-global-namespace 2 1) 2)}}{{title}}{{/markup/h}}
20-
{{~>symbol/detail/members-table-impl members=members isName=(eq members[0].class "name") includeBrief=(select (any_of_by members "doc.brief") true false) title=title}}
20+
{{~>symbol/detail/members-table-impl members=members isName=(eq members.[0].$meta.type "Name") includeBrief=(select (any_of_by members "doc.brief") true false) title=title}}
2121

2222
{{/if}}
2323
{{/if}}

src/lib/Metadata/Name.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
//
66
// Copyright (c) 2023 Krystian Stasiowski (sdkrystian@gmail.com)
77
// Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com)
8+
// Copyright (c) 2026 Gennaro Prota (gennaro.prota@gmail.com)
89
//
910
// Official repository: https://github.com/cppalliance/mrdocs
1011
//
1112

13+
#include <lib/Support/Reflection/MapReflectedType.hpp>
1214
#include <mrdocs/Dom/LazyArray.hpp>
1315
#include <mrdocs/Dom/LazyObject.hpp>
1416
#include <mrdocs/Metadata/DomCorpus.hpp>
@@ -150,7 +152,7 @@ tag_invoke(
150152
Name const& I,
151153
DomCorpus const* domCorpus)
152154
{
153-
io.map("class", std::string("name"));
155+
addMetaObject<Name>(io);
154156
io.map("kind", I.Kind);
155157
visit(I, [domCorpus, &io]<typename T>(T const& t)
156158
{

src/lib/Metadata/Source.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ tag_invoke(
9494
IO& io,
9595
Location const& loc)
9696
{
97-
mapReflectedType(io, loc);
97+
mapReflectedType<true>(io, loc);
9898
}
9999

100100
void

src/lib/Metadata/Symbol/Record.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ tag_invoke(
151151
BaseInfo const& I,
152152
DomCorpus const* domCorpus)
153153
{
154-
mapReflectedType(io, I, domCorpus);
154+
mapReflectedType<true>(io, I, domCorpus);
155155
io.map("isPublic", I.Access == AccessKind::Public);
156156
io.map("isProtected", I.Access == AccessKind::Protected);
157157
io.map("isPrivate", I.Access == AccessKind::Private);

src/lib/Metadata/Type.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
55
//
66
// Copyright (c) 2023 Krystian Stasiowski (sdkrystian@gmail.com)
7+
// Copyright (c) 2026 Gennaro Prota (gennaro.prota@gmail.com)
78
//
89
// Official repository: https://github.com/cppalliance/mrdocs
910
//
1011

12+
#include <lib/Support/Reflection/MapReflectedType.hpp>
1113
#include <mrdocs/Dom/LazyArray.hpp>
1214
#include <mrdocs/Dom/LazyObject.hpp>
1315
#include <mrdocs/Metadata/Name.hpp>
@@ -419,7 +421,7 @@ tag_invoke(
419421
Type const& I,
420422
DomCorpus const* domCorpus)
421423
{
422-
io.map("class", std::string("type"));
424+
addMetaObject<Type>(io);
423425
io.map("kind", I.Kind);
424426
io.map("is-pack", I.IsPackExpansion);
425427
visit(I, [&io, domCorpus]<typename T>(T const& t)

src/lib/Support/Reflection/MapReflectedType.hpp

Lines changed: 163 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#ifndef MRDOCS_LIB_SUPPORT_REFLECTION_MAPREFLECTEDTYPE_HPP
1212
#define MRDOCS_LIB_SUPPORT_REFLECTION_MAPREFLECTEDTYPE_HPP
1313

14+
#include <mrdocs/Dom/Array.hpp>
1415
#include <mrdocs/Dom/LazyArray.hpp>
1516
#include <mrdocs/Metadata/Expression.hpp>
1617
#include <mrdocs/Metadata/Specifiers/ConstexprKind.hpp>
@@ -103,10 +104,6 @@ normalizeMemberName(std::string_view name)
103104
{
104105
return "tag";
105106
}
106-
else if (name == "Class")
107-
{
108-
return "usingClass";
109-
}
110107
else
111108
{
112109
std::string result(name);
@@ -118,6 +115,116 @@ normalizeMemberName(std::string_view name)
118115
}
119116
}
120117

118+
/** Remove namespace qualifiers from a type name.
119+
120+
E.g.: "mrdocs::FunctionSymbol" -> "FunctionSymbol".
121+
*/
122+
constexpr std::string_view
123+
removeNamespaceQualifiers(std::string_view name)
124+
{
125+
constexpr std::string_view scopeDelimiter = "::";
126+
std::string_view::size_type const pos = name.rfind(scopeDelimiter);
127+
if (pos != std::string_view::npos)
128+
{
129+
return name.substr(pos + scopeDelimiter.size());
130+
}
131+
return name;
132+
}
133+
134+
/** Get the unqualified name of a type.
135+
136+
Extracts the name from __PRETTY_FUNCTION__ (Clang/GCC) or __FUNCSIG__ (MSVC).
137+
138+
E.g.: readableTypeName<mrdocs::FunctionSymbol>() -> "FunctionSymbol".
139+
*/
140+
template <typename T>
141+
constexpr std::string_view
142+
readableTypeName()
143+
{
144+
constexpr std::string_view unknown = "Unknown";
145+
146+
#if defined(__clang__) || defined(__GNUC__)
147+
// Clang: "std::string_view mrdocs::detail::readableTypeName() [T = mrdocs::FunctionSymbol]"
148+
// GCC: "constexpr std::string_view mrdocs::detail::readableTypeName() [with T = mrdocs::FunctionSymbol; ...]"
149+
constexpr std::string_view typePrefix = "T = ";
150+
std::string_view const fn = __PRETTY_FUNCTION__;
151+
std::string_view::size_type start = fn.find(typePrefix);
152+
if (start == std::string_view::npos)
153+
{
154+
return unknown;
155+
}
156+
start += typePrefix.size();
157+
std::string_view::size_type const end = fn.find_first_of(";]", start);
158+
std::string_view const name = fn.substr(start, end - start);
159+
return removeNamespaceQualifiers(name);
160+
161+
#elif defined(_MSC_VER)
162+
// MSVC: "... __cdecl mrdocs::detail::readableTypeName<struct mrdocs::FunctionSymbol>(void)"
163+
constexpr std::string_view funcPrefix = "readableTypeName<";
164+
constexpr std::string_view structPrefix = "struct ";
165+
constexpr std::string_view classPrefix = "class ";
166+
constexpr std::string_view enumPrefix = "enum ";
167+
168+
std::string_view const fn = __FUNCSIG__;
169+
std::string_view::size_type start = fn.find(funcPrefix);
170+
if (start == std::string_view::npos)
171+
{
172+
return unknown;
173+
}
174+
start += funcPrefix.size();
175+
176+
// Skip "struct ", "class ", "enum ".
177+
if (fn.substr(start, structPrefix.size()) == structPrefix)
178+
{
179+
start += structPrefix.size();
180+
}
181+
else if (fn.substr(start, classPrefix.size()) == classPrefix)
182+
{
183+
start += classPrefix.size();
184+
}
185+
else if (fn.substr(start, enumPrefix.size()) == enumPrefix)
186+
{
187+
start += enumPrefix.size();
188+
}
189+
190+
std::string_view::size_type const end = fn.find('>', start);
191+
std::string_view const name = fn.substr(start, end - start);
192+
return removeNamespaceQualifiers(name);
193+
194+
#else
195+
return unknown;
196+
#endif
197+
}
198+
199+
/** Collect all base class names recursively.
200+
201+
Traverses the class hierarchy using Boost.Describe and collects
202+
the names of all base classes. This enables templates to check
203+
inheritance relationships (e.g., whether a type is derived from
204+
Symbol or Name).
205+
*/
206+
template <typename T>
207+
std::vector<std::string>
208+
collectBaseNames()
209+
{
210+
std::vector<std::string> names;
211+
if constexpr (boost::describe::has_describe_bases<T>::value)
212+
{
213+
boost::mp11::mp_for_each<boost::describe::describe_bases<T, boost::describe::mod_any_access>>(
214+
[&](auto const& descriptor)
215+
{
216+
using BaseType = typename std::decay_t<decltype(descriptor)>::type;
217+
constexpr std::string_view name = readableTypeName<BaseType>();
218+
names.emplace_back(name);
219+
// Recursively collect the bases of this base class.
220+
std::vector<std::string> const baseNames = collectBaseNames<BaseType>();
221+
names.insert(names.end(), baseNames.cbegin(), baseNames.cend());
222+
}
223+
);
224+
}
225+
return names;
226+
}
227+
121228
/** Map a single member to the IO object.
122229
123230
This function template also decides how to map the member based on its type.
@@ -151,11 +258,45 @@ mapMember(
151258

152259
}
153260

261+
/** Add a $meta object with type information.
262+
263+
Creates a $meta object containing:
264+
- type: The unqualified C++ class name (e.g., "FunctionSymbol").
265+
- bases: Array of base class names (e.g., ["Symbol", "SourceInfo"]).
266+
267+
The bases array allows templates to check inheritance relationships.
268+
For example, a template can verify if an object is derived from Symbol
269+
or Name without knowing the exact derived type.
270+
271+
The bases array is always included (even if empty), so templates can
272+
safely access it.
273+
*/
274+
template <typename T, typename IO>
275+
void
276+
addMetaObject(IO& io)
277+
{
278+
dom::Object meta;
279+
constexpr std::string_view typeName = detail::readableTypeName<T>();
280+
meta.set("type", typeName);
281+
282+
std::vector<std::string> const baseNames = detail::collectBaseNames<T>();
283+
dom::Array bases;
284+
for (std::string const& name : baseNames)
285+
{
286+
bases.push_back(name);
287+
}
288+
meta.set("bases", std::move(bases));
289+
290+
io.map("$meta", meta);
291+
}
292+
154293
/** Automatically map all Boost.Describe'd members of a type to the DOM.
155294
156295
This replaces the manual `tag_invoke()` implementations with a single
157296
call that handles all member mappings via reflection.
158297
298+
@tparam isMostDerived Whether this is the most-derived type.
299+
When true, adds the $meta object.
159300
@param io The IO object to use for mapping.
160301
@param obj The object to be mapped.
161302
@param domCorpus The DomCorpus used to create the DOM values, or a null pointer.
@@ -170,22 +311,25 @@ mapMember(
170311
FunctionSymbol const& I,
171312
DomCorpus const* domCorpus)
172313
{
173-
// First, map base Symbol members.
174-
tag_invoke(t, io, I.asInfo(), domCorpus);
175-
176-
// Then, automatically map all FunctionSymbol-specific members.
177-
mapReflectedType(io, I, domCorpus);
314+
// Automatically map all members including bases.
315+
// Pass true for isMostDerived to add $meta.
316+
mapReflectedType<true>(io, I, domCorpus);
178317
}
179318
@endcode
180319
*/
181-
template <typename IO, typename T>
320+
template <bool isMostDerived, typename IO, typename T>
182321
requires boost::describe::has_describe_members<T>::value
183322
void
184323
mapReflectedType(
185324
IO& io,
186325
T const& obj,
187326
DomCorpus const* domCorpus)
188327
{
328+
if constexpr (isMostDerived)
329+
{
330+
addMetaObject<T>(io);
331+
}
332+
189333
// First, map all bases.
190334
boost::mp11::mp_for_each<boost::describe::describe_bases<T, boost::describe::mod_any_access>>(
191335
[&](auto const& descriptor)
@@ -194,8 +338,8 @@ mapReflectedType(
194338

195339
if constexpr (boost::describe::has_describe_members<BaseType>::value)
196340
{
197-
// Base is described: recurse.
198-
mapReflectedType(io, static_cast<BaseType const&>(obj), domCorpus);
341+
// Base is described: recurse (not most-derived).
342+
mapReflectedType<false>(io, static_cast<BaseType const&>(obj), domCorpus);
199343
}
200344
else
201345
{
@@ -231,16 +375,22 @@ mapReflectedType(
231375
This version passes raw member values to `io.map()`, letting
232376
the IO object handle conversion with its stored context.
233377
378+
@tparam isMostDerived Whether this is the most-derived type (adds $meta if true).
234379
@param io The IO object to use for mapping.
235380
@param obj The object to be mapped.
236381
*/
237-
template <typename IO, typename T>
382+
template <bool isMostDerived, typename IO, typename T>
238383
requires boost::describe::has_describe_members<T>::value
239384
void
240385
mapReflectedType(
241386
IO& io,
242387
T const& obj)
243388
{
389+
if constexpr (isMostDerived)
390+
{
391+
addMetaObject<T>(io);
392+
}
393+
244394
boost::mp11::mp_for_each<boost::describe::describe_members<T, boost::describe::mod_any_access>>(
245395
[&](auto const& descriptor)
246396
{

0 commit comments

Comments
 (0)