diff --git a/Semantics.Test/Quantities/UnkeepableRelationshipTests.cs b/Semantics.Test/Quantities/UnkeepableRelationshipTests.cs index 55f4351..e2c6c8d 100644 --- a/Semantics.Test/Quantities/UnkeepableRelationshipTests.cs +++ b/Semantics.Test/Quantities/UnkeepableRelationshipTests.cs @@ -3,7 +3,6 @@ namespace ktsu.Semantics.Test.Quantities; using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.Json; @@ -108,8 +107,8 @@ public void TheSensitivityBugIsCaughtWithBothDimensionsWrittenOut() refused.Subject == "Sensitivity * Pressure -> ElectricPotential"); Assert.AreEqual(VocabularyIssueKind.NotDimensionallyTrue, issue.Kind); - StringAssert.Contains(issue.Reason, "L⁻² I", StringComparison.Ordinal); - StringAssert.Contains(issue.Reason, "L² M T⁻³ I⁻¹", StringComparison.Ordinal); + Assert.Contains("L⁻² I", issue.Reason); + Assert.Contains("L² M T⁻³ I⁻¹", issue.Reason); } /// @@ -119,14 +118,16 @@ public void TheSensitivityBugIsCaughtWithBothDimensionsWrittenOut() [TestMethod] public void TheKindsWithTheirOwnDiagnosticsAreKeptSeparate() { - IReadOnlyList refused = Vocabulary().Refused; + VocabularyIssueKind[] kinds = [.. Vocabulary().Refused.Select(issue => issue.Kind)]; - Assert.IsFalse( - refused.Any(issue => issue.Kind is VocabularyIssueKind.UnknownDimension), + Assert.DoesNotContain( + VocabularyIssueKind.UnknownDimension, + kinds, "an unknown dimension is SEM001's to report, and the metadata should have none."); - Assert.IsFalse( - refused.Any(issue => issue.Kind is VocabularyIssueKind.NoMagnitudeForm), + Assert.DoesNotContain( + VocabularyIssueKind.NoMagnitudeForm, + kinds, "every dimension should declare a magnitude form for the other forms to measure against."); } } diff --git a/Semantics.Vocabulary/QuantityVocabulary.cs b/Semantics.Vocabulary/QuantityVocabulary.cs index c0ddf60..b18c2c3 100644 --- a/Semantics.Vocabulary/QuantityVocabulary.cs +++ b/Semantics.Vocabulary/QuantityVocabulary.cs @@ -274,13 +274,33 @@ private static IEnumerable Declared( overload.Description, exponents, Refines: declared.Base, - form != 0 ? Magnitude.Signed - : overload.IsStrictlyPositive ? Magnitude.Positive : Magnitude.NonNegative, + BoundOf(form, overload), form, magnitudeType); } } + /// + /// How far down an overload is bounded. + /// + /// How many components the form has. + /// The overload being declared. + /// Its lower bound. + /// + /// Only the magnitude form is bounded below at all, which is the whole reason the forms are + /// separate types: a component of a vector is signed whatever the overload asked for, so a + /// stricter floor declared on one applies to the magnitude and to nothing else. + /// + private static Magnitude BoundOf(int form, OverloadDeclaration overload) + { + if (form != 0) + { + return Magnitude.Signed; + } + + return overload.IsStrictlyPositive ? Magnitude.Positive : Magnitude.NonNegative; + } + private static string Describe(string dimension, int form) => form switch { 0 => $"The magnitude of {Article(dimension)} {Spaced(dimension)}.", @@ -401,9 +421,7 @@ private static List At( List refused) { bool crossed = kind == RelationshipKind.Cross; - IReadOnlyList wanted = relationship.Forms.Count > 0 - ? [.. relationship.Forms] - : crossed ? [3] : [.. Enumerable.Range(0, DimensionDeclaration.FormCount)]; + IReadOnlyList wanted = Wanted(relationship, crossed); List emitted = []; @@ -436,6 +454,27 @@ private static List At( return emitted; } + /// + /// The forms a relationship asks to be emitted at. + /// + /// The declared relationship. + /// Whether it is a cross product. + /// The forms, in the order they should be walked. + /// + /// An explicit list is taken as it stands. With none, a cross product defaults to three + /// components and nothing else, because that is where a cross product exists; everything else + /// defaults to every form and lets the participants decide which of them it reaches. + /// + private static IReadOnlyList Wanted(RelationshipDeclaration relationship, bool crossed) + { + if (relationship.Forms.Count > 0) + { + return [.. relationship.Forms]; + } + + return crossed ? [3] : [.. Enumerable.Range(0, DimensionDeclaration.FormCount)]; + } + private static string Missing( IReadOnlyDictionary declarations, int form,