question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Unexpected warning '[FS3511] This state machine is not statically compilable'

See original GitHub issue

We have a number of problems where tasks are not being statically compiled.

See also https://github.com/demetrixbio/Plough.WebApi/pull/5 as an example where a library hit this.


Repro steps

I had this problem in more complicated code, however it boils down to something like this:

type Foo = { X: int option }

type BigRecord =
    {
        a1: string
        a2: string
        a3: string
        a4: string
        a5: string
        a6: string
        a7: string
        a8: string
        a9: string
        a10: string
        a11: string
        a12: string
        a13: string
        a14: string
        a15: string
        a16: string
        a17: string
        a18: string
        a19: string
        a20: string
        a21: string
        a22: string
        a23: string
        a24: string
        a25: string
        a26: string
        a27: string
        a28: string
        a29: string
        a30: string
        a31: string
        a32: string
        a33: string
        a34: string
        a35: string
        a36: string // no warning if at least one field removed

        a37Optional: string option
    }

let testStateMachine (bigRecord: BigRecord) =
    task {
        match Some 5 with // no warn if this match removed and only inner one kept
        | Some _ ->
            match Unchecked.defaultof<Foo>.X with // no warning if replaced with `match Some 5 with`
            | Some _ ->
                let d = { bigRecord with a37Optional = None } // no warning if d renamed as _ or ignore function used
                ()
            | None -> ()
        | _ -> ()
    }

printfn "Hello from F#"

Then compile it in release mode.

Original code doesn’t have Unchecked.defaultof and gets Foo from another function but I wasn’t able to create minimal example without it. I guess, it doesn’t really matter and root cause is the same.

Expected behavior

No warning or better explanation of the problem and how to fix it.

Actual behavior

When compiling in Release mode:

dotnet build -c Release

Program.fs(46, 5): [FS3511] This state machine is not statically compilable. A resumable code invocation at '(46,4--46,8)' could not be reduced. An alternative dynamic implementation will be used, which may be slower. Consider adjusting your code to ensure this state machine is statically compilable, or else suppress this warning.

Known workarounds

In original code I had to create another function that works with d and inline usage let d = { bigRecord with a37Optional = None } variable at line 51:

...
match Unchecked.defaultof<Foo>.X with
| Some _ ->
    do! testStateMachineInner { bigRecord with a37Optional = None }
| None -> ()
...

Related information

.NET SDK (reflecting any global.json): Version: 6.0.201 Commit: ef40e6aa06

Runtime Environment: OS Name: Windows OS Version: 10.0.19044 OS Platform: Windows RID: win10-x64 Base Path: C:\Program Files\dotnet\sdk\6.0.201\

Host (useful for support): Version: 6.0.3 Commit: c24d9a9c91

Issue Analytics

  • State:open
  • Created 2 years ago
  • Reactions:3
  • Comments:9 (9 by maintainers)

github_iconTop GitHub Comments

3reactions
natalie-o-perretcommented, Nov 8, 2022

@natalie-o-perret in your first snippet you have use sourceEnumerator = source.GetEnumerator(), which isn’t used anywhere. Is that necessary for the repro?

From the looks of it, it appears that it has something to do with the combination of disposable types. What library are you referencing? Maybe we can dumb this sample down even further and do some more investigation.

Not sure all cases here are related, though.

We should also emphasise that there’s no danger to this warning. In certain cases it may not even perform so much noticeably slower (it depends, I know). It merely means you get the ‘old style’ binding that would’ve been used were task implemented pre F# 6.0, without statically compiled resumable state.

Still, would be nice if we could solve this 😃.

Thanks, it was a bad copy-paste hiccup when submitting my comment at the time.

Here is a full sample for repro purposes:

open System.IO
open System.Text
open System.Threading.Tasks

open FSharpPlus

open FSharp.Collections

open ICSharpCode.SharpZipLib.Zip
open ICSharpCode.SharpZipLib.Zip.Compression


[<RequireQualifiedAccess>]
module ZipOutputStream =
    let writeToTask1 (level: Deflater.CompressionLevel) outputStream (source: (string * byte array) seq) =
        task {
            use zipOutputStream = new ZipOutputStream(outputStream)
            zipOutputStream.SetLevel(int32 level)

            for name, bytes in source do
                let zipEntry = ZipEntry(name=name)
                do! zipOutputStream.PutNextEntryAsync(zipEntry)
                do! zipOutputStream.WriteAsync(bytes)
        }

    let writeToTask2 (level: Deflater.CompressionLevel) outputStream (source: (string * byte array) seq) =
        task {
            use zipOutputStream = new ZipOutputStream(outputStream)
            zipOutputStream.SetLevel(int32 level)

            use sourceEnumerator = source.GetEnumerator()
            while sourceEnumerator.MoveNext() do
                let name, bytes = sourceEnumerator.Current
                let zipEntry = ZipEntry(name=name)
                do! zipOutputStream.PutNextEntryAsync(zipEntry)
                do! zipOutputStream.WriteAsync(bytes)
        }

[<RequireQualifiedAccess>]
module Task =
    let getAwaiterResult (t: Task<'T>) = t.GetAwaiter().GetResult()

[<EntryPoint>]
let main _ =
    use ms1 = new MemoryStream()
    use ms2 = new MemoryStream()
    let pseudoFiles = seq {
        for c in 'a' .. 'z' do
            let s = string c
            yield s, Encoding.UTF8.GetBytes(s)
    }
    ZipOutputStream.writeToTask1 Deflater.CompressionLevel.BEST_COMPRESSION ms1 pseudoFiles |> Task.getAwaiterResult
    ZipOutputStream.writeToTask2 Deflater.CompressionLevel.BEST_COMPRESSION ms2 pseudoFiles |> Task.getAwaiterResult

    0

Notes about dependencies:

  • SharpZipLib, version: 1.4.1
  • FSharpPlus, version: 1.2.5

Building it with the release configuration flag:

# natalie-perret @ nppc in ~\Desktop\Personal\playground\fsharp\FSharpPlayground [05:57:10]
$ dotnet build -c release
MSBuild version 17.4.0+18d5aef85 for .NET
  Determining projects to restore...
  All projects are up-to-date for restore.
C:\Users\natalie-perret\Desktop\Personal\playground\fsharp\FSharpPlayground\Program.fs(16,9): warning FS3511: This state machine is not statically comp
ilable. A resumable code invocation at '(20,12--20,15)' could not be reduced. An alternative dynamic implementation will be used, which may be slower. 
Consider adjusting your code to ensure this state machine is statically compilable, or else suppress this warning. [C:\Users\natalie-perret\Desktop\Per
sonal\playground\fsharp\FSharpPlayground\FSharpPlayground.fsproj]
  FSharpPlayground -> C:\Users\natalie-perret\Desktop\Personal\playground\fsharp\FSharpPlayground\bin\Release\net7.0\FSharpPlayground.dll

Build succeeded.

C:\Users\natalie-perret\Desktop\Personal\playground\fsharp\FSharpPlayground\Program.fs(16,9): warning FS3511: This state machine is not statically comp
ilable. A resumable code invocation at '(20,12--20,15)' could not be reduced. An alternative dynamic implementation will be used, which may be slower. 
Consider adjusting your code to ensure this state machine is statically compilable, or else suppress this warning. [C:\Users\natalie-perret\Desktop\Per
sonal\playground\fsharp\FSharpPlayground\FSharpPlayground.fsproj]
    1 Warning(s)
    0 Error(s)

Time Elapsed 00:00:06.11

Note about the .NET version in use:

  • <TargetFramework>net7.0</TargetFramework> (installed on the release date / conf with winget install Microsoft.DotNet.SDK.7 btw thanks @thinkbeforecoding), just wanted to make sure that using the very latest version didn’t fix the warning.
2reactions
natalie-o-perretcommented, Nov 8, 2022

We keep bumping into that issue every now and again, often when there are for loops wrapped in a task CE, for instance like below:

let writeToZipOutputStreamTask (level: Deflater.CompressionLevel) outputStream (source: (string * byte array) seq) =
    task {
        use zipOutputStream = new ZipOutputStream(outputStream)
        zipOutputStream.SetLevel(int32 level)

        for name, bytes in source do
            let zipEntry = ZipEntry(name=name)
            do! zipOutputStream.PutNextEntryAsync(zipEntry)
            do! zipOutputStream.WriteAsync(bytes)
    }

The workaround that has worked for us is to simply leverage the relevant enumerator +MoveNext() + Current property:

let writeToZipOutputStreamTask (level: Deflater.CompressionLevel) outputStream (source: (string * byte array) seq) =
    task {
        use zipOutputStream = new ZipOutputStream(outputStream)
        zipOutputStream.SetLevel(int32 level)

        use sourceEnumerator = source.GetEnumerator()
        while sourceEnumerator.MoveNext() do
            let name, bytes = sourceEnumerator.Current
            let zipEntry = ZipEntry(name=name)
            do! zipOutputStream.PutNextEntryAsync(zipEntry)
            do! zipOutputStream.WriteAsync(bytes)
    }
Read more comments on GitHub >

github_iconTop Results From Across the Web

Resumable State Machines - F# Compiler Community Session
In this community session, Don Syme talks about how does the new F# compiler feature work - Resumable State Machines. To learn more...
Read more >
Static finite state machine library : r/cpp
Hi, guys! In my free time I wrote a simple library that handles an FSM at compile time, aiming to have no overhead...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found