Two things a schema could not say about the code it describes - #159
Merged
Conversation
Both promote a fact that was previously the generator's to invent. A class can promise it travels as bytes. SchemaClass.TravelsAsBytes says an instance is copied whole - across a language boundary, into a save file, onto the wire - without anyone reading a field on the way. Member order stops being merely meaningful and becomes load-bearing: reordering members changes the binary layout of every saved instance and every packet carrying one. The promise constrains the members, and validation enforces it: a String, an Array, a Span, an Optional, a Result, an Interface, or an Object naming a class that makes no such promise are all refused, because each would arrive as an address that means nothing where it landed. A Handle is accepted - an index and the generation its slot had is bytes whatever it identifies, which is the whole reason a component holds one rather than a reference. A Vector is accepted when its components are and a Semantic when its representation is. The check reads the flag off a named class rather than walking into it, so two classes holding each other validates rather than hanging the validator. Reading through a semantic type needed the same care for a different reason: a refinement cycle makes Representation() return another Semantic, so following it recursed with no bottom. It stops at the first representation that is still semantic, which is exactly the case ValidateUnderlyingType already reports. The schema names the enum a failed Result carries. Schema.ErrorType holds it once for every fallible signature, for the same reason the other four conventions are global: a signature that could choose its own error type is one that has to be read to find out. An enum rather than any type, because an error is one of a closed set of reasons and a caller switching on the reason needs the set to be enumerable. A schema that never returns a Result needs none; one that does is reported at the signature, which is the declaration whose meaning is incomplete. Nothing about a CLR type says either thing, so generated C# carries SchemaTravelsAsBytesAttribute and ClrTypeImporter reads it back, the same arrangement the member metadata uses. The round-trip test now carries a class that makes the promise, and fails if either half stops doing its part - checked by breaking the importer and watching it fail rather than assuming. The attribute records the promise rather than enforcing it. A sequential-layout struct would enforce it and would also turn every generated component from a class into a value type, which is a decision about the C# API rather than about the schema. formatVersion moves to 5: both are additive and the class flag is omitted when false, but a version 4 reader would drop either and go on generating - a type that quietly stopped promising its layout, or a fallible call with nothing to say when it fails. 397 tests pass in Schema.Test (21 new), 201 in SchemaEditor.Test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AhoPJ5AbxP8QEBNxPQYEPk
|
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.



Both promote a fact that was previously the generator's to invent. They are the two gaps the C++ target document exposed that were genuinely the schema's rather than a language's.
A class can promise it travels as bytes
{ "members": [ ... ], "travelsAsBytes": true, "name": "RigidBody" }An instance is copied whole — across a language boundary, into a save file, onto the wire — without anyone reading a field on the way. Member order stops being merely meaningful and becomes load-bearing: reordering members changes the binary layout of every saved instance and every packet carrying one, so it is a change to the class rather than a tidy-up.
The promise constrains the members, and validation enforces it:
StringArraySpanOptional,ResultInterfaceObjectnaming a class that makes no such promiseA
Handleis accepted: an index and the generation its slot had is bytes whatever it identifies, which is the whole reason a component holds one rather than a reference. AVectoris accepted when its components are, and aSemanticwhen its representation is.Two ways this could have looped forever
The check reads the flag off a named class rather than walking into it, so two classes holding each other validates rather than hanging the validator.
Reading through a semantic type needed the same care for a different reason, and this one I got wrong first: a refinement cycle makes
Representation()return anotherSemantic, so following it recursed with no bottom. It now stops at the first representation that is still semantic — which is exactly the caseValidateUnderlyingTypealready reports as a cycle. Both cases have a test, and I confirmed the semantic one crashes with a stack overflow without the guard rather than assuming it would.The schema names the enum a failed
Resultcarries{ "errorType": "ErrorCode", "enums": [ { "name": "ErrorCode", "values": [ "NotFound", "OutOfCapacity" ] } ] }Held once for every fallible signature, for the same reason the other four conventions are global: a signature that could choose its own error type is one that has to be read to find out, and every interface language that allowed that grew per-declaration annotations until it was a worse version of the language it described.
An enum rather than any type, because an error is one of a closed set of reasons — which is what an enum is — and a caller switching on the reason needs the set to be enumerable.
A schema that never returns a
Resultneeds none, and declaring none is not a mistake in itself. A schema that does is reported at the signature rather than at the root: that is the declaration whose meaning is incomplete, since a generator reaching it has aResultto emit and nothing to put in its error position.Keeping the C# round trip honest
Nothing about a CLR type says either thing, so generated C# carries
SchemaTravelsAsBytesAttributeandClrTypeImporterreads it back — the same arrangement the member metadata already uses. The generate-compile-reimport test now carries a class that makes the promise and fails if either half stops doing its part; I checked that by breaking the importer and watching it fail, rather than trusting a passing test to be a meaningful one.The attribute records the promise rather than enforcing it. A sequential-layout
structwould enforce it in C# and would also turn every generated component from a class into a value type — a decision about the C# API rather than about the schema, and not one this attribute is entitled to make.Format version
formatVersionmoves to 5. Both are additive and the class flag is omitted when false, but a version 4 reader would drop either and go on generating: a type that quietly stopped promising its layout, or a fallible call with nothing to say when it fails.Testing
397 tests pass in
Schema.Test(21 new acrossTravelsAsBytesTestsandErrorTypeTests), 201 inSchemaEditor.Test, whole solution builds with 0 warnings.Noticed, not touched
Schema.CanResolvePathsis a computed property that is being serialised into the file. It predates this change and fixing it would alter every written document, so it is out of scope here — flagging it rather than folding it in.🤖 Generated with Claude Code
https://claude.ai/code/session_01AhoPJ5AbxP8QEBNxPQYEPk
Generated by Claude Code