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
36 changes: 36 additions & 0 deletions include/mrdocs/Metadata/DomCorpus.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,42 @@ class MRDOCS_DECL
virtual
dom::Value
getDocComment(DocComment const& jd) const;

/** Check whether a symbol is extracted in Regular mode.

@return `false` when the ID is invalid, the symbol is not in
the corpus, or its extraction mode is anything other than
@ref ExtractionMode::Regular. Used to decide whether a
symbol's documentation page will actually be rendered.

@param id The symbol to check.
*/
bool
isRegular(SymbolID const& id) const;

/** Return specializations of a primary class or function template.

Walks the corpus once on first access and caches an inverse
index from primary ID to the list of specialization IDs.

@param primary The ID of the primary template.
@return A `dom::Array` of resolved symbol objects, or an
empty array when the primary has no specializations.
*/
dom::Value
getSpecializations(SymbolID const& primary) const;

/** Return the deduction guides for a primary class template.

Walks the corpus once on first access and caches an inverse
index from deduced primary ID to the list of guide IDs.

@param primary The ID of the primary class template.
@return A `dom::Array` of resolved guide objects, or an
empty array when the primary has no deduction guides.
*/
dom::Value
getDeductionGuides(SymbolID const& primary) const;
};

/** Return a list of the parent symbols of the specified Info.
Expand Down
28 changes: 28 additions & 0 deletions include/mrdocs/Metadata/Symbol/Function.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <mrdocs/Metadata/Symbol/SymbolBase.hpp>
#include <mrdocs/Metadata/Template.hpp>
#include <mrdocs/Support/Describe.hpp>
#include <mrdocs/Support/MapReflectedType.hpp>
#include <string>
#include <vector>

Expand Down Expand Up @@ -164,6 +165,33 @@ MRDOCS_DESCRIBE_STRUCT(
RefQualifier, Explicit, Attributes, FunctionObjectImpl)
)

/** Map a FunctionSymbol to a dom::Object with computed isSpecialization.
@param io The IO object to map into.
@param I The FunctionSymbol to map.
@param domCorpus The DomCorpus context.
*/
template <typename IO>
void
tag_invoke(
dom::LazyObjectMapTag,
IO& io,
FunctionSymbol const& I,
DomCorpus const* domCorpus)
{
mapReflectedType<true>(io, I, domCorpus);
// True only when this specialization's primary will be rendered:
// an orphan specialization (primary excluded) stays in the parent's
// listing so it remains reachable from the index.
io.map(
"isSpecialization",
I.Template &&
I.Template->specializationKind() != TemplateSpecKind::Primary &&
domCorpus->isRegular(I.Template->Primary));
io.defer("specializations", [&I, domCorpus] {
return domCorpus->getSpecializations(I.id);
});
}

/** Map a vector of parameters to a @ref dom::Value object.

@param v The output parameter to receive the dom::Value.
Expand Down
14 changes: 14 additions & 0 deletions include/mrdocs/Metadata/Symbol/Record.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,20 @@ tag_invoke(
{
mapReflectedType<true>(io, I, domCorpus);
io.map("defaultAccess", std::string(getDefaultAccessString(I.KeyKind)));
// True only when this specialization's primary will be rendered:
// an orphan specialization (primary excluded) stays in the parent's
// listing so it remains reachable from the index.
io.map(
"isSpecialization",
I.Template &&
I.Template->specializationKind() != TemplateSpecKind::Primary &&
domCorpus->isRegular(I.Template->Primary));
io.defer("specializations", [&I, domCorpus] {
return domCorpus->getSpecializations(I.id);
});
io.defer("deductionGuides", [&I, domCorpus] {
return domCorpus->getDeductionGuides(I.id);
});
}

/** View all record members across access levels.
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 @@ -145,6 +145,16 @@
{{/each}}
|===

{{/if}}
{{! Specializations of this primary template }}
{{#if symbol.specializations}}
{{>symbol/members-table members=symbol.specializations title="Specializations"}}

{{/if}}
{{! Deduction guides for this class template }}
{{#if symbol.deductionGuides}}
{{>symbol/members-table members=symbol.deductionGuides title="Deduction Guides"}}

{{/if}}
{{! Using symbols }}
{{#if symbol.shadowDeclarations}}
Expand Down
17 changes: 10 additions & 7 deletions share/mrdocs/addons/generator/common/partials/symbol/tranche.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@

Each value in the tranche is a list of symbols that belong to the tranche.

Template specializations and deduction guides are filtered out: they are
listed instead on the primary template's own page, via the dedicated
"Specializations" / "Deduction Guides" sections.

Expected Context: {Tranche Object}

Example:
Expand All @@ -17,21 +21,20 @@
{{#if is-namespace}}
{{>symbol/members-table members=tranche.namespaces title="Namespaces"}}
{{>symbol/members-table members=tranche.namespaceAliases title="Namespace Aliases"}}
{{>symbol/members-table members=(concat tranche.records tranche.typedefs) title=(concat (select label (concat label " ") "") "Types")}}
{{>symbol/members-table members=(concat (reject_by tranche.records "isSpecialization") tranche.typedefs) title=(concat (select label (concat label " ") "") "Types")}}
{{>symbol/members-table members=tranche.enums title=(concat (select label (concat label " ") "") "Enums")}}
{{>symbol/members-table members=tranche.functions title="Functions"}}
{{>symbol/members-table members=(reject_by tranche.functions "isSpecialization") title="Functions"}}
{{>symbol/members-table members=tranche.variables title="Variables"}}
{{>symbol/members-table members=tranche.concepts title="Concepts"}}
{{>symbol/members-table members=tranche.guides title=(concat (select label (concat label " ") "") "Deduction Guides")}}
{{>symbol/members-table members=tranche.usings title=(concat (select label (concat label " ") "") "Using Declarations")}}
{{else}}
{{>symbol/members-table members=tranche.namespaceAliases title="Namespace Aliases"}}
{{>symbol/members-table members=(concat tranche.records tranche.typedefs) title=(concat (select label (concat label " ") "") "Types")}}
{{>symbol/members-table members=(concat (reject_by tranche.records "isSpecialization") tranche.typedefs) title=(concat (select label (concat label " ") "") "Types")}}
{{>symbol/members-table members=tranche.enums title=(concat (select label (concat label " ") "") "Enums")}}
{{>symbol/members-table members=tranche.functions title=(concat (select label (concat label " ") "") "Member Functions")}}
{{>symbol/members-table members=tranche.staticFunctions title=(concat (select label (concat label " ") "") "Static Member Functions")}}
{{>symbol/members-table members=(reject_by tranche.functions "isSpecialization") title=(concat (select label (concat label " ") "") "Member Functions")}}
{{>symbol/members-table members=(reject_by tranche.staticFunctions "isSpecialization") title=(concat (select label (concat label " ") "") "Static Member Functions")}}
{{>symbol/members-table members=tranche.variables title=(concat (select label (concat label " ") "") "Data Members")}}
{{>symbol/members-table members=tranche.staticVariables title=(concat (select label (concat label " ") "") "Static Data Members")}}
{{>symbol/members-table members=tranche.aliases title=(concat (select label (concat label " ") "") "Aliases")}}
{{>symbol/members-table members=tranche.usings title=(concat (select label (concat label " ") "") "Using Declarations")}}
{{/if}}
{{/if}}
12 changes: 12 additions & 0 deletions share/mrdocs/addons/generator/html/partials/symbol.html.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,18 @@
</table>
</div>
{{/if}}
{{! Specializations of this primary template }}
{{#if symbol.specializations}}
<div>
{{>symbol/members-table members=symbol.specializations title="Specializations"}}
</div>
{{/if}}
{{! Deduction guides for this class template }}
{{#if symbol.deductionGuides}}
<div>
{{>symbol/members-table members=symbol.deductionGuides title="Deduction Guides"}}
</div>
{{/if}}
{{! Using symbols }}
{{#if symbol.shadowDeclarations}}
<div>
Expand Down
159 changes: 159 additions & 0 deletions src/lib/Metadata/DomCorpus.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com)
// Copyright (c) 2023 Krystian Stasiowski (sdkrystian@gmail.com)
// Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com)
// Copyright (c) 2026 Gennaro Prota (gennaro.prota@gmail.com)
//
// Official repository: https://github.com/cppalliance/mrdocs
//
Expand All @@ -15,17 +16,135 @@
#include <mrdocs/Metadata.hpp>
#include <mrdocs/Metadata/DocComment.hpp>
#include <mrdocs/Metadata/DomCorpus.hpp>
#include <algorithm>
#include <memory>
#include <mutex>
#include <ranges>
#include <unordered_map>
#include <vector>

namespace mrdocs {

namespace {

// Return the SymbolID of the primary class template that a
// deduction guide deduces, or `SymbolID::invalid` when it cannot
// be determined.
SymbolID
deducedPrimaryID(GuideSymbol const& guide)
{
if (guide.Deduced.valueless_after_move()
|| !guide.Deduced->isNamed())
{
return SymbolID::invalid;
}
NamedType const& namedType = guide.Deduced->asNamed();
if (namedType.Name.valueless_after_move())
{
return SymbolID::invalid;
}
return namedType.Name->id;
}

} // (unnamed)

class DomCorpus::Impl
{
using value_type = std::weak_ptr<dom::ObjectImpl>;

DomCorpus const& domCorpus_;
Corpus const& corpus_;

// Inverse indices from primary template ID to the IDs of the
// symbols that reference it as their primary. Built lazily on
// first access; immutable thereafter.
mutable std::once_flag indexFlag_;
mutable std::unordered_map<SymbolID, std::vector<SymbolID>>
specializationsByPrimary_;
mutable std::unordered_map<SymbolID, std::vector<SymbolID>>
deductionGuidesByPrimary_;

void
buildIndex() const
{
for (Symbol const& I : corpus_)
{
if (I.Extraction != ExtractionMode::Regular)
{
continue;
}
if (I.isRecord())
{
RecordSymbol const& record = I.asRecord();
if (record.Template
&& record.Template->Primary != SymbolID::invalid)
{
specializationsByPrimary_[record.Template->Primary]
.push_back(record.id);
}
}
else if (I.isFunction())
{
FunctionSymbol const& func = I.asFunction();
if (func.Template
&& func.Template->Primary != SymbolID::invalid)
{
specializationsByPrimary_[func.Template->Primary]
.push_back(func.id);
}
}
else if (I.isGuide())
{
GuideSymbol const& guide = I.asGuide();
SymbolID const primary = deducedPrimaryID(guide);
if (primary != SymbolID::invalid)
{
deductionGuidesByPrimary_[primary].push_back(guide.id);
}
}
}
sortByReferentName(specializationsByPrimary_);
sortByReferentName(deductionGuidesByPrimary_);
}

void
sortByReferentName(
std::unordered_map<SymbolID, std::vector<SymbolID>>& index) const
{
auto byName = [this](SymbolID const& lhs, SymbolID const& rhs)
{
Symbol const* lhsInfo = corpus_.find(lhs);
Symbol const* rhsInfo = corpus_.find(rhs);
if (!lhsInfo || !rhsInfo)
{
return lhs < rhs;
}
if (lhsInfo->Name != rhsInfo->Name)
{
return lhsInfo->Name < rhsInfo->Name;
}
return lhs < rhs;
};
for (std::vector<SymbolID>& ids : std::views::values(index))
{
std::ranges::sort(ids, byName);
}
}

std::vector<SymbolID> const&
lookup(
std::unordered_map<SymbolID, std::vector<SymbolID>> const& index,
SymbolID const& primary) const
{
static std::vector<SymbolID> const empty;
auto const it = index.find(primary);
if (it == index.end())
{
return empty;
}
return it->second;
}

public:
Impl(
DomCorpus const& domCorpus,
Expand Down Expand Up @@ -56,6 +175,20 @@ class DomCorpus::Impl
MRDOCS_CHECK_OR(I, {});
return create(*I);
}

std::vector<SymbolID> const&
getSpecializationIDs(SymbolID const& primary) const
{
std::call_once(indexFlag_, [this] { buildIndex(); });
return lookup(specializationsByPrimary_, primary);
}

std::vector<SymbolID> const&
getDeductionGuideIDs(SymbolID const& primary) const
{
std::call_once(indexFlag_, [this] { buildIndex(); });
return lookup(deductionGuidesByPrimary_, primary);
}
};

DomCorpus::
Expand Down Expand Up @@ -105,6 +238,32 @@ getDocComment(DocComment const&) const
return nullptr;
}

bool
DomCorpus::
isRegular(SymbolID const& id) const
{
if (!id)
{
return false;
}
Symbol const* sym = getCorpus().find(id);
return sym && sym->Extraction == ExtractionMode::Regular;
}

dom::Value
DomCorpus::
getSpecializations(SymbolID const& primary) const
{
return dom::LazyArray(impl_->getSpecializationIDs(primary), this);
}

dom::Value
DomCorpus::
getDeductionGuides(SymbolID const& primary) const
{
return dom::LazyArray(impl_->getDeductionGuideIDs(primary), this);
}

dom::Array
getParents(DomCorpus const& C, Symbol const& I)
{
Expand Down
Loading
Loading