From 0c4166fd0e411f1fa104b639df9fe442829106bb Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Wed, 22 Apr 2026 14:46:35 +0200 Subject: [PATCH 1/4] add test --- .../Language/StateMachineTests.fs | 85 +++++++++++++------ 1 file changed, 59 insertions(+), 26 deletions(-) diff --git a/tests/FSharp.Compiler.ComponentTests/Language/StateMachineTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/StateMachineTests.fs index e32817f799d..36c49541af6 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/StateMachineTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/StateMachineTests.fs @@ -6,15 +6,32 @@ open Xunit open FSharp.Test.Assert open FSharp.Test.Compiler +module StateMachineTests = -// Inlined helper containing a "if __useResumableCode ..." construct failed to expand correctly, -// 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 - open FSharp.Core.CompilerServices.StateMachineHelpers - open System.Runtime.CompilerServices + let verify3511AndRun code = + Fsx code + |> withNoOptimize + |> compile + |> shouldFail + |> withWarningCode 3511 + |> ignore + + Fsx code + |> withNoOptimize + |> withOptions ["--nowarn:3511"] + |> compileExeAndRun + // Inlined helper containing a "if __useResumableCode ..." construct failed to expand correctly, + // executing the dynmamic branch at runtime even when the state machine was compiled statically. + // see https://github.com/dotnet/fsharp/issues/19296 + [] + let ``Nested __useResumableCode is expanded correctly`` () = + Fsx """ +open FSharp.Core.CompilerServices +open FSharp.Core.CompilerServices.StateMachineHelpers +open System.Runtime.CompilerServices + +module FailingInlinedHelper = let inline MoveOnce(x: byref<'T> when 'T :> IAsyncStateMachine and 'T :> IResumableStateMachine<'Data>) = x.MoveNext() x.Data @@ -27,7 +44,7 @@ module FailingInlinedHelper = else failwith "unexpected dynamic branch at runtime") - #nowarn 3513 // Resumable code invocation. + #nowarn 3513 let inline repro x = if __useResumableCode then __stateMachine @@ -38,25 +55,10 @@ module FailingInlinedHelper = failwith "dynamic state machine" #warnon 3513 -module StateMachineTests = - - let verify3511AndRun code = - Fsx code - |> withNoOptimize - |> compile - |> shouldFail - |> withWarningCode 3511 - |> ignore - - Fsx code - |> withNoOptimize - |> withOptions ["--nowarn:3511"] +if FailingInlinedHelper.repro 42 <> 42 then failwith "unexpected result" +""" |> compileExeAndRun - - [] - let ``Nested __useResumableCode is expanded correctly`` () = - FailingInlinedHelper.repro 42 - |> shouldEqual 42 + |> shouldSucceed [] // https://github.com/dotnet/fsharp/issues/13067 let ``Local function with a flexible type``() = @@ -467,3 +469,34 @@ if result[0].x <> 1 then failwith $"unexpected result {result[0]}" |> asExe |> compileExeAndRun |> shouldSucceed + + [] + let ``Debug-mode: mixing resumable and standard computation expressions compiles``() = + FSharp """ +module ReproMixedBuilders +open System.Threading.Tasks + +type TaskMaybeBuilder() = + + member inline _.Zero() = Task.FromResult None + + member inline _.Delay([] f) = task { return! f () } + + member inline _.Bind(value, [] f) = + task { + match value with + | None -> return None + | Some result -> return! f result + } + +let taskMaybe = TaskMaybeBuilder() + +let trigger() = + taskMaybe { + do! None + } +""" + |> withDebug + |> withNoOptimize + |> compile + |> shouldSucceed \ No newline at end of file From ae97a95f841a1e77c906eeeef9094e087f8cff7a Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Wed, 22 Apr 2026 14:49:53 +0200 Subject: [PATCH 2/4] revert and try another fix for nested __useResumableCode --- src/Compiler/Optimize/LowerStateMachines.fs | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/Compiler/Optimize/LowerStateMachines.fs b/src/Compiler/Optimize/LowerStateMachines.fs index 2c5dea5ff07..a55e5916b04 100644 --- a/src/Compiler/Optimize/LowerStateMachines.fs +++ b/src/Compiler/Optimize/LowerStateMachines.fs @@ -338,6 +338,9 @@ type LowerStateMachine(g: TcGlobals, outerResumableCodeDefns: ValMap) = if sm_verbose then printfn "expanding defns and reducing %A..." expr //if sm_verbose then printfn "checking %A for possible resumable code application..." expr match expr with + | IfUseResumableStateMachinesExpr g (thenExpr, _) when Option.isNone (IsStateMachineExpr g thenExpr) -> + Some (remake thenExpr) + // defn --> [expand_code] | Expr.Val (defnRef, _, _) when env.ResumableCodeDefns.ContainsVal defnRef.Deref -> let defn = env.ResumableCodeDefns[defnRef.Deref] @@ -372,22 +375,13 @@ type LowerStateMachine(g: TcGlobals, outerResumableCodeDefns: ValMap) = // Repeated top-down rewrite 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) + match TryReduceExpr env e [] id with + | Some e2 -> Some (cont e2) + | None -> None) PostTransform = (fun _ -> None) PreInterceptBinding = None RewriteQuotations=true - StackGuard = StackGuard("LowerStateMachineStackGuardDepth") } + StackGuard = StackGuard("LowerStateMachineStackGuard") } let ConvertStateMachineLeafExpression (env: env) expr = if sm_verbose then printfn "ConvertStateMachineLeafExpression for %A..." expr From 120f77cea825c53ff295fbc0da539538be9cff6a Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Wed, 22 Apr 2026 16:05:10 +0200 Subject: [PATCH 3/4] release notes --- docs/release-notes/.FSharp.Compiler.Service/11.0.100.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md index e175f4dce0e..1fd00f48104 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -35,6 +35,7 @@ * Fix signature generation: `private` keyword placement for prefix-style type abbreviations. ([Issue #15560](https://github.com/dotnet/fsharp/issues/15560), [PR #19586](https://github.com/dotnet/fsharp/pull/19586)) * Fix signature generation: missing `[]` attribute for types without visible constructors. ([Issue #16531](https://github.com/dotnet/fsharp/issues/16531), [PR #19586](https://github.com/dotnet/fsharp/pull/19586)) * Fix methods being tagged as `Member` instead of `Method` in tooltips. ([Issue #10540](https://github.com/dotnet/fsharp/issues/10540), [PR #19507](https://github.com/dotnet/fsharp/pull/19507)) +* Fix Debug-mode compilation when mixing resumable and standard computation expressions. ([Issue #19625](https://github.com/dotnet/fsharp/issues/19625), [PR #19630](https://github.com/dotnet/fsharp/pull/19630)) ### Added From efa65475ad4166659c133394d3e6b924df7e31eb Mon Sep 17 00:00:00 2001 From: Jakub Majocha <1760221+majocha@users.noreply.github.com> Date: Wed, 22 Apr 2026 16:08:29 +0200 Subject: [PATCH 4/4] add a comment and verbose message --- src/Compiler/Optimize/LowerStateMachines.fs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Compiler/Optimize/LowerStateMachines.fs b/src/Compiler/Optimize/LowerStateMachines.fs index a55e5916b04..cb9453d3177 100644 --- a/src/Compiler/Optimize/LowerStateMachines.fs +++ b/src/Compiler/Optimize/LowerStateMachines.fs @@ -338,7 +338,11 @@ type LowerStateMachine(g: TcGlobals, outerResumableCodeDefns: ValMap) = if sm_verbose then printfn "expanding defns and reducing %A..." expr //if sm_verbose then printfn "checking %A for possible resumable code application..." expr match expr with + // Reduce helper-local 'if __useResumableCode then ... else ...' after inlining, + // but preserve real nested state machines so their own lowering can still choose + // the dynamic fallback if static compilation fails. | IfUseResumableStateMachinesExpr g (thenExpr, _) when Option.isNone (IsStateMachineExpr g thenExpr) -> + if sm_verbose then printfn "reducing helper-local 'if __useResumableCode ...' to static branch" Some (remake thenExpr) // defn --> [expand_code]