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 9441f8587e0..d4c4acddd2b 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -97,6 +97,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 F# exception serialization now preserves fields (gated behind `--langversion:11`). (Issue [#878](https://github.com/dotnet/fsharp/issues/878), [PR #19342](https://github.com/dotnet/fsharp/pull/19342), [PR #19746](https://github.com/dotnet/fsharp/pull/19746)) +* Fix computation expressions desugaring final-position `do!` as `return!`/`yield!`; it now uses `Bind` while explicit `return!`/`yield!` retain final methods. ([Issue #20448](https://github.com/dotnet/fsharp/issues/20448), [PR #20449](https://github.com/dotnet/fsharp/pull/20449)) * 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)) * IlxGen: fix missing CompilationMapping attribute for generic values ([PR #19643](https://github.com/dotnet/fsharp/pull/19643)) diff --git a/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs b/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs index a935bd15e4b..a9dd1205512 100644 --- a/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckComputationExpressions.fs @@ -2878,22 +2878,6 @@ and TranslateComputationExpression (ceenv: ComputationExpressionContext<'a>) fir | None -> // This only occurs in final position in a sequence match comp with - // "do! expr;" in tail call position is treated as { return! expr } when ReturnFromFinal is provided - | SynExpr.DoBang(rhsExpr, m, _) when ceenv.tailCall && (hasBuilderMethod ceenv m "ReturnFromFinal") -> - let returnFrom = - // Flags indicate isTrueYield, isTrueReturn - SynExpr.YieldOrReturnFrom((false, true), rhsExpr, m, SynExprYieldOrReturnFromTrivia.Zero) - - TranslateComputationExpression ceenv CompExprTranslationPass.Initial q varSpace returnFrom translatedCtxt - - // "do! expr;" in tail call position is treated as { yield! expr } when YieldFromFinal is provided - | SynExpr.DoBang(rhsExpr, m, _) when ceenv.tailCall && (hasBuilderMethod ceenv m "YieldFromFinal") -> - let returnFrom = - // Flags indicate isTrueYield, isTrueReturn - SynExpr.YieldOrReturnFrom((true, false), rhsExpr, m, SynExprYieldOrReturnFromTrivia.Zero) - - TranslateComputationExpression ceenv CompExprTranslationPass.Initial q varSpace returnFrom translatedCtxt - // "do! expr;" in final position is treated as { let! () = expr in return () } when Return is provided (and no Zero with Default attribute is available) or as { let! () = expr in zero } otherwise | SynExpr.DoBang(expr = rhsExpr; trivia = { DoBangKeyword = m }) -> let mUnit = rhsExpr.Range diff --git a/tests/FSharp.Compiler.ComponentTests/Language/coroutines.fsx b/tests/FSharp.Compiler.ComponentTests/Language/coroutines.fsx index a5f6027c721..71ffa24f1a6 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/coroutines.fsx +++ b/tests/FSharp.Compiler.ComponentTests/Language/coroutines.fsx @@ -267,14 +267,6 @@ let testTailcallTiny () = testTailcallTiny() |> expect 2 0 -let testTailcallTinyDoBang () = - coroutine { - printfn "in testTailcallTinyDoBang, desugaring do!" - do! t1() // this should desugr to YieldFromFinal, because ReturnFromFinal is not provided. - } - -testTailcallTinyDoBang() |> expect 2 0 - let rec testTailcall (n: int) = coroutine { if n % 10_000 = 0 then printfn $"in testTailcall, n = {n}" @@ -431,4 +423,3 @@ let testHandlerNonTailCall () = testHandlerNonTailCall () |> expect 0 2 - diff --git a/tests/FSharp.Compiler.ComponentTests/Language/tailcalls.fsx b/tests/FSharp.Compiler.ComponentTests/Language/tailcalls.fsx index 06a09660d93..00bb438ce2c 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/tailcalls.fsx +++ b/tests/FSharp.Compiler.ComponentTests/Language/tailcalls.fsx @@ -37,10 +37,10 @@ do do - let sync = SyncBuilder (expect ReturnFromFinal) + let sync = SyncBuilder expectNone sync { - printf "expect ReturnFromFinal: " + printf "expectNone: " do! sync { printfn "inner" } } |> run @@ -154,6 +154,27 @@ do shouldEqual b.YieldFromCount 1 shouldEqual b.YieldFromFinalCount 0 +// do! in final position should use Bind rather than a final YieldFrom method. +type DoBangBuilder() = + member _.Bind(_: System.Threading.Tasks.Task, continuation: unit -> int list) = continuation() + member _.Yield(value: int) = [ value ] + member _.YieldFrom(_: int list) = [] + member _.YieldFromFinal(_: int list) = [] + member _.Return(_: unit) = [] + member _.Combine(first: int list, second: unit -> int list) = first @ second () + member _.Delay(computation: unit -> int list) = computation + member _.Run(computation: unit -> int list) = computation () + +do + let builder = DoBangBuilder() + let result = + builder { + yield 1 + do! System.Threading.Tasks.Task.CompletedTask + } + + shouldEqual result [ 1 ] + // yield! in try/finally body → YieldFrom (not tail), result correct do let b = ListBuilder()