Incoherent behavior when using a task with a unit returning statement, between Release and Debug · Issue #13099 · dotnet/fsharp · GitHub
Skip to content

Incoherent behavior when using a task with a unit returning statement, between Release and Debug  #13099

Closed

Description

@jp-fournier-dev

Please provide a succinct description of the issue.

When executing inside a task, a function that raises an exception is not called if the resulting expression ends in a property that has a unit value.

Provide the steps required to reproduce the problem:

  1. Run the attached code in debug, see that it does raise the exception with failwith using : dotnet run --configuration Debug
  2. Run the attached code in release, see that it does not raise the exception using : dotnet run --configuration Release
printfn "Hello from F#"

type SomeOutputType() =
    member x.End = ()

let someFunctionWithReturnType () = 
    failwith "This should be raised"
    SomeOutputType()

let theTaskAtHand () = 
    task {
        (someFunctionWithReturnType ()).End
    }

theTaskAtHand().Wait()

Provide a description of the expected behavior.

In both debug and release, the program fails after the exception is raised

Provide a description of the actual behavior observed.

In release, the exception is essentially ignored and nothing happens. Program exits successfully
In debug, the exception is raised and the program fails

Provide a description of any known workarounds.

You can pretty easily change this by switching it to being a function like so : member x.End = ignore and calling said function.
Using ``member x.End() = ()` will not work

Doing so inside an async computation expression will result in the expected behavior with the original code

Provide any related information:

Operating system: Windows 10
NET Runtime kind : .NET 6.0.201

Activity

  1. jp-fournier-dev commented on May 4, 2022

    @jp-fournier-dev
    Author

    To add some context :

    • this was discovered in some tests where we end a fluent assertion with an .End property that results in the value (). This isn't amazing code, but it does provide some level of clarity better than having ignore
    • for me (and from my understanding) the issue here is that we don't get the same behavior, especially since the compiler has removed a statement that could contain important processes. In my use case, it would skip assertions entirely.

    This seems more like and edge case, but still offers plenty of surprise when it happens

  2. En3Tho commented on May 4, 2022

    @En3Tho
    Contributor

    It seems that this function is not even called.
    state machine's MoveNext method from original code:
    image

    when that line is changed to someFunctionWithReturnType() |> ignore
    image

  3. added
    Area-Compiler-StateMachinesSequence, list, task and other state machine compilation
    Impact-High(Internal MS Team use only) Describes an issue with extreme impact on existing code.
    and removed on May 23, 2022
  4. self-assigned this
    on May 23, 2022
  5. added this to the Backlog milestone on Sep 21, 2022
  6. removed their assignment
    on Jan 5, 2023
  7. removed this from the Backlog milestone on Feb 6, 2023
  8. 11 remaining items

  9. modified the milestones: July-2023, Backlog on Jul 6, 2023
  10. removed their assignment
    on Jul 6, 2023
  11. github-actions commented on Apr 21, 2026

    @github-actions
    Contributor

    🤖 This is an automated response from Repo Assist.

    Confirmed still present in F# 11.0 (FSI 15.2.101.0, commit 2535da2, 2026-04-20).

    Verified reproduction — Debug raises the exception, Release silently skips it:

    type SomeOutputType() =
        member x.End = ()
    
    let someFunctionWithReturnType () = 
        failwith "This should be raised"
        SomeOutputType()
    
    let theTaskAtHand () = 
        task {
            (someFunctionWithReturnType ()).End
        }
    
    try
        theTaskAtHand().Wait()
        printfn "BUG: Exception was NOT raised!"
    with
    | :? System.AggregateException as e ->
        printfn "PASS: Exception raised: %s" (e.InnerException.Message)
    Mode Output
    dotnet run -c Debug PASS: Exception raised: This should be raised ✅
    dotnet run -c Release BUG: Exception was NOT raised! ❌

    Root cause: The optimizer eliminates the call to someFunctionWithReturnType() because the only use of the result is accessing .End (a unit-valued property). Since .End is a constant (), the optimizer treats the entire expression as dead code — but someFunctionWithReturnType() has side effects (the failwith). This is a correctness bug in the optimizer's side-effect analysis for method calls whose results flow only into unit-typed property access.

    This has been reproducible since .NET 6 (original report, 2022) through the current .NET 10 / F# 11.0.

    Generated by 🌈 Repo Assist, see workflow run. Learn more.

    To install this agentic workflow, run

    gh aw add githubnext/agentics/workflows/repo-assist.md@7c7feb61a52b662eb2089aa2945588b7a200d404
    
  12. added 4 commits that reference this issue on Jul 2, 2026
  13. added 2 commits that reference this issue on Jul 8, 2026
    a6626ac
    b3c5124
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area-Compiler-StateMachinesSequence, list, task and other state machine compilationBugImpact-High(Internal MS Team use only) Describes an issue with extreme impact on existing code.

    Type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions