Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/verify-generated.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Verify Generated Files

# Two kinds of generated artifacts are committed to this repo:
# 1. Source-generator output under Semantics.Quantities/Generated/ (EmitCompilerGeneratedFiles=true).
# 2. The storage-type alias props under Semantics.Quantities.<T>/buildTransitive/, produced by
# 2. The storage-type alias props under Semantics.Quantities.<T>/build/, produced by
# scripts/Generate-AliasProps.ps1 from the quantity catalogue.
# This job rebuilds and regenerates both, then fails if anything drifts from its source — so a
# committed generated file or alias-props file can never go stale.
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ The opt-in lives in `.sonarlint/sonar-local.props` (analyzer package) and `.sona
| `Semantics.Color` | Physically-grounded color types. Canonical linear-RGB `Color` hub plus color-space satellites (`Srgb`, `Hsl`, `Hsv`, `Oklab`, `Oklch`); every type converts to and from every other, routed through the nearest shared hub (`Srgb` within the sRGB family, `Oklab` within the perceptual family, linear `Color` across families) so no conversion takes a redundant gamma round-trip. Also WCAG accessibility tooling, HSL/perceptual adjustment operations (lighten/saturate/hue/invert), and `NamedColors`. Targets `net8.0`–`net10.0` + `netstandard2.0`/`netstandard2.1`. |
| `Semantics.Quantities` | Hand-written runtime types (`IPhysicalQuantity<TSelf, T>`, `PhysicalQuantityCore`, `IVector0`..`IVector4`, `UnitSystem`) plus generator output under `Generated/`. Every generated quantity is a `readonly record struct`. |
| `Semantics.SourceGenerators` | Roslyn incremental generators that emit quantity types, units, conversions, magnitudes, physical constants, and storage-type helpers from metadata. Only the physics-specific half lives here — `Models/`, `Metadata/`, `Generators/`, and the bindings in `SemanticsGenerator`/`SemanticsDiagnostics`/`Emit`. The C# syntax templates come from `ktsu.CodeBlocker.Templates`; the metadata-driven generator base, metadata loading and the diagnostic catalogue come from `ktsu.SourceGeneratorToolkit` (#181, #192). |
| `Semantics.Quantities.{Double,Float,Decimal,Precise}` | Props-only satellite packages. Each ships a `buildTransitive` props file (generated by `scripts/Generate-AliasProps.ps1`) that injects global-using aliases binding every quantity to one storage type, so consumers write `Mass` instead of `Mass<double>`. `Precise` binds to `ktsu.PreciseNumber.PreciseNumber` and is the one whose storage type comes from a package rather than being a C# keyword, so it carries a `PackageReference` the others do not; the core `Semantics.Quantities` still has no PreciseNumber dependency. |
| `Semantics.Quantities.{Double,Float,Decimal,Precise}` | Props-only satellite packages. Each ships a `build` props file (generated by `scripts/Generate-AliasProps.ps1`) that injects global-using aliases binding every quantity to one storage type, so consumers write `Mass` instead of `Mass<double>`. `build` rather than `buildTransitive` on purpose: the aliases are project-wide global usings keyed on the bare type name, so two of these packages reaching one project define every alias twice and the compile fails with one `CS1537` per quantity. `build` binds the aliases in the project that declares the reference and nowhere else, which is what "one alias package per project" means; a project downstream of that one references the package it wants for itself. `Precise` binds to `ktsu.PreciseNumber.PreciseNumber` and is the one whose storage type comes from a package rather than being a C# keyword, so it carries a `PackageReference` the others do not; the core `Semantics.Quantities` still has no PreciseNumber dependency. |
| `Semantics.Benchmarks` | BenchmarkDotNet suite covering quantities, strings and paths. Not shipped and not covered by tests, so it carries `SonarQubeExclude`. Feeds the per-release charts in `docs/benchmarks/`. |
| `Semantics.Vocabulary` | **Shared source, not a project.** Resolves `dimensions.json` into the quantities and operators it describes and separates out what cannot be honoured. Compiled into both `Semantics.SourceGenerators` and `Semantics.Cpp` via `Compile Include`; see its README for why source rather than an assembly, and what that costs. |
| `Semantics.Cpp` | The C++ projection of the quantity vocabulary, in its own project because `ktsu.Coder` ships no `net8.0`. Reads `dimensions.json` and emits one C++ class per dimension, per vector form and per named overload, plus the declared relationships as operators. |
Expand Down Expand Up @@ -431,7 +431,7 @@ var converted = sourceString.As<SourceType, TargetType>();
- Edit `Semantics.SourceGenerators/Metadata/dimensions.json` to add a dimension, vector form, semantic overload, or relationship.
- Rebuild `Semantics.SourceGenerators` and the consuming `Semantics.Quantities` project; emitted files appear in `Semantics.Quantities/Generated/Semantics.SourceGenerators/<GeneratorName>/`.
- Treat generator output as committed source. Diff it before commit so accidental regressions are visible.
- After adding or renaming a quantity, regenerate the storage-type alias props with `pwsh scripts/Generate-AliasProps.ps1` (it reads the generated catalogue and rewrites `Semantics.Quantities.{Double,Float,Decimal,Precise}/buildTransitive/*.props`) and commit them. The `verify-generated` workflow rebuilds, regenerates, and fails the PR if either the generated sources or the alias props drift.
- After adding or renaming a quantity, regenerate the storage-type alias props with `pwsh scripts/Generate-AliasProps.ps1` (it reads the generated catalogue and rewrites `Semantics.Quantities.{Double,Float,Decimal,Precise}/build/*.props`) and commit them. The `verify-generated` workflow rebuilds, regenerates, and fails the PR if either the generated sources or the alias props drift.
- Factory names are the **singular lemma** (#49). The generator emits `From{name}` using each unit's `name` from `units.json` verbatim (e.g. `Length.FromMeter`, `Mass.FromKilogram`, `Speed.FromMeterPerSecond`, `Length.FromFoot`, `Frequency.FromHertz`). The rule is purely mechanical, so `name` must itself be the singular lemma — including compounds, whose leading noun is singular too (`MeterPerSecond`, `RevolutionPerMinute`, `PartPerMillion`, not `MeterPerSecond`/`RevolutionPerMinute`/`PartPerMillion`). There is no `factoryName` field and no pluralisation step; the generator never has to know English pluralisation.
- Generator diagnostics:
- **SEM001** — a relationship in `dimensions.json` references a dimension that does not exist (typo or rename). The operator is silently dropped.
Expand Down
11 changes: 10 additions & 1 deletion Semantics.Quantities.Decimal/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

Every quantity in `ktsu.Semantics.Quantities` is generic over its numeric storage type, so you normally write `Mass<decimal>`, `Speed<decimal>`, and so on. If a project uses one storage type throughout, that generic argument is noise.

This package is props-only. It ships no assembly, just a `buildTransitive` props file that injects one C# global-using alias per quantity, binding each open generic type to `decimal`. Reference it and you can write `Mass`, `Speed`, `Force3D` with no generic argument, and every quantity resolves to its `decimal` form. The aliases are real `Mass<decimal>` (and so on), so they interoperate with the entire API with no conversion.
This package is props-only. It ships no assembly, just a `build` props file that injects one C# global-using alias per quantity, binding each open generic type to `decimal`. Reference it and you can write `Mass`, `Speed`, `Force3D` with no generic argument, and every quantity resolves to its `decimal` form. The aliases are real `Mass<decimal>` (and so on), so they interoperate with the entire API with no conversion.

Installing this package also pulls in the matching version of `ktsu.Semantics.Quantities` as a dependency, so it is the only reference you need.

Expand Down Expand Up @@ -70,6 +70,15 @@ The aliases are project-wide global usings keyed on the bare type name (`Mass`,

A project that genuinely needs mixed storage types should skip the alias packages and reference `ktsu.Semantics.Quantities` directly, writing the closed generic (`Mass<decimal>`) explicitly.

The binding applies to the project that declares the `PackageReference`, and to no other. The props
ship in the package's `build/` folder rather than `buildTransitive/`, so they are not inherited by
projects that reference *this* project. An application assembled from several projects, each wrapping
a different storage type, therefore compiles: each project that wants aliases references its own alias
package, and no project collects a binding it never asked for. Earlier versions shipped the props in
`buildTransitive/`, where the binding did flow downstream and two of them in one dependency graph
produced a wall of `CS1537` against a generated file the author never wrote; `PrivateAssets="all"` on
the alias reference was the workaround for that, and is no longer needed.

The alias lists are generated from the quantity catalogue by `scripts/Generate-AliasProps.ps1` and validated in CI, so they stay in lockstep with the quantities the core package emits.

## Contributing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@
<PropertyGroup>
<!-- Matches Semantics.Quantities (which requires INumber<T>, so net8.0+). -->
<TargetFrameworks>net10.0;net9.0;net8.0</TargetFrameworks>
<!-- Props-only package: no compiled assembly, just the buildTransitive aliases. -->
<!-- Props-only package: no compiled assembly, just the build/ aliases. -->
<IncludeBuildOutput>false</IncludeBuildOutput>
<!-- No assembly means no symbols and no source to ship. ktsu.Sdk defaults IncludeSource=true,
which emits an empty .snupkg that nuget.org rejects with HTTP 400 ("does not contain any
symbol (.pdb) files"), aborting publish. Suppress symbol/source package generation. -->
<IncludeSymbols>false</IncludeSymbols>
<IncludeSource>false</IncludeSource>
<Description>Storage-type aliases for ktsu.Semantics.Quantities. Reference this package to write `Mass` instead of `Mass&lt;decimal&gt;` (and every other quantity) bound to decimal, project-wide. Use one storage-type alias package per project.</Description>
<Description>Storage-type aliases for ktsu.Semantics.Quantities. Reference this package to write `Mass` instead of `Mass&lt;decimal&gt;` (and every other quantity) bound to decimal, project-wide. Use one storage-type alias package per project. The aliases bind only in the project that references this package, not in projects downstream of it, so each project that wants them references the package itself.</Description>
<!-- NU5128: a dependencies-only package legitimately has no lib/ assemblies. -->
<NoWarn>$(NoWarn);NU5128</NoWarn>
</PropertyGroup>

<ItemGroup>
<None Include="buildTransitive\ktsu.Semantics.Quantities.Decimal.props" Pack="true" PackagePath="buildTransitive\" />
<None Include="build\ktsu.Semantics.Quantities.Decimal.props" Pack="true" PackagePath="build\" />
</ItemGroup>

<ItemGroup>
Expand Down
11 changes: 10 additions & 1 deletion Semantics.Quantities.Double/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

Every quantity in `ktsu.Semantics.Quantities` is generic over its numeric storage type, so you normally write `Mass<double>`, `Speed<double>`, and so on. If a project uses one storage type throughout, that generic argument is noise.

This package is props-only. It ships no assembly, just a `buildTransitive` props file that injects one C# global-using alias per quantity, binding each open generic type to `double`. Reference it and you can write `Mass`, `Speed`, `Force3D` with no generic argument, and every quantity resolves to its `double` form. The aliases are real `Mass<double>` (and so on), so they interoperate with the entire API with no conversion.
This package is props-only. It ships no assembly, just a `build` props file that injects one C# global-using alias per quantity, binding each open generic type to `double`. Reference it and you can write `Mass`, `Speed`, `Force3D` with no generic argument, and every quantity resolves to its `double` form. The aliases are real `Mass<double>` (and so on), so they interoperate with the entire API with no conversion.

Installing this package also pulls in the matching version of `ktsu.Semantics.Quantities` as a dependency, so it is the only reference you need.

Expand Down Expand Up @@ -70,6 +70,15 @@ The aliases are project-wide global usings keyed on the bare type name (`Mass`,

A project that genuinely needs mixed storage types should skip the alias packages and reference `ktsu.Semantics.Quantities` directly, writing the closed generic (`Mass<double>`) explicitly.

The binding applies to the project that declares the `PackageReference`, and to no other. The props
ship in the package's `build/` folder rather than `buildTransitive/`, so they are not inherited by
projects that reference *this* project. An application assembled from several projects, each wrapping
a different storage type, therefore compiles: each project that wants aliases references its own alias
package, and no project collects a binding it never asked for. Earlier versions shipped the props in
`buildTransitive/`, where the binding did flow downstream and two of them in one dependency graph
produced a wall of `CS1537` against a generated file the author never wrote; `PrivateAssets="all"` on
the alias reference was the workaround for that, and is no longer needed.

The alias lists are generated from the quantity catalogue by `scripts/Generate-AliasProps.ps1` and validated in CI, so they stay in lockstep with the quantities the core package emits.

## Contributing
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@
<PropertyGroup>
<!-- Matches Semantics.Quantities (which requires INumber<T>, so net8.0+). -->
<TargetFrameworks>net10.0;net9.0;net8.0</TargetFrameworks>
<!-- Props-only package: no compiled assembly, just the buildTransitive aliases. -->
<!-- Props-only package: no compiled assembly, just the build/ aliases. -->
<IncludeBuildOutput>false</IncludeBuildOutput>
<!-- No assembly means no symbols and no source to ship. ktsu.Sdk defaults IncludeSource=true,
which emits an empty .snupkg that nuget.org rejects with HTTP 400 ("does not contain any
symbol (.pdb) files"), aborting publish. Suppress symbol/source package generation. -->
<IncludeSymbols>false</IncludeSymbols>
<IncludeSource>false</IncludeSource>
<Description>Storage-type aliases for ktsu.Semantics.Quantities. Reference this package to write `Mass` instead of `Mass&lt;double&gt;` (and every other quantity) bound to double, project-wide. Use one storage-type alias package per project.</Description>
<Description>Storage-type aliases for ktsu.Semantics.Quantities. Reference this package to write `Mass` instead of `Mass&lt;double&gt;` (and every other quantity) bound to double, project-wide. Use one storage-type alias package per project. The aliases bind only in the project that references this package, not in projects downstream of it, so each project that wants them references the package itself.</Description>
<!-- NU5128: a dependencies-only package legitimately has no lib/ assemblies. -->
<NoWarn>$(NoWarn);NU5128</NoWarn>
</PropertyGroup>

<ItemGroup>
<None Include="buildTransitive\ktsu.Semantics.Quantities.Double.props" Pack="true" PackagePath="buildTransitive\" />
<None Include="build\ktsu.Semantics.Quantities.Double.props" Pack="true" PackagePath="build\" />
</ItemGroup>

<ItemGroup>
Expand Down
11 changes: 10 additions & 1 deletion Semantics.Quantities.Float/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

Every quantity in `ktsu.Semantics.Quantities` is generic over its numeric storage type, so you normally write `Mass<float>`, `Speed<float>`, and so on. If a project uses one storage type throughout, that generic argument is noise.

This package is props-only. It ships no assembly, just a `buildTransitive` props file that injects one C# global-using alias per quantity, binding each open generic type to `float`. Reference it and you can write `Mass`, `Speed`, `Force3D` with no generic argument, and every quantity resolves to its `float` form. The aliases are real `Mass<float>` (and so on), so they interoperate with the entire API with no conversion.
This package is props-only. It ships no assembly, just a `build` props file that injects one C# global-using alias per quantity, binding each open generic type to `float`. Reference it and you can write `Mass`, `Speed`, `Force3D` with no generic argument, and every quantity resolves to its `float` form. The aliases are real `Mass<float>` (and so on), so they interoperate with the entire API with no conversion.

Installing this package also pulls in the matching version of `ktsu.Semantics.Quantities` as a dependency, so it is the only reference you need.

Expand Down Expand Up @@ -70,6 +70,15 @@ The aliases are project-wide global usings keyed on the bare type name (`Mass`,

A project that genuinely needs mixed storage types should skip the alias packages and reference `ktsu.Semantics.Quantities` directly, writing the closed generic (`Mass<float>`) explicitly.

The binding applies to the project that declares the `PackageReference`, and to no other. The props
ship in the package's `build/` folder rather than `buildTransitive/`, so they are not inherited by
projects that reference *this* project. An application assembled from several projects, each wrapping
a different storage type, therefore compiles: each project that wants aliases references its own alias
package, and no project collects a binding it never asked for. Earlier versions shipped the props in
`buildTransitive/`, where the binding did flow downstream and two of them in one dependency graph
produced a wall of `CS1537` against a generated file the author never wrote; `PrivateAssets="all"` on
the alias reference was the workaround for that, and is no longer needed.

The alias lists are generated from the quantity catalogue by `scripts/Generate-AliasProps.ps1` and validated in CI, so they stay in lockstep with the quantities the core package emits.

## Contributing
Expand Down
6 changes: 3 additions & 3 deletions Semantics.Quantities.Float/Semantics.Quantities.Float.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,20 @@
<PropertyGroup>
<!-- Matches Semantics.Quantities (which requires INumber<T>, so net8.0+). -->
<TargetFrameworks>net10.0;net9.0;net8.0</TargetFrameworks>
<!-- Props-only package: no compiled assembly, just the buildTransitive aliases. -->
<!-- Props-only package: no compiled assembly, just the build/ aliases. -->
<IncludeBuildOutput>false</IncludeBuildOutput>
<!-- No assembly means no symbols and no source to ship. ktsu.Sdk defaults IncludeSource=true,
which emits an empty .snupkg that nuget.org rejects with HTTP 400 ("does not contain any
symbol (.pdb) files"), aborting publish. Suppress symbol/source package generation. -->
<IncludeSymbols>false</IncludeSymbols>
<IncludeSource>false</IncludeSource>
<Description>Storage-type aliases for ktsu.Semantics.Quantities. Reference this package to write `Mass` instead of `Mass&lt;float&gt;` (and every other quantity) bound to float, project-wide. Use one storage-type alias package per project.</Description>
<Description>Storage-type aliases for ktsu.Semantics.Quantities. Reference this package to write `Mass` instead of `Mass&lt;float&gt;` (and every other quantity) bound to float, project-wide. Use one storage-type alias package per project. The aliases bind only in the project that references this package, not in projects downstream of it, so each project that wants them references the package itself.</Description>
<!-- NU5128: a dependencies-only package legitimately has no lib/ assemblies. -->
<NoWarn>$(NoWarn);NU5128</NoWarn>
</PropertyGroup>

<ItemGroup>
<None Include="buildTransitive\ktsu.Semantics.Quantities.Float.props" Pack="true" PackagePath="buildTransitive\" />
<None Include="build\ktsu.Semantics.Quantities.Float.props" Pack="true" PackagePath="build\" />
</ItemGroup>

<ItemGroup>
Expand Down
Loading
Loading