diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index c1c85c377..8bcf7515d 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -57,6 +57,7 @@ read first. | loud | `MathS.Quantum.IsNormalised` | the British spelling | `MathS.Quantum.IsNormalized` | | loud | the target frameworks | `net7.0;netstandard2.0` | `netstandard2.0;net8.0;net10.0` | | **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 | --- @@ -99,6 +100,56 @@ the built assemblies: present in `net8.0` and `net10.0`, absent from `netstandar ## Types and members +### The implicit conversion from `List` is removed + +`Entity` had two implicit conversions from a collection, both building a `FiniteSet`: + +```csharp +public static implicit operator Entity(Entity[] elements); +public static implicit operator Entity(List elements); // removed +``` + +The second one made three public members impossible to call with a `List`: + +```csharp +var equations = new List { "x - 1", "y - 2" }; +new EquationSystem(equations); // error CS0121: the call is ambiguous +new FiniteSet(equations); // error CS0121 +MathS.Equations(equations); // error CS0121 +``` + +Each of those has both an `IEnumerable` overload and a `params Entity[]` overload. With +the conversion in place a `List` also converts to a single `Entity`, so the `params` +overload becomes applicable in its expanded form, neither candidate is better than the other, +and the call does not compile. The array, `IEnumerable`, and variadic forms were always +fine — only a concrete `List` broke, which is why it survived to a 2.0 preview. + +This was not three separate defects. One conversion made *every* `params Entity[]` overload in +the library uncallable with a list, including any added later. + +**This one moves between previews.** The conversion is in `2.0.0-preview.1` and +`2.0.0-preview.2`, both published, and is removed in the release that follows them. It is so +far the only entry here that changes between two previews rather than between releases: a +reader coming from 1.3.0 or 1.4.0 can ignore the distinction, one already on a preview cannot. + +**What breaks.** Assigning a list where an `Entity` is expected: + +```csharp +Entity set = new List { 1, 2, 3 }; // no longer compiles +``` + +Write one of these instead: + +```csharp +Entity set = new FiniteSet(new List { 1, 2, 3 }); +Entity set = new List { 1, 2, 3 }.ToSet(); +Entity set = new Entity[] { 1, 2, 3 }; // the array conversion is unchanged +``` + +The `Entity[]` conversion is kept: an array argument binds to the `params` overload in its normal +form by an identity conversion, which beats the alternatives outright, so it never produced the +ambiguity. + ### `Minusf`'s two operands exchanged names In `a - b`, `a` is the minuend and `b` is the subtrahend. The record declared them the other way diff --git a/Sources/.editorconfig b/Sources/.editorconfig index ddceb7143..08be865e7 100644 --- a/Sources/.editorconfig +++ b/Sources/.editorconfig @@ -24,6 +24,9 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed [Tests/UnitTests/Core/Transformations/*.cs] file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n +[Tests/UnitTests/Common/ListArgumentOverloadTest.cs] +file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n + # A new file in a directory whose other files predate it, so the section is on the file # rather than the folder. [AngouriMath/Core/Entity/Continuous/Entity.Continuous.{Floors,Rounding}.Classes.cs] diff --git a/Sources/AngouriMath/Core/Entity/Omni/Entity.Set.cs b/Sources/AngouriMath/Core/Entity/Omni/Entity.Set.cs index 96e409428..ba2bb62c9 100644 --- a/Sources/AngouriMath/Core/Entity/Omni/Entity.Set.cs +++ b/Sources/AngouriMath/Core/Entity/Omni/Entity.Set.cs @@ -83,7 +83,6 @@ public bool Contains(Entity entity) 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); public static implicit operator Entity(Entity[] elements) => new FiniteSet(elements); - public static implicit operator Entity(List elements) => new FiniteSet((IEnumerable)elements); #pragma warning restore CS1591 } diff --git a/Sources/Tests/UnitTests/Common/ListArgumentOverloadTest.cs b/Sources/Tests/UnitTests/Common/ListArgumentOverloadTest.cs new file mode 100644 index 000000000..cb6854b17 --- /dev/null +++ b/Sources/Tests/UnitTests/Common/ListArgumentOverloadTest.cs @@ -0,0 +1,72 @@ +// +// Copyright (c) 2019-2026 Angouri. +// AngouriMath is licensed under MIT. +// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md. +// Website: https://am.angouri.org. +// + +using System.Collections.Generic; +using AngouriMath; +using AngouriMath.Core; +using Xunit; + +namespace AngouriMath.Tests.Common +{ + /// + /// Every member here takes both an and a params Entity[]. + /// While Entity also had an implicit conversion from List<Entity>, a list + /// argument matched the params overload in its expanded form as well, neither candidate was + /// better than the other, and none of these calls compiled -- CS0121. The conversion is gone; + /// this file failing to compile is what says it has not come back. + /// + [Trait("Area", "Common")] + public sealed class ListArgumentOverloadTest + { + static List Equations => new() { "x - 1", "y - 2" }; + + [Fact] + public void EquationSystemTakesAList() + { + var solutions = new EquationSystem(Equations).Solve("x", "y"); + Assert.NotNull(solutions); + Assert.Equal(1, solutions.RowCount); + Assert.Equal(2, solutions.ColumnCount); + } + + [Fact] + public void MathSEquationsTakesAList() + { + var solutions = MathS.Equations(Equations).Solve("x", "y"); + Assert.NotNull(solutions); + Assert.Equal(1, solutions.RowCount); + } + + [Fact] + public void FiniteSetTakesAList() + { + var set = new Entity.Set.FiniteSet(new List { 1, 2, 3 }); + Assert.Equal(3, set.Count); + } + + /// The array and variadic forms were never ambiguous, and must stay that way. + [Fact] + public void ArrayAndVariadicFormsStillBind() + { + Entity[] equations = { "x - 1", "y - 2" }; + Assert.NotNull(new EquationSystem(equations).Solve("x", "y")); + Assert.NotNull(new EquationSystem("x - 1", "y - 2").Solve("x", "y")); + Assert.Equal(3, new Entity.Set.FiniteSet(1, 2, 3).Count); + } + + /// + /// The conversion from an array is deliberately kept, so an array in an + /// Entity position is still a set. + /// + [Fact] + public void ArrayStillConvertsToASet() + { + Entity set = new Entity[] { 1, 2, 3 }; + Assert.Equal(new Entity.Set.FiniteSet(1, 2, 3), set); + } + } +} diff --git a/Sources/Tests/UnitTests/Common/PublicApi.txt b/Sources/Tests/UnitTests/Common/PublicApi.txt index b8f1bc72f..33e85f487 100644 --- a/Sources/Tests/UnitTests/Common/PublicApi.txt +++ b/Sources/Tests/UnitTests/Common/PublicApi.txt @@ -2132,7 +2132,6 @@ AngouriMath.Entity.op_Implicit(PeterO.Numbers.EInteger) : AngouriMath.Entity AngouriMath.Entity.op_Implicit(PeterO.Numbers.ERational) : AngouriMath.Entity AngouriMath.Entity.op_Implicit(System.Boolean) : AngouriMath.Entity AngouriMath.Entity.op_Implicit(System.Byte) : AngouriMath.Entity -AngouriMath.Entity.op_Implicit(System.Collections.Generic.List) : AngouriMath.Entity AngouriMath.Entity.op_Implicit(System.Decimal) : AngouriMath.Entity AngouriMath.Entity.op_Implicit(System.Double) : AngouriMath.Entity AngouriMath.Entity.op_Implicit(System.Int16) : AngouriMath.Entity