feat(generator): emit From{Unit} factory for every availableUnit (closes #48, supersedes #69)#71
Merged
matt-edmondson merged 4 commits intovectorsfrom May 9, 2026
Merged
Conversation
Closes #48. Previously QuantitiesGenerator emitted only From{firstUnit} on each generated type — Length had FromMeter but no FromKilometer/FromFoot/FromInch/FromMile, Mass had FromKilogram but no FromGram/FromPound/FromTonne, etc. Consumers had to convert manually before constructing a quantity. Now: a From{Unit} factory is emitted for every entry in dimensions.json's availableUnits, applying the conversion declared in units.json: public static Length<T> FromMeter(T value) => Create(value); public static Length<T> FromKilometer(T value) => Create((value * T.CreateChecked(MetricMagnitudes.Kilo))); public static Length<T> FromFoot(T value) => Create((value * T.CreateChecked(Units.ConversionConstants.FeetToMeters))); The conversion expression honours: - magnitude (Kilo, Centi, Nano, …) -> MetricMagnitudes constants - conversionFactor (FeetToMeters, PoundToKilograms, …) -> ConversionConstants - offset (CelsiusToKelvinOffset, FahrenheitToKelvinOffset) -> additive after scaling, e.g. K = (F * FahrenheitScale) + FahrenheitToKelvinOffset Implementation: - GeneratorBase.Initialize is now virtual so QuantitiesGenerator can override it to load both dimensions.json and units.json (the base only loads one metadata file). - New AddUnitFactories helper centralises the emission so V0 base, V1 base, and overload emit paths produce identical factory shapes. - New BuildToBaseExpression composes the conversion expression from the unit's metadata. - The legacy abstract Generate is preserved as a shim that calls GenerateInner with empty UnitsMetadata so other code paths still work. If a unit name listed in availableUnits has no matching entry in units.json, the factory falls back to identity and a future SEM00x diagnostic could surface the gap (follow-up for the metadata-validation work in #60). Test plan: MultiUnitFactoryTests covers identity (base unit), magnitude scaling (Kilometer, Centimeter, Millimeter, Gram), conversion-factor scaling (Foot, Inch, Mile, Pound), Time conversions (Minute, Hour), overload inheritance (Distance.FromKilometer, Diameter.FromMillimeter, Wavelength.FromNanometer), and storage genericity (float, decimal). Note: the on-disk Generated/*.g.cs files are not regenerated in this commit (no dotnet available locally). CI's first build with the new generator will emit the new factories in-memory and the tests will pass; the committed Generated/ files will be refreshed on the next regeneration sweep.
# Conflicts: # Semantics.SourceGenerators/Generators/QuantitiesGenerator.cs
…0 non-negativity Resolves the conflict between #48 (multi-unit From{Unit} factories) and #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 #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 #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).
…tories with SEM002 + AnalyzerReleases #70 merged into vectors while this branch was up, so re-merging brought in: - The SEM002 dimensions.json schema validation diagnostic and its invocation at the top of QuantitiesGenerator.Generate / GenerateInner. - AnalyzerReleases.Shipped.md / Unshipped.md tracking files (RS2008). - The CLAUDE.md tweaks for the V0 invariant and PhysicalConstants surface. - A regenerated Generated/ tree. Conflicts and resolutions: 1. QuantitiesGenerator.cs: SEM002 metadata.Validate() + ReportDiagnostic loop runs first, then BuildUnitMap(units) builds the unit lookup that AddUnitFactories needs for #48. Both belong; combined. 2. AnalyzerReleases.Unshipped.md: kept both SEM001 (this branch) and SEM002 (from #70) entries. 3. Semantics.Quantities/Generated/*.g.cs (~130 files): both branches regenerated. Discarded the merge attempt and re-ran the generator from scratch. Spot-checked Length: multi-unit factories (FromMeter/FromKilometer/FromCentimeter/FromFoot/FromInch/FromMile) each wrap their conversion in Vector0Guards.EnsureNonNegative, and the V0-V0 absolute subtraction operator (Length<T> operator -(Length<T>, Length<T>) => Create(T.Abs(...))) is intact. Source generator builds clean (0 warnings, 0 errors); generator runs clean against current dimensions.json (no SEM001 / SEM002 diagnostics).
|
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
Replaces #69. Same multi-unit-factories work, rebased onto current
vectorsso the V0 non-negativity / V0–V0 absolute subtraction work from #68 is integrated. The sandbox proxy refused force-pushes onto thework/issue-48-rebasebranch (same restriction noted in #69's body), so the resolved commit lives onclaude/rebase-issue-48.Closes #48.
What changed during the rebase
The merge conflict between #69 (multi-unit factories) and #68 (V0 non-negativity) was in
QuantitiesGenerator's factory emission. Resolution:AddUnitFactories(...)now takes a newbool applyV0Guardparameter.applyV0Guard: true(V0 base types and V0 overloads), the per-unit body isCreate(Vector0Guards.EnsureNonNegative(<conversion>, nameof(value)))— guard runs after the unit conversion so e.g.Temperature.FromCelsius(-300)→-26.85 Kstill throwsArgumentException.false(V1 base types and V1 overloads), the body staysCreate(<conversion>)— V1 quantities are signed.V0 - V0 → T.Abs(left − right)operator from Resolve open design decision for Vector0 subtraction #52 is preserved on every V0 base and V0 overload.The
record CombinedMetadatafrom #69 was changed to a sealed class because positional records needSystem.Runtime.CompilerServices.IsExternalInit, whichnetstandard2.0(the source-generator target) doesn't ship with.AnalyzerReleases.Shipped.md/AnalyzerReleases.Unshipped.mdare included so the SEM001 diagnostic from #65 satisfiesRS2008and the source-generator project actually builds. (My #70 also adds these — whichever lands first wins.)Spot-checked generator output
Mass:FromKilogram/FromGram/FromTon/FromPound/FromOunceall wrap their conversion inVector0Guards.EnsureNonNegative.Temperature:FromKelvin(identity),FromCelsius(+ CelsiusToKelvinOffset),FromFahrenheit(offset+scale) all guard the post-conversion value.Length:FromMeter/FromKilometer/FromCentimeter/FromFoot/FromInch/FromMileetc. all guarded;FromMileusesMileToMeters.Velocity1D(V1):FromMetersPerSecond/FromKilometersPerHour/FromMilesPerHour— no guard, signed.V0 - V0operator (e.g.Mass<T> operator -(Mass<T>, Mass<T>) => Create(T.Abs(...))) intact on every V0 base.Test plan
dotnet build Semantics.SourceGenerators— cleandotnet build Semantics.Quantities— generator runs cleanly, noSEM001/SEM002warnings,Generated/refreshed in-tree.dotnet test—Semantics.Testcannot restore in this sandbox (Microsoft.NETCore.App.Host.ubuntu.24.04-x64not in feeds). Coverage in feat(generator): emit From{Unit} factory for every availableUnit (closes #48, replaces #67) #69'sMultiUnitFactoryTests.csstill applies; we should re-confirm the tests pass after merge.https://claude.ai/code/session_01Tj63Rddvs9frqLUgsjNEP5
Generated by Claude Code