From 9604f9d88945e8fecb4e675e4bc327ddf1febac1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 01:20:54 +0000 Subject: [PATCH 1/2] test: add SideEffects module to TaskSeq.FirstLastDefault.Tests.fs Add SideEffects tests for firstOrDefault and lastOrDefault, covering: - firstOrDefault stops consuming after the first element (matches head behaviour) - firstOrDefault executes side effects that occur before the first yield - lastOrDefault fully drains the sequence (matches last/tryLast behaviour) - TestSideEffectTaskSeq theory variants for both functions 58 tests now pass for the FirstLastDefault suite (19 new). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../TaskSeq.FirstLastDefault.Tests.fs | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.FirstLastDefault.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.FirstLastDefault.Tests.fs index 47d0974c..5fd2653c 100644 --- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.FirstLastDefault.Tests.fs +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.FirstLastDefault.Tests.fs @@ -91,3 +91,81 @@ module Immutable = let! result = TaskSeq.singleton 42 |> TaskSeq.lastOrDefault 0 result |> should equal 42 } + + +module SideEffects = + [] + let ``TaskSeq-firstOrDefault __special-case__ prove it does not read beyond first yield`` () = task { + let mutable x = 42 + + let ts = taskSeq { + yield x + x <- x + 1 // we never get here + } + + let! fortyTwo = ts |> TaskSeq.firstOrDefault 0 + let! stillFortyTwo = ts |> TaskSeq.firstOrDefault 0 // the statement after 'yield' will never be reached + + fortyTwo |> should equal 42 + stillFortyTwo |> should equal 42 + } + + [] + let ``TaskSeq-firstOrDefault __special-case__ prove early side effect is executed`` () = task { + let mutable x = 42 + + let ts = taskSeq { + x <- x + 1 + x <- x + 1 + yield 42 + x <- x + 200 // we won't get here! + } + + let! result = ts |> TaskSeq.firstOrDefault 0 + result |> should equal 42 + x |> should equal 44 + + let! result = ts |> TaskSeq.firstOrDefault 0 + result |> should equal 42 + x |> should equal 46 + } + + [] + let ``TaskSeq-lastOrDefault __special-case__ prove it reads the entire sequence`` () = task { + let mutable x = 42 + + let ts = taskSeq { + yield x + x <- x + 1 // will be executed + yield x + x <- x + 1 // will be executed + } + + let! result = ts |> TaskSeq.lastOrDefault -1 + result |> should equal 43 + x |> should equal 44 + } + + [)>] + let ``TaskSeq-firstOrDefault returns first item in a side-effect sequence`` variant = task { + let ts = Gen.getSeqWithSideEffect variant + + let! first = ts |> TaskSeq.firstOrDefault 0 + first |> should equal 1 + + // side effect: re-enumerating changes the first item + let! secondFirst = ts |> TaskSeq.firstOrDefault 0 + secondFirst |> should not' (equal 1) + } + + [)>] + let ``TaskSeq-lastOrDefault returns last item and exhausts the sequence`` variant = task { + let ts = Gen.getSeqWithSideEffect variant + + let! last = ts |> TaskSeq.lastOrDefault 0 + last |> should equal 10 + + // side effect: re-enumerating continues from mutated state + let! secondLast = ts |> TaskSeq.lastOrDefault 0 + secondLast |> should equal 20 + } From 9650486f568eccad664d3f984d901827e188db03 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 22 Apr 2026 01:20:57 +0000 Subject: [PATCH 2/2] ci: trigger checks