From 67e4bc22a51a853fc3da3787bab2dc48fd79e4df Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sun, 9 Aug 2026 17:45:37 +0000 Subject: [PATCH 1/3] Drop the implicit List conversion that made three params overloads uncallable Entity had implicit conversions to itself from both Entity[] and List, each building a FiniteSet. The List one meant a list also converted to a single Entity, so for any member with both an IEnumerable overload and a params Entity[] overload the params form became applicable in its expanded form, neither candidate was better, and the call did not compile: var equations = new List { "x - 1", "y - 2" }; new EquationSystem(equations); // error CS0121 new FiniteSet(equations); // error CS0121 MathS.Equations(equations); // error CS0121 Not three defects -- one conversion breaking every params Entity[] overload in the library, including any added later. A sweep of all 2602 members in PublicApi.txt for the IEnumerable/T[] overload pattern found exactly these three, and each was confirmed by compiling the call in isolation. The array, IEnumerable and variadic forms always compiled; only a concrete List broke, which is how it reached a 2.0 preview unnoticed. The Entity[] conversion is kept. An array binds to the params overload in its normal form by an identity conversion, which wins outright, so it never had the ambiguity. The new test compiling is the regression guard: restore the conversion and the test project stops building. Sources/.editorconfig gets a per-file header section for it, since Tests/UnitTests/Common still carries the 2022 template. Verified: 6055 C# tests and 130 F# tests pass, the library, F# wrapper and Utils build clean, and all ten call shapes in the ambiguity matrix now compile. Co-Authored-By: Claude Opus 5 (1M context) --- BREAKING-CHANGES.md | 46 ++++++++++++ Sources/.editorconfig | 3 + .../Core/Entity/Omni/Entity.Set.cs | 1 - .../Common/ListArgumentOverloadTest.cs | 72 +++++++++++++++++++ Sources/Tests/UnitTests/Common/PublicApi.txt | 1 - 5 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 Sources/Tests/UnitTests/Common/ListArgumentOverloadTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index c1c85c377..f44f6af83 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,51 @@ 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. + +**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 From 597dcf3434492b3f842b68b5d834ff90f1d9b88d Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sun, 9 Aug 2026 18:21:28 +0000 Subject: [PATCH 2/3] Say that this break falls between preview.1 and preview.2 preview.1 is published, and it has the conversion. Every other entry in the file describes a change from 1.3.0 or 1.4.0, so the file's implicit frame is release-to-release and a reader already on preview.1 would not learn from it that this one lands on them. Checked: #848 is the only other commit to touch the file since the preview.1 tag, and what it added is a 1.4.0 section, not a preview delta. Co-Authored-By: Claude Opus 5 (1M context) --- BREAKING-CHANGES.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index f44f6af83..671a04018 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -127,6 +127,11 @@ fine — only a concrete `List` broke, which is why it survived to a 2.0 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 present in `2.0.0-preview.1`, which is +published, and gone from `2.0.0-preview.2`. 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 preview.1 cannot. + **What breaks.** Assigning a list where an `Entity` is expected: ```csharp From edee86c31572c2c2534239a50308227115a8615f Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sun, 9 Aug 2026 18:27:39 +0000 Subject: [PATCH 3/3] Correct which previews carry the conversion preview.2 was tagged while this branch sat unmerged, so it ships the conversion too and the previous commit's "gone from preview.2" is wrong. Naming the version that removes something from a branch that has not merged bets on merge order; the entry now says "the release that follows them" and stops guessing. Co-Authored-By: Claude Opus 5 (1M context) --- BREAKING-CHANGES.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 671a04018..8bcf7515d 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -127,10 +127,10 @@ fine — only a concrete `List` broke, which is why it survived to a 2.0 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 present in `2.0.0-preview.1`, which is -published, and gone from `2.0.0-preview.2`. 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 preview.1 cannot. +**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: