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/with with a filtered handler:
seq {
try raise (System.InvalidOperationException "boom")
with :? System.ArgumentException -> yield 0
} |> Seq.toList |> ignore
- Inspect the typed tree for the
RuntimeHelpers.EnumerateTryWith handler 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)
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/withinside a sequence expression (seq { }) that uses a filtered handler — a type test (with :? SomeExn ->) or awhenguard — is lowered toRuntimeHelpers.EnumerateTryWith. InCheckSequenceExpressions.fsthe handler clauses are compiled withFailFilteras the match-failure action, so the unmatched fallback branch is emitted as a literal0of typeintin a position where the handler must returnseq<'T>. A normal (non-sequence)try/withcompiles its handler withRethrow, which is correctly typed. The wrong type produces invalid output on downstream consumers that rely on the typed tree.Repro steps
try/withwith a filtered handler: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 theseq<'T>result type, and it is semantically correct (a non-matching exception should re-propagate).In
CheckExpressions.fs,TcExprTryWithalready does this — the handler is compiled withRethrow:CheckSequenceExpressions.fsshould useRethrowfor the handler in the same way.Actual behavior
CheckSequenceExpressions.fscompiles the handler withFailFilterinstead ofRethrow:FailFilterlowers the fallback leaf to a literalint 0:So the handler's unmatched fallback is
int 0wheregenOuterTy(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'sActionOnFailurefromFailFiltertoRethrow, mirroringCheckExpressions.fs:6512.Known workarounds
In the Fable compiler we post-process the
EnumerateTryWithhandler lambda and rewrite the mistypedint 0fallback leaf into a rethrow of the caught exception (walking throughif/then/else, decision trees, andletbindings to reach the leaf).Related information
CheckSequenceExpressions.fs; surfaced via the Fable compiler