From 22ea68c947cc28ecb16ee5f91cf3cf49f76bb4ce Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 14 Sep 2026 10:47:15 +0000 Subject: [PATCH 1/2] Say what a vector quantity's bytes are [patch] The reflection table's `representation` says what the bytes of a member are, and for a quantity it said the number the quantity is stored in. For a magnitude that is right: a Mass over a float is a float. For a vector form it is false - a Velocity3D over a float is three floats, which is a Vector3. False in the way that reads as true, which is what makes it worth a fix rather than a note. A consumer walking the table asks the representation how many lanes a member has. Told Float, Holotype's validator takes a Velocity3D's first four bytes for the whole value and checks one component against the member's range while the other two go unexamined - and its network codec's numeric test has the same shape. A check that quietly examines a third of what it was given is worse than one that declines. So a quantity of two, three or four components reports Vector2, Vector3 or Vector4, and one of zero or one reports its storage. The arity comes from QuantityRegistry, which reads it off the IVectorN the quantity implements, so it cannot disagree with the type it describes. The `kind` is unaffected: all of them are still declared Quantity. That is the whole point of the table carrying both - kind is what the schema said, representation is what the bytes are. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UGHDsYaaTQdzVR4XBR6miu --- Schema.Cpp.Test/QuantityCppTests.cs | 37 +++++++++++++++++++++++++++++ Schema.Cpp/CppReflection.cs | 31 +++++++++++++++++++++--- 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/Schema.Cpp.Test/QuantityCppTests.cs b/Schema.Cpp.Test/QuantityCppTests.cs index 1ed0c07..e778a4d 100644 --- a/Schema.Cpp.Test/QuantityCppTests.cs +++ b/Schema.Cpp.Test/QuantityCppTests.cs @@ -315,6 +315,43 @@ public void ADefaultIsConstructedTheWayTheVocabularyAcceptsIt() AssertCompiles(configured, TargetOptions); } + /// + /// The table says what a quantity's bytes are, which for a vector form is not its storage. + /// + /// + /// A magnitude is one number and reports the number. A vector form is two to four of them side + /// by side, and reporting Float for one would be false about the bytes rather than + /// merely vague - false in the way that reads as true, since a consumer asks the representation + /// how many lanes a member has. Told Float, it takes a Velocity3D's first four + /// bytes for the whole value and checks one component while the other two go unexamined. + /// + [TestMethod] + public void TheTableSaysWhatAQuantitysBytesAre() + { + Schema schema = new(); + SchemaClass body = schema.AddClass("Body".As())!; + + foreach ((string member, string quantity) in new[] + { + ("Mass", "Mass"), + ("Heading", "Heading"), + ("Velocity", "Velocity3D"), + }) + { + body.AddMember(member.As())! + .SetType(new Quantity { QuantityName = quantity.As() }); + } + + string table = Generate(Configured(schema), TargetOptions with { Reflection = true }) + .Single(file => file.Key.Contains("reflection", StringComparison.Ordinal)).Value; + + // Every one of them is declared a Quantity; what differs is what the bytes are. + Assert.AreEqual(3, table.Split(".kind = TypeKind::Quantity").Length - 1); + + Assert.Contains(".representation = TypeKind::Float", table, StringComparison.Ordinal); + Assert.Contains(".representation = TypeKind::Vector3", table, StringComparison.Ordinal); + } + /// /// A schema of one class holding one quantity. /// diff --git a/Schema.Cpp/CppReflection.cs b/Schema.Cpp/CppReflection.cs index be77cfc..d7d64d3 100644 --- a/Schema.Cpp/CppReflection.cs +++ b/Schema.Cpp/CppReflection.cs @@ -130,13 +130,38 @@ internal static string Kind(BaseType type) => { Semantic { Declaration: SchemaSemanticType declaration } => Kind(declaration.Representation()), - // A quantity says what it is stored in directly, so there is no chain to walk: a - // Velocity3D over a float is three floats, and the table's reader needs the float. - Quantity quantity => Kind(quantity.Storage), + Quantity quantity => QuantityRepresentation(quantity), _ => Kind(type), }; + /// + /// What the bytes of a quantity are. + /// + /// + /// + /// A magnitude or a signed scalar is one number, so it is the number it is stored in. A vector + /// form is two to four of them laid out side by side, which is a Vector2, Vector3 + /// or Vector4 - and saying Float for one would be false about the bytes, not + /// merely less specific. + /// + /// + /// It is false in a way that reads as true, which is why it is worth spelling out. A consumer + /// walking the table asks the representation how many lanes a member has; told Float, it + /// takes a Velocity3D's first four bytes for the whole value and checks one component + /// against the member's range while the other two go unexamined. A check that quietly examines + /// a third of what it was given is worse than one that declines. + /// + /// + private static string QuantityRepresentation(Quantity quantity) => + quantity.Resolved?.Components switch + { + 2 => Kind(new Vector2()), + 3 => Kind(new Vector3()), + 4 => Kind(new Vector4()), + _ => Kind(quantity.Storage), + }; + /// /// How a member's interpolation is named in the table. /// From 6b6a844fe8f191a6efe43e24f52625a8699453ca Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 14 Sep 2026 11:10:19 +0000 Subject: [PATCH 2/2] Pin every arity a quantity's representation can have SonarCloud's quality gate failed the branch at 75% coverage on new code against a required 80%. The two uncovered lines were the `Vector2` and `Vector4` arms of `QuantityRepresentation`, which the test reached neither of. Covering them is worth more than the gate. The rule the table states is that a quantity's representation is the shape of its bytes, and the test asserted it for a magnitude and a three-component vector -- a sample of the rule, from which the other two arms have to be inferred. All four arities are real: the vocabulary has 148 magnitudes, 27 signed scalars, and 8, 22 and 7 quantities of two, three and four components, so `Velocity2D` and `Velocity4D` pin the remaining arms with types a schema may actually name rather than with defensive code. Verified by regression rather than by the gate: with the `Vector2` arm returning the storage instead, `Drift` reports `Float` and the test fails. 102 of 102 pass with the arm restored. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UGHDsYaaTQdzVR4XBR6miu --- Schema.Cpp.Test/QuantityCppTests.cs | 34 +++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/Schema.Cpp.Test/QuantityCppTests.cs b/Schema.Cpp.Test/QuantityCppTests.cs index e778a4d..80fed5d 100644 --- a/Schema.Cpp.Test/QuantityCppTests.cs +++ b/Schema.Cpp.Test/QuantityCppTests.cs @@ -324,6 +324,12 @@ public void ADefaultIsConstructedTheWayTheVocabularyAcceptsIt() /// merely vague - false in the way that reads as true, since a consumer asks the representation /// how many lanes a member has. Told Float, it takes a Velocity3D's first four /// bytes for the whole value and checks one component while the other two go unexamined. + /// + /// All five arities the vocabulary has are pinned rather than the two that would carry the + /// argument. Every one of them is a real quantity - 148 magnitudes, 27 signed scalars, and 8, + /// 22 and 7 of two, three and four components - so none of these arms is defensive code, and + /// a reader of this test can see the whole rule instead of inferring it from a sample of it. + /// /// [TestMethod] public void TheTableSaysWhatAQuantitysBytesAre() @@ -331,12 +337,19 @@ public void TheTableSaysWhatAQuantitysBytesAre() Schema schema = new(); SchemaClass body = schema.AddClass("Body".As())!; - foreach ((string member, string quantity) in new[] - { - ("Mass", "Mass"), - ("Heading", "Heading"), - ("Velocity", "Velocity3D"), - }) + // A magnitude and a signed scalar are both one number, which is why they report the same + // thing from different arities: what the representation answers is the shape of the bytes, + // not how many directions the quantity has. + (string Member, string Quantity, string Representation)[] members = + [ + ("Mass", "Mass", "Float"), + ("Heading", "Heading", "Float"), + ("Drift", "Velocity2D", "Vector2"), + ("Velocity", "Velocity3D", "Vector3"), + ("Worldline", "Velocity4D", "Vector4"), + ]; + + foreach ((string member, string quantity, _) in members) { body.AddMember(member.As())! .SetType(new Quantity { QuantityName = quantity.As() }); @@ -346,10 +359,13 @@ public void TheTableSaysWhatAQuantitysBytesAre() .Single(file => file.Key.Contains("reflection", StringComparison.Ordinal)).Value; // Every one of them is declared a Quantity; what differs is what the bytes are. - Assert.AreEqual(3, table.Split(".kind = TypeKind::Quantity").Length - 1); + Assert.AreEqual(members.Length, table.Split(".kind = TypeKind::Quantity").Length - 1); - Assert.Contains(".representation = TypeKind::Float", table, StringComparison.Ordinal); - Assert.Contains(".representation = TypeKind::Vector3", table, StringComparison.Ordinal); + foreach ((string member, _, string representation) in members) + { + Assert.Contains($".representation = TypeKind::{representation}", table, + StringComparison.Ordinal, $"{member} should be {representation}"); + } } ///