diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 0e3e67374..7fcd7bb4a 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -59,6 +59,7 @@ read first. | **silent** | `abs(x) = c` for a negative `c` | a set of non-solutions | the empty set | | loud | implicit `List` to `Entity` | made a `FiniteSet`, and made three `params` overloads uncallable | removed | | loud | implicit `Entity[]` to `Entity` | made a `FiniteSet`, discarding order and repeats | removed | +| loud | implicit `(Entity, Entity)` to `Entity` | made a **closed** interval, though `(a, b)` reads as the open one | removed | | **silent** | a `MathS.Settings` scope across an `await`, or inside a task | lost, or somebody else's | follows the call | | **silent** | a `RewriteRecording` across an `await`, or work started under it | lost, or somebody else's | follows the call | @@ -164,6 +165,32 @@ Entity set = new Entity[] { 1, 2, 3 }.ToSet(); Only one place in the whole repository relied on it, which was the test pinning it. +**And the conversion from a pair, which had the same fault from the other side.** A +two-element tuple became an `Interval`, and since a tuple says nothing about whether its +endpoints are included, the conversion had to supply that: + +```csharp +Entity e = ((Entity)1, (Entity)5); +// was: [1; 5] — both endpoints included, and 1 is a member +``` + +Where the array conversion dropped information that was there, this one produced +information that was not — and chose the reading opposite to the notation, since `(1, 5)` +is the open interval in ordinary mathematical writing and `[1, 5]` the closed one. A caller +writing what looks like an open interval got a closed one, silently. + +```csharp +Entity e = MathS.Interval(1, 5); // closed, said out loud +Entity e = MathS.Interval(1, false, 5, false); // open +Entity e = new Interval(1, true, 5, true); +``` + +Nothing in the library, the tests, the F# wrapper or the utilities used it. + +The pairs elsewhere in the API are unaffected, because none of them has to guess: an +integration `Range`, the arguments of `Substitute`, the cases of `MathS.Piecewise` and +`ToProvided` are all ordered pairs whose two halves have distinct, stated roles. + ### A settings scope belongs to the call, not to the thread `MathS.Settings` values were held in `[ThreadStatic]` fields — fourteen of them. A scope diff --git a/Sources/AngouriMath/Core/Entity/Omni/Entity.Set.cs b/Sources/AngouriMath/Core/Entity/Omni/Entity.Set.cs index 2a266bba2..0b52c47cb 100644 --- a/Sources/AngouriMath/Core/Entity/Omni/Entity.Set.cs +++ b/Sources/AngouriMath/Core/Entity/Omni/Entity.Set.cs @@ -81,7 +81,6 @@ public bool Contains(Entity entity) #pragma warning disable CS1591 public static implicit operator Entity(Domain domain) => Set.SpecialSet.Create(domain); - public static implicit operator Entity((Entity left, Entity right) interval) => new Interval(interval.left, true, interval.right, true); #pragma warning restore CS1591 } diff --git a/Sources/Tests/UnitTests/Common/ImplicitOperators.cs b/Sources/Tests/UnitTests/Common/ImplicitOperators.cs index 6aaaea3d7..cb589ee9c 100644 --- a/Sources/Tests/UnitTests/Common/ImplicitOperators.cs +++ b/Sources/Tests/UnitTests/Common/ImplicitOperators.cs @@ -33,5 +33,26 @@ public sealed class ImplicitOperators [Fact] public void FromEInteger() => Test(EInteger.FromString("32324"), "32324"); [Fact] public void FromERational() => Test(ERational.Create(EInteger.FromString("32324"), EInteger.FromString("243244")), ((Entity)"32324/243244").InnerSimplified); [Fact] public void FromEDecimal() => Test(EDecimal.FromString("3.4"), "3.4"); + + /// + /// The conversions that used to exist from a collection and from a pair are gone, + /// because each had to supply something its input did not carry — an array has an + /// order and repeats where a set has neither, and a pair says nothing about whether + /// its endpoints are included. An interval is asked for by name instead, which also + /// makes the choice visible rather than assumed. + /// + /// + /// This test compiling is the part that matters: bring either conversion back and the + /// assertions below start passing for the wrong reason, but the ones in + /// ListArgumentOverloadTest stop building. + /// + [Fact] + public void AnIntervalIsAskedForByName() + { + Test(MathS.Interval(1, 5), new Entity.Set.Interval(1, true, 5, true)); + Assert.NotEqual( + (Entity)MathS.Interval(1, true, 5, true), + (Entity)MathS.Interval(1, false, 5, false)); + } } } diff --git a/Sources/Tests/UnitTests/Common/PublicApi.txt b/Sources/Tests/UnitTests/Common/PublicApi.txt index 60378c567..538ff42d1 100644 --- a/Sources/Tests/UnitTests/Common/PublicApi.txt +++ b/Sources/Tests/UnitTests/Common/PublicApi.txt @@ -2144,7 +2144,6 @@ AngouriMath.Entity.op_Implicit(System.String) : AngouriMath.Entity AngouriMath.Entity.op_Implicit(System.UInt16) : AngouriMath.Entity AngouriMath.Entity.op_Implicit(System.UInt32) : AngouriMath.Entity AngouriMath.Entity.op_Implicit(System.UInt64) : AngouriMath.Entity -AngouriMath.Entity.op_Implicit(System.ValueTuple) : AngouriMath.Entity AngouriMath.Entity.op_Inequality(AngouriMath.Entity, AngouriMath.Entity) : System.Boolean AngouriMath.Entity.op_LessThan(AngouriMath.Entity, AngouriMath.Entity) : AngouriMath.Entity AngouriMath.Entity.op_LessThanOrEqual(AngouriMath.Entity, AngouriMath.Entity) : AngouriMath.Entity