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..07c7c247 --- /dev/null +++ b/src/FSharp.Control.TaskSeq.Test/TaskSeq.Using.Tests.fs @@ -0,0 +1,106 @@ +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(disposed: bool ref) = + inherit OneGetter() + + interface IDisposable with + member _.Dispose() = disposed.Value <- true + +type private AsyncDisposable(disposed: bool ref) = + inherit OneGetter() + + interface IAsyncDisposable with + member _.DisposeAsync() = ValueTask(task { do disposed.Value <- true }) + +type private MultiDispose(disposed: int ref) = + inherit OneGetter() + + interface IDisposable with + member _.Dispose() = disposed.Value <- 1 + + interface IAsyncDisposable with + member _.DisposeAsync() = ValueTask(task { do disposed.Value <- -1 }) + +let private check = TaskSeq.length >> Task.map (should equal 1) + +[] +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 disposed = ref false + + let ts = taskSeq { + 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 disposed = ref 0 + + let ts = taskSeq { + 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) // should prefer IAsyncDisposable, which returns -1 + +[] +let ``CE task: Using! when type implements IDisposable`` () = + let disposed = ref false + + let ts = taskSeq { + 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 disposed = ref false + + let ts = taskSeq { + 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 disposed = ref 0 + + let ts = taskSeq { + 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) // should prefer IAsyncDisposable, which returns -1 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/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. 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..a372b14f 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,34 +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 - - // A using statement is just a try/finally with the finally block disposing if non-null. - 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.Dispose()) - ) - - member inline this.Using - ( - disp: #IAsyncDisposable, - body: #IAsyncDisposable -> TaskSeqCode<'T>, - ?priority: IPriority1 - ) : TaskSeqCode<'T> = - - // FIXME: what about priorities? - ignore priority + 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( @@ -528,23 +494,6 @@ type TaskSeqBuilder() = 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( - 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.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 -> // This will yield with __stack_fin = false @@ -556,11 +505,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> = TaskSeqCode<'T>(fun sm -> let mutable awaiter = task.GetAwaiter() @@ -610,3 +554,57 @@ 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: #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( + (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)))) + ) + + member inline this.YieldFrom(source: seq<'T>) : TaskSeqCode<'T> = this.For(source, (fun v -> this.Yield(v))) + + 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 =