Skip to content

Commit a9d2cd3

Browse files
committed
Implement TaskResultOption more correctly
1 parent e4cdc6c commit a9d2cd3

File tree

2 files changed

+216
-20
lines changed

2 files changed

+216
-20
lines changed

src/FsToolkit.ErrorHandling.TaskResult/TaskResultCE.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ open Microsoft.FSharp.Control
227227
open Microsoft.FSharp.Collections
228228

229229

230-
/// Task<'T option>
230+
/// Task<Result<'T, 'Error>>
231231
type TaskResult<'T, 'Error> = Task<Result<'T, 'Error>>
232232

233233
[<Struct; NoComparison; NoEquality>]

src/FsToolkit.ErrorHandling.TaskResult/TaskResultOptionCE.fs

Lines changed: 215 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
namespace FsToolkit.ErrorHandling
22

33
open System.Threading.Tasks
4+
open FsToolkit.ErrorHandling
45
#if NETSTANDARD2_0
56
open FSharp.Control.Tasks.Affine.Unsafe
67
open FSharp.Control.Tasks.Affine
@@ -43,37 +44,232 @@ module TaskResultOptionCE =
4344

4445
#else
4546

47+
open System
48+
open System.Runtime.CompilerServices
49+
open System.Threading
50+
open System.Threading.Tasks
51+
open Microsoft.FSharp.Core
52+
open Microsoft.FSharp.Core.CompilerServices
53+
open Microsoft.FSharp.Core.CompilerServices.StateMachineHelpers
54+
open Microsoft.FSharp.Core.LanguagePrimitives.IntrinsicOperators
55+
open Microsoft.FSharp.Control
56+
open Microsoft.FSharp.Collections
57+
58+
59+
/// Task<Result<'T option, 'Error>>
60+
type TaskResultOption<'T, 'Error> = Task<Result<'T option, 'Error>>
61+
62+
[<Struct; NoComparison; NoEquality>]
63+
type TaskResultOptionStateMachineData<'T, 'Error> =
64+
65+
[<DefaultValue(false)>]
66+
val mutable Result: Result<'T option, 'Error>
67+
68+
[<DefaultValue(false)>]
69+
val mutable MethodBuilder: AsyncTaskResultOptionMethodBuilder<'T, 'Error>
70+
71+
member this.IsResultError =
72+
match this.Result with
73+
| Error _ -> true
74+
| Ok None -> true
75+
| _ -> false
76+
77+
member this.IsTaskCompleted = this.MethodBuilder.Task.IsCompleted
78+
79+
and AsyncTaskResultOptionMethodBuilder<'TOverall, 'Error> = AsyncTaskMethodBuilder<Result<'TOverall option, 'Error>>
80+
81+
and TaskResultOptionStateMachine<'TOverall, 'Error> =
82+
ResumableStateMachine<TaskResultOptionStateMachineData<'TOverall, 'Error>>
83+
84+
and TaskResultOptionResumptionFunc<'TOverall, 'Error> =
85+
ResumptionFunc<TaskResultOptionStateMachineData<'TOverall, 'Error>>
86+
87+
and TaskResultOptionResumptionDynamicInfo<'TOverall, 'Error> =
88+
ResumptionDynamicInfo<TaskResultOptionStateMachineData<'TOverall, 'Error>>
89+
90+
and TaskResultOptionCode<'TOverall, 'Error, 'T> =
91+
ResumableCode<TaskResultOptionStateMachineData<'TOverall, 'Error>, 'T>
92+
4693
type TaskResultOptionBuilder() =
47-
member inline _.Return(value: 'T) : TaskCode<_, _> =
48-
option.Return value
49-
|> result.Return
50-
|> task.Return
94+
member inline _.Return(value: 'T) : TaskResultOptionCode<'T, 'Error, 'T> =
95+
TaskResultOptionCode<'T, 'Error, _>
96+
(fun sm ->
97+
// printfn "Return Called --> "
98+
sm.Data.Result <- Ok(Some value)
99+
100+
true)
101+
102+
static member BindDynamic
103+
(
104+
sm: byref<_>,
105+
task: TaskResultOption<'TResult1, 'Error>,
106+
continuation: ('TResult1 -> TaskResultOptionCode<'TOverall, 'Error, 'TResult2>)
107+
) : bool =
108+
let mutable awaiter = task.GetAwaiter()
109+
110+
let cont =
111+
(TaskResultOptionResumptionFunc<'TOverall, 'Error>
112+
(fun sm ->
113+
// printfn "ByndDynamic --> %A" sm.Data.Result
114+
let result = awaiter.GetResult()
51115

52-
member inline _.ReturnFrom(taskResult: Task<Result<'T option, 'TError>>) : TaskCode<_, _> =
116+
match result with
117+
| Ok (Some result) -> (continuation result).Invoke(&sm)
118+
| Ok None ->
119+
sm.Data.Result <- Ok None
120+
true
121+
| Error e ->
122+
sm.Data.Result <- Error e
123+
true))
53124

54-
task.ReturnFrom taskResult
125+
// shortcut to continue immediately
126+
if awaiter.IsCompleted then
127+
cont.Invoke(&sm)
128+
else
129+
sm.ResumptionDynamicInfo.ResumptionData <- (awaiter :> ICriticalNotifyCompletion)
130+
sm.ResumptionDynamicInfo.ResumptionFunc <- cont
131+
false
55132

56133
member inline _.Bind
57134
(
58-
taskResult: Task<Result<'T option, 'TError>>,
59-
[<InlineIfLambda>] binder: 'T -> TaskCode<_, _>
60-
) : TaskCode<_, _> =
135+
task: TaskResultOption<'TResult1, 'Error>,
136+
continuation: ('TResult1 -> TaskResultOptionCode<'TOverall, 'Error, 'TResult2>)
137+
) : TaskResultOptionCode<'TOverall, 'Error, 'TResult2> =
138+
139+
TaskResultOptionCode<'TOverall, 'Error, _>
140+
(fun sm ->
141+
if __useResumableCode then
142+
//-- RESUMABLE CODE START
143+
// Get an awaiter from the task
144+
// printfn "Bynd--> %A" sm.Data.Result
145+
let mutable awaiter = task.GetAwaiter()
146+
147+
let mutable __stack_fin = true
148+
149+
if not awaiter.IsCompleted then
150+
// This will yield with __stack_yield_fin = false
151+
// This will resume with __stack_yield_fin = true
152+
let __stack_yield_fin = ResumableCode.Yield().Invoke(&sm)
153+
__stack_fin <- __stack_yield_fin
154+
155+
if __stack_fin then
156+
let result = awaiter.GetResult()
157+
158+
match result with
159+
| Ok (Some result) -> (continuation result).Invoke(&sm)
160+
| Ok None ->
161+
sm.Data.Result <- Ok None
162+
true
163+
| Error e ->
164+
sm.Data.Result <- Error e
165+
true
166+
167+
else if sm.Data.MethodBuilder.Task.IsCompleted then
168+
true
169+
else
170+
sm.Data.MethodBuilder.AwaitUnsafeOnCompleted(&awaiter, &sm)
171+
false
172+
173+
else
174+
TaskResultOptionBuilder.BindDynamic(&sm, task, continuation)
175+
//-- RESUMABLE CODE END
176+
)
177+
178+
179+
member inline this.ReturnFrom(task: TaskResultOption<'T, 'Error>) : TaskResultOptionCode<'T, 'Error, 'T> =
180+
this.Bind(task, (fun v -> this.Return v))
61181

62-
task.Bind(
63-
taskResult,
64-
function
65-
| Ok (Some x) -> binder x
66-
| Ok None -> task.Return <| Ok None
67-
| Error x -> task.Return <| Error x
68-
)
69182

70183
member inline _.Combine(tro1, tro2) =
71184

72-
task.Combine(tro1, tro2)
185+
ResumableCode.Combine(tro1, tro2)
186+
187+
member inline _.Delay
188+
(generator: unit -> TaskResultOptionCode<'TOverall, 'Error, 'T>)
189+
: TaskResultOptionCode<'TOverall, 'Error, 'T> =
190+
TaskResultOptionCode<'TOverall, 'Error, 'T>(fun sm -> (generator ()).Invoke(&sm))
191+
192+
static member RunDynamic(code: TaskResultOptionCode<'T, 'Error, 'T>) : TaskResultOption<'T, 'Error> =
193+
let mutable sm =
194+
TaskResultOptionStateMachine<'T, 'Error>()
195+
196+
let initialResumptionFunc =
197+
TaskResultOptionResumptionFunc<'T, 'Error>(fun sm -> code.Invoke(&sm))
198+
199+
let resumptionInfo =
200+
{ new TaskResultOptionResumptionDynamicInfo<_, _>(initialResumptionFunc) with
201+
member info.MoveNext(sm) =
202+
let mutable savedExn = null
203+
204+
try
205+
sm.ResumptionDynamicInfo.ResumptionData <- null
206+
// printfn "RunDynamic BeforeInvoke Data --> %A" sm.Data.Result
207+
let step = info.ResumptionFunc.Invoke(&sm)
208+
// printfn "RunDynamic AfterInvoke Data --> %A %A" sm.Data.Result sm.Data.MethodBuilder.Task.Status
209+
210+
// If the `sm.Data.MethodBuilder` has already been set somewhere else (like While/WhileDynamic), we shouldn't continue
211+
if sm.Data.IsTaskCompleted then
212+
()
213+
elif step then
214+
// printfn "RunDynamic Data --> %A" sm.Data.Result
215+
sm.Data.MethodBuilder.SetResult(sm.Data.Result)
216+
else
217+
let mutable awaiter =
218+
sm.ResumptionDynamicInfo.ResumptionData :?> ICriticalNotifyCompletion
219+
220+
assert not (isNull awaiter)
221+
sm.Data.MethodBuilder.AwaitUnsafeOnCompleted(&awaiter, &sm)
222+
223+
with
224+
| exn -> savedExn <- exn
225+
// Run SetException outside the stack unwind, see https://github.com/dotnet/roslyn/issues/26567
226+
match savedExn with
227+
| null -> ()
228+
| exn -> sm.Data.MethodBuilder.SetException exn
229+
230+
member _.SetStateMachine(sm, state) =
231+
sm.Data.MethodBuilder.SetStateMachine(state) }
232+
233+
sm.ResumptionDynamicInfo <- resumptionInfo
234+
sm.Data.MethodBuilder <- AsyncTaskResultOptionMethodBuilder<'T, 'Error>.Create ()
235+
sm.Data.MethodBuilder.Start(&sm)
236+
sm.Data.MethodBuilder.Task
237+
238+
member inline _.Run(code: TaskResultOptionCode<'T, 'Error, 'T>) : TaskResultOption<'T, 'Error> =
239+
if __useResumableCode then
240+
__stateMachine<TaskResultOptionStateMachineData<'T, 'Error>, TaskResultOption<'T, 'Error>>
241+
(MoveNextMethodImpl<_>
242+
(fun sm ->
243+
//-- RESUMABLE CODE START
244+
__resumeAt sm.ResumptionPoint
245+
246+
let mutable __stack_exn: Exception = null
73247

74-
member inline _.Delay(f) = task.Delay f
248+
try
249+
// printfn "Run BeforeInvoke Task.Status --> %A" sm.Data.MethodBuilder.Task.Status
250+
let __stack_code_fin = code.Invoke(&sm)
251+
// printfn "Run Task.Status --> %A" sm.Data.MethodBuilder.Task.Status
252+
// If the `sm.Data.MethodBuilder` has already been set somewhere else (like While/WhileDynamic), we shouldn't continue
253+
if __stack_code_fin && not sm.Data.IsTaskCompleted then
75254

76-
member inline _.Run([<InlineIfLambda>] f: TaskCode<_, _>) = task.Run f
255+
// printfn "Run __stack_code_fin Data --> %A" sm.Data.Result
256+
sm.Data.MethodBuilder.SetResult(sm.Data.Result)
257+
with
258+
| exn -> __stack_exn <- exn
259+
// Run SetException outside the stack unwind, see https://github.com/dotnet/roslyn/issues/26567
260+
match __stack_exn with
261+
| null -> ()
262+
| exn -> sm.Data.MethodBuilder.SetException exn
263+
//-- RESUMABLE CODE END
264+
))
265+
(SetStateMachineMethodImpl<_>(fun sm state -> sm.Data.MethodBuilder.SetStateMachine(state)))
266+
(AfterCode<_, _>
267+
(fun sm ->
268+
sm.Data.MethodBuilder <- AsyncTaskResultOptionMethodBuilder<'T, 'Error>.Create ()
269+
sm.Data.MethodBuilder.Start(&sm)
270+
sm.Data.MethodBuilder.Task))
271+
else
272+
TaskResultOptionBuilder.RunDynamic(code)
77273

78274
let taskResultOption = TaskResultOptionBuilder()
79275

0 commit comments

Comments
 (0)