From cb3a3eeb619a8012e54d737e5e721a9777f1d9ef Mon Sep 17 00:00:00 2001 From: Apoorv Darshan Date: Wed, 18 Feb 2026 02:08:12 +0530 Subject: [PATCH 1/3] Fix Seq.empty rendering as "EmptyEnumerable" in serializers (#17864) Seq.empty used a custom DU type EmptyEnumerable<'T> that serializers detected as an F# union, rendering "EmptyEnumerable" instead of []. - Change Seq.empty to delegate to System.Linq.Enumerable.Empty<'T>() - Update concat optimization from :? EmptyEnumerable<'T> to :? ICollection<'T> when Count = 0 for broader empty detection - Add tests verifying Seq.empty is not a DU type and concat still works --- src/FSharp.Core/seq.fs | 2 +- src/FSharp.Core/seqcore.fs | 4 ++-- .../Microsoft.FSharp.Collections/SeqModule.fs | 15 ++++++++++++++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/FSharp.Core/seq.fs b/src/FSharp.Core/seq.fs index c05dbdc3701..ed0842488ef 100644 --- a/src/FSharp.Core/seq.fs +++ b/src/FSharp.Core/seq.fs @@ -608,7 +608,7 @@ module Seq = mkUnfoldSeq generator state [] - let empty<'T> = (EmptyEnumerable :> seq<'T>) + let empty<'T> = System.Linq.Enumerable.Empty<'T>() [] let initInfinite initializer = diff --git a/src/FSharp.Core/seqcore.fs b/src/FSharp.Core/seqcore.fs index 1445f9b16c0..784bdb60ea2 100644 --- a/src/FSharp.Core/seqcore.fs +++ b/src/FSharp.Core/seqcore.fs @@ -313,9 +313,9 @@ module RuntimeHelpers = let rec takeOuter() = if outerEnum.MoveNext() then let ie = outerEnum.Current - // Optimization to detect the statically-allocated empty IEnumerables + // Optimization to detect empty IEnumerables without calling GetEnumerator match box ie with - | :? EmptyEnumerable<'T> -> + | :? ICollection<'T> as c when c.Count = 0 -> // This one is empty, just skip, don't call GetEnumerator, try again takeOuter() | _ -> diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/SeqModule.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/SeqModule.fs index 5d37b1ecdf2..ba70556361b 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/SeqModule.fs +++ b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/SeqModule.fs @@ -1523,4 +1523,17 @@ type SeqModule() = let choice = seq |> Seq.randomSampleBy (fun () -> 1.0) 0 - Assert.AreEqual(0, choice |> Seq.length) \ No newline at end of file + Assert.AreEqual(0, choice |> Seq.length) + + [] + member _.``Seq.empty is not a discriminated union type``() = + let empty = Seq.empty + let ty = empty.GetType() + let isUnion = Microsoft.FSharp.Reflection.FSharpType.IsUnion(ty) + Assert.False(isUnion, "Seq.empty should not be a discriminated union type") + + [] + member _.``Seq.concat with empty elements works``() = + let result = Seq.concat [ Seq.empty; seq { 1; 2 }; Seq.empty; seq { 3 }; Seq.empty ] + let expected = [| 1; 2; 3 |] + Assert.AreEqual(expected, result |> Seq.toArray) \ No newline at end of file From 80be18159ed6bbda22e5fc2d5a057926ca2ddf4f Mon Sep 17 00:00:00 2001 From: Apoorv Darshan Date: Wed, 18 Feb 2026 02:34:57 +0530 Subject: [PATCH 2/3] Add release notes for Seq.empty serialization fix --- docs/release-notes/.FSharp.Core/10.0.300.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/.FSharp.Core/10.0.300.md b/docs/release-notes/.FSharp.Core/10.0.300.md index 90fd2f1e9ad..6271cd349eb 100644 --- a/docs/release-notes/.FSharp.Core/10.0.300.md +++ b/docs/release-notes/.FSharp.Core/10.0.300.md @@ -6,6 +6,7 @@ * Fix tuple/multi-value projections in queries to use Queryable.Select instead of Enumerable.Select when the source is IQueryable, preserving query composition and enabling async operations like ToListAsync() in Entity Framework Core. ([Issue #3782](https://github.com/dotnet/fsharp/issues/3782), [Issue #15133](https://github.com/dotnet/fsharp/issues/15133)) * Fix EvaluateQuotation to handle Sequential expressions, void method calls (unit return), and other patterns that were previously throwing NotSupportedException. Also properly handles unit-returning expressions by using Action delegates instead of Func delegates. ([Issue #19099](https://github.com/dotnet/fsharp/issues/19099)) * Fix query conditionals without else branch (if-then only) that were causing type mismatch errors. Now properly extracts element type from IQueryable for creating empty sequences. ([Issue #3445](https://github.com/dotnet/fsharp/issues/3445)) +* Fix `Seq.empty` rendering as `"EmptyEnumerable"` in serializers by delegating to `System.Linq.Enumerable.Empty<'T>()` instead of using a custom DU type. ([Issue #17864](https://github.com/dotnet/fsharp/issues/17864), [PR #19317](https://github.com/dotnet/fsharp/pull/19317)) ### Added From a1522eceda5bc83b8ce213b6fa5d57d0efcdde9a Mon Sep 17 00:00:00 2001 From: Apoorv Darshan Date: Wed, 18 Feb 2026 04:46:59 +0530 Subject: [PATCH 3/3] Update expected trimmed binary sizes after Seq.empty change --- tests/AheadOfTime/Trimming/check.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/AheadOfTime/Trimming/check.ps1 b/tests/AheadOfTime/Trimming/check.ps1 index ec64b57863f..2b15c468c2c 100644 --- a/tests/AheadOfTime/Trimming/check.ps1 +++ b/tests/AheadOfTime/Trimming/check.ps1 @@ -63,10 +63,10 @@ function CheckTrim($root, $tfm, $outputfile, $expected_len, $callerLineNumber) { $allErrors = @() # Check net9.0 trimmed assemblies -$allErrors += CheckTrim -root "SelfContained_Trimming_Test" -tfm "net9.0" -outputfile "FSharp.Core.dll" -expected_len 311296 -callerLineNumber 66 +$allErrors += CheckTrim -root "SelfContained_Trimming_Test" -tfm "net9.0" -outputfile "FSharp.Core.dll" -expected_len 310784 -callerLineNumber 66 # Check net9.0 trimmed assemblies with static linked FSharpCore -$allErrors += CheckTrim -root "StaticLinkedFSharpCore_Trimming_Test" -tfm "net9.0" -outputfile "StaticLinkedFSharpCore_Trimming_Test.dll" -expected_len 9169408 -callerLineNumber 69 +$allErrors += CheckTrim -root "StaticLinkedFSharpCore_Trimming_Test" -tfm "net9.0" -outputfile "StaticLinkedFSharpCore_Trimming_Test.dll" -expected_len 9168384 -callerLineNumber 69 # Check net9.0 trimmed assemblies with F# metadata resources removed $allErrors += CheckTrim -root "FSharpMetadataResource_Trimming_Test" -tfm "net9.0" -outputfile "FSharpMetadataResource_Trimming_Test.dll" -expected_len 7609344 -callerLineNumber 72