Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `[<Class>]` 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

Expand Down
24 changes: 11 additions & 13 deletions src/Compiler/Optimize/LowerStateMachines.fs
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,13 @@ type LowerStateMachine(g: TcGlobals, outerResumableCodeDefns: ValMap<Expr>) =
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]
Expand Down Expand Up @@ -372,22 +379,13 @@ type LowerStateMachine(g: TcGlobals, outerResumableCodeDefns: ValMap<Expr>) =
// 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
[<Fact>]
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
Expand All @@ -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<int, int>
Expand All @@ -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

[<Fact>]
let ``Nested __useResumableCode is expanded correctly`` () =
FailingInlinedHelper.repro 42
|> shouldEqual 42
|> shouldSucceed

[<Fact>] // https://github.com/dotnet/fsharp/issues/13067
let ``Local function with a flexible type``() =
Expand Down Expand Up @@ -467,3 +469,34 @@ if result[0].x <> 1 then failwith $"unexpected result {result[0]}"
|> asExe
|> compileExeAndRun
|> shouldSucceed

[<Fact>]
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([<InlineIfLambda>] f) = task { return! f () }

member inline _.Bind(value, [<InlineIfLambda>] 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
Loading