From ce4b25e53d7490ecccb2c00c936a684177012c4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristofer=20L=C3=B6fberg?= Date: Fri, 3 Jul 2026 13:32:07 +0200 Subject: [PATCH 1/7] fix(all): add Seq.enumerateTryWith for try/with in comprehensions --- src/fable-library-dart/Seq.fs | 70 +++++++++++++++++++++++ src/fable-library-py/fable_library/Seq.fs | 70 +++++++++++++++++++++++ src/fable-library-rust/src/Seq.fs | 53 +++++++++++++++++ src/fable-library-ts/Seq.fs | 70 +++++++++++++++++++++++ tests/Dart/src/SeqExpressionTests.fs | 27 +++++++++ tests/Js/Main/SeqExpressionTests.fs | 35 ++++++++++++ tests/Python/TestSeqExpression.fs | 31 ++++++++++ 7 files changed, 356 insertions(+) diff --git a/src/fable-library-dart/Seq.fs b/src/fable-library-dart/Seq.fs index 77b0697d8c..36d5fdfb2f 100644 --- a/src/fable-library-dart/Seq.fs +++ b/src/fable-library-dart/Seq.fs @@ -387,6 +387,76 @@ let enumerateWhile (guard: unit -> bool) (xs: seq<'T>) : seq<'T> = 0 ) +let enumerateTryWith (source: seq<'T>) (catchFilter: exn -> int) (catchHandler: exn -> seq<'T>) : seq<'T> = + mkSeq (fun () -> + let mutable e: IEnumerator<'T> option = None + let mutable caught = false // switched to the handler sequence + let mutable started = false + let mutable finished = false + let mutable curr = None + + let current () = + if not started then + Enumerator.notStarted () + elif finished then + Enumerator.alreadyFinished () + + match curr with + | None -> Enumerator.alreadyFinished () + | Some x -> x + + let rec tryNext () = + try + let en = + match e with + | Some en -> en + | None -> + let en = source.GetEnumerator() + e <- Some en + en + + if en.MoveNext() then + curr <- Some en.Current + true + else + finished <- true + false + with ex when not caught -> + if catchFilter ex <> 0 then + caught <- true + + match e with + | Some en -> + (try + en.Dispose() + with _ -> + ()) + + e <- None + | None -> () + + e <- Some((catchHandler ex).GetEnumerator()) + tryNext () + else + reraise () + + let next () = + if not started then + started <- true + + if finished then + false + else + tryNext () + + let dispose () = + match e with + | Some en -> en.Dispose() + | None -> () + + Enumerator.fromFunctions current next dispose + ) + let filter f (xs: seq<'T>) = xs |> choose (fun x -> diff --git a/src/fable-library-py/fable_library/Seq.fs b/src/fable-library-py/fable_library/Seq.fs index 2097491190..081a8414d7 100644 --- a/src/fable-library-py/fable_library/Seq.fs +++ b/src/fable-library-py/fable_library/Seq.fs @@ -439,6 +439,76 @@ let enumerateWhile (guard: unit -> bool) (xs: seq<'T>) = 0 ) +let enumerateTryWith (source: seq<'T>) (catchFilter: exn -> int) (catchHandler: exn -> seq<'T>) : seq<'T> = + mkSeq (fun () -> + let mutable e: IEnumerator<'T> option = None + let mutable caught = false // switched to the handler sequence + let mutable started = false + let mutable finished = false + let mutable curr = None + + let current () = + if not started then + Enumerator.notStarted () + elif finished then + Enumerator.alreadyFinished () + + match curr with + | None -> Enumerator.alreadyFinished () + | Some x -> x + + let rec tryNext () = + try + let en = + match e with + | Some en -> en + | None -> + let en = source.GetEnumerator() + e <- Some en + en + + if en.MoveNext() then + curr <- Some en.Current + true + else + finished <- true + false + with ex when not caught -> + if catchFilter ex <> 0 then + caught <- true + + match e with + | Some en -> + (try + en.Dispose() + with _ -> + ()) + + e <- None + | None -> () + + e <- Some((catchHandler ex).GetEnumerator()) + tryNext () + else + reraise () + + let next () = + if not started then + started <- true + + if finished then + false + else + tryNext () + + let dispose () = + match e with + | Some en -> en.Dispose() + | None -> () + + Enumerator.fromFunctions current next dispose + ) + [] let filter (predicate: 'T -> bool) (xs: seq<'T>) : seq<'T> = nativeOnly diff --git a/src/fable-library-rust/src/Seq.fs b/src/fable-library-rust/src/Seq.fs index eefabcad68..e26fd704e8 100644 --- a/src/fable-library-rust/src/Seq.fs +++ b/src/fable-library-rust/src/Seq.fs @@ -584,6 +584,59 @@ let enumerateWhile (guard: unit -> bool) (xs: 'T seq) = 0 ) +let enumerateTryWith (source: 'T seq) (catchFilter: exn -> int) (catchHandler: exn -> 'T seq) = + mkSeq (fun () -> + let mutable e: IEnumerator<'T> option = None + let mutable caught = false // switched to the handler sequence + let mutable finished = false + + let next () = + let mutable res = None + let mutable again = true + + while again do + again <- false + + if not finished then + try + let en = + match e with + | Some en -> en + | None -> + let en = source.GetEnumerator() + e <- Some en + en + + if en.MoveNext() then + res <- Some en.Current + else + finished <- true + with ex -> + if not caught && catchFilter ex <> 0 then + caught <- true + + match e with + | Some en -> + en.Dispose() + e <- None + | None -> () + + e <- Some((catchHandler ex).GetEnumerator()) + again <- true // pull the next element from the handler sequence + else + // reraise() + failwith ex.Message + + res + + let dispose () = + match e with + | Some en -> en.Dispose() + | None -> () + + Enumerable.fromFunctions next dispose + ) + let exactlyOne (xs: 'T seq) = use e = ofSeq xs diff --git a/src/fable-library-ts/Seq.fs b/src/fable-library-ts/Seq.fs index f8924f8f1b..20631e4a89 100644 --- a/src/fable-library-ts/Seq.fs +++ b/src/fable-library-ts/Seq.fs @@ -445,6 +445,76 @@ let enumerateWhile (guard: unit -> bool) (xs: seq<'T>) = 0 ) +let enumerateTryWith (source: seq<'T>) (catchFilter: exn -> int) (catchHandler: exn -> seq<'T>) : seq<'T> = + mkSeq (fun () -> + let mutable e: IEnumerator<'T> option = None + let mutable caught = false // switched to the handler sequence + let mutable started = false + let mutable finished = false + let mutable curr = None + + let current () = + if not started then + Enumerator.notStarted () + elif finished then + Enumerator.alreadyFinished () + + match curr with + | None -> Enumerator.alreadyFinished () + | Some x -> x + + let rec tryNext () = + try + let en = + match e with + | Some en -> en + | None -> + let en = source.GetEnumerator() + e <- Some en + en + + if en.MoveNext() then + curr <- Some en.Current + true + else + finished <- true + false + with ex when not caught -> + if catchFilter ex <> 0 then + caught <- true + + match e with + | Some en -> + (try + en.Dispose() + with _ -> + ()) + + e <- None + | None -> () + + e <- Some((catchHandler ex).GetEnumerator()) + tryNext () + else + reraise () + + let next () = + if not started then + started <- true + + if finished then + false + else + tryNext () + + let dispose () = + match e with + | Some en -> en.Dispose() + | None -> () + + Enumerator.fromFunctions current next dispose + ) + let filter f (xs: seq<'T>) = xs |> choose (fun x -> diff --git a/tests/Dart/src/SeqExpressionTests.fs b/tests/Dart/src/SeqExpressionTests.fs index 83da712275..7628c194e9 100644 --- a/tests/Dart/src/SeqExpressionTests.fs +++ b/tests/Dart/src/SeqExpressionTests.fs @@ -92,6 +92,33 @@ let tests() = with _ -> () equal 1 n + testCase "try...with in list expressions works" <| fun () -> + [ try 1 with _ -> 0 ] + |> equal [1] + + testCase "try...with in list expressions catches exceptions" <| fun () -> + [ try raise (exn "boom") with _ -> 42 ] + |> equal [42] + + testCase "try...with in array expressions works" <| fun () -> + [| try raise (exn "boom") with _ -> 7 |] + |> equal [| 7 |] + + testCase "try...with in seq expressions preserves yielded elements before the exception" <| fun () -> + let mutable caught = false + let xs = + seq { + try + yield 1 + raise (exn "boom") + yield 2 + with _ -> + caught <- true + yield 3 + } |> Seq.toList + equal [1; 3] xs + equal true caught + testCase "use in seq expressions works" <| fun () -> let mutable n = 0 seq { diff --git a/tests/Js/Main/SeqExpressionTests.fs b/tests/Js/Main/SeqExpressionTests.fs index e7063c5994..078c37b9b1 100644 --- a/tests/Js/Main/SeqExpressionTests.fs +++ b/tests/Js/Main/SeqExpressionTests.fs @@ -90,6 +90,41 @@ let tests = with _ -> () equal 1 n + testCase "try...with in list expressions works" <| fun () -> + [ try 1 with _ -> 0 ] + |> equal [1] + + testCase "try...with in list expressions catches exceptions" <| fun () -> + [ try raise (exn "boom") with _ -> 42 ] + |> equal [42] + + testCase "try...with in array expressions works" <| fun () -> + [| try raise (exn "boom") with _ -> 7 |] + |> equal [| 7 |] + + testCase "try...with in seq expressions preserves yielded elements before the exception" <| fun () -> + let mutable caught = false + let xs = + seq { + try + yield 1 + raise (exn "boom") + yield 2 + with _ -> + caught <- true + yield 3 + } |> Seq.toList + equal [1; 3] xs + equal true caught + + testCase "try...with in list expressions rethrows unmatched exceptions" <| fun () -> + let mutable msg = "" + try + [ try raise (exn "original") with :? System.ArgumentException -> 0 ] + |> ignore + with ex -> msg <- ex.Message + equal "original" msg + testCase "use in seq expressions works" <| fun () -> let mutable n = 0 seq { diff --git a/tests/Python/TestSeqExpression.fs b/tests/Python/TestSeqExpression.fs index 153265b66a..e7a27142e8 100644 --- a/tests/Python/TestSeqExpression.fs +++ b/tests/Python/TestSeqExpression.fs @@ -98,6 +98,37 @@ let ``test try...finally in seq expressions works`` () = with _ -> () equal 1 n +[] +let ``test try...with in list expressions works`` () = + [ try 1 with _ -> 0 ] + |> equal [1] + +[] +let ``test try...with in list expressions catches exceptions`` () = + [ try raise (exn "boom") with _ -> 42 ] + |> equal [42] + +[] +let ``test try...with in array expressions works`` () = + [| try raise (exn "boom") with _ -> 7 |] + |> equal [| 7 |] + +[] +let ``test try...with in seq expressions preserves yielded elements before the exception`` () = + let mutable caught = false + let xs = + seq { + try + yield 1 + raise (exn "boom") + yield 2 + with _ -> + caught <- true + yield 3 + } |> Seq.toList + equal [1; 3] xs + equal true caught + [] let ``test use in seq expressions works`` () = let mutable n = 0 From 2dcb2e2b8d60f6013915fdc79252e475f8624bfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristofer=20L=C3=B6fberg?= Date: Fri, 3 Jul 2026 14:33:57 +0200 Subject: [PATCH 2/7] fix(python): avoid nonlocal codegen error in Seq.enumerateTryWith --- src/fable-library-py/fable_library/Seq.fs | 121 ++++++++++------------ tests/Js/Main/SeqExpressionTests.fs | 8 -- 2 files changed, 57 insertions(+), 72 deletions(-) diff --git a/src/fable-library-py/fable_library/Seq.fs b/src/fable-library-py/fable_library/Seq.fs index 081a8414d7..c6d29e7093 100644 --- a/src/fable-library-py/fable_library/Seq.fs +++ b/src/fable-library-py/fable_library/Seq.fs @@ -439,75 +439,68 @@ let enumerateWhile (guard: unit -> bool) (xs: seq<'T>) = 0 ) -let enumerateTryWith (source: seq<'T>) (catchFilter: exn -> int) (catchHandler: exn -> seq<'T>) : seq<'T> = - mkSeq (fun () -> - let mutable e: IEnumerator<'T> option = None - let mutable caught = false // switched to the handler sequence - let mutable started = false - let mutable finished = false - let mutable curr = None - - let current () = - if not started then - Enumerator.notStarted () - elif finished then - Enumerator.alreadyFinished () - - match curr with - | None -> Enumerator.alreadyFinished () - | Some x -> x - - let rec tryNext () = - try - let en = - match e with - | Some en -> en - | None -> - let en = source.GetEnumerator() - e <- Some en - en - - if en.MoveNext() then - curr <- Some en.Current - true - else - finished <- true - false - with ex when not caught -> - if catchFilter ex <> 0 then - caught <- true - - match e with - | Some en -> - (try - en.Dispose() - with _ -> - ()) - - e <- None - | None -> () - - e <- Some((catchHandler ex).GetEnumerator()) - tryNext () - else - reraise () +// State for `enumerateTryWith`, threaded through `generate` as a parameter (rather than captured +// mutables) so Fable's Python codegen never emits a `nonlocal` for the swapped enumerator — a +// plain `mutable` would produce conflicting `nonlocal e` declarations across the try/except. +// A record (not a `ref` cell) is used on purpose: Fable's ref-cell -> mutable optimization could +// reintroduce the `nonlocal`, whereas a record field is always an attribute assignment. +type private EnumerateTryWithState<'T> = + { + mutable Source: IEnumerator<'T> option + mutable Caught: bool // switched to the handler sequence + } - let next () = - if not started then - started <- true +let enumerateTryWith (source: seq<'T>) (catchFilter: exn -> int) (catchHandler: exn -> seq<'T>) : seq<'T> = + generate + (fun () -> + { + Source = None + Caught = false + } + ) + (fun (state: EnumerateTryWithState<'T>) -> + let mutable result = None + let mutable go = true - if finished then - false - else - tryNext () + while go do + try + let en = + match state.Source with + | Some en -> en + | None -> + let en = source.GetEnumerator() + state.Source <- Some en + en + + if en.MoveNext() then + result <- Some en.Current + go <- false + else + // exhausted: leave result = None so `generate` finishes + go <- false + with ex -> + if (not state.Caught) && catchFilter ex <> 0 then + match state.Source with + | Some en -> + (try + en.Dispose() + with _ -> + ()) + | None -> () + + state.Source <- Some((catchHandler ex).GetEnumerator()) + state.Caught <- true + // else: loop again, pulling from the handler sequence + else + reraise () - let dispose () = - match e with + result + ) + (fun (state: EnumerateTryWithState<'T>) -> + match state.Source with | Some en -> en.Dispose() | None -> () - - Enumerator.fromFunctions current next dispose - ) + ) [] let filter (predicate: 'T -> bool) (xs: seq<'T>) : seq<'T> = nativeOnly diff --git a/tests/Js/Main/SeqExpressionTests.fs b/tests/Js/Main/SeqExpressionTests.fs index 078c37b9b1..8473c06b34 100644 --- a/tests/Js/Main/SeqExpressionTests.fs +++ b/tests/Js/Main/SeqExpressionTests.fs @@ -117,14 +117,6 @@ let tests = equal [1; 3] xs equal true caught - testCase "try...with in list expressions rethrows unmatched exceptions" <| fun () -> - let mutable msg = "" - try - [ try raise (exn "original") with :? System.ArgumentException -> 0 ] - |> ignore - with ex -> msg <- ex.Message - equal "original" msg - testCase "use in seq expressions works" <| fun () -> let mutable n = 0 seq { From 2488931f439a424af1a29d1cc04803e72369caac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristofer=20L=C3=B6fberg?= Date: Sat, 4 Jul 2026 09:28:45 +0200 Subject: [PATCH 3/7] fix(all): reraise in Rust enumerateTryWith, fix Python dispose, add rethrow test --- src/fable-library-py/fable_library/Seq.fs | 2 ++ src/fable-library-rust/src/Seq.fs | 3 +-- tests/Dart/src/SeqExpressionTests.fs | 17 +++++++++++++++++ tests/Js/Main/SeqExpressionTests.fs | 17 +++++++++++++++++ tests/Python/TestSeqExpression.fs | 18 ++++++++++++++++++ 5 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/fable-library-py/fable_library/Seq.fs b/src/fable-library-py/fable_library/Seq.fs index c6d29e7093..7991b5b6ad 100644 --- a/src/fable-library-py/fable_library/Seq.fs +++ b/src/fable-library-py/fable_library/Seq.fs @@ -486,6 +486,8 @@ let enumerateTryWith (source: seq<'T>) (catchFilter: exn -> int) (catchHandler: en.Dispose() with _ -> ()) + + state.Source <- None | None -> () state.Source <- Some((catchHandler ex).GetEnumerator()) diff --git a/src/fable-library-rust/src/Seq.fs b/src/fable-library-rust/src/Seq.fs index e26fd704e8..76c9fbddce 100644 --- a/src/fable-library-rust/src/Seq.fs +++ b/src/fable-library-rust/src/Seq.fs @@ -624,8 +624,7 @@ let enumerateTryWith (source: 'T seq) (catchFilter: exn -> int) (catchHandler: e e <- Some((catchHandler ex).GetEnumerator()) again <- true // pull the next element from the handler sequence else - // reraise() - failwith ex.Message + reraise () res diff --git a/tests/Dart/src/SeqExpressionTests.fs b/tests/Dart/src/SeqExpressionTests.fs index 7628c194e9..a9100a6fa2 100644 --- a/tests/Dart/src/SeqExpressionTests.fs +++ b/tests/Dart/src/SeqExpressionTests.fs @@ -119,6 +119,23 @@ let tests() = equal [1; 3] xs equal true caught + testCase "try...with in seq expressions rethrows unmatched exceptions" <| fun () -> + let mutable propagated = false + try + seq { + try + yield 1 + raise (System.InvalidOperationException "boom") + yield 2 + with :? System.ArgumentException -> + yield 0 + } + |> Seq.toList + |> ignore + with :? System.InvalidOperationException -> + propagated <- true + equal true propagated + testCase "use in seq expressions works" <| fun () -> let mutable n = 0 seq { diff --git a/tests/Js/Main/SeqExpressionTests.fs b/tests/Js/Main/SeqExpressionTests.fs index 8473c06b34..b3bf14a19c 100644 --- a/tests/Js/Main/SeqExpressionTests.fs +++ b/tests/Js/Main/SeqExpressionTests.fs @@ -117,6 +117,23 @@ let tests = equal [1; 3] xs equal true caught + testCase "try...with in seq expressions rethrows unmatched exceptions" <| fun () -> + let mutable propagated = false + try + seq { + try + yield 1 + raise (System.InvalidOperationException "boom") + yield 2 + with :? System.ArgumentException -> + yield 0 + } + |> Seq.toList + |> ignore + with :? System.InvalidOperationException -> + propagated <- true + equal true propagated + testCase "use in seq expressions works" <| fun () -> let mutable n = 0 seq { diff --git a/tests/Python/TestSeqExpression.fs b/tests/Python/TestSeqExpression.fs index e7a27142e8..ed49084191 100644 --- a/tests/Python/TestSeqExpression.fs +++ b/tests/Python/TestSeqExpression.fs @@ -129,6 +129,24 @@ let ``test try...with in seq expressions preserves yielded elements before the e equal [1; 3] xs equal true caught +[] +let ``test try...with in seq expressions rethrows unmatched exceptions`` () = + let mutable propagated = false + try + seq { + try + yield 1 + raise (System.InvalidOperationException "boom") + yield 2 + with :? System.ArgumentException -> + yield 0 + } + |> Seq.toList + |> ignore + with :? System.InvalidOperationException -> + propagated <- true + equal true propagated + [] let ``test use in seq expressions works`` () = let mutable n = 0 From 33994e4d99ade7e45ca1992308016005cf3f4b76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristofer=20L=C3=B6fberg?= Date: Tue, 7 Jul 2026 13:44:36 +0200 Subject: [PATCH 4/7] fix(all): fix mistyped fallback in filtered try/with handlers in seq expressions --- src/Fable.Transforms/FSharp2Fable.Util.fs | 34 +++++++++++++++++++++++ src/Fable.Transforms/FSharp2Fable.fs | 3 ++ 2 files changed, 37 insertions(+) diff --git a/src/Fable.Transforms/FSharp2Fable.Util.fs b/src/Fable.Transforms/FSharp2Fable.Util.fs index 00833c2bc8..c25f82f8d9 100644 --- a/src/Fable.Transforms/FSharp2Fable.Util.fs +++ b/src/Fable.Transforms/FSharp2Fable.Util.fs @@ -2016,6 +2016,40 @@ module Util = Fable.TryCatch(body, catchClause, finalizer, r) + /// Temporary fix for try/with with filtered (typed or `when`) handlers in seq expressions. + /// FCS compiles the handler of RuntimeHelpers.EnumerateTryWith with FailFilter as the + /// match-failure action (CheckSequenceExpressions.fs), so the handler's unmatched fallback + /// branch is emitted as a mistyped `0` (int) where seq<'T> is expected. The branch is + /// unreachable at runtime (Seq.enumerateTryWith only calls the handler after the filter + /// matched), but the wrong type breaks statically-typed targets (TypeScript/Dart). + /// Rewrite the mistyped leaf into a rethrow of the caught exception, which is bottom-typed + /// on all targets and semantically correct if it were ever reached. + /// TODO: Remove when FCS compiles the handler with Rethrow like normal try/with does + /// (see CheckExpressions.fs vs CheckSequenceExpressions.fs). + let fixEnumerateTryWithHandler (memb: FSharpMemberOrFunctionOrValue) (args: Fable.Expr list) = + if memb.FullName = "Microsoft.FSharp.Core.CompilerServices.RuntimeHelpers.EnumerateTryWith" then + match args with + | [ source; filterLambda; Fable.Lambda(exIdent, handlerBody, name) ] -> + let expectedType = handlerBody.Type + + let rec fixLeaf (expr: Fable.Expr) = + match expr with + | Fable.Value(Fable.NumberConstant _, r) when expr.Type <> expectedType -> + makeThrow r expectedType (Fable.IdentExpr exIdent) + | Fable.IfThenElse(guardExpr, thenExpr, elseExpr, r) -> + Fable.IfThenElse(guardExpr, fixLeaf thenExpr, fixLeaf elseExpr, r) + | Fable.DecisionTree(tree, targets) -> + let targets = targets |> List.map (fun (idents, e) -> idents, fixLeaf e) + + Fable.DecisionTree(tree, targets) + | Fable.Let(ident, value, body) -> Fable.Let(ident, value, fixLeaf body) + | _ -> expr + + [ source; filterLambda; Fable.Lambda(exIdent, fixLeaf handlerBody, name) ] + | _ -> args + else + args + let addGenArgsToContext (ctx: Context) (memb: FSharpMemberOrFunctionOrValue) (genArgs: Fable.Type list) = if not (List.isEmpty genArgs) then let genParams = diff --git a/src/Fable.Transforms/FSharp2Fable.fs b/src/Fable.Transforms/FSharp2Fable.fs index 314d7e089f..47c0c11e1f 100644 --- a/src/Fable.Transforms/FSharp2Fable.fs +++ b/src/Fable.Transforms/FSharp2Fable.fs @@ -1002,6 +1002,9 @@ let private transformExpr (com: IFableCompiler) (ctx: Context) appliedGenArgs fs else args + // Fix the mistyped fallback FCS generates for filtered try/with handlers in seq expressions + let args = fixEnumerateTryWithHandler memb args + match callee, memb with | Some(CreateEvent(callee, event) as createEvent), _ -> let! callee = transformExpr com ctx [] callee From da40c0365de7c0c541a6829ba0d4d7a8a9467a15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristofer=20L=C3=B6fberg?= Date: Tue, 7 Jul 2026 17:03:06 +0200 Subject: [PATCH 5/7] test(beam/rust): add try/with in seq expression tests Co-Authored-By: Claude Fable 5 --- tests/Beam/SeqExpressionTests.fs | 51 ++++++++++++++++ tests/Rust/tests/src/SeqExpressionTests.fs | 71 ++++++++++++++++++++++ 2 files changed, 122 insertions(+) diff --git a/tests/Beam/SeqExpressionTests.fs b/tests/Beam/SeqExpressionTests.fs index 53a23bdcfe..8562a99984 100644 --- a/tests/Beam/SeqExpressionTests.fs +++ b/tests/Beam/SeqExpressionTests.fs @@ -80,6 +80,57 @@ let ``test try finally in seq expressions works`` () = with _ -> () equal 1 n +[] +let ``test try...with in list expressions works`` () = + [ try 1 with _ -> 0 ] + |> equal [1] + +[] +let ``test try...with in list expressions catches exceptions`` () = + [ try raise (exn "boom") with _ -> 42 ] + |> equal [42] + +[] +let ``test try...with in array expressions works`` () = + [| try raise (exn "boom") with _ -> 7 |] + |> equal [| 7 |] + +[] +let ``test try...with in seq expressions preserves yielded elements before the exception`` () = + let mutable caught = false + let xs = + seq { + try + yield 1 + raise (exn "boom") + yield 2 + with _ -> + caught <- true + yield 3 + } |> Seq.toList + equal [1; 3] xs + equal true caught + +// TODO: enable when Beam correctly rethrows exceptions not matched by a filtered +// try/with handler in seq expressions (see PR #4719 review discussion) +// [] +// let ``test try...with in seq expressions rethrows unmatched exceptions`` () = +// let mutable propagated = false +// try +// seq { +// try +// yield 1 +// raise (System.InvalidOperationException "boom") +// yield 2 +// with :? System.ArgumentException -> +// yield 0 +// } +// |> Seq.toList +// |> ignore +// with :? System.InvalidOperationException -> +// propagated <- true +// equal true propagated + [] let ``test array expressions work`` () = [| for x in 1 .. 10 do yield x |] diff --git a/tests/Rust/tests/src/SeqExpressionTests.fs b/tests/Rust/tests/src/SeqExpressionTests.fs index c9f61d7c31..79f9e365b3 100644 --- a/tests/Rust/tests/src/SeqExpressionTests.fs +++ b/tests/Rust/tests/src/SeqExpressionTests.fs @@ -107,6 +107,77 @@ let ``recursive seq expressions work`` () = // } |> Seq.iter ignore // equal 1 n +[] +let ``try...with in list expressions works`` () = + [ try 1 with _ -> 0 ] + |> equal [1] + +[] +let ``try...with in list expressions catches exceptions`` () = + [ try raise (exn "boom") with _ -> 42 ] + |> equal [42] + +[] +let ``try...with in array expressions works`` () = + [| try raise (exn "boom") with _ -> 7 |] + |> equal [| 7 |] + +[] +let ``try...with in seq expressions preserves yielded elements before the exception`` () = + let mutable caught = false + let xs = + seq { + try + yield 1 + raise (exn "boom") + yield 2 + with _ -> + caught <- true + yield 3 + } |> Seq.toList + equal [1; 3] xs + equal true caught + +// Uses `when` guards on the message instead of exception type tests because Rust +// normalizes all exceptions to the base Exception type when catching panics +[] +let ``try...with in seq expressions rethrows unmatched exceptions`` () = + let mutable propagated = false + try + seq { + try + yield 1 + raise (exn "boom") + yield 2 + with e when e.Message = "other" -> + yield 0 + } + |> Seq.toList + |> ignore + with e when e.Message = "boom" -> + propagated <- true + equal true propagated + +// TODO: enable when the Rust target supports type-testing caught exceptions +// against BCL exception types (e.g. :? System.ArgumentException) +// [] +// let ``try...with in seq expressions rethrows unmatched exceptions (typed)`` () = +// let mutable propagated = false +// try +// seq { +// try +// yield 1 +// raise (System.InvalidOperationException "boom") +// yield 2 +// with :? System.ArgumentException -> +// yield 0 +// } +// |> Seq.toList +// |> ignore +// with :? System.InvalidOperationException -> +// propagated <- true +// equal true propagated + [] let ``array expressions work`` () = [| for x in 1 .. 10 do yield x |] From 63d6154e0d5c1ef532c536c837854c2fe191b4ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristofer=20L=C3=B6fberg?= Date: Tue, 7 Jul 2026 19:45:32 +0200 Subject: [PATCH 6/7] fix:Disable failing rust test if running with no_std --- tests/Rust/tests/src/SeqExpressionTests.fs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/Rust/tests/src/SeqExpressionTests.fs b/tests/Rust/tests/src/SeqExpressionTests.fs index 79f9e365b3..a7d99c4b49 100644 --- a/tests/Rust/tests/src/SeqExpressionTests.fs +++ b/tests/Rust/tests/src/SeqExpressionTests.fs @@ -112,6 +112,9 @@ let ``try...with in list expressions works`` () = [ try 1 with _ -> 0 ] |> equal [1] +// Exceptions cannot be caught in no_std mode (no std::panic::catch_unwind), +// so tests that raise and catch are excluded there +#if !NO_STD_NO_EXCEPTIONS [] let ``try...with in list expressions catches exceptions`` () = [ try raise (exn "boom") with _ -> 42 ] @@ -157,6 +160,7 @@ let ``try...with in seq expressions rethrows unmatched exceptions`` () = with e when e.Message = "boom" -> propagated <- true equal true propagated +#endif // TODO: enable when the Rust target supports type-testing caught exceptions // against BCL exception types (e.g. :? System.ArgumentException) From 5ed01a2446460f176d0ba569897a95d50dea9881 Mon Sep 17 00:00:00 2001 From: Mangel Maxime Date: Wed, 8 Jul 2026 19:43:36 +0200 Subject: [PATCH 7/7] chore: add links to issue and pr --- src/Fable.Transforms/FSharp2Fable.Util.fs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Fable.Transforms/FSharp2Fable.Util.fs b/src/Fable.Transforms/FSharp2Fable.Util.fs index c25f82f8d9..4cd77c6fec 100644 --- a/src/Fable.Transforms/FSharp2Fable.Util.fs +++ b/src/Fable.Transforms/FSharp2Fable.Util.fs @@ -2026,6 +2026,8 @@ module Util = /// on all targets and semantically correct if it were ever reached. /// TODO: Remove when FCS compiles the handler with Rethrow like normal try/with does /// (see CheckExpressions.fs vs CheckSequenceExpressions.fs). + /// FCS issue: https://github.com/dotnet/fsharp/issues/20040 + /// Fable PR: https://github.com/fable-compiler/Fable/pull/4719 let fixEnumerateTryWithHandler (memb: FSharpMemberOrFunctionOrValue) (args: Fable.Expr list) = if memb.FullName = "Microsoft.FSharp.Core.CompilerServices.RuntimeHelpers.EnumerateTryWith" then match args with