diff --git a/CodeGen/Generators/QuantityRelationsParser.cs b/CodeGen/Generators/QuantityRelationsParser.cs index f7ce09fa37..94398df895 100644 --- a/CodeGen/Generators/QuantityRelationsParser.cs +++ b/CodeGen/Generators/QuantityRelationsParser.cs @@ -25,13 +25,16 @@ internal static class QuantityRelationsParser /// /// The format of a relation definition is "Quantity.Unit operator Quantity.Unit = Quantity.Unit" (See examples below). /// "double" can be used as a unitless operand. - /// "1" can be used as the left operand to define inverse relations. + /// "1" can be used as the result operand to define inverse relations. + /// + /// Division relations are inferred from multiplication relations, + /// but this can be skipped if the string ends with "NoInferredDivision". /// /// /// [ - /// "Power.Watt = ElectricPotential.Volt * ElectricCurrent.Ampere", - /// "Speed.MeterPerSecond = Length.Meter / Duration.Second", - /// "ReciprocalLength.InverseMeter = 1 / Length.Meter" + /// "1 = Length.Meter * ReciprocalLength.InverseMeter" + /// "Power.Watt = ElectricPotential.Volt * ElectricCurrent.Ampere", + /// "Mass.Kilogram = MassConcentration.KilogramPerCubicMeter * Volume.CubicMeter -- NoInferredDivision", /// ] /// /// Repository root directory. @@ -61,7 +64,7 @@ public static void ParseAndApplyRelations(string rootDir, Quantity[] quantities) // We can infer division relations from multiplication relations. relations.AddRange(relations - .Where(r => r.Operator is "*") + .Where(r => r is { Operator: "*", NoInferredDivision: false }) .Select(r => r with { Operator = "/", @@ -74,9 +77,6 @@ public static void ParseAndApplyRelations(string rootDir, Quantity[] quantities) .Where(r => r.LeftQuantity != r.RightQuantity) .ToList()); - // Remove inferred relation "MassConcentration = Mass / Volume" because it duplicates "Density = Mass / Volume" - relations.RemoveAll(r => r is { Operator: "/", ResultQuantity.Name: "MassConcentration", LeftQuantity.Name: "Mass", RightQuantity.Name: "Volume" }); - // We can infer TimeSpan relations from Duration relations. var timeSpanQuantity = pseudoQuantity with { Name = "TimeSpan" }; relations.AddRange(relations @@ -103,6 +103,18 @@ public static void ParseAndApplyRelations(string rootDir, Quantity[] quantities) throw new UnitsNetCodeGenException($"Duplicate inferred relations:\n {list}"); } + var ambiguous = relations + .GroupBy(r => $"{r.LeftQuantity.Name} {r.Operator} {r.RightQuantity.Name}") + .Where(g => g.Count() > 1) + .Select(g => g.Key) + .ToList(); + + if (ambiguous.Any()) + { + var list = string.Join("\n ", ambiguous); + throw new UnitsNetCodeGenException($"Ambiguous inferred relations:\n {list}\n\nHint: you could use NoInferredDivision in the definition file."); + } + foreach (var quantity in quantities) { var quantityRelations = new List(); @@ -151,7 +163,7 @@ private static QuantityRelation ParseRelation(string relationString, IReadOnlyDi { var segments = relationString.Split(' '); - if (segments is not [_, "=", _, "*", _]) + if (segments is not [_, "=", _, "*", _, ..]) { throw new Exception($"Invalid relation string: {relationString}"); } @@ -176,6 +188,7 @@ private static QuantityRelation ParseRelation(string relationString, IReadOnlyDi return new QuantityRelation { + NoInferredDivision = segments.Contains("NoInferredDivision"), Operator = @operator, LeftQuantity = leftQuantity, LeftUnit = leftUnit, diff --git a/CodeGen/JsonTypes/QuantityRelation.cs b/CodeGen/JsonTypes/QuantityRelation.cs index 9aec29c2a9..35e97a6bc0 100644 --- a/CodeGen/JsonTypes/QuantityRelation.cs +++ b/CodeGen/JsonTypes/QuantityRelation.cs @@ -7,6 +7,7 @@ namespace CodeGen.JsonTypes { internal record QuantityRelation : IComparable { + public bool NoInferredDivision = false; public string Operator = null!; public Quantity LeftQuantity = null!; diff --git a/Common/UnitRelations.json b/Common/UnitRelations.json index 519ea946d4..b6e6cc2ad3 100644 --- a/Common/UnitRelations.json +++ b/Common/UnitRelations.json @@ -37,7 +37,7 @@ "Mass.Kilogram = AreaDensity.KilogramPerSquareMeter * Area.SquareMeter", "Mass.Kilogram = Density.KilogramPerCubicMeter * Volume.CubicMeter", "Mass.Kilogram = LinearDensity.KilogramPerMeter * Length.Meter", - "Mass.Kilogram = MassConcentration.KilogramPerCubicMeter * Volume.CubicMeter", + "Mass.Kilogram = MassConcentration.KilogramPerCubicMeter * Volume.CubicMeter -- NoInferredDivision", "Mass.Kilogram = MassFlow.KilogramPerSecond * Duration.Second", "Mass.Kilogram = MassFraction.DecimalFraction * Mass.Kilogram", "MassConcentration.KilogramPerCubicMeter = Molarity.MolePerCubicMeter * MolarMass.KilogramPerMole",