From 339d0ee5932a0f2c7049978004049270efa47296 Mon Sep 17 00:00:00 2001 From: "Peter D. Faria" Date: Tue, 15 Nov 2022 13:45:43 -0500 Subject: [PATCH 1/6] Added test cases to showcase bug. When a type implements both IDisposable and IAsyncDisposable, F# cannot determine which overload of TaskSeqBuilder.Using to use. --- .../FSharp.Control.TaskSeq.Test.fsproj | 1 + .../TaskSeq.Using.Tests.fs | 97 +++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs diff --git a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj index 02c3b80d..5a5d0df0 100644 --- a/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj +++ b/src/FSharp.Control.TaskSeq.Test/FSharp.Control.TaskSeq.Test.fsproj @@ -50,6 +50,7 @@ + diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs new file mode 100644 index 00000000..004e8878 --- /dev/null +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs @@ -0,0 +1,97 @@ +module FSharp.Control.TaskSeq.Test + +open System +open System.Threading.Tasks +open FSharp.Control +open FsUnit +open Xunit + + +type private OneGetter() = + member _.Get1() = 1 + +type private Disposable() = + inherit OneGetter() + + interface IDisposable with + member _.Dispose() = () + +type private AsyncDisposable() = + inherit OneGetter() + + interface IAsyncDisposable with + member _.DisposeAsync() = ValueTask() + +type private MultiDispose() = + inherit OneGetter() + + interface IDisposable with + member _.Dispose() = + () + + interface IAsyncDisposable with + member _.DisposeAsync() = + ValueTask() + +let private check ts = task { + let! length = ts |> TaskSeq.length + length |> should equal 1 +} + +[] +let ``CE task: Using when type implements IDisposable``() = + let ts = taskSeq { + use x = new Disposable() + + yield x.Get1() + } + + check ts + +[] +let ``CE task: Using when type implements IAsyncDisposable``() = + let ts = taskSeq { + use x = AsyncDisposable() + yield x.Get1() + } + + check ts + + +[] +let ``CE task: Using when type implements IDisposable and IAsyncDisposable``() = + let ts = taskSeq { + use x = new MultiDispose() + yield x.Get1() + } + + check ts + +[] +let ``CE task: Using! when type implements IDisposable``() = + let ts = taskSeq { + use! x = task { return new Disposable() } + yield x.Get1() + } + + check ts + + +[] +let ``CE task: Using! when type implements IAsyncDisposable``() = + let ts = taskSeq { + use! x = task { return AsyncDisposable() } + yield x.Get1() + } + + check ts + + +[] +let ``CE task: Using! when type implements IDisposable and IAsyncDisposable``() = + let ts = taskSeq { + use! x = task { return new MultiDispose() } + yield x.Get1() + } + + check ts From e5795e988a0e48b921645ea84f3b2e2677feb4d6 Mon Sep 17 00:00:00 2001 From: "Peter D. Faria" Date: Tue, 15 Nov 2022 13:49:35 -0500 Subject: [PATCH 2/6] Added comments showing which lines fail to compile. --- src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs index 004e8878..17c8e01f 100644 --- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs @@ -61,7 +61,7 @@ let ``CE task: Using when type implements IAsyncDisposable``() = [] let ``CE task: Using when type implements IDisposable and IAsyncDisposable``() = let ts = taskSeq { - use x = new MultiDispose() + use x = new MultiDispose() // Fails to compile yield x.Get1() } @@ -90,7 +90,7 @@ let ``CE task: Using! when type implements IAsyncDisposable``() = [] let ``CE task: Using! when type implements IDisposable and IAsyncDisposable``() = let ts = taskSeq { - use! x = task { return new MultiDispose() } + use! x = task { return new MultiDispose() } // Fails to compile yield x.Get1() } From a65a43a89c052f30ec1f20f2c402250e22f72c4b Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Sun, 27 Nov 2022 01:20:18 +0100 Subject: [PATCH 3/6] Implement priority-overload layers with modules, similar to task, to solve #97 `use` overload resolution bug --- src/FSharp.Control.TaskSeq/AsyncExtensions.fs | 2 - .../AsyncExtensions.fsi | 1 - src/FSharp.Control.TaskSeq/TaskExtensions.fs | 2 - src/FSharp.Control.TaskSeq/TaskExtensions.fsi | 1 - src/FSharp.Control.TaskSeq/TaskSeq.fs | 2 - src/FSharp.Control.TaskSeq/TaskSeq.fsi | 1 - src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs | 99 ++++++++++--------- src/FSharp.Control.TaskSeq/TaskSeqInternal.fs | 1 - 8 files changed, 51 insertions(+), 58 deletions(-) diff --git a/src/FSharp.Control.TaskSeq/AsyncExtensions.fs b/src/FSharp.Control.TaskSeq/AsyncExtensions.fs index 51c46b4e..25817526 100644 --- a/src/FSharp.Control.TaskSeq/AsyncExtensions.fs +++ b/src/FSharp.Control.TaskSeq/AsyncExtensions.fs @@ -1,7 +1,5 @@ namespace FSharp.Control -open FSharp.Control.TaskSeqBuilders - [] module AsyncExtensions = diff --git a/src/FSharp.Control.TaskSeq/AsyncExtensions.fsi b/src/FSharp.Control.TaskSeq/AsyncExtensions.fsi index 7470eafd..cf96281e 100644 --- a/src/FSharp.Control.TaskSeq/AsyncExtensions.fsi +++ b/src/FSharp.Control.TaskSeq/AsyncExtensions.fsi @@ -2,7 +2,6 @@ namespace FSharp.Control [] module AsyncExtensions = - open FSharp.Control.TaskSeqBuilders type AsyncBuilder with diff --git a/src/FSharp.Control.TaskSeq/TaskExtensions.fs b/src/FSharp.Control.TaskSeq/TaskExtensions.fs index aa40599f..b168f358 100644 --- a/src/FSharp.Control.TaskSeq/TaskExtensions.fs +++ b/src/FSharp.Control.TaskSeq/TaskExtensions.fs @@ -7,8 +7,6 @@ open System.Threading.Tasks open Microsoft.FSharp.Core.CompilerServices open Microsoft.FSharp.Core.LanguagePrimitives.IntrinsicOperators -open FSharp.Control.TaskSeqBuilders - #nowarn "57" #nowarn "1204" #nowarn "3513" diff --git a/src/FSharp.Control.TaskSeq/TaskExtensions.fsi b/src/FSharp.Control.TaskSeq/TaskExtensions.fsi index b5a97e7d..76cd8f9d 100644 --- a/src/FSharp.Control.TaskSeq/TaskExtensions.fsi +++ b/src/FSharp.Control.TaskSeq/TaskExtensions.fsi @@ -4,7 +4,6 @@ namespace FSharp.Control [] module TaskExtensions = - open FSharp.Control.TaskSeqBuilders type TaskBuilder with diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fs b/src/FSharp.Control.TaskSeq/TaskSeq.fs index e1a13d1b..05f5313c 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fs @@ -7,8 +7,6 @@ open System.Threading.Tasks #nowarn "57" module TaskSeq = - // F# BUG: the following module is 'AutoOpen' and this isn't needed in the Tests project. Why do we need to open it? - open FSharp.Control.TaskSeqBuilders // Just for convenience module Internal = TaskSeqInternal diff --git a/src/FSharp.Control.TaskSeq/TaskSeq.fsi b/src/FSharp.Control.TaskSeq/TaskSeq.fsi index 172ab83c..1f0f1497 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeq.fsi +++ b/src/FSharp.Control.TaskSeq/TaskSeq.fsi @@ -5,7 +5,6 @@ namespace FSharp.Control module TaskSeq = open System.Collections.Generic open System.Threading.Tasks - open FSharp.Control.TaskSeqBuilders /// Initialize an empty taskSeq. val empty<'T> : taskSeq<'T> diff --git a/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs index 195ed87b..ab48de43 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs @@ -1,4 +1,4 @@ -namespace FSharp.Control.TaskSeqBuilders +namespace FSharp.Control open System.Diagnostics @@ -47,13 +47,6 @@ module Internal = // cannot be marked with 'internal' scope type taskSeq<'T> = IAsyncEnumerable<'T> -type IPriority1 = - interface - end - -type IPriority2 = - interface - end [] type TaskSeqStateMachineData<'T>() = @@ -489,15 +482,7 @@ type TaskSeqBuilder() = true) ) - member inline this.Using - ( - disp: #IDisposable, - body: #IDisposable -> TaskSeqCode<'T>, - ?priority: IPriority2 - ) : TaskSeqCode<'T> = - - // FIXME: what about priority? - ignore priority + member inline this.Using(disp: #IDisposable, body: #IDisposable -> TaskSeqCode<'T>) : TaskSeqCode<'T> = // A using statement is just a try/finally with the finally block disposing if non-null. this.TryFinally( @@ -508,26 +493,6 @@ type TaskSeqBuilder() = disp.Dispose()) ) - member inline this.Using - ( - disp: #IAsyncDisposable, - body: #IAsyncDisposable -> TaskSeqCode<'T>, - ?priority: IPriority1 - ) : TaskSeqCode<'T> = - - // FIXME: what about priorities? - ignore priority - - // A using statement is just a try/finally with the finally block disposing if non-null. - this.TryFinallyAsync( - (fun sm -> (body disp).Invoke(&sm)), - (fun () -> - if not (isNull (box disp)) then - disp.DisposeAsync().AsTask() - else - Task.CompletedTask) - ) - member inline this.For(sequence: seq<'TElement>, body: 'TElement -> TaskSeqCode<'T>) : TaskSeqCode<'T> = // A for loop is just a using statement on the sequence's enumerator... this.Using( @@ -536,14 +501,6 @@ type TaskSeqBuilder() = (fun e -> this.While((fun () -> e.MoveNext()), (fun sm -> (body e.Current).Invoke(&sm)))) ) - member inline this.For(source: #IAsyncEnumerable<'TElement>, body: 'TElement -> TaskSeqCode<'T>) : TaskSeqCode<'T> = - TaskSeqCode<'T>(fun sm -> - this - .Using( - source.GetAsyncEnumerator(sm.Data.cancellationToken), - (fun e -> this.WhileAsync((fun () -> e.MoveNextAsync()), (fun sm -> (body e.Current).Invoke(&sm)))) - ) - .Invoke(&sm)) member inline _.Yield(v: 'T) : TaskSeqCode<'T> = TaskSeqCode<'T>(fun sm -> @@ -556,9 +513,6 @@ type TaskSeqBuilder() = sm.Data.awaiter <- null __stack_fin) - member inline this.YieldFrom(source: IAsyncEnumerable<'T>) : TaskSeqCode<'T> = - this.For(source, (fun v -> this.Yield(v))) - member inline this.YieldFrom(source: seq<'T>) : TaskSeqCode<'T> = this.For(source, (fun v -> this.Yield(v))) member inline _.Bind(task: Task<'TResult1>, continuation: ('TResult1 -> TaskSeqCode<'T>)) : TaskSeqCode<'T> = @@ -610,3 +564,52 @@ type TaskSeqBuilder() = sm.Data.awaiter <- awaiter sm.Data.current <- ValueNone false) + +// +// These "modules of priority" allow for an indecisive F# to resolve +// the proper overload if a single type implements more than one +// interface. For instance, a type implementing 'IDisposable' and +// 'IAsyncDisposable'. +// +// See for more info tasks.fs in F# Core. +// +// This section also includes the dependencies of such overloads +// (like For depending on Using etc). +// + +[] +module MediumPriority = + type TaskSeqBuilder with + + member inline this.Using + ( + disp: #IAsyncDisposable, + body: #IAsyncDisposable -> TaskSeqCode<'T> + ) : TaskSeqCode<'T> = + + // A using statement is just a try/finally with the finally block disposing if non-null. + this.TryFinallyAsync( + (fun sm -> (body disp).Invoke(&sm)), + (fun () -> + if not (isNull (box disp)) then + disp.DisposeAsync().AsTask() + else + Task.CompletedTask) + ) + + member inline this.For + ( + source: #IAsyncEnumerable<'TElement>, + body: 'TElement -> TaskSeqCode<'T> + ) : TaskSeqCode<'T> = + TaskSeqCode<'T>(fun sm -> + this + .Using( + source.GetAsyncEnumerator(sm.Data.cancellationToken), + (fun e -> + this.WhileAsync((fun () -> e.MoveNextAsync()), (fun sm -> (body e.Current).Invoke(&sm)))) + ) + .Invoke(&sm)) + + member inline this.YieldFrom(source: IAsyncEnumerable<'T>) : TaskSeqCode<'T> = + this.For(source, (fun v -> this.Yield(v))) diff --git a/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs b/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs index e2f2293a..deeaefbd 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs @@ -4,7 +4,6 @@ open System open System.Collections.Generic open System.Threading open System.Threading.Tasks -open FSharp.Control.TaskSeqBuilders [] module ExtraTaskSeqOperators = From abdeb708ef8c66872547ba9713ddb4c47c9be5b6 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Sun, 27 Nov 2022 01:21:21 +0100 Subject: [PATCH 4/6] Improve tests to ensure the Dispose/DisposeAsync is actually called --- .../TaskSeq.Using.Tests.fs | 67 +++++++++++-------- 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs index 17c8e01f..fc5a62c7 100644 --- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs @@ -10,88 +10,97 @@ open Xunit type private OneGetter() = member _.Get1() = 1 -type private Disposable() = +type private Disposable(disposed: bool ref) = inherit OneGetter() interface IDisposable with - member _.Dispose() = () + member _.Dispose() = disposed.Value <- true -type private AsyncDisposable() = +type private AsyncDisposable(disposed: bool ref) = inherit OneGetter() interface IAsyncDisposable with - member _.DisposeAsync() = ValueTask() + member _.DisposeAsync() = ValueTask(task { do disposed.Value <- true }) -type private MultiDispose() = +type private MultiDispose(disposed: int ref) = inherit OneGetter() interface IDisposable with - member _.Dispose() = - () + member _.Dispose() = disposed.Value <- !disposed + 1 interface IAsyncDisposable with - member _.DisposeAsync() = - ValueTask() + member _.DisposeAsync() = ValueTask(task { do disposed.Value <- !disposed + 1 }) -let private check ts = task { - let! length = ts |> TaskSeq.length - length |> should equal 1 -} +let private check = TaskSeq.length >> Task.map (should equal 1) [] -let ``CE task: Using when type implements IDisposable``() = - let ts = taskSeq { - use x = new Disposable() +let ``CE task: Using when type implements IDisposable`` () = + let disposed = ref false + let ts = taskSeq { + use x = new Disposable(disposed) yield x.Get1() } check ts + |> Task.map (fun _ -> disposed.Value |> should be True) [] -let ``CE task: Using when type implements IAsyncDisposable``() = +let ``CE task: Using when type implements IAsyncDisposable`` () = + let disposed = ref false + let ts = taskSeq { - use x = AsyncDisposable() + use x = AsyncDisposable(disposed) yield x.Get1() } check ts - + |> Task.map (fun _ -> disposed.Value |> should be True) [] -let ``CE task: Using when type implements IDisposable and IAsyncDisposable``() = +let ``CE task: Using when type implements IDisposable and IAsyncDisposable`` () = + let disposed = ref 0 + let ts = taskSeq { - use x = new MultiDispose() // Fails to compile + use x = new MultiDispose(disposed) // Used to fail to compile (see #97) yield x.Get1() } check ts + |> Task.map (fun _ -> disposed.Value |> should equal 1) // only one of the two dispose method should fire [] -let ``CE task: Using! when type implements IDisposable``() = +let ``CE task: Using! when type implements IDisposable`` () = + let disposed = ref false + let ts = taskSeq { - use! x = task { return new Disposable() } + use! x = task { return new Disposable(disposed) } yield x.Get1() } check ts - + |> Task.map (fun _ -> disposed.Value |> should be True) [] -let ``CE task: Using! when type implements IAsyncDisposable``() = +let ``CE task: Using! when type implements IAsyncDisposable`` () = + let disposed = ref false + let ts = taskSeq { - use! x = task { return AsyncDisposable() } + use! x = task { return AsyncDisposable(disposed) } yield x.Get1() } check ts - + |> Task.map (fun _ -> disposed.Value |> should be True) [] -let ``CE task: Using! when type implements IDisposable and IAsyncDisposable``() = +let ``CE task: Using! when type implements IDisposable and IAsyncDisposable`` () = + let disposed = ref 0 + let ts = taskSeq { - use! x = task { return new MultiDispose() } // Fails to compile + use! x = task { return new MultiDispose(disposed) } // Used to fail to compile (see #97) yield x.Get1() } check ts + |> Task.map (fun _ -> disposed.Value |> should equal 1) // only one of the two dispose method should fire From e9c20613effe3c788f3309a3c71e1a93b609c371 Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Sun, 27 Nov 2022 01:23:10 +0100 Subject: [PATCH 5/6] Update changelog --- src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj b/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj index 1575392d..c276bfce 100644 --- a/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj +++ b/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj @@ -24,10 +24,11 @@ Generates optimized IL code through the new resumable state machines, and comes Release notes: 0.3.0 (unreleased) - - adds support for 'for .. in ..' with task sequences in F# tasks and async, #75, #93 and #99 (with help from @theangrybyrd) - - adds TaskSeq.singleton, #90 (by @gusty) - - improves TaskSeq.empty by not relying on resumable state, #89 (by @gusty) - - does not throw exceptions anymore for unequal lengths in TaskSeq.zip, fixes #32 + - adds support for 'for .. in ..' with task sequences in F# tasks and async, #75, #93 and #99 (with help from @theangrybyrd). + - adds TaskSeq.singleton, #90 (by @gusty). + - fixes overload resolution bug with 'use' and 'use!', #97 (thanks @peterfaria). + - improves TaskSeq.empty by not relying on resumable state, #89 (by @gusty). + - does not throw exceptions anymore for unequal lengths in TaskSeq.zip, fixes #32. 0.2.2 - removes TaskSeq.toSeqCachedAsync, which was incorrectly named. Use toSeq or toListAsync instead. - renames TaskSeq.toSeqCached to TaskSeq.toSeq, which was its actual operational behavior. From 8a14b56293b80547f92716ab568b69fb148072ec Mon Sep 17 00:00:00 2001 From: Abel Braaksma Date: Sun, 27 Nov 2022 01:47:52 +0100 Subject: [PATCH 6/6] Give `IAsyncDisposable` a higher precedence than `IDisposable`, part of #97 --- .../TaskSeq.Using.Tests.fs | 8 ++-- src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs | 43 ++++++++----------- 2 files changed, 23 insertions(+), 28 deletions(-) diff --git a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs index fc5a62c7..07c7c247 100644 --- a/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs @@ -26,10 +26,10 @@ type private MultiDispose(disposed: int ref) = inherit OneGetter() interface IDisposable with - member _.Dispose() = disposed.Value <- !disposed + 1 + member _.Dispose() = disposed.Value <- 1 interface IAsyncDisposable with - member _.DisposeAsync() = ValueTask(task { do disposed.Value <- !disposed + 1 }) + member _.DisposeAsync() = ValueTask(task { do disposed.Value <- -1 }) let private check = TaskSeq.length >> Task.map (should equal 1) @@ -67,7 +67,7 @@ let ``CE task: Using when type implements IDisposable and IAsyncDisposable`` () } check ts - |> Task.map (fun _ -> disposed.Value |> should equal 1) // only one of the two dispose method should fire + |> Task.map (fun _ -> disposed.Value |> should equal -1) // should prefer IAsyncDisposable, which returns -1 [] let ``CE task: Using! when type implements IDisposable`` () = @@ -103,4 +103,4 @@ let ``CE task: Using! when type implements IDisposable and IAsyncDisposable`` () } check ts - |> Task.map (fun _ -> disposed.Value |> should equal 1) // only one of the two dispose method should fire + |> Task.map (fun _ -> disposed.Value |> should equal -1) // should prefer IAsyncDisposable, which returns -1 diff --git a/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs index ab48de43..a372b14f 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs @@ -482,26 +482,18 @@ type TaskSeqBuilder() = true) ) - member inline this.Using(disp: #IDisposable, body: #IDisposable -> TaskSeqCode<'T>) : TaskSeqCode<'T> = + member inline this.Using(disp: #IAsyncDisposable, body: #IAsyncDisposable -> TaskSeqCode<'T>) : TaskSeqCode<'T> = // A using statement is just a try/finally with the finally block disposing if non-null. - this.TryFinally( + this.TryFinallyAsync( (fun sm -> (body disp).Invoke(&sm)), (fun () -> - // yes, this can be null from time to time if not (isNull (box disp)) then - disp.Dispose()) - ) - - member inline this.For(sequence: seq<'TElement>, body: 'TElement -> TaskSeqCode<'T>) : TaskSeqCode<'T> = - // A for loop is just a using statement on the sequence's enumerator... - this.Using( - sequence.GetEnumerator(), - // ... and its body is a while loop that advances the enumerator and runs the body on each element. - (fun e -> this.While((fun () -> e.MoveNext()), (fun sm -> (body e.Current).Invoke(&sm)))) + disp.DisposeAsync().AsTask() + else + Task.CompletedTask) ) - member inline _.Yield(v: 'T) : TaskSeqCode<'T> = TaskSeqCode<'T>(fun sm -> // This will yield with __stack_fin = false @@ -513,8 +505,6 @@ type TaskSeqBuilder() = sm.Data.awaiter <- null __stack_fin) - member inline this.YieldFrom(source: seq<'T>) : TaskSeqCode<'T> = this.For(source, (fun v -> this.Yield(v))) - member inline _.Bind(task: Task<'TResult1>, continuation: ('TResult1 -> TaskSeqCode<'T>)) : TaskSeqCode<'T> = TaskSeqCode<'T>(fun sm -> let mutable awaiter = task.GetAwaiter() @@ -581,22 +571,27 @@ type TaskSeqBuilder() = module MediumPriority = type TaskSeqBuilder with - member inline this.Using - ( - disp: #IAsyncDisposable, - body: #IAsyncDisposable -> TaskSeqCode<'T> - ) : TaskSeqCode<'T> = + member inline this.Using(disp: #IDisposable, body: #IDisposable -> TaskSeqCode<'T>) : TaskSeqCode<'T> = // A using statement is just a try/finally with the finally block disposing if non-null. - this.TryFinallyAsync( + this.TryFinally( (fun sm -> (body disp).Invoke(&sm)), (fun () -> + // yes, this can be null from time to time if not (isNull (box disp)) then - disp.DisposeAsync().AsTask() - else - Task.CompletedTask) + disp.Dispose()) ) + member inline this.For(sequence: seq<'TElement>, body: 'TElement -> TaskSeqCode<'T>) : TaskSeqCode<'T> = + // A for loop is just a using statement on the sequence's enumerator... + this.Using( + sequence.GetEnumerator(), + // ... and its body is a while loop that advances the enumerator and runs the body on each element. + (fun e -> this.While((fun () -> e.MoveNext()), (fun sm -> (body e.Current).Invoke(&sm)))) + ) + + member inline this.YieldFrom(source: seq<'T>) : TaskSeqCode<'T> = this.For(source, (fun v -> this.Yield(v))) + member inline this.For ( source: #IAsyncEnumerable<'TElement>,