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
183322void
184323mapReflectedType (
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
239384void
240385mapReflectedType (
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