Skip to content

Commit 956839a

Browse files
committed
feat: move template specializations and deduction guides off parent scope page
Class template specializations, function template specializations, and deduction guides used to share the enclosing scope's listing with their primary template. Users have repeatedly reported this as confusing: `vector` and `vector<int>` appearing side by side in the namespace index reads as if they were independent siblings, and a primary's variants were nowhere on its own page. Specializations now appear in a dedicated "Specializations" section on the primary's documentation page, and deduction guides in a "Deduction Guides" section on the deduced class's page. The parent scope's listing carries only the primary itself. An orphan specialization (one whose primary has been excluded from extraction) stays in the parent's listing so the index can still reach it. The corpus is unchanged: XML output and consumers of the metadata schema see the same data as before. The new sections are derived in the presentation layer. Closes issue #1154.
1 parent 9b193a5 commit 956839a

74 files changed

Lines changed: 2319 additions & 488 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

include/mrdocs/Metadata/DomCorpus.hpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,42 @@ class MRDOCS_DECL
104104
virtual
105105
dom::Value
106106
getDocComment(DocComment const& jd) const;
107+
108+
/** Check whether a symbol is extracted in Regular mode.
109+
110+
@return `false` when the ID is invalid, the symbol is not in
111+
the corpus, or its extraction mode is anything other than
112+
@ref ExtractionMode::Regular. Used to decide whether a
113+
symbol's documentation page will actually be rendered.
114+
115+
@param id The symbol to check.
116+
*/
117+
bool
118+
isRegular(SymbolID const& id) const;
119+
120+
/** Return specializations of a primary class or function template.
121+
122+
Walks the corpus once on first access and caches an inverse
123+
index from primary ID to the list of specialization IDs.
124+
125+
@param primary The ID of the primary template.
126+
@return A `dom::Array` of resolved symbol objects, or an
127+
empty array when the primary has no specializations.
128+
*/
129+
dom::Value
130+
getSpecializations(SymbolID const& primary) const;
131+
132+
/** Return the deduction guides for a primary class template.
133+
134+
Walks the corpus once on first access and caches an inverse
135+
index from deduced primary ID to the list of guide IDs.
136+
137+
@param primary The ID of the primary class template.
138+
@return A `dom::Array` of resolved guide objects, or an
139+
empty array when the primary has no deduction guides.
140+
*/
141+
dom::Value
142+
getDeductionGuides(SymbolID const& primary) const;
107143
};
108144

109145
/** Return a list of the parent symbols of the specified Info.

include/mrdocs/Metadata/Symbol/Function.hpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <mrdocs/Metadata/Symbol/SymbolBase.hpp>
2323
#include <mrdocs/Metadata/Template.hpp>
2424
#include <mrdocs/Support/Describe.hpp>
25+
#include <mrdocs/Support/MapReflectedType.hpp>
2526
#include <string>
2627
#include <vector>
2728

@@ -164,6 +165,33 @@ MRDOCS_DESCRIBE_STRUCT(
164165
RefQualifier, Explicit, Attributes, FunctionObjectImpl)
165166
)
166167

168+
/** Map a FunctionSymbol to a dom::Object with computed isSpecialization.
169+
@param io The IO object to map into.
170+
@param I The FunctionSymbol to map.
171+
@param domCorpus The DomCorpus context.
172+
*/
173+
template <typename IO>
174+
void
175+
tag_invoke(
176+
dom::LazyObjectMapTag,
177+
IO& io,
178+
FunctionSymbol const& I,
179+
DomCorpus const* domCorpus)
180+
{
181+
mapReflectedType<true>(io, I, domCorpus);
182+
// True only when this specialization's primary will be rendered:
183+
// an orphan specialization (primary excluded) stays in the parent's
184+
// listing so it remains reachable from the index.
185+
io.map(
186+
"isSpecialization",
187+
I.Template &&
188+
I.Template->specializationKind() != TemplateSpecKind::Primary &&
189+
domCorpus->isRegular(I.Template->Primary));
190+
io.defer("specializations", [&I, domCorpus] {
191+
return domCorpus->getSpecializations(I.id);
192+
});
193+
}
194+
167195
/** Map a vector of parameters to a @ref dom::Value object.
168196
169197
@param v The output parameter to receive the dom::Value.

include/mrdocs/Metadata/Symbol/Record.hpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,20 @@ tag_invoke(
130130
{
131131
mapReflectedType<true>(io, I, domCorpus);
132132
io.map("defaultAccess", std::string(getDefaultAccessString(I.KeyKind)));
133+
// True only when this specialization's primary will be rendered:
134+
// an orphan specialization (primary excluded) stays in the parent's
135+
// listing so it remains reachable from the index.
136+
io.map(
137+
"isSpecialization",
138+
I.Template &&
139+
I.Template->specializationKind() != TemplateSpecKind::Primary &&
140+
domCorpus->isRegular(I.Template->Primary));
141+
io.defer("specializations", [&I, domCorpus] {
142+
return domCorpus->getSpecializations(I.id);
143+
});
144+
io.defer("deductionGuides", [&I, domCorpus] {
145+
return domCorpus->getDeductionGuides(I.id);
146+
});
133147
}
134148

135149
/** View all record members across access levels.

share/mrdocs/addons/generator/adoc/partials/symbol.adoc.hbs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,16 @@
145145
{{/each}}
146146
|===
147147

148+
{{/if}}
149+
{{! Specializations of this primary template }}
150+
{{#if symbol.specializations}}
151+
{{>symbol/members-table members=symbol.specializations title="Specializations"}}
152+
153+
{{/if}}
154+
{{! Deduction guides for this class template }}
155+
{{#if symbol.deductionGuides}}
156+
{{>symbol/members-table members=symbol.deductionGuides title="Deduction Guides"}}
157+
148158
{{/if}}
149159
{{! Using symbols }}
150160
{{#if symbol.shadowDeclarations}}

share/mrdocs/addons/generator/common/partials/symbol/tranche.hbs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
88
Each value in the tranche is a list of symbols that belong to the tranche.
99
10+
Template specializations and deduction guides are filtered out: they are
11+
listed instead on the primary template's own page, via the dedicated
12+
"Specializations" / "Deduction Guides" sections.
13+
1014
Expected Context: {Tranche Object}
1115
1216
Example:
@@ -17,21 +21,20 @@
1721
{{#if is-namespace}}
1822
{{>symbol/members-table members=tranche.namespaces title="Namespaces"}}
1923
{{>symbol/members-table members=tranche.namespaceAliases title="Namespace Aliases"}}
20-
{{>symbol/members-table members=(concat tranche.records tranche.typedefs) title=(concat (select label (concat label " ") "") "Types")}}
24+
{{>symbol/members-table members=(concat (reject_by tranche.records "isSpecialization") tranche.typedefs) title=(concat (select label (concat label " ") "") "Types")}}
2125
{{>symbol/members-table members=tranche.enums title=(concat (select label (concat label " ") "") "Enums")}}
22-
{{>symbol/members-table members=tranche.functions title="Functions"}}
26+
{{>symbol/members-table members=(reject_by tranche.functions "isSpecialization") title="Functions"}}
2327
{{>symbol/members-table members=tranche.variables title="Variables"}}
2428
{{>symbol/members-table members=tranche.concepts title="Concepts"}}
25-
{{>symbol/members-table members=tranche.guides title=(concat (select label (concat label " ") "") "Deduction Guides")}}
2629
{{>symbol/members-table members=tranche.usings title=(concat (select label (concat label " ") "") "Using Declarations")}}
2730
{{else}}
2831
{{>symbol/members-table members=tranche.namespaceAliases title="Namespace Aliases"}}
29-
{{>symbol/members-table members=(concat tranche.records tranche.typedefs) title=(concat (select label (concat label " ") "") "Types")}}
32+
{{>symbol/members-table members=(concat (reject_by tranche.records "isSpecialization") tranche.typedefs) title=(concat (select label (concat label " ") "") "Types")}}
3033
{{>symbol/members-table members=tranche.enums title=(concat (select label (concat label " ") "") "Enums")}}
31-
{{>symbol/members-table members=tranche.functions title=(concat (select label (concat label " ") "") "Member Functions")}}
32-
{{>symbol/members-table members=tranche.staticFunctions title=(concat (select label (concat label " ") "") "Static Member Functions")}}
34+
{{>symbol/members-table members=(reject_by tranche.functions "isSpecialization") title=(concat (select label (concat label " ") "") "Member Functions")}}
35+
{{>symbol/members-table members=(reject_by tranche.staticFunctions "isSpecialization") title=(concat (select label (concat label " ") "") "Static Member Functions")}}
3336
{{>symbol/members-table members=tranche.variables title=(concat (select label (concat label " ") "") "Data Members")}}
3437
{{>symbol/members-table members=tranche.staticVariables title=(concat (select label (concat label " ") "") "Static Data Members")}}
3538
{{>symbol/members-table members=tranche.aliases title=(concat (select label (concat label " ") "") "Aliases")}}
3639
{{>symbol/members-table members=tranche.usings title=(concat (select label (concat label " ") "") "Using Declarations")}}
37-
{{/if}}
40+
{{/if}}

share/mrdocs/addons/generator/html/partials/symbol.html.hbs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,18 @@
192192
</table>
193193
</div>
194194
{{/if}}
195+
{{! Specializations of this primary template }}
196+
{{#if symbol.specializations}}
197+
<div>
198+
{{>symbol/members-table members=symbol.specializations title="Specializations"}}
199+
</div>
200+
{{/if}}
201+
{{! Deduction guides for this class template }}
202+
{{#if symbol.deductionGuides}}
203+
<div>
204+
{{>symbol/members-table members=symbol.deductionGuides title="Deduction Guides"}}
205+
</div>
206+
{{/if}}
195207
{{! Using symbols }}
196208
{{#if symbol.shadowDeclarations}}
197209
<div>

src/lib/Metadata/DomCorpus.cpp

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
// Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com)
77
// Copyright (c) 2023 Krystian Stasiowski (sdkrystian@gmail.com)
88
// Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com)
9+
// Copyright (c) 2026 Gennaro Prota (gennaro.prota@gmail.com)
910
//
1011
// Official repository: https://github.com/cppalliance/mrdocs
1112
//
@@ -15,17 +16,135 @@
1516
#include <mrdocs/Metadata.hpp>
1617
#include <mrdocs/Metadata/DocComment.hpp>
1718
#include <mrdocs/Metadata/DomCorpus.hpp>
19+
#include <algorithm>
1820
#include <memory>
21+
#include <mutex>
22+
#include <ranges>
23+
#include <unordered_map>
24+
#include <vector>
1925

2026
namespace mrdocs {
2127

28+
namespace {
29+
30+
// Return the SymbolID of the primary class template that a
31+
// deduction guide deduces, or `SymbolID::invalid` when it cannot
32+
// be determined.
33+
SymbolID
34+
deducedPrimaryID(GuideSymbol const& guide)
35+
{
36+
if (guide.Deduced.valueless_after_move()
37+
|| !guide.Deduced->isNamed())
38+
{
39+
return SymbolID::invalid;
40+
}
41+
NamedType const& namedType = guide.Deduced->asNamed();
42+
if (namedType.Name.valueless_after_move())
43+
{
44+
return SymbolID::invalid;
45+
}
46+
return namedType.Name->id;
47+
}
48+
49+
} // (unnamed)
50+
2251
class DomCorpus::Impl
2352
{
2453
using value_type = std::weak_ptr<dom::ObjectImpl>;
2554

2655
DomCorpus const& domCorpus_;
2756
Corpus const& corpus_;
2857

58+
// Inverse indices from primary template ID to the IDs of the
59+
// symbols that reference it as their primary. Built lazily on
60+
// first access; immutable thereafter.
61+
mutable std::once_flag indexFlag_;
62+
mutable std::unordered_map<SymbolID, std::vector<SymbolID>>
63+
specializationsByPrimary_;
64+
mutable std::unordered_map<SymbolID, std::vector<SymbolID>>
65+
deductionGuidesByPrimary_;
66+
67+
void
68+
buildIndex() const
69+
{
70+
for (Symbol const& I : corpus_)
71+
{
72+
if (I.Extraction != ExtractionMode::Regular)
73+
{
74+
continue;
75+
}
76+
if (I.isRecord())
77+
{
78+
RecordSymbol const& record = I.asRecord();
79+
if (record.Template
80+
&& record.Template->Primary != SymbolID::invalid)
81+
{
82+
specializationsByPrimary_[record.Template->Primary]
83+
.push_back(record.id);
84+
}
85+
}
86+
else if (I.isFunction())
87+
{
88+
FunctionSymbol const& func = I.asFunction();
89+
if (func.Template
90+
&& func.Template->Primary != SymbolID::invalid)
91+
{
92+
specializationsByPrimary_[func.Template->Primary]
93+
.push_back(func.id);
94+
}
95+
}
96+
else if (I.isGuide())
97+
{
98+
GuideSymbol const& guide = I.asGuide();
99+
SymbolID const primary = deducedPrimaryID(guide);
100+
if (primary != SymbolID::invalid)
101+
{
102+
deductionGuidesByPrimary_[primary].push_back(guide.id);
103+
}
104+
}
105+
}
106+
sortByReferentName(specializationsByPrimary_);
107+
sortByReferentName(deductionGuidesByPrimary_);
108+
}
109+
110+
void
111+
sortByReferentName(
112+
std::unordered_map<SymbolID, std::vector<SymbolID>>& index) const
113+
{
114+
auto byName = [this](SymbolID const& lhs, SymbolID const& rhs)
115+
{
116+
Symbol const* lhsInfo = corpus_.find(lhs);
117+
Symbol const* rhsInfo = corpus_.find(rhs);
118+
if (!lhsInfo || !rhsInfo)
119+
{
120+
return lhs < rhs;
121+
}
122+
if (lhsInfo->Name != rhsInfo->Name)
123+
{
124+
return lhsInfo->Name < rhsInfo->Name;
125+
}
126+
return lhs < rhs;
127+
};
128+
for (std::vector<SymbolID>& ids : std::views::values(index))
129+
{
130+
std::ranges::sort(ids, byName);
131+
}
132+
}
133+
134+
std::vector<SymbolID> const&
135+
lookup(
136+
std::unordered_map<SymbolID, std::vector<SymbolID>> const& index,
137+
SymbolID const& primary) const
138+
{
139+
static std::vector<SymbolID> const empty;
140+
auto const it = index.find(primary);
141+
if (it == index.end())
142+
{
143+
return empty;
144+
}
145+
return it->second;
146+
}
147+
29148
public:
30149
Impl(
31150
DomCorpus const& domCorpus,
@@ -56,6 +175,20 @@ class DomCorpus::Impl
56175
MRDOCS_CHECK_OR(I, {});
57176
return create(*I);
58177
}
178+
179+
std::vector<SymbolID> const&
180+
getSpecializationIDs(SymbolID const& primary) const
181+
{
182+
std::call_once(indexFlag_, [this] { buildIndex(); });
183+
return lookup(specializationsByPrimary_, primary);
184+
}
185+
186+
std::vector<SymbolID> const&
187+
getDeductionGuideIDs(SymbolID const& primary) const
188+
{
189+
std::call_once(indexFlag_, [this] { buildIndex(); });
190+
return lookup(deductionGuidesByPrimary_, primary);
191+
}
59192
};
60193

61194
DomCorpus::
@@ -105,6 +238,32 @@ getDocComment(DocComment const&) const
105238
return nullptr;
106239
}
107240

241+
bool
242+
DomCorpus::
243+
isRegular(SymbolID const& id) const
244+
{
245+
if (!id)
246+
{
247+
return false;
248+
}
249+
Symbol const* sym = getCorpus().find(id);
250+
return sym && sym->Extraction == ExtractionMode::Regular;
251+
}
252+
253+
dom::Value
254+
DomCorpus::
255+
getSpecializations(SymbolID const& primary) const
256+
{
257+
return dom::LazyArray(impl_->getSpecializationIDs(primary), this);
258+
}
259+
260+
dom::Value
261+
DomCorpus::
262+
getDeductionGuides(SymbolID const& primary) const
263+
{
264+
return dom::LazyArray(impl_->getDeductionGuideIDs(primary), this);
265+
}
266+
108267
dom::Array
109268
getParents(DomCorpus const& C, Symbol const& I)
110269
{

0 commit comments

Comments
 (0)