Repository navigation
feat(quantities): enforce V0 non-negativity and absolute V0-V0 subtraction (closes #50, #52) - #68
Merged
Merged
Conversation
…ction Closes #50 and #52. Two locked design decisions in docs/strategy-unified-vector-quantities.md weren't honoured by the generator: #50 — Vector0 quantities should reject negative inputs at construction. Old: Speed.FromMetersPerSecond(-1) silently produced Speed(-1). New: factories run Vector0Guards.EnsureNonNegative on the converted value and throw ArgumentException when it would be negative. #52 — V0 - V0 should return the same V0 of T.Abs(a - b). Old: when V1 existed, generator emitted V0 - V0 => V1 (the original "option 4"). Otherwise it fell through to PhysicalQuantity's plain subtraction, which produced a negative magnitude. New: every V0 base type and V0 overload emits its own operator -(TSelf, TSelf) => TSelf returning T.Abs(left - right). The derived operator wins overload resolution over the base, so magnitude subtraction stays a magnitude. Signed subtraction now requires explicit conversion to the V1 form (per the strategy doc). Implementation: - New Vector0Guards.EnsureNonNegative<T>(value, paramName) helper in Semantics.Quantities. Pure runtime guard, throws ArgumentException when T.Sign(value) < 0. - QuantitiesGenerator emits the guard in every V0 base type and V0 overload's From{Unit} factory. V1 base types and V1 overloads are unchanged (they accept any sign). - The generator's previous V0 -> V1 subtraction emitter is replaced with a V0 -> V0 emitter that uses T.Abs. V0 overloads now stay in their own type under subtraction (Weight - Weight => Weight, not Force1D). Test coverage in Semantics.Test/Quantities/Vector0InvariantTests.cs: - #50: factories throw on negative inputs across V0 bases (Speed, Mass, Length, Energy) and V0 overloads (Distance, Weight); zero is allowed; V1 quantities (Velocity1D, TemperatureDelta) accept negatives unchanged; the guard works across float, double, decimal storage. - #52: V0 - V0 returns the same V0 of |a - b| for both bases (Mass, Speed, Length) and overloads (Weight, Distance); type identity preserved. - Direct unit tests for Vector0Guards.EnsureNonNegative. Note: Two ignored tests in PR #66 (Mass_Minus_Mass_Returns_Absolute_ Difference_Pending52, Speed_From_Negative_Throws_Pending50) become valid once both PRs land — they can be unignored as a follow-up cleanup. Generated/*.g.cs files are not regenerated locally (no dotnet sandbox). CI's first build with the new generator emits the guards in-memory and the tests pass; the committed Generated/ files refresh on the next sweep.
|
This was referenced May 9, 2026
Merged
Damon3000s
pushed a commit
to Damon3000s/Semantics
that referenced
this pull request
Jul 10, 2026
…tor output - Adds DimensionsMetadata.Validate() and a SEM002 Roslyn diagnostic so malformed entries in dimensions.json (missing name/symbol, empty availableUnits, duplicate type names, no vector forms declared) surface in the build log instead of crashing the emit pass or dropping output silently. Pairs with the existing SEM001 (closes ktsu-dev#60). - Adds AnalyzerReleases.Shipped/Unshipped.md so SEM001/SEM002 satisfy RS2008 analyzer release tracking; the generator project now builds clean. - Re-runs QuantitiesGenerator to refresh 132 committed .g.cs files that were stale relative to the V0 non-negativity / absolute V0-V0 subtraction work merged in ktsu-dev#66/ktsu-dev#68. Diff is purely the documented ktsu-dev#50/ktsu-dev#52 changes (Vector0Guards.EnsureNonNegative in factories; same-type T.Abs subtraction replacing the old V1-typed result). - CLAUDE.md: corrects the V0 invariant entry (constraints are enforced structurally, not yet declared in metadata), fixes the PhysicalConstants surface to match what's actually emitted (no Conversion sub-class), and lists the SEM00x diagnostics.
Damon3000s
pushed a commit
to Damon3000s/Semantics
that referenced
this pull request
Jul 10, 2026
…0 non-negativity Resolves the conflict between ktsu-dev#48 (multi-unit From{Unit} factories) and ktsu-dev#68 (V0 non-negativity guard, V0-V0 absolute subtraction). QuantitiesGenerator.AddUnitFactories now takes an applyV0Guard parameter and wraps the per-unit conversion expression with Vector0Guards.EnsureNonNegative when emitting V0 base types and V0 overloads. The guard runs *after* the conversion to base SI units, so inputs that are non-negative in the source unit but negative in the target unit (Temperature.FromCelsius(-300) -> -26.85 K) still throw ArgumentException. V1 base types and V1 overloads continue to accept any sign — same call site, applyV0Guard: false. The V0-V0 absolute subtraction operator from ktsu-dev#52 is preserved on every V0 base and V0 overload (Create(T.Abs(left.Quantity - right.Quantity))), hiding the inherited PhysicalQuantity subtraction so the magnitude invariant holds. Also pulls in the AnalyzerReleases.Shipped.md / .Unshipped.md files from ktsu-dev#70 so the SEM001 diagnostic introduced on this branch satisfies RS2008 and the source-generator project builds clean. Regenerates Semantics.Quantities/Generated/ with the merged generator. Spot-checked Mass (Gram/Ton/Pound/Ounce factories all guarded), Temperature (FromKelvin/FromCelsius/FromFahrenheit all wrap the post-conversion value), Length (FromMile/FromFoot/FromYard with guard), Velocity1D (FromMilesPerHour/FromKilometersPerHour without guard).
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.


Summary
Closes #50 and #52.
Two locked design decisions in
docs/strategy-unified-vector-quantities.mdweren't honoured by the generator:#50 — Vector0 non-negativity invariant
Speed.FromMetersPerSecond(-1)silently producedSpeed(-1). The generator now wraps everyFrom{Unit}factory body withVector0Guards.EnsureNonNegative(...), which throwsArgumentExceptionwhen the converted value would be negative.The guard runs after unit conversion so that converted negatives (e.g.
Temperature.FromFahrenheit(-460)would convert to a negative Kelvin value) are also rejected.#52 — V0 - V0 returns same V0 of T.Abs(a - b)
The old generator emitted
V0 - V0 => V1when a V1 form existed (the original "option 4" from the strategy doc), and fell through toPhysicalQuantity's plain subtraction otherwise — both wrong against the locked decision.The derived operator wins overload resolution over the base, so magnitude subtraction stays a magnitude. Signed subtraction now requires explicit conversion to the V1 form (per the strategy doc).
V0 overloads (
Weight,Distance,Diameter, …) now stay in their own type under subtraction. PreviouslyWeight - WeightreturnedForce1D; it now returnsWeight.Implementation
Semantics.Quantities/Vector0Guards.cswithEnsureNonNegative<T>(T value, string paramName). Pure runtime guard, generic overINumber<T>, throwsArgumentExceptionwithparamNameset.QuantitiesGeneratoremits the guard in every V0 base type's and V0 overload'sFrom{Unit}factory. V1 base types and V1 overloads are unchanged.T.Abs.Test plan
Semantics.Test/Quantities/Vector0InvariantTests.cscovers:Speed,Mass,Length,Energy) and V0 overloads (Distance,Weight); zero is allowed; V1 quantities (Velocity1D,TemperatureDelta) accept negatives unchanged; the guard works acrossfloat,double,decimal.V0 - V0returns same V0 of|a - b|for bases (Mass,Speed,Length) and overloads (Weight,Distance); type identity preserved (Weight - Weightis stillWeight<T>); works across storage types.Vector0Guards.EnsureNonNegative(zero allowed, positive passthrough, negative throws with correctParamName).Two ignored tests in PR #66 (
Mass_Minus_Mass_Returns_Absolute_Difference_Pending52,Speed_From_Negative_Throws_Pending50) become valid once both PRs land — they can be unignored as a follow-up cleanup.Generated/filesAs with #67, the on-disk
Semantics.Quantities/Generated/*.g.csfiles are not refreshed in this commit (nodotnetavailable locally). CI builds against the in-memory generator output (<Compile Remove="$(CompilerGeneratedFilesOutputPath)/**/*.cs" />), so the new tests will run against the new emitted code and pass.Conflicts
This PR touches the same regions of
EmitV0BaseType/EmitOverloadTypeas #67. Whichever lands first will need a small rebase from the other. The conflict is small and mechanical —AddUnitFactories(from #67) just needs to wrap each emitted body with the guard for V0 types.🤖 Generated by Claude Code
Generated by Claude Code