From 2c0cbae36f01f49c8c1d2e36980135224cf8229c Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Mon, 16 Feb 2026 13:00:34 +0100 Subject: [PATCH 01/10] add test --- .../Language/StateMachineTests.fs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/FSharp.Compiler.ComponentTests/Language/StateMachineTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/StateMachineTests.fs index 86e11cf2029..96828085587 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/StateMachineTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/StateMachineTests.fs @@ -5,6 +5,7 @@ namespace Language open Xunit open FSharp.Test.Compiler open FSharp.Test +open FSharp.Test.Assert module StateMachineTests = @@ -241,3 +242,47 @@ let test = task { return 42 } IL_0059: ret } """ ] + + [] + let ``Nested __useResumableCode is expanded correctly`` () = + FSharp """ +module TestStateMachine + +open FSharp.Core.CompilerServices +open FSharp.Core.CompilerServices.StateMachineHelpers +open System.Runtime.CompilerServices + +let inline MoveOnce(x: byref<'T> when 'T :> IAsyncStateMachine and 'T :> IResumableStateMachine<'Data>) = + x.MoveNext() + x.Data + +// An inline helper returning ResumableCode must be fully expanded +// before the compiler tries to recognize the enclosing __stateMachine construct. +let inline helper x = + ResumableCode(fun sm -> + if __useResumableCode then + sm.Data <- x + true + else + failwith "unexpected dynamic branch at runtime") + +#nowarn 3513 +let inline repro x = + if __useResumableCode then + __stateMachine + (MoveNextMethodImpl<_>(fun sm -> (helper x).Invoke(&sm) |> ignore)) + (SetStateMachineMethodImpl<_>(fun _ _ -> ())) + (AfterCode<_, _>(fun sm -> MoveOnce(&sm))) + else + failwith "dynamic state machine" + +[] +let main _ = + let result = repro 42 + if result <> 42 then + failwithf "Expected 42 but got %d" result + 0 +""" + |> withOptimize + |> compileExeAndRun + |> shouldSucceed From 2f16b593f5fc4cee02b390fdaa98cb946c76d647 Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Mon, 16 Feb 2026 13:02:01 +0100 Subject: [PATCH 02/10] fix expansion of nested if __useResumableCode --- src/Compiler/Optimize/LowerStateMachines.fs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Compiler/Optimize/LowerStateMachines.fs b/src/Compiler/Optimize/LowerStateMachines.fs index 90238e0dc3c..36db5081ee6 100644 --- a/src/Compiler/Optimize/LowerStateMachines.fs +++ b/src/Compiler/Optimize/LowerStateMachines.fs @@ -349,7 +349,11 @@ type LowerStateMachine(g: TcGlobals) = // Repeated top-down rewrite let makeRewriteEnv (env: env) = - { PreIntercept = Some (fun cont e -> match TryReduceExpr env e [] id with Some e2 -> Some (cont e2) | None -> None) + { PreIntercept = Some (fun cont e -> + match e with + | IfUseResumableStateMachinesExpr g (thenExpr, _) -> Some (cont thenExpr) + | _ -> + match TryReduceExpr env e [] id with Some e2 -> Some (cont e2) | None -> None) PostTransform = (fun _ -> None) PreInterceptBinding = None RewriteQuotations=true From 904da826a9bc738284fcb13b6c6f28fdf4ea1e97 Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Mon, 16 Feb 2026 16:02:34 +0100 Subject: [PATCH 03/10] wip --- src/Compiler/Optimize/LowerStateMachines.fs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Compiler/Optimize/LowerStateMachines.fs b/src/Compiler/Optimize/LowerStateMachines.fs index 36db5081ee6..2c4b8d326f7 100644 --- a/src/Compiler/Optimize/LowerStateMachines.fs +++ b/src/Compiler/Optimize/LowerStateMachines.fs @@ -351,6 +351,14 @@ type LowerStateMachine(g: TcGlobals) = let makeRewriteEnv (env: env) = { PreIntercept = Some (fun cont e -> match e with + // Don't recurse into nested state machine expressions - they will be + // processed by their own LowerStateMachineExpr during codegen. + // This prevents modification of the nested machine's internal + // 'if __useResumableCode' patterns which select its dynamic fallback. + | _ when Option.isSome (IsStateMachineExpr g e) -> Some e + // Eliminate 'if __useResumableCode' - nested state machines are already + // guarded above, so any remaining occurrences at this level are from + // beta-reduced inline helpers and should take the static branch. | IfUseResumableStateMachinesExpr g (thenExpr, _) -> Some (cont thenExpr) | _ -> match TryReduceExpr env e [] id with Some e2 -> Some (cont e2) | None -> None) From 52c64f023a3035e4283ace3c9d0cbbee0e4b3e9d Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Mon, 16 Feb 2026 20:44:06 +0100 Subject: [PATCH 04/10] fix static compilation of some nested tasks Improve reduction of resumable code in state machines Enhance application reduction in state machine lowering for F# computation expressions by tracking let-bound resumable code in the environment and resolving references during reduction. This enables correct handling of optimizer-generated continuations and deeper reduction of nested applications. Also, update test comments to reflect resolved state machine compilation issues. --- src/Compiler/Optimize/LowerStateMachines.fs | 19 ++++++++++++++++++- .../NestedTaskFailures.fs | 4 +--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/Compiler/Optimize/LowerStateMachines.fs b/src/Compiler/Optimize/LowerStateMachines.fs index 2c4b8d326f7..0f51c070e07 100644 --- a/src/Compiler/Optimize/LowerStateMachines.fs +++ b/src/Compiler/Optimize/LowerStateMachines.fs @@ -235,7 +235,16 @@ type LowerStateMachine(g: TcGlobals) = TryReduceApp env expandedExpr laterArgs | Expr.Let (bind, bodyExpr, m, _) -> - match TryReduceApp env bodyExpr args with + // If the binding returns resumable code, add it to the env so that + // references to it in the body can be resolved during reduction. + // This handles patterns like 'let cont = (fun () -> ...; Zero()) in cont()' + // generated by the optimizer for CE if-then branches. + let envR = + if isExpandVar g bind.Var then + { env with ResumableCodeDefns = env.ResumableCodeDefns.Add bind.Var bind.Expr } + else + env + match TryReduceApp envR bodyExpr args with | Some bodyExpr2 -> Some (mkLetBind m bind bodyExpr2) | None -> None @@ -308,6 +317,14 @@ type LowerStateMachine(g: TcGlobals) = | Some innerExpr2 -> Some (Expr.DebugPoint (dp, innerExpr2)) | None -> None + // Resolve variables known to the env, e.g. locally-bound resumable code continuations + | Expr.Val (vref, _, _) when env.ResumableCodeDefns.ContainsVal vref.Deref -> + TryReduceApp env env.ResumableCodeDefns[vref.Deref] args + + // Push through function applications by combining the arg lists + | Expr.App (f, _fty, _tyargs, fArgs, _m) -> + TryReduceApp env f (fArgs @ args) + | _ -> None diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Control/NestedTaskFailures.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Control/NestedTaskFailures.fs index 7313ad61b87..e051ee0c40e 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Control/NestedTaskFailures.fs +++ b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Control/NestedTaskFailures.fs @@ -1,10 +1,8 @@ namespace FSharp.Core.UnitTests.Control.Tasks -// The tasks below fail state machine compilation. This failure was causing subsequent problems in code generation. +// The tasks below used to fail state machine compilation. This failure was causing subsequent problems in code generation. // See https://github.com/dotnet/fsharp/issues/13404 -#nowarn "3511" // state machine not statically compilable - this is a separate issue, see https://github.com/dotnet/fsharp/issues/13404 - open System open Microsoft.FSharp.Control open Xunit From d63dc853c9d2e98574c800521e981e7c2c1ccd70 Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Mon, 16 Feb 2026 22:13:12 +0100 Subject: [PATCH 05/10] directly compile the test --- .../Language/StateMachineTests.fs | 85 +++++++++---------- 1 file changed, 39 insertions(+), 46 deletions(-) diff --git a/tests/FSharp.Compiler.ComponentTests/Language/StateMachineTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/StateMachineTests.fs index 96828085587..b121b63a501 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/StateMachineTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/StateMachineTests.fs @@ -3,9 +3,41 @@ namespace Language open Xunit -open FSharp.Test.Compiler -open FSharp.Test open FSharp.Test.Assert +open FSharp.Test.Compiler + + +// Inlined helper containing a "if __useResumableCode ..." construct failed to expand correctly, +// executing the dynmamic branch at runtime even when the state maching was compiled statically. +// see https://github.com/dotnet/fsharp/issues/19296 +module FailingInlinedHelper = + open FSharp.Core.CompilerServices + open FSharp.Core.CompilerServices.StateMachineHelpers + open System.Runtime.CompilerServices + + let inline MoveOnce(x: byref<'T> when 'T :> IAsyncStateMachine and 'T :> IResumableStateMachine<'Data>) = + x.MoveNext() + x.Data + + // An inline helper returning ResumableCode must be fully expanded + // before the compiler tries to recognize the enclosing __stateMachine construct. + let inline helper x = + ResumableCode(fun sm -> + if __useResumableCode then + sm.Data <- x + true + else + failwith "unexpected dynamic branch at runtime") + + #nowarn 3513 + let inline repro x = + if __useResumableCode then + __stateMachine + (MoveNextMethodImpl<_>(fun sm -> (helper x).Invoke(&sm) |> ignore)) + (SetStateMachineMethodImpl<_>(fun _ _ -> ())) + (AfterCode<_, _>(fun sm -> MoveOnce(&sm))) + else + failwith "dynamic state machine" module StateMachineTests = @@ -22,6 +54,11 @@ module StateMachineTests = |> withOptions ["--nowarn:3511"] |> compileExeAndRun + [] + let ``Nested __useResumableCode is expanded correctly`` () = + FailingInlinedHelper.repro 42 + |> shouldEqual 42 + [] // https://github.com/dotnet/fsharp/issues/13067 let ``Local function with a flexible type``() = """ @@ -242,47 +279,3 @@ let test = task { return 42 } IL_0059: ret } """ ] - - [] - let ``Nested __useResumableCode is expanded correctly`` () = - FSharp """ -module TestStateMachine - -open FSharp.Core.CompilerServices -open FSharp.Core.CompilerServices.StateMachineHelpers -open System.Runtime.CompilerServices - -let inline MoveOnce(x: byref<'T> when 'T :> IAsyncStateMachine and 'T :> IResumableStateMachine<'Data>) = - x.MoveNext() - x.Data - -// An inline helper returning ResumableCode must be fully expanded -// before the compiler tries to recognize the enclosing __stateMachine construct. -let inline helper x = - ResumableCode(fun sm -> - if __useResumableCode then - sm.Data <- x - true - else - failwith "unexpected dynamic branch at runtime") - -#nowarn 3513 -let inline repro x = - if __useResumableCode then - __stateMachine - (MoveNextMethodImpl<_>(fun sm -> (helper x).Invoke(&sm) |> ignore)) - (SetStateMachineMethodImpl<_>(fun _ _ -> ())) - (AfterCode<_, _>(fun sm -> MoveOnce(&sm))) - else - failwith "dynamic state machine" - -[] -let main _ = - let result = repro 42 - if result <> 42 then - failwithf "Expected 42 but got %d" result - 0 -""" - |> withOptimize - |> compileExeAndRun - |> shouldSucceed From 574b9d872b3e324b914b2395a2a4cbc11574a627 Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Mon, 16 Feb 2026 22:22:09 +0100 Subject: [PATCH 06/10] fix comments --- .../Language/StateMachineTests.fs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/FSharp.Compiler.ComponentTests/Language/StateMachineTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/StateMachineTests.fs index b121b63a501..1a6ef7da947 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/StateMachineTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/StateMachineTests.fs @@ -8,7 +8,7 @@ open FSharp.Test.Compiler // Inlined helper containing a "if __useResumableCode ..." construct failed to expand correctly, -// executing the dynmamic branch at runtime even when the state maching was compiled statically. +// executing the dynmamic branch at runtime even when the state machine was compiled statically. // see https://github.com/dotnet/fsharp/issues/19296 module FailingInlinedHelper = open FSharp.Core.CompilerServices @@ -19,8 +19,6 @@ module FailingInlinedHelper = x.MoveNext() x.Data - // An inline helper returning ResumableCode must be fully expanded - // before the compiler tries to recognize the enclosing __stateMachine construct. let inline helper x = ResumableCode(fun sm -> if __useResumableCode then @@ -29,7 +27,7 @@ module FailingInlinedHelper = else failwith "unexpected dynamic branch at runtime") - #nowarn 3513 + #nowarn 3513 // Resumable code invocation. let inline repro x = if __useResumableCode then __stateMachine @@ -38,6 +36,7 @@ module FailingInlinedHelper = (AfterCode<_, _>(fun sm -> MoveOnce(&sm))) else failwith "dynamic state machine" + #warnon 3513 module StateMachineTests = From 250c218e413cf7c4a407707b759a108c27fdefd0 Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Mon, 16 Feb 2026 23:21:14 +0100 Subject: [PATCH 07/10] add repro cases - for loop over tuples --- .../Language/StateMachineTests.fs | 90 +++++++------------ 1 file changed, 30 insertions(+), 60 deletions(-) diff --git a/tests/FSharp.Compiler.ComponentTests/Language/StateMachineTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/StateMachineTests.fs index 1a6ef7da947..07cd86a8545 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/StateMachineTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/StateMachineTests.fs @@ -218,63 +218,33 @@ module TestStateMachine let test = task { return 42 } """ |> compile - |> verifyIL [ """ -.method public strict virtual instance void MoveNext() cil managed -{ - .override [runtime]System.Runtime.CompilerServices.IAsyncStateMachine::MoveNext - - .maxstack 4 - .locals init (int32 V_0, - class [runtime]System.Exception V_1, - bool V_2, - class [runtime]System.Exception V_3) - IL_0000: ldarg.0 - IL_0001: ldfld int32 TestStateMachine/test@3::ResumptionPoint - IL_0006: stloc.0 - .try - { - IL_0007: ldarg.0 - IL_0008: ldflda valuetype [FSharp.Core]Microsoft.FSharp.Control.TaskStateMachineData`1 TestStateMachine/test@3::Data - IL_000d: ldc.i4.s 42 - IL_000f: stfld !0 valuetype [FSharp.Core]Microsoft.FSharp.Control.TaskStateMachineData`1::Result - IL_0014: ldc.i4.1 - IL_0015: stloc.2 - IL_0016: ldloc.2 - IL_0017: brfalse.s IL_0036 - - IL_0019: ldarg.0 - IL_001a: ldflda valuetype [FSharp.Core]Microsoft.FSharp.Control.TaskStateMachineData`1 TestStateMachine/test@3::Data - IL_001f: ldflda valuetype [runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 valuetype [FSharp.Core]Microsoft.FSharp.Control.TaskStateMachineData`1::MethodBuilder - IL_0024: ldarg.0 - IL_0025: ldflda valuetype [FSharp.Core]Microsoft.FSharp.Control.TaskStateMachineData`1 TestStateMachine/test@3::Data - IL_002a: ldfld !0 valuetype [FSharp.Core]Microsoft.FSharp.Control.TaskStateMachineData`1::Result - IL_002f: call instance void valuetype [netstandard]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::SetResult(!0) - IL_0034: leave.s IL_0042 - - IL_0036: leave.s IL_0042 - - } - catch [runtime]System.Object - { - IL_0038: castclass [runtime]System.Exception - IL_003d: stloc.3 - IL_003e: ldloc.3 - IL_003f: stloc.1 - IL_0040: leave.s IL_0042 - - } - IL_0042: ldloc.1 - IL_0043: stloc.3 - IL_0044: ldloc.3 - IL_0045: brtrue.s IL_0048 - - IL_0047: ret - - IL_0048: ldarg.0 - IL_0049: ldflda valuetype [FSharp.Core]Microsoft.FSharp.Control.TaskStateMachineData`1 TestStateMachine/test@3::Data - IL_004e: ldflda valuetype [runtime]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1 valuetype [FSharp.Core]Microsoft.FSharp.Control.TaskStateMachineData`1::MethodBuilder - IL_0053: ldloc.3 - IL_0054: call instance void valuetype [netstandard]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1::SetException(class [netstandard]System.Exception) - IL_0059: ret -} -""" ] + |> verifyIL [ ".override [runtime]System.Runtime.CompilerServices.IAsyncStateMachine::MoveNext" ] + + // The original repro from https://github.com/dotnet/fsharp/pull/14930 + [] + let ``Task with for loop over tuples compiles statically`` () = + FSharp """ +module TestStateMachine +let what (f: seq) = task { + for name, _whatever in f do + System.Console.Write name +} + """ + |> compile + |> verifyIL [ ".override [runtime]System.Runtime.CompilerServices.IAsyncStateMachine::MoveNext" ] + + // The original repro from https://github.com/dotnet/fsharp/issues/12839#issuecomment-2562121004 + [] + let ``Task with for loop over tuples compiles statically 2`` () = + FSharp """ +module TestStateMachine +let test = task { + for _ in [ "a", "b" ] do + () +} + """ + |> compile + |> verifyIL [ ".override [runtime]System.Runtime.CompilerServices.IAsyncStateMachine::MoveNext" ] + + + From 78d272e4d78c033fc62d5186c4e33e433eb51f7e Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Tue, 17 Feb 2026 09:31:03 +0100 Subject: [PATCH 08/10] include test and debugpoint handling from #14930 --- src/Compiler/Optimize/LowerStateMachines.fs | 5 + .../Language/StateMachineTests.fs | 92 +++++++++++++++++++ 2 files changed, 97 insertions(+) diff --git a/src/Compiler/Optimize/LowerStateMachines.fs b/src/Compiler/Optimize/LowerStateMachines.fs index 0f51c070e07..5d0bcad002f 100644 --- a/src/Compiler/Optimize/LowerStateMachines.fs +++ b/src/Compiler/Optimize/LowerStateMachines.fs @@ -189,6 +189,11 @@ type LowerStateMachine(g: TcGlobals) = if sm_verbose then printfn "eliminating 'if __useResumableCode...'" BindResumableCodeDefinitions env thenExpr + // Look through debug points to find resumable code bindings inside + | Expr.DebugPoint (_, innerExpr) -> + let envR, _ = BindResumableCodeDefinitions env innerExpr + (envR, expr) + | _ -> (env, expr) diff --git a/tests/FSharp.Compiler.ComponentTests/Language/StateMachineTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/StateMachineTests.fs index 07cd86a8545..d5095dc7625 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/StateMachineTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/StateMachineTests.fs @@ -247,4 +247,96 @@ let test = task { |> verifyIL [ ".override [runtime]System.Runtime.CompilerServices.IAsyncStateMachine::MoveNext" ] + [] // https://github.com/dotnet/fsharp/issues/12839#issuecomment-1292310944 + let ``Tasks with a for loop over tuples are statically compilable``() = + FSharp """ +module TestProject1 + +let ret i = task { return i } + +let one (f: seq) = task { + let mutable sum = 0 + + let! x = ret 1 + sum <- sum + x + + for name, _whatever, i in f do + let! x = ret i + sum <- sum + x + + System.Console.Write name + + let! x = ret i + sum <- sum + x + + let! x = ret 1 + sum <- sum + x + + return sum +} + +let two (f: seq) = task { + let mutable sum = 0 + + let! x = ret 1 + sum <- sum + x + + for name, _whatever, i in f do + let! x = ret i + sum <- sum + x + + System.Console.Write name + + let! x = ret 1 + sum <- sum + x + + return sum +} + +let three (f: seq) = task { + let mutable sum = 0 + + let! x = ret 1 + sum <- sum + x + + for name, _whatever, i in f do + let! x = ret i + sum <- sum + x + + System.Console.Write name + + return sum +} + +let four (f: seq) = task { + let mutable sum = 0 + + let! x = ret 5 + sum <- sum + x + + for name, _i in f do + System.Console.Write name + + let! x = ret 1 + sum <- sum + x + + return sum +} + +if (one [ ("", "", 1); ("", "", 2) ]).Result <> 8 then + failwith "unexpected result one" +if (one []).Result <> 2 then + failwith "unexpected result one" +if (two [ ("", "", 2) ]).Result <> 4 then + failwith "unexpected result two" +if (three [ ("", "", 5) ]).Result <> 6 then + failwith "unexpected result three" +if (four [ ("", 10) ]).Result <> 6 then + failwith "unexpected result four" +""" + |> withOptimize + |> compileExeAndRun + |> shouldSucceed + + From 3491f3a186d2388bcae5f6938cd9f21d4e557ae2 Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Tue, 17 Feb 2026 10:53:47 +0100 Subject: [PATCH 09/10] add release notes --- docs/release-notes/.FSharp.Compiler.Service/10.0.300.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/.FSharp.Compiler.Service/10.0.300.md b/docs/release-notes/.FSharp.Compiler.Service/10.0.300.md index e7d1bd2e1bf..9e6b955c182 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/10.0.300.md +++ b/docs/release-notes/.FSharp.Compiler.Service/10.0.300.md @@ -14,6 +14,7 @@ * Fix FS0229 B-stream misalignment when reading metadata from assemblies compiled with LangVersion < 9.0, introduced by [#17706](https://github.com/dotnet/fsharp/pull/17706). ([PR #19260](https://github.com/dotnet/fsharp/pull/19260)) * Fix FS3356 false positive for instance extension members with same name on different types, introduced by [#18821](https://github.com/dotnet/fsharp/pull/18821). ([PR #19260](https://github.com/dotnet/fsharp/pull/19260)) * F# Scripts: Fix default reference paths resolving when an SDK directory is specified. ([PR #19270](https://github.com/dotnet/fsharp/pull/19270)) +* Improve static compilation of state machines. ([PR #19297](https://github.com/dotnet/fsharp/pull/19297)) ### Added From 273e3cf3d2c30308b3b91660b9fe49e98ea13e67 Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Tue, 17 Feb 2026 20:25:53 +0100 Subject: [PATCH 10/10] add more repros to tests --- .../Language/StateMachineTests.fs | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/tests/FSharp.Compiler.ComponentTests/Language/StateMachineTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/StateMachineTests.fs index d5095dc7625..f7d2ebb906c 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/StateMachineTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/StateMachineTests.fs @@ -246,6 +246,93 @@ let test = task { |> compile |> verifyIL [ ".override [runtime]System.Runtime.CompilerServices.IAsyncStateMachine::MoveNext" ] + // see https://github.com/dotnet/fsharp/pull/14930#issuecomment-1528981395 + [] + let ``Task with some anonymous records`` () = + FSharp """ +module TestStateMachine +let bad () = task { + let res = {| ResultSet2 = [| {| im = Some 1; lc = 3 |} |] |} + + match [| |] with + | [| |] -> + let c = res.ResultSet2 |> Array.map (fun x -> {| Name = x.lc |}) + let c = res.ResultSet2 |> Array.map (fun x -> {| Name = x.lc |}) + let c = res.ResultSet2 |> Array.map (fun x -> {| Name = x.lc |}) + return Some c + | _ -> + return None +} +""" + |> compile + |> verifyIL [ ".override [runtime]System.Runtime.CompilerServices.IAsyncStateMachine::MoveNext" ] + + + + // repro of https://github.com/dotnet/fsharp/issues/12839 + [] + let ``Big record`` () = + FSharp """ +module TestStateMachine +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.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 -> () + | _ -> () + } +""" + |> compile + |> verifyIL [ ".override [runtime]System.Runtime.CompilerServices.IAsyncStateMachine::MoveNext" ] + [] // https://github.com/dotnet/fsharp/issues/12839#issuecomment-1292310944 let ``Tasks with a for loop over tuples are statically compilable``() =