Let a class declaration be for a type rather than of one - #53
Merged
Conversation
A generated reflection table has to be reachable from the type it describes, and C++ does that by specialising a template on it: template<> struct Describe<RigidBody>. The AST could say everything such a table contains - namespace-scope inline constexpr arrays of braced lists, designated initialisers, nested lists - and could not say the declaration that anchors them, which made the whole artefact unreachable from the thing it is about. The alternative was naming. A DescribeRigidBody beside RigidBody works, and it is exactly what a lookup by type exists to avoid: every consumer has to spell the convention for itself, and nothing checks that it got it right. ClassDeclaration gains SpecialisationArguments, an ordered list that is empty on an ordinary declaration. Empty is what makes this safe to add: IsSpecialisation is false for every type this library has ever emitted, so nothing moves. The arguments are TypeReference rather than text, which is the one place this departs from CompileTimeAssertion's precedent. An assertion's condition is opaque to everything but the compiler, so a string costs nothing; a specialisation argument is a type, the AST already knows how to be a type, and the comma in Result<Handle, Error> belongs to that type rather than separating two arguments - which text would have to guess at, and a list does not. Only C++ honours it. The other three write the type it was specialised for as a comment above an ordinary declaration, because a generated file that quietly drops what it was for looks like one that still means it. That is now the second node with this shape and the reason is the same both times. Seven tests, and the one that matters as much as the positive cases is that a declaration with no arguments still writes no template: unconditional would have broken every type this library emits. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UGHDsYaaTQdzVR4XBR6miu
A null or empty entry in the document is not an argument, and saying so in a Where rather than in an if inside the loop leaves the loop doing one thing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UGHDsYaaTQdzVR4XBR6miu
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



A generated reflection table has to be reachable from the type it describes, and C++ does that by specialising a template on it:
The AST could already say everything such a table contains — namespace-scope
inline constexprarrays of braced lists, designated initialisers, nested lists one per line — and could not say the declaration that anchors them. That made the whole artefact unreachable from the thing it is about.The alternative was naming. A
DescribeRigidBodybesideRigidBodyworks, and it is exactly what a lookup by type exists to avoid: every consumer has to spell the convention for itself, and nothing checks that it got it right.The change
ClassDeclarationgainsSpecialisationArguments, an ordered list that is empty on an ordinary declaration:Empty is what makes this safe to add.
IsSpecialisationis false for every type this library has ever emitted, so nothing moves.Why the arguments are types and not text
This is the one place it departs from
CompileTimeAssertion's precedent. An assertion's condition is opaque to everything but the compiler, so carrying it as a string costs nothing. A specialisation argument is a type — the AST already knows how to be one, and the comma inResult<Handle, Error>belongs to that type rather than separating two arguments. Text would have to guess; a list does not, and there's a test for exactly that case.One generator honours it, three say what it was
template <>thenstruct Describe<holo::components::RigidBody>// specialised for holo::components::RigidBodyabove an ordinarystruct Describe# specialised for holo::components::RigidBody// specialised for holo::components::RigidBodyA generated file that quietly drops what it was for looks like one that still means it. That is now the second node with this shape, and the reason is the same both times.
Tests
Seven, and the one that matters as much as the positive cases is
Cpp_WritesAnOrdinaryDeclarationWithNone— a declaration with no arguments still writes notemplate. Unconditional would have broken every type this library emits. Also covered: argument order, a nested generic argument kept whole, the comment in all three other languages, YAML round-trip (nested argument included), clone independence, and that an ordinary declaration serialises no key at all.Full suite: 503 passed.
🤖 Generated with Claude Code
https://claude.ai/code/session_01UGHDsYaaTQdzVR4XBR6miu
Generated by Claude Code