feat: schema generation#1178
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 #1178 +/- ##
===========================================
- Coverage 83.16% 83.10% -0.06%
===========================================
Files 35 34 -1
Lines 3658 3599 -59
Branches 843 823 -20
===========================================
- Hits 3042 2991 -51
+ Misses 409 407 -2
+ Partials 207 201 -6
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
An automated preview of the documentation is available at https://1178.mrdocs.prtest2.cppalliance.org/index.html If more commits are pushed to the pull request, the docs will rebuild at the same URL. 2026-06-23 16:25:51 UTC |
cdda298 to
b9fac7c
Compare
|
Path coverage is OK, isn't it? I added the PR description. |
alandefreitas
left a comment
There was a problem hiding this comment.
What about documentation? How do I use the feature? What about the schema in the documentation? Do we have no schema files in the repository? How do we check if the files are up to date in CI? Some of the changes in the golden files are also kind of weird (like ids changing and things like that and some groups are just empty, which could probably be removed in the schema - I'm not sure).
The PR is great though. The only reason I left many comment is because the PR is huge. 😅
| // ------------------------------------------------------- | ||
| // Header | ||
| // ------------------------------------------------------- | ||
| line("#"); |
There was a problem hiding this comment.
This main function is kind of hard for a human to read. Is this one of these cases where instead of using reflection to improve the schema, we make the function very complex to match the bad pattern we used to have before?
There was a problem hiding this comment.
Indeed. The function is long because the schema is currently describing what XMLWriter happens to emit. That's bad.
There was a problem hiding this comment.
So has this one been resolved?
There was a problem hiding this comment.
RncSchemaWriter.hpp no longer exists, because we emit RNG directly now. RngSchemaWriter.cpp should be relatively easy to read.
73561af to
f9d5f63
Compare
f9d5f63 to
fb9c795
Compare
`Symbol`'s `tag_invoke` overload added four convenience booleans --
`isRegular`, `isSeeBelow`, `isImplementationDefined`, `isDependency` --
outside the described struct. Because reflection couldn't see them, the
JSON schema writer had to mirror the same hardcoded list.
This removes those booleans and lets templates compare the described
enum directly; e.g.:
{{#if isRegular}} -> {{#if (eq extraction "regular")}}
For the two `filter_by` / `any_of_by` sites that previously keyed on the
booleans, the helper family gains variadic siblings `filter_by_eq` and
`any_of_by_eq` -- signature `(container, key, value1, value2, ...)`.
This addresses review feedback on PR cppalliance#1178.
fb9c795 to
0ae30ec
Compare
`Symbol`'s `tag_invoke` overload added four convenience booleans --
`isRegular`, `isSeeBelow`, `isImplementationDefined`, `isDependency` --
outside the described struct. Because reflection couldn't see them, the
JSON schema writer had to mirror the same hardcoded list.
This removes those booleans and lets templates compare the described
enum directly; e.g.:
{{#if isRegular}} -> {{#if (eq extraction "regular")}}
For the two `filter_by` / `any_of_by` sites that previously keyed on the
booleans, the helper family gains variadic siblings `filter_by_eq` and
`any_of_by_eq` -- signature `(container, key, value1, value2, ...)`.
This addresses review feedback on PR cppalliance#1178.
0ae30ec to
903033b
Compare
`Symbol`'s `tag_invoke` overload added four convenience booleans --
`isRegular`, `isSeeBelow`, `isImplementationDefined`, `isDependency` --
outside the described struct. Because reflection couldn't see them, the
JSON schema writer had to mirror the same hardcoded list.
This removes those booleans and lets templates compare the described
enum directly; e.g.:
{{#if isRegular}} -> {{#if (eq extraction "regular")}}
For the two `filter_by` / `any_of_by` sites that previously keyed on the
booleans, the helper family gains variadic siblings `filter_by_eq` and
`any_of_by_eq` -- signature `(container, key, value1, value2, ...)`.
This addresses review feedback on PR cppalliance#1178.
903033b to
019d019
Compare
|
Thanks, removing the Is there a way to let reflection drive the missing parts that are manually written right now? If that's not possible right now, could we open an issue to track it? This is exactly what reflection was meant to solve. Separately, as I mentioned in the previous comment, a few of the golden XML changes still looked odd to me. |
I opened issue #1215 for this.
I think you are referring to the IDs of global overload sets? Those change because the hashed string changes: |
So it is not possible now? Why?
Well... I exemplified it with too many ID changes that seemed arbitrary (you just explained it) and new empty groups, but I was referring to the sheer number of changes. In other words, the categories of changes (the examples I mentioned, but really any recurring changes) should be determined, understood, and evaluated individually.
What's in the hashed string again? I thought the access let alone the serialization of toString(access) wouldn't affect the individual strings getting hashed. |
`Symbol`'s `tag_invoke` overload added four convenience booleans --
`isRegular`, `isSeeBelow`, `isImplementationDefined`, `isDependency` --
outside the described struct. Because reflection couldn't see them, the
JSON schema writer had to mirror the same hardcoded list.
This removes those booleans and lets templates compare the described
enum directly; e.g.:
{{#if isRegular}} -> {{#if (eq extraction "regular")}}
For the two `filter_by` / `any_of_by` sites that previously keyed on the
booleans, the helper family gains variadic siblings `filter_by_eq` and
`any_of_by_eq` -- signature `(container, key, value1, value2, ...)`.
This addresses review feedback on PR cppalliance#1178.
019d019 to
8cbb13b
Compare
By "now" you mean "in this PR"? That's possible but requires implementing
There are two "kinds" of IDs: For "real" symbols (functions, records, etc.), the string that gets hashed is the Clang USR. The access isn't part of the USR, so those IDs are unaffected by this PR's changes. Overload sets, instead, have no USR, and are hashed from a composed key that does include the access. From src/lib/Metadata/Symbol/Overloads.cpp: When P.S.: I think the doc-comment attached to |
Add a --schemas[=<dir>] option that writes a JSON Schema file (mrdocs-dom-schema.json) describing every object and field available to Handlebars templates. The schema is derived from the same compile-time reflection metadata used by MapReflectedType.hpp, so it stays in sync with the code automatically. The option requires no config file or source files — it writes the schema and exits immediately.
OperatorKind was the only enum serialized as a raw integer. All other enums serialize as human-readable strings. Change tag_invoke to use getOperatorName, consistent with the rest.
…ClassKind
Replace manual toString and tag_invoke overloads with
MRDOCS_DESCRIBE_ENUM for four enums whose kebab-case names match the
existing string representations.
The XML writer now emits these fields (e.g. <access>public</access>,
<constexpr>constexpr</constexpr>) where they were previously silently
skipped. None/none sentinel values are suppressed via a generic
has_none_enumerator check.
TypeKind stays manual because toKebabCase("LValueReference") produces
"l-value-reference", not the established "lvalue-reference".
--schemas now writes both mrdocs-dom-schema.json (Handlebars DOM) and mrdocs.rnc (XML output). The XML schema mirrors XMLWriter.cpp's serialization.
… --schemas This guarantees the RELAX NG schema stays in sync with the C++ type definitions. Every CI run now validates all golden test XML files against a schema derived from the same reflection metadata that produces the XML.
This adds a small lookup table keyed by (typeName, memberName) carrying hand-written descriptions for the DOM seen by Handlebars templates which become "description" fields in the JSON schema.
The newly-added JsonEmitter.hpp duplicated functionality already provided by `dom::JSON::stringify`. This drops the new header and uses the existing `stringify`.
The new function name is more descriptive. Contextually, this expands the doc-comment to spell out what the function returns.
The schema headers are only used by ToolMain and the unit tests. They don't need to live under include/mrdocs/.
XMLWriter silently dropped three kinds of data: - `SpecializationName::TemplateArgs`: `Polymorphic<Name>` fell into `writePolymorphic`'s generic branch with `T` deduced to the base Name (`Polymorphic::operator*` returns a base reference), so only `Name`'s own described members were emitted. Template specializations rendered as `<name>SmallVector</name>` rather than `<specialization-name>SmallVector<...></specialization-name>`. - `NoexceptInfo`: no `MRDOCS_DESCRIBE_STRUCT` - it serializes to a string via `tag_invoke`, which `writeElement` had no path for. Functions with noexcept-specifications produced no `<noexcept>` element. - `ExplicitInfo`: same pattern. explicit constructors and conversion operators produced no `<explicit>` element. This adds a `NameKind` branch in `writePolymorphic` (using a "-name" suffix on the kind tag to disambiguate from the `Name::Identifier` field), and adds a `NoexceptInfo`/`ExplicitInfo` branch in `writeElement` that emits the `toString()` value as text, skipping the empty case. Also update `RncSchemaWriter` to match: `Polymorphic<Name>` -> `AnyName`, drop `NoexceptInfo`/`ExplicitInfo` from the omit list. Most XML golden fixtures regenerate to include the now-emitted elements.
The XMLTags helper in src/lib/Gen/Xml/ contained two pieces of machinery that aren't specific to XML doc generation: an XML escaper (xmlEscape) and a tag/indent stream emitter. A RELAX NG schema writer, which will be introduced with the next commit, also needs them. So, we factored them out.
The --schemas option now writes a RELAX NG XML document directly. This gets rid of the trang RNC->RNG conversion step. Which, in turn, means we no longer need Java. The bootstrap script dependency on Java will be removed with the next commit.
`Symbol`'s `tag_invoke` overload added four convenience booleans --
`isRegular`, `isSeeBelow`, `isImplementationDefined`, `isDependency` --
outside the described struct. Because reflection couldn't see them, the
JSON schema writer had to mirror the same hardcoded list.
This removes those booleans and lets templates compare the described
enum directly; e.g.:
{{#if isRegular}} -> {{#if (eq extraction "regular")}}
For the two `filter_by` / `any_of_by` sites that previously keyed on the
booleans, the helper family gains variadic siblings `filter_by_eq` and
`any_of_by_eq` -- signature `(container, key, value1, value2, ...)`.
This addresses review feedback on PR cppalliance#1178.
8cbb13b to
1716719
Compare
The bootstrap script checked for Java because the build needed it to run trang.jar, which converted the RNC schema to RNG. But trang.jar is no longer used (the --schemas option directly emits a .rng), so Java is no longer needed.
The schema writer emits a `description` field for every type and every described member it touches, looking it up from DomDescriptions.hpp. When the lookup failed, it silently returned an empty string, so forgetting a description caused an undocumented `$defs` entry to be silently emitted. This adds an assert that fires when the lookup of a type or a member finds no entry. This allowed finding many missing entries, which have been added. Any future described type added to the schema trips the assert at build time until descriptions for it and its members are provided.
…ation targets Both schema files are now committed under docs/, parallel to the existing docs/mrdocs.schema.json (the YAML config schema) and are exposed to the Antora docs site as downloadable attachments. Two new CTest targets, `rng-schema-check` and `dom-schema-check`, run `cmake -E compare_files` between the freshly-generated schemas in the build tree and the checked-in copies; drift fails the test. The schemas custom_command is lifted out of the LibXml2 conditional so the freshness checks run independently of whether libxml2 is available. .gitattributes pins the two schema files to LF line endings, because --schemas emits LF line endings and we do a byte-for-byte comparison.
The page's Schema section still pointed at the old hand-written mrdocs.rnc. The --schemas option now emits mrdocs.rng directly, so update the root-element example and the canonical-schema reference to mrdocs.rng and point readers at the new Output Schemas page.
This replaces the hand-written DOM reference with one generated from mrdocs-dom-schema.json. A new Antora extension walks the file's `$defs` in source order and emits one section per type.
`Symbol`'s `tag_invoke` overload added four convenience booleans --
`isRegular`, `isSeeBelow`, `isImplementationDefined`, `isDependency` --
outside the described struct. Because reflection couldn't see them, the
JSON schema writer had to mirror the same hardcoded list.
This removes those booleans and lets templates compare the described
enum directly; e.g.:
{{#if isRegular}} -> {{#if (eq extraction "regular")}}
For the two `filter_by` / `any_of_by` sites that previously keyed on the
booleans, the helper family gains variadic siblings `filter_by_eq` and
`any_of_by_eq` -- signature `(container, key, value1, value2, ...)`.
This addresses review feedback on PR cppalliance#1178.
Rebasing onto develop brought two model changes the schema generators did not yet account for. Attributes became a polymorphic family (a base attribute plus one type per kind, some carrying an argument) instead of a flat string list, and records and functions gained the specialization-placement fields: whether a specialization is listed on its primary, the list of specializations, and a record's deduction guides. This teaches both schema writers about them.
The committed mrdocs.rng and mrdocs-dom-schema.json now cover the attribute family. The golden XML files that could not be merged during the rebase are regenerated to match this branch's output: structured names, the `noexcept` and `explicit` specifiers, the described-enum elements, and the RELAX NG schema location.
1716719 to
21452d4
Compare
Interesting. Thanks
Agree. For overloads (or any entity clang doesn't have a USR for), we use a variant that's just a hash of something else. Something like "This is calculated as the SHA1 digest of the USR or other SymbolIDs" or something like that.
Mmmm... This is a hard question. Because if the scope as defined by the PR is "feat: schema generation", it's maybe out of scope because MRDOCS_DESCRIBE_COMPUTED_PROPERTIES is about "improving schema generation" rather than "schema generation". An improvement is about how well you do the thing, but it's definitely about the scope of the thing. But it can also be left out because things don't have to be perfect. Or maybe what you really mean is "isn't there already too much code in here" rather than "is the scope" improving schema generation" inside the scope "schema generation", to which the answer would be true. But from that point of view, this PR became a proof of concept for something else that is the much bigger problem: if we're replacing a hand-curated adoc version of the reference with a ~30k LOC diff "Hand-curated descriptions for the JSON Schema produced by @ref DomSchemaWriter" that comes with an application that coverts it back adoc, then I'm not sure we have good motivation for the work here. If we had to literally list every member in the cpp file and it was still hand-curated, I'm not sure we'd have achieved anything, since we'd have the same result but much more code to maintain. Maybe the difference is that now we get compiler errors? Maybe I thought we would at least get computed properties, which diverge a little more, but these would also be a lot of work and we got into the question about scope. So... at the same time the scope question is valid, I don't think this is a scope issue at all. I see it as an issue of basic usefulness / making sense / motivation. Because I don't see this PR as "feat: schema generation", as the title describes. Because "feat: schema generation" is a feature that already exists. It's best, it's a refactor from one manual strategy to another. The feat schema already exists and is, unfortunately, done manually. The one done here is also manual. So I originally saw this PR as a refactor to improve the schema generation by using reflection to automate it and keeping it up to date in the future (although I wasn't sure what solution we could have for the brief and descriptions). In other words, if the PR is about making it automatic in a smarter way rather than manual, and it still needs to hard-code not only the exceptions but all fields of each type, then at best, this is a proof of concept that we don't have the technology to make it automatic yet. Because we can keep things hard-coded by not doing anything. It means reflection doesn't help us with that. To be honest, I don't have a good solution for any of that. If anything, it seems like MRDOCS_DESCRIBE_COMPUTED_PROPERTIES is what should ship first (because it's what actually useful for reflection so the objects finally have everything without relying on anything manual) and automatic schema generation would only ship after that if and only if we still find a solution to how to describe these things. Another thing that makes this issue quite low ROI is that the real unknown is the script generator, and now that everything is described, we could just adjust the reference section to say the thing is a |
|
Makes sense. I'll split On the descriptions: I agree the single source of truth should be the header doc-comments. I'll look at sourcing the field descriptions from the headers rather than keeping a hand-written table, leaving only the projection divergences (casing, computed properties, |
On a technical level, I agree. We should first reach a level where everything can be done automatically. At best, I would just mention that these other properties can also be made automatic. We also have the doc-comments for the computed properties, so this can be done automatically. And casing obviously can be done automatically. And if there's any other divergence, it could also be resolved before we attempt this (for instance, we have an issue for the "is-*" parameters). In other words, this refactor should be a low-hanging-fruit celebration that reflection made everything consistent across our application. If we haven't reached this point yet, we can just make it consistent before we try to improve this. Otherwise, the refactor doesn't give us much ROI. We can already point users to the reference section today, showing it to them with the projections is nice but noone else claimouring for that, and the investment to create and maintain a code with exceptions to match the old behavior is very high. |


This adds a
--schemas[=<dir>]option that emits two schemas derived from the reflection metadata in the source tree: mrdocs-dom-schema.json, which describes the Handlebars DOM produced by the generator, and mrdocs.rng (RelaxNG), which describes the XML output. The hand-written mrdocs.rnc is removed and replaced by the generated .rng everywhere CI used it, eliminating drift between the schema and the actual output.The schemas are produced by walking the reflection types and member descriptions in the source tree, so they stay in sync with the code automatically as new metadata types are added. The DOM reference documentation is switched from a hand-maintained page to one generated by an Antora extension that reads the JSON schema.
A generic XML emitter is factored out of the existing
XMLTags/XMLWritercode into src/lib/Support/Xml.{cpp,hpp}, and several reflection types (AccessKind,ConstexprKind,OperatorKind,StorageClassKind,ParamDirection,TypeKind) are made describable through newMapReflectedType/MergeReflectedType/TypeTraitsheaders in include/mrdocs/Support/. Three small fixes ride along:OperatorKindis serialized as a string in the DOM (not the underlying enum), the XML emitter no longer drops template args /noexcept/explicitspecifiers, andasserts fire when a reflection type or member lacks a description.Changes
--schemaswired through tool/ToolArgs.{cpp,hpp} and tool/ToolMain.cpp. Several specifier headers in include/mrdocs/Metadata/ refactored alongside newMapReflectedType,MergeReflectedType, andTypeTraitsheaders in include/mrdocs/Support/. Schema-writer headers live in src/lib/Schemas/ (deliberately kept out of the public API).noexcept,explicit) that the old XML writer was silently dropping. These are intentional output corrections.--schemas. docs/extensions/dom-reference.js renders the DOM reference page from the JSON schema; the corresponding hand-written section was removed from generators.adoc. mrdocs-dom-schema.json and mrdocs.rng are also linked as downloadable attachments.CMakeLists.txtupdated so mrdocs.rng and mrdocs-dom-schema.json are emitted and verified during the build. The Java prerequisite (previously needed to process mrdocs.rnc) is removed.noexcept, andexplicitattributes that were silently omitted before; consumers parsing the XML and not expecting those attributes may need to adjust.Testing
noexcept/explicitwill cause the golden tests to fail.assertfires when a reflection type or member lacks a description, so any future addition to the reflection metadata that ships without schema metadata trips immediately in CI rather than silently producing an under-specified schema.Documentation
Two pieces:
--schemasoption.