Skip to content

feat(generator): emit From{Unit} factory for every availableUnit (closes #48, supersedes #69)#71

Merged
matt-edmondson merged 4 commits intovectorsfrom
claude/rebase-issue-48
May 9, 2026
Merged

feat(generator): emit From{Unit} factory for every availableUnit (closes #48, supersedes #69)#71
matt-edmondson merged 4 commits intovectorsfrom
claude/rebase-issue-48

Conversation

@matt-edmondson
Copy link
Copy Markdown
Contributor

Summary

Replaces #69. Same multi-unit-factories work, rebased onto current vectors so the V0 non-negativity / V0–V0 absolute subtraction work from #68 is integrated. The sandbox proxy refused force-pushes onto the work/issue-48-rebase branch (same restriction noted in #69's body), so the resolved commit lives on claude/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 new bool applyV0Guard parameter.
  • When applyV0Guard: true (V0 base types and V0 overloads), the per-unit body is Create(Vector0Guards.EnsureNonNegative(<conversion>, nameof(value))) — guard runs after the unit conversion so e.g. Temperature.FromCelsius(-300)-26.85 K still throws ArgumentException.
  • When false (V1 base types and V1 overloads), the body stays Create(<conversion>) — V1 quantities are signed.
  • The 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 CombinedMetadata from #69 was changed to a sealed class because positional records need System.Runtime.CompilerServices.IsExternalInit, which netstandard2.0 (the source-generator target) doesn't ship with.

AnalyzerReleases.Shipped.md / AnalyzerReleases.Unshipped.md are included so the SEM001 diagnostic from #65 satisfies RS2008 and the source-generator project actually builds. (My #70 also adds these — whichever lands first wins.)

Spot-checked generator output

  • Mass: FromKilogram / FromGram / FromTon / FromPound / FromOunce all wrap their conversion in Vector0Guards.EnsureNonNegative.
  • Temperature: FromKelvin (identity), FromCelsius (+ CelsiusToKelvinOffset), FromFahrenheit (offset+scale) all guard the post-conversion value.
  • Length: FromMeter / FromKilometer / FromCentimeter / FromFoot / FromInch / FromMile etc. all guarded; FromMile uses MileToMeters.
  • Velocity1D (V1): FromMetersPerSecond / FromKilometersPerHour / FromMilesPerHour — no guard, signed.
  • V0 - V0 operator (e.g. Mass<T> operator -(Mass<T>, Mass<T>) => Create(T.Abs(...))) intact on every V0 base.

Test plan

  • dotnet build Semantics.SourceGenerators — clean
  • dotnet build Semantics.Quantities — generator runs cleanly, no SEM001/SEM002 warnings, Generated/ refreshed in-tree.
  • dotnet testSemantics.Test cannot restore in this sandbox (Microsoft.NETCore.App.Host.ubuntu.24.04-x64 not in feeds). Coverage in feat(generator): emit From{Unit} factory for every availableUnit (closes #48, replaces #67) #69's MultiUnitFactoryTests.cs still applies; we should re-confirm the tests pass after merge.

https://claude.ai/code/session_01Tj63Rddvs9frqLUgsjNEP5


Generated by Claude Code

claude added 3 commits May 9, 2026 11:33
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).
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented May 9, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
1 Security Hotspot
0.0% Coverage on New Code (required ≥ 80%)
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 e55b50f into vectors May 9, 2026
4 of 5 checks passed
@matt-edmondson matt-edmondson deleted the claude/rebase-issue-48 branch May 9, 2026 13:10
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