Skip to content

test(quantities): generator-output invariants — no duplicate signatures, commutative * (closes #57)#74

Merged
matt-edmondson merged 1 commit into
vectorsfrom
claude/issue-57-no-duplicates
May 10, 2026
Merged

test(quantities): generator-output invariants — no duplicate signatures, commutative * (closes #57)#74
matt-edmondson merged 1 commit into
vectorsfrom
claude/issue-57-no-duplicates

Conversation

@matt-edmondson
Copy link
Copy Markdown
Contributor

Summary

Closes #57.

The original concern was that QuantitiesGenerator's string-keyed dedup ({op}:{left}:{right}:{ret} etc.) could let two methods with the same name + parameter types land on the same generated type, or could let one direction of a commutative cross-dimensional multiplication get silently dropped. The C# compiler catches identical signatures at build time, but neither property was tested explicitly.

This PR adds two reflection-based invariant tests in Semantics.Test/Quantities/GeneratorOutputInvariantTests.cs:

  1. NoDuplicatePublicStaticMethodsOrOperatorsPerGeneratedType — for every generated type implementing IVector0..IVector4 in ktsu.Semantics.Quantities, no two public static methods (including operator overloads) share the same name + parameter type list. Walks the runtime assembly so the test sees what consumers would see.
  2. EveryCrossDimensionalMultiplicationHasBothOperandOrders — for every observed op_Multiply(A, B) → C, the swapped pair op_Multiply(B, A) → C must also exist. Locks in the commutativity that CollectAllOperators already emits via the AddOp forward+commutative pair. A regression that drops one direction would mean accel * mass stops compiling — this test catches that before users do.

Same-type operators (Length * Length = Area) are exempt from the second test because the swap is identity. Cross/dot products aren't covered here: cross is anti-commutative by design (A × B ≠ B × A), and dot products are emitted as A.Dot(B) / B.Dot(A) on separate types rather than operator pairs.

Test plan

  • dotnet build Semantics.SourceGenerators and dotnet build Semantics.Quantities are clean.
  • dotnet testSemantics.Test cannot restore in this sandbox (Microsoft.NETCore.App.Host.ubuntu.24.04-x64 not in feeds); both tests are reflection-only with no extra dependencies, so CI is the validator.

Why two tests instead of one

The first test would silently always pass on a successful build (the compiler already enforces unique signatures). The second test asserts an actual generator-behaviour invariant that is expressible at runtime — both directions of every cross-dim multiplication exist. Together they cover both halves of #57's concern: "no duplicates" and "no missing pairs".

https://claude.ai/code/session_01Tj63Rddvs9frqLUgsjNEP5


Generated by Claude Code

…es, commutative * (closes #57)

Adds Semantics.Test/Quantities/GeneratorOutputInvariantTests.cs with
two reflection-based invariant tests against the compiled generator
output:

1. NoDuplicatePublicStaticMethodsOrOperatorsPerGeneratedType
   For every generated type implementing IVector0..IVector4 in
   ktsu.Semantics.Quantities, no two public static methods (including
   operator overloads) share the same name + parameter type list. The
   C# compiler already enforces this when the generator emits — the
   test makes the property explicit and regression-proof if
   QuantitiesGenerator's dedup keys ever change.

2. EveryCrossDimensionalMultiplicationHasBothOperandOrders
   For every observed `op_Multiply(A, B) -> C` across the generated
   surface, the corresponding `op_Multiply(B, A) -> C` must also exist.
   This locks in the commutativity property the generator builds via
   the AddOp pair in CollectAllOperators (forward + commutative). A
   dedup-key regression that drops one direction would cause user code
   like `accel * mass` to stop compiling — this test catches it before
   it reaches a downstream consumer.

Same-type operators (T * T -> U, e.g. Length * Length = Area) are
exempt because the swap is identity. Cross/Dot products are not
checked here: cross is anti-commutative by design, and dot is emitted
as A.Dot(B) / B.Dot(A) on separate types rather than as operator pairs.
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
1 Security Hotspot
0.0% Coverage on New Code (required ≥ 80%)
C Security Rating on New Code (required ≥ A)
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@matt-edmondson matt-edmondson merged commit 21d6ad8 into vectors May 10, 2026
4 of 5 checks passed
@matt-edmondson matt-edmondson deleted the claude/issue-57-no-duplicates branch May 10, 2026 13:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants