Filtered try/with in seq { } emits int 0 where seq<'T> is expected for the unmatched case

Open Beginner friendly
#20,040 0 comments 0 reactions 0 assignees View on GitHub

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

Bug Needs-Triage

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

  1. 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
    
  2. 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)
Dominant language
F#
Stars
4.3k
Forks
877
Avg merge
5d 17h
Merged PRs (30d)
139

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from dotnet/fsharp

All issues in dotnet/fsharp

Similar issues

More Compilers issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.