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 diff --git a/src/Compiler/Optimize/LowerStateMachines.fs b/src/Compiler/Optimize/LowerStateMachines.fs index 2c5dea5ff07..cb9453d3177 100644 --- a/src/Compiler/Optimize/LowerStateMachines.fs +++ b/src/Compiler/Optimize/LowerStateMachines.fs @@ -338,6 +338,13 @@ 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] | Expr.Val (defnRef, _, _) when env.ResumableCodeDefns.ContainsVal defnRef.Deref -> let defn = env.ResumableCodeDefns[defnRef.Deref] @@ -372,22 +379,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 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