From c00fc085be0152370384b587d9b872d50f8d6553 Mon Sep 17 00:00:00 2001 From: Matt Edmondson Date: Thu, 25 Jun 2026 15:42:52 +1000 Subject: [PATCH 1/2] fix: close V0 release blockers and drop out-of-support TFMs #50/#52 were already implemented in the generator (V0 factories guard via Vector0Guards.EnsureNonNegative; V0 - V0 returns T.Abs(left - right)) but the covering tests were left [Ignore]d. Un-skip and rename them. Drop net5.0/net6.0/net7.0: System.Text.Json 10 and friends no longer ship assets for those runtimes (they are out of support). Strings/Paths keep netstandard2.0/2.1 so older consumers still resolve; Quantities goes to net8.0 floor (it needs INumber). Document the TFM change in the 2.0 migration guide. --- Semantics.Paths/Semantics.Paths.csproj | 2 +- .../Semantics.Quantities.csproj | 2 +- Semantics.Strings/Semantics.Strings.csproj | 2 +- .../Quantities/VectorQuantityTests.cs | 18 +++++++----------- docs/migration-guide-2.0.md | 5 ++++- 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/Semantics.Paths/Semantics.Paths.csproj b/Semantics.Paths/Semantics.Paths.csproj index 426ab04..e498fea 100644 --- a/Semantics.Paths/Semantics.Paths.csproj +++ b/Semantics.Paths/Semantics.Paths.csproj @@ -2,7 +2,7 @@ - net10.0;net9.0;net8.0;net7.0;net6.0;net5.0;netstandard2.0;netstandard2.1 + net10.0;net9.0;net8.0;netstandard2.0;netstandard2.1 $(NoWarn);CA1716;CA2225;IDE0032;CA1866;CA2249;IDE0056;IDE0057 diff --git a/Semantics.Quantities/Semantics.Quantities.csproj b/Semantics.Quantities/Semantics.Quantities.csproj index 12e8d9d..0deb3f1 100644 --- a/Semantics.Quantities/Semantics.Quantities.csproj +++ b/Semantics.Quantities/Semantics.Quantities.csproj @@ -2,7 +2,7 @@ - net10.0;net9.0;net8.0;net7.0 + net10.0;net9.0;net8.0 $(NoWarn);CA1716;CA2225;KTSU0003;IDE0032 true Generated diff --git a/Semantics.Strings/Semantics.Strings.csproj b/Semantics.Strings/Semantics.Strings.csproj index 04d2585..4b57fd6 100644 --- a/Semantics.Strings/Semantics.Strings.csproj +++ b/Semantics.Strings/Semantics.Strings.csproj @@ -2,7 +2,7 @@ - net10.0;net9.0;net8.0;net7.0;net6.0;net5.0;netstandard2.0;netstandard2.1 + net10.0;net9.0;net8.0;netstandard2.0;netstandard2.1 $(NoWarn);CA1716;CA1866;CA2249;IDE0057 diff --git a/Semantics.Test/Quantities/VectorQuantityTests.cs b/Semantics.Test/Quantities/VectorQuantityTests.cs index 13499a5..a8d3087 100644 --- a/Semantics.Test/Quantities/VectorQuantityTests.cs +++ b/Semantics.Test/Quantities/VectorQuantityTests.cs @@ -165,13 +165,11 @@ public void Speed_Plus_Speed_Returns_Speed() } // ------------------------------------------------------------- V0 - V0 - // Locked design decision in #52: V0 - V0 should return the same V0 of T.Abs(a - b). - // Generator currently emits unsigned subtraction via the SemanticQuantity base, which - // can produce a negative magnitude. Tracked as a follow-up. + // #52: V0 - V0 returns the same V0 of T.Abs(a - b). The generated derived operator + // wins overload resolution over PhysicalQuantity's plain (signable) subtraction. [TestMethod] - [Ignore("Locked in #52: V0 - V0 should return the same V0 of T.Abs(a - b). Generator currently emits unsigned subtraction.")] - public void Mass_Minus_Mass_Returns_Absolute_Difference_Pending52() + public void Mass_Minus_Mass_Returns_Absolute_Difference() { Mass a = Mass.FromKilogram(3.0); Mass b = Mass.FromKilogram(5.0); @@ -180,20 +178,18 @@ public void Mass_Minus_Mass_Returns_Absolute_Difference_Pending52() } // ---------------------------------------------------- V0 non-negativity - // Tracked in #50: factories on Vector0 quantities should reject negative inputs - // with ArgumentException. The current generator does not emit guards. + // #50: factories on Vector0 quantities reject negative inputs with ArgumentException + // via Vector0Guards.EnsureNonNegative (the guard runs after unit conversion). [TestMethod] - [Ignore("Tracked in #50: V0 factories should reject negative inputs.")] - public void Speed_From_Negative_Throws_Pending50() + public void Speed_From_Negative_Throws() { _ = Assert.ThrowsExactly( () => Speed.FromMeterPerSecond(-1.0)); } [TestMethod] - [Ignore("Tracked in #50: V0 factories should reject negative inputs.")] - public void Mass_From_Negative_Throws_Pending50() + public void Mass_From_Negative_Throws() { _ = Assert.ThrowsExactly( () => Mass.FromKilogram(-1.0)); diff --git a/docs/migration-guide-2.0.md b/docs/migration-guide-2.0.md index 0ebdd8d..7e2dfad 100644 --- a/docs/migration-guide-2.0.md +++ b/docs/migration-guide-2.0.md @@ -214,6 +214,10 @@ different dimensions throws `ArgumentException`; equality across dimensions is | audio-engineering `Percent` | the `Percent` **unit** on the Dimensionless dimension: `Ratio.FromPercent(50)` and `ratio.In(Units.Percent)` | | audio-engineering `Gain` record struct | `Gain` is now a generated semantic overload of `Ratio` (a record class, non-negative, widens implicitly to `Ratio`); `Unity`, `Silence`, the `Decibels` conversions, and `*` live in a partial | +## Target frameworks + +The out-of-support runtimes `net5.0`, `net6.0`, and `net7.0` were dropped — `System.Text.Json` 10 (and friends) no longer ship assets for them. `ktsu.Semantics.Quantities` now targets `net8.0`–`net10.0` (it requires `INumber`, so it has no `netstandard` target). `Semantics.Strings` and `Semantics.Paths` target `net8.0`–`net10.0` plus `netstandard2.0`/`netstandard2.1`, so consumers on older runtimes still resolve via `netstandard`. + ## What didn't change - `Semantics.Strings` and `Semantics.Paths` — same namespaces, same types; @@ -224,7 +228,6 @@ different dimensions throws `ArgumentException`; equality across dimensions is through the generated `Ratio`, and (for the logarithmic ones) are generated from `logarithmic.json`. `Percent` became a unit and `Gain` a generated `Ratio` overload — see the removed-APIs table. -- Target frameworks: `net7.0` through `net10.0`. - Quantities remain generic over the numeric storage type (`where T : struct, INumber`). - `UnitSystem` and `UnitConversionException` keep their names and meaning. From 793315b1697facee6727a82e9353ecbac8e1b32d Mon Sep 17 00:00:00 2001 From: Matt Edmondson Date: Thu, 25 Jun 2026 15:49:26 +1000 Subject: [PATCH 2/2] chore: sync stale Units.g.cs and add generated-files CI guard The committed Units.g.cs predated the catalogue's ordinal sort, so every build re-ordered one singleton (FootPerSecond) and showed the file dirty. The generator is deterministic (verified: two clean builds are byte-identical), so this is a one-time resync. Add a standalone verify-generated workflow that rebuilds and fails if anything under Semantics.Quantities/Generated/ drifts from its generator. Kept separate from the centrally-synced dotnet.yml. --- .github/workflows/verify-generated.yml | 48 +++++++++++++++++++ .../Units.g.cs | 6 +-- 2 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/verify-generated.yml diff --git a/.github/workflows/verify-generated.yml b/.github/workflows/verify-generated.yml new file mode 100644 index 0000000..5b4f142 --- /dev/null +++ b/.github/workflows/verify-generated.yml @@ -0,0 +1,48 @@ +name: Verify Generated Files + +# Source-generator output under Semantics.Quantities/Generated/ is committed to the +# repo (EmitCompilerGeneratedFiles=true). This job rebuilds and fails if the committed +# files are stale, so a generated file can never drift from its generator. +# +# NOTE: this is a standalone workflow on purpose — dotnet.yml is centrally synced from a +# shared template across ktsu repos, so a step added there would be overwritten. If this +# check belongs in the shared KtsuBuild pipeline long-term, move it there and delete this. + +on: + pull_request: + paths-ignore: ["**.md"] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: verify-generated-${{ github.ref }} + cancel-in-progress: true + +jobs: + verify-generated: + name: Generated files up to date + # windows-latest so git's autocrlf matches the committed CRLF line endings and the + # diff doesn't flag end-of-line differences. + runs-on: windows-latest + timeout-minutes: 15 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: "10.0.x" + + - name: Build (regenerates source-generator output into Generated/) + run: dotnet build Semantics.Quantities/Semantics.Quantities.csproj -c Release + + - name: Fail if generated files are stale + shell: bash + run: | + if ! git diff --exit-code -- 'Semantics.Quantities/Generated/'; then + echo "::error::Generated files are out of date. Run 'dotnet build Semantics.Quantities/Semantics.Quantities.csproj' and commit the changes under Semantics.Quantities/Generated/." + exit 1 + fi diff --git a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.UnitsGenerator/Units.g.cs b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.UnitsGenerator/Units.g.cs index 1b1e59a..ea08567 100644 --- a/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.UnitsGenerator/Units.g.cs +++ b/Semantics.Quantities/Generated/Semantics.SourceGenerators/Semantics.SourceGenerators.UnitsGenerator/Units.g.cs @@ -5422,9 +5422,6 @@ public static class Units{ /// Singleton FaradPerMeter instance. public static readonly FaradPerMeter FaradPerMeter = new FaradPerMeter(); - /// Singleton FootPerSecond instance. - public static readonly FootPerSecond FootPerSecond = new FootPerSecond(); - /// Singleton Foot instance. public static readonly Foot Foot = new Foot(); @@ -5434,6 +5431,9 @@ public static class Units{ /// Singleton FootLambert instance. public static readonly FootLambert FootLambert = new FootLambert(); + /// Singleton FootPerSecond instance. + public static readonly FootPerSecond FootPerSecond = new FootPerSecond(); + /// Singleton Gallon instance. public static readonly Gallon Gallon = new Gallon();