Filtered try/with in seq { } emits int 0 where seq<'T> is expected for the unmatched case
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 78/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Quiet
- Tech stack
- fsharp
- Domain
- compilers
Research direction
Start in CheckSequenceExpressions.fs at the handler compilation around lines 356-360, then compare it with TcExprTryWith in CheckExpressions.fs around lines 6511-6512 and the failure actions in PatternMatchCompilation.fs. Reproduce the filtered try/with sequence and inspect its typed tree. Done means the unmatched handler branch is a rethrow rather than an int 0, while the filter still uses FailFilter.
Written by the indexing model from the issue text.
Description
This affects F# Compiler Services (FCS). We hit it in the Fable compiler, which builds on FCS and consumes the typed tree to transpile F# to other languages. The mistyped node breaks Fable's statically-typed targets.
A try/with inside a sequence expression (seq { }) that uses a filtered handler — a type test (with :? SomeExn ->) or a when guard — is lowered to RuntimeHelpers.EnumerateTryWith. In CheckSequenceExpressions.fs the handler clauses are compiled with FailFilter as the match-failure action, so the unmatched fallback branch is emitted as a literal 0 of type int in a position where the handler must return seq<'T>. A normal (non-sequence) try/with compiles its handler with Rethrow, which is correctly typed. The wrong type produces invalid output on downstream consumers that rely on the typed tree.
Repro steps
- Compile a sequence expression containing a
try/withwith a filtered handler:seq { try raise (System.InvalidOperationException "boom") with :? System.ArgumentException -> yield 0 } |> Seq.toList |> ignore - Inspect the typed tree for the
RuntimeHelpers.EnumerateTryWithhandler lambda and look at the branch taken when the exception does not match the filter.
Expected behavior
The unmatched/fallback case of the handler should be compiled the same way as a normal try/with — as a rethrow (reraise) of the caught exception. That expression is bottom-typed and therefore compatible with the seq<'T> result type, and it is semantically correct (a non-matching exception should re-propagate).
In CheckExpressions.fs, TcExprTryWith already does this — the handler is compiled with Rethrow:
// CheckExpressions.fs:6511-6512
let v1, filterExpr = CompilePatternForMatchClauses cenv env mWithToLast mWithToLast true FailFilter None g.exn_ty g.int_ty checkedFilterClauses
let v2, handlerExpr = CompilePatternForMatchClauses cenv env mWithToLast mWithToLast true Rethrow None g.exn_ty overallTy.Commit checkedHandlerClauses
CheckSequenceExpressions.fs should use Rethrow for the handler in the same way.
Actual behavior
CheckSequenceExpressions.fs compiles the handler with FailFilter instead of Rethrow:
// CheckSequenceExpressions.fs:356-360
let v1, filterExpr =
CompilePatternForMatchClauses cenv env withRange withRange true FailFilter None g.exn_ty g.int_ty filterClauses
// correct: int
let v2, handlerExpr =
CompilePatternForMatchClauses cenv env withRange withRange true FailFilter None g.exn_ty genOuterTy handlers
// BUG: should be Rethrow
FailFilter lowers the fallback leaf to a literal int 0:
// PatternMatchCompilation.fs:1029-1035
| FailFilter -> mkInt g mMatch 0 // returns 0 (int) — wrong when result type is seq<'T>
| Rethrow -> mkReraise mMatch resultTy // bottom-typed — what's needed here
So the handler's unmatched fallback is int 0 where genOuterTy (seq<'T>) is expected. The branch is unreachable at runtime (the handler only runs after the filter matches), so it is harmless on .NET, but the mistyped node is invalid for consumers of the typed tree that require correct types on every branch. In Fable this breaks compilation of the statically-typed targets.
Suggested fix: in CheckSequenceExpressions.fs (line 360), change the handler's ActionOnFailure from FailFilter to Rethrow, mirroring CheckExpressions.fs:6512.
Known workarounds
In the Fable compiler we post-process the EnumerateTryWith handler lambda and rewrite the mistyped int 0 fallback leaf into a rethrow of the caught exception (walking through if/then/else, decision trees, and let bindings to reach the leaf).
Related information
- Affected component: F# Compiler Services (FCS) —
CheckSequenceExpressions.fs; surfaced via the Fable compiler - Operating system: macOS (Darwin), cross-platform
- .NET Runtime kind: .NET (Core), SDK 10+
- Editing Tools: N/A — reproduces via the compiler directly (F# Compiler Services)
- Dominant language
- F#
- Stars
- 4.3k
- Forks
- 877
- Avg merge
- 5d 17h
- Merged PRs (30d)
- 139
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from dotnet/fsharp
-
Needs-Triage
Difficulty 1/5 Under an hour Newbie friendliness 90/100
-
Needs-Triage
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
Needs-Triage
Difficulty 5/5 Over a week Newbie friendliness 35/100
-
Needs-Triage
Difficulty 3/5 1-2 days Newbie friendliness 68/100
-
Needs-Triage
Difficulty 5/5 Over a week Newbie friendliness 48/100
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
objectionary/eo#8869 · 1 comment ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
EricSpencer00/Resilient#4824 · 1 comment ·
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
objectionary/jeo-maven-plugin#1758 ·
-
generics
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
-
mlir
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
llvm/llvm-project#224908 · 1 comment ·