feat: move template specializations and deduction guides off parent scope page#1199
Conversation
✨ Highlights
🧾 Changes by Scope
🔝 Top Files
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #1199 +/- ##
========================================
Coverage 82.12% 82.12%
========================================
Files 33 33
Lines 3149 3149
Branches 734 734
========================================
Hits 2586 2586
Misses 387 387
Partials 176 176
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
An automated preview of the documentation is available at https://1199.mrdocs.prtest2.cppalliance.org/index.html If more commits are pushed to the pull request, the docs will rebuild at the same URL. 2026-05-25 09:24:03 UTC |
fb11020 to
956839a
Compare
|
This is adding exceptions to reflection. It’s reintroducing the exact problem we were fixing with reflection. It’d be easier to just store the specialization and deduction guides again in the corpus as members of the records and functions. This way, everyone has this data readily available in O(1), and the template changes are trivial. If we need exceptions to reflection, it means the data model is bad. |
|
Also, since we touched on this topic, I think we ended up forgetting about the lazy DOM object directly based on reflection. That could make things much faster. |
5dec630 to
cc36d55
Compare
|
Please check the new approach. As it concerns building DOM objects lazily, I opened an issue: #1211. |
What is it? |
It adds new members to Of course, the bespoke (! :-)) |
alandefreitas
left a comment
There was a problem hiding this comment.
The ASTVisitor can already know if something is a specialization. In fact, even the templates know this in O(1), but I was surprised I didn't see this convenience flag set during the extraction phase, since it needs no info about other types.
Unless you are foreseeing a case I didn't think about: what happens with a library that provides specializations for symbols outside itself. For instance, if my library provides a specialization for std::hash, where would I see this specialization under the new model?
| specializations (primary excluded from extraction) keep | ||
| the flag `false` so they remain reachable from the parent. | ||
| */ | ||
| bool IsSpecialization = false; |
There was a problem hiding this comment.
This one seems redundant. Doesn't the template info give us that in O(1)?
| {{>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")}} |
There was a problem hiding this comment.
This is precisely the issue we discussed in the other PR, where we noticed that removing the "is*" items makes it more inconvenient. We need a solution to pass predicates to these kinds of helpers or we'll have to keep creating is* members. But is* members is a problem for reflection.
|
|
||
| hbs.registerHelper("filter_by", filter_by_fn); | ||
|
|
||
| static auto reject_by_fn = dom::makeVariadicInvocable([]( |
There was a problem hiding this comment.
I've never seen a view by this name. Maybe "remove_if"? We'd have to check what the options are and how they compare to what we have (which are Python-inspired right now). But I've never seen this name for this operation.
There was a problem hiding this comment.
Thanks for the careful pass. All four points addressed.
(1) Function.hpp + the extraction-phase observation + the std::hash question
IsSpecialization was badly named: it didn't mean "is this a template specialization", which the AST already answers in O(1) via Template->specializationKind(). It actually meant:
(a) the symbol is a template specialization, and (b) its primary is being extracted in Regular mode.
I've renamed the field to IsListedOnPrimary and expanded the doc-comment on both RecordSymbol and FunctionSymbol to spell out both conditions and call out the presentation-layer purpose.
std::hash<MyType> exercises exactly the orphan branch: std::hash isn't extracted, so condition (b) fails, the flag stays false, the specialization stays in your library's namespace listing.
(2) The is*-as-filter pattern
Renaming the field to isListedOnPrimary is a partial mitigation - the template now reads "drop the entries that are listed on a primary's page," which at least is no longer encoding a structural property as a Boolean field. But the deeper point stands: filtering by is* fields is a recurring tax on the data model.
For this PR, I'm keeping the renamed flag - the predicate-passing redesign is its own design problem (subexpressions in Handlebars, how it composes with the existing filter_by/filter_out shape, implications for the lazy-DOM work in #1211). Opened #1213 to track it, so the conversation has a home.
(3) The helper name
Renamed reject_by to filter_out, which mirrors the filter_* prefix of the existing filter_by.
f93594e to
ff4f6fc
Compare
|
Thanks. The PR is going to look great. Regarding 1) and 2), this is much better. Do specializations provide cheap access to the primary? Is this something we need? And if so, wouldn't IsListedOnPrimary be also O(1)? I agree it's O(1), but annoying to calculate from templates. But it's something worth discussing, and this seems to come up recurrently. I think there was also a question about ASTVisitor, but I guess the answer is implied in the change of semantics. Let me know if I'm wrong. Regarding 3) I think this is much better. But I still don't think this matches anything I've seen in any programming language. Especially in our case, where we're (mostly) taking inspiration from Python and JavaScript (so users can more easily remember the helper names). It's also uncommon to have a double negation in a function name. To give historical context, the problem here is that we're trying to replicate the names used in Python and JavaScript. And in both of these languages, we have a function called The problem now is that it's hard to apply this pattern for the negative because these languages don't have a function to filter the negative. What you do in these languages is you use the same I think some languages have
To be honest, I think I would just apologize and go with reject_by again. What do you think? |
|
On The reason it's a corpus field today is purely that templates can't conveniently express the second half. Handlebars has no "look up a symbol by ID" helper, so although the computation is O(1) in C++ terms, from a template's perspective it might as well be impossible without a precomputed value. That's exactly the recurrent pattern you're pointing at: any time we want a template to make a decision that depends on another symbol, we end up baking the answer into the corpus as an #1213 is where the design problem lives, and where the corpus-lookup helper would land alongside predicate-passing in On the ASTVisitor question: you're right - the change of semantics is exactly the answer. The flag's value depends on the primary's On |
…cope 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 relationship is stored in the corpus: each primary record or function template carries the IDs of its specializations (and, for class templates, of its deduction guides), and each specialization carries an `IsSpecialization` flag. The lists are populated by a finalizer pass and exposed via reflection, so the XML output gains matching `<specializations>`, `<deduction-guides>`, and `<is-specialization>` elements and the DOM/Handlebars layer renders the new sections through plain field access. Closes issue cppalliance#1154.
ff4f6fc to
8094506
Compare
|
One more idea we could explore is having some kind of trait that lets us express/describe computed properties for objects, in addition to the object's own properties. This way, we can define operations as functions in the .hpp of that symbol and MRDOCS_DESCRIBE_WHATEVER_NAME_FOR_THIS the list of computed properties of that type. This way, we have a clear separation of things. The XML generator can ignore the computed properties because they're cheap to compute, and the Handlebars generators can receive the symbol with their predetermined computed options that are cheap and annoying to compute. Because these are properly described, the schema file would reflect them perfectly with and without it for the Handlebars schema and for the XML schema. The documentation could even make the distinction clear. |
|
Nice. Yet another idea could be using the same trick as
|
Yes. Maybe I didn't express myself very well, but that's exactly what I had in mind (maybe MRDOCS_DESCRIBE_COMPUTED_PROPERTIES). Then all of the important "is_" functions can just become computed properties. This is important because even when we have helpers that take predicates, these would be much slower than using these computed properties directly. So this would be extremely useful for the default templates, which we don't want to be slow. |
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:
vectorandvector<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 primary-to-variant link is corpus-resident:
RecordSymbolandFunctionSymbolgainSpecializations(and, on records,DeductionGuides) lists plus anIsListedOnPrimaryflag, populated by a newSpecializationFinalizerpass. Every Handlebars template, schema consumer, and downstream tool that reads the corpus sees the new shape automatically through reflection.Closes issue #1154.
[danger skip docs].
Summary
Moves class template specializations, function template specializations, and deduction guides off the enclosing scope's listing and onto the primary template's own documentation page. Closes #1154.
Specializations and primaries used to share the enclosing scope's listing, so
vectorandvector<int>appeared side by side in the namespace index. Readers reported this as confusing, since the two are not independent siblings, and the primary's own page did not mention its variants at all.After this PR:
The primary-to-variant link is corpus-resident:
RecordSymbolandFunctionSymbolgainSpecializations(and, on records,DeductionGuides) lists plus anIsListedOnPrimaryflag. Populated by a newSpecializationFinalizerpass. Every Handlebars template, schema consumer, and downstream tool that reads the corpus sees the new shape automatically through reflection.Changes
src/lib/Metadata/Finalizers/SpecializationFinalizer.{cpp,hpp}populates the back-pointers and sorts them.include/mrdocs/Metadata/Symbol/Function.hppandinclude/mrdocs/Metadata/Symbol/Record.hppdeclare the new fields and add them to theirMRDOCS_DESCRIBE_STRUCTlists so reflection picks them up.src/lib/Support/Handlebars.cppgains afilter_outhelper (mirroring the existingfilter_by) used by the parent-tranche template to drop entries listed elsewhere. Templates updated undershare/mrdocs/addons/:common/partials/symbol/tranche.hbs(the per-tranche listing that no longer prints specializations alongside the primary), and the per-formatadoc/partials/symbol.adoc.hbsandhtml/partials/symbol.html.hbsrendering the new "Specializations" / "Deduction Guides" sections.test-files/golden-tests/symbols/,test-files/golden-tests/config/,test-files/golden-tests/templates/,test-files/golden-tests/javadoc/, andtest-files/golden-tests/filters/to reflect the new layout: specializations and deduction guides moved off parent listings and onto primary pages. The.xmlfixtures also gain new tags (<specializations>,<deduction-guides>,<is-listed-on-primary>) emitted by the reflection-driven XML writer for the new fields. Each updated fixture is an intentional rendering or schema-output change, not a regeneration.<specializations>,<deduction-guides>,<is-listed-on-primary>); readers that don't know about them are unaffected. The rendered HTML / adoc output for templates changes: downstream tooling that scrapes the parent scope's listing and expects to find specializations alongside the primary will need to look on the primary's own page instead.Testing
.cppinput and asserts the resulting output byte-for-byte. The newSpecializationFinalizeris exercised through these.test-files/golden-tests/on every build, so the new expectations stay enforced going forward.Documentation
No user-facing documentation page is added (
[danger skip docs]). The change is a layout / discoverability improvement for the generated documentation: it does not introduce a new option, flag, or configuration knob a user would need to look up. The new "Specializations" / "Deduction Guides" sections are self-describing in the rendered output.