diff --git a/azure-pipelines-PR.yml b/azure-pipelines-PR.yml index ff6f4603975..6b831af89e6 100644 --- a/azure-pipelines-PR.yml +++ b/azure-pipelines-PR.yml @@ -757,35 +757,34 @@ stages: displayName: UMX_Slow_Repro expectLocalCore: true - repo: fsprojects/FSharpPlus - commit: f614035b75922aba41ed6a36c2fc986a2171d2b8 + commit: f42f81885111c652b08218e0880c264447ae56e4 buildScript: build.cmd displayName: FSharpPlus_Windows - repo: fsprojects/FSharpPlus - commit: f614035b75922aba41ed6a36c2fc986a2171d2b8 + commit: f42f81885111c652b08218e0880c264447ae56e4 buildScript: build.sh displayName: FSharpPlus_Linux useVmImage: $(LinuxMachineQueueName) usePool: $(DncEngPublicBuildPool) - repo: fsprojects/FSharpPlus - commit: 2648efe + commit: f42f81885111c652b08218e0880c264447ae56e4 buildScript: dotnet build tests/FSharpPlus.Tests/FSharpPlus.Tests.fsproj -c Release -bl displayName: FsharpPlus_NET10_Build_Lib_Tests expectLocalCore: true - # remove this before merging - repo: fsprojects/FSharpPlus - commit: 2648efe + commit: f42f81885111c652b08218e0880c264447ae56e4 buildScript: dotnet msbuild build.proj -t:Build;Test -bl displayName: FsharpPlus_NET10_Test_Debug - repo: fsprojects/FSharpPlus - commit: 2648efe + commit: f42f81885111c652b08218e0880c264447ae56e4 buildScript: dotnet msbuild build.proj -t:Build;Test -p:Configuration=Release -bl displayName: FsharpPlus_NET10_Test_Release - repo: fsprojects/FSharpPlus - commit: 2648efe + commit: f42f81885111c652b08218e0880c264447ae56e4 buildScript: dotnet msbuild build.proj -t:Build;AllDocs -bl displayName: FsharpPlus_NET10_Docs - repo: fsprojects/FSharpPlus - commit: 2648efe + commit: f42f81885111c652b08218e0880c264447ae56e4 buildScript: build.sh displayName: FsharpPlus_Net10_Linux useVmImage: $(LinuxMachineQueueName) 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 38dd2d65b0a..abbf3304ee1 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -1,5 +1,6 @@ ### Fixed +* Fix recursive inline SRTP resolution being truncated by one currying level (e.g. FSharpPlus `memoizeN`), a regression from the function-domain unification order change in [PR #15181](https://github.com/dotnet/fsharp/pull/15181); the contravariant domain now keeps the inference variable that still carries the pending member constraint. ([PR #20247](https://github.com/dotnet/fsharp/pull/20247)) * Fix incorrect `StructLayout(Size = 1)` emission for data-less struct unions where the compiler-generated tag field makes the actual runtime size larger. ([PR #19759](https://github.com/dotnet/fsharp/pull/19759)) * Fix FS0750 "This construct may only be used within computation expressions" incorrectly raised for `let!`/`use!`/`do!` appearing in the right-hand side of a plain `let` binding inside a computation expression. The right-hand side is now desugared as a nested computation of the same builder whose result is bound with `let!`, keeping its bindings correctly scoped. ([Issue #19457](https://github.com/dotnet/fsharp/issues/19457), [PR #19868](https://github.com/dotnet/fsharp/pull/19868)) * Stop leaking a `System.Diagnostics.Metrics.MeterListener` per `Cache` in DEBUG builds. Each cache created a `CacheMetrics.CacheMetricsListener` (which starts a `MeterListener` registered in the process-global metrics registry) and never disposed it, so listeners accumulated for the lifetime of the process. Because every cache hit/miss/add published to all registered listeners, the per-operation cost grew linearly with the number of leaked listeners, so repeated checks (and Debug FCS test runs) slowed down over time. The per-cache `CacheMetricsListener` and the per-instance `cacheId` tag are removed; `DebugDisplay` and tests now read the existing name-aggregated stats populated by the single `ListenToAll` listener, so no per-cache listener is created and no per-operation cost is added. ([PR #19995](https://github.com/dotnet/fsharp/pull/19995)) diff --git a/docs/release-notes/.FSharp.Core/11.0.100.md b/docs/release-notes/.FSharp.Core/11.0.100.md index 6904710cc82..884146bc4e4 100644 --- a/docs/release-notes/.FSharp.Core/11.0.100.md +++ b/docs/release-notes/.FSharp.Core/11.0.100.md @@ -7,6 +7,7 @@ ### Added +* Add `Async.Await`, mirroring `Async.AwaitTask` semantics, but elides egregious `AggregateException` wrapping. Includes `ValueTask` support, and a SRTP-based overload accepting any Task-like value that supports the `GetAwaiter` protocol. ([Language Suggestion #840](https://github.com/fsharp/fslang-suggestions/issues/840), [PR #19785](https://github.com/dotnet/fsharp/pull/19785)) * `Async.RunSynchronouslyImmediate`: runs work on the calling thread until the first asynchronous suspension (as opposed to `RunSynchronously`, which immediately offloads if not on a background and/or threadpool thread). ([Issue #1042](https://github.com/fsharp/fslang-suggestions/issues/1042), [PR #19804](https://github.com/dotnet/fsharp/pull/19804)) * Added modules for `Async`, `Task` and `ValueTask` with consistent `result`, `map`, `bind`, `ignore`, `catchWith`, `catch`, and `empty` functions ([LanguageSuggestion #1466](https://github.com/fsharp/fslang-suggestions/issues/1466), [PR #19844](https://github.com/dotnet/fsharp/pull/19844)) * Added conversion functions `Task.ofValueTask` and `ValueTask.ofTask`. ([LanguageSuggestion #1466](https://github.com/fsharp/fslang-suggestions/issues/1466), [PR #19844](https://github.com/dotnet/fsharp/pull/19844)) diff --git a/src/Compiler/Checking/ConstraintSolver.fs b/src/Compiler/Checking/ConstraintSolver.fs index f8ece244530..dc57f3e973c 100644 --- a/src/Compiler/Checking/ConstraintSolver.fs +++ b/src/Compiler/Checking/ConstraintSolver.fs @@ -1500,7 +1500,20 @@ and SolveFunTypeEqn csenv ndeep m2 trace cxsln domainTy1 domainTy2 rangeTy1 rang trackErrors { let g = csenv.g let domainTy2 = reqTyForArgumentNullnessInference g domainTy1 domainTy2 - do! SolveTypeEqualsTypeKeepAbbrevsWithCxsln csenv ndeep m2 trace cxsln domainTy2 domainTy1 + // Keep an inference variable that still carries an unsolved SRTP constraint as the + // unification representative: if the required domain absorbs it, the pending recursive + // trait resolution is merged away and recursive SRTP specialization is truncated by one + // currying level. This restores the forward domain order that nullness PR #15181 reversed, + // but only for that case; skipped under MatchingOnly, where only the left type variable may + // be solved (see SolveTypeEqualsType). + let inline isUnsolvedTraitTypar ty = + match tryDestTyparTy g ty with + | ValueSome tp -> tp |> HasConstraint (function TyparConstraint.MayResolveMember(traitInfo, _) -> traitInfo.Solution.IsNone | _ -> false) + | _ -> false + if not csenv.MatchingOnly && isUnsolvedTraitTypar domainTy2 && not (isUnsolvedTraitTypar domainTy1) then + do! SolveTypeEqualsTypeKeepAbbrevsWithCxsln csenv ndeep m2 trace cxsln domainTy1 domainTy2 + else + do! SolveTypeEqualsTypeKeepAbbrevsWithCxsln csenv ndeep m2 trace cxsln domainTy2 domainTy1 return! SolveTypeEqualsTypeKeepAbbrevsWithCxsln csenv ndeep m2 trace cxsln rangeTy1 rangeTy2 } diff --git a/src/FSharp.Core/async.fs b/src/FSharp.Core/async.fs index 73c004b4260..e73dc4aa230 100644 --- a/src/FSharp.Core/async.fs +++ b/src/FSharp.Core/async.fs @@ -13,6 +13,7 @@ open System.Runtime.ExceptionServices open System.Threading open System.Threading.Tasks open Microsoft.FSharp.Core +open Microsoft.FSharp.Core.CompilerServices open Microsoft.FSharp.Core.LanguagePrimitives.IntrinsicOperators open Microsoft.FSharp.Control open Microsoft.FSharp.Collections @@ -1203,16 +1204,30 @@ module AsyncPrimitives = task + // Used by Async.Await path to elide egregious AggregateException wrapping + [] + let UnwrapExn (exn: AggregateException) = + if exn.InnerExceptions.Count = 1 then + exn.InnerExceptions[0] + else + exn + // Call the appropriate continuation on completion of a task [] - let OnTaskCompleted (completedTask: Task<'T>) (ctxt: AsyncActivation<'T>) = + let OnTaskCompleted unwrap (completedTask: Task<'T>) (ctxt: AsyncActivation<'T>) = assert completedTask.IsCompleted if completedTask.IsCanceled then let edi = ExceptionDispatchInfo.Capture(TaskCanceledException completedTask) ctxt.econt edi elif completedTask.IsFaulted then - let edi = ExceptionDispatchInfo.RestoreOrCapture completedTask.Exception + let e = + if unwrap then + UnwrapExn completedTask.Exception + else + completedTask.Exception + + let edi = ExceptionDispatchInfo.RestoreOrCapture e ctxt.econt edi else ctxt.cont completedTask.Result @@ -1222,14 +1237,20 @@ module AsyncPrimitives = // the overall async (they may be governed by different cancellation tokens, or // the task may not have a cancellation token at all). [] - let OnUnitTaskCompleted (completedTask: Task) (ctxt: AsyncActivation) = + let OnUnitTaskCompleted unwrap (completedTask: Task) (ctxt: AsyncActivation) = assert completedTask.IsCompleted if completedTask.IsCanceled then let edi = ExceptionDispatchInfo.Capture(TaskCanceledException(completedTask)) ctxt.econt edi elif completedTask.IsFaulted then - let edi = ExceptionDispatchInfo.RestoreOrCapture completedTask.Exception + let e = + if unwrap then + UnwrapExn completedTask.Exception + else + completedTask.Exception + + let edi = ExceptionDispatchInfo.RestoreOrCapture e ctxt.econt edi else ctxt.cont () @@ -1239,10 +1260,10 @@ module AsyncPrimitives = // completing the task. This will install a new trampoline on that thread and continue the // execution of the async there. [] - let AttachContinuationToTask (task: Task<'T>) (ctxt: AsyncActivation<'T>) = + let AttachContinuationToTask unwrap (task: Task<'T>) (ctxt: AsyncActivation<'T>) = task.ContinueWith( Action>(fun completedTask -> - ctxt.trampolineHolder.ExecuteWithTrampoline(fun () -> OnTaskCompleted completedTask ctxt) + ctxt.trampolineHolder.ExecuteWithTrampoline(fun () -> OnTaskCompleted unwrap completedTask ctxt) |> unfake), TaskContinuationOptions.ExecuteSynchronously ) @@ -1254,16 +1275,36 @@ module AsyncPrimitives = // completing the task. This will install a new trampoline on that thread and continue the // execution of the async there. [] - let AttachContinuationToUnitTask (task: Task) (ctxt: AsyncActivation) = + let AttachContinuationToUnitTask unwrap (task: Task) (ctxt: AsyncActivation) = task.ContinueWith( Action(fun completedTask -> - ctxt.trampolineHolder.ExecuteWithTrampoline(fun () -> OnUnitTaskCompleted completedTask ctxt) + ctxt.trampolineHolder.ExecuteWithTrampoline(fun () -> OnUnitTaskCompleted unwrap completedTask ctxt) |> unfake), TaskContinuationOptions.ExecuteSynchronously ) |> ignore |> fake + let AwaitTask unwrap (task: Task<'T>) = + MakeAsyncWithCancelCheck(fun ctxt -> + if task.IsCompleted then + // Run synchronously without installing new trampoline + OnTaskCompleted unwrap task ctxt + else + // Continue asynchronously, via syncContext if necessary, installing new trampoline + let ctxt = DelimitSyncContext ctxt + ctxt.ProtectCode(fun () -> AttachContinuationToTask unwrap task ctxt)) + + let AwaitUnitTask unwrap (task: Task) = + MakeAsyncWithCancelCheck(fun ctxt -> + if task.IsCompleted then + // Continue synchronously without installing new trampoline + OnUnitTaskCompleted unwrap task ctxt + else + // Continue asynchronously, via syncContext if necessary, installing new trampoline + let ctxt = DelimitSyncContext ctxt + ctxt.ProtectCode(fun () -> AttachContinuationToUnitTask unwrap task ctxt)) + /// Removes a registration places on a cancellation token let DisposeCancellationRegistration (registration: byref) = match registration with @@ -2202,24 +2243,58 @@ type Async = CreateWhenCancelledAsync compensation computation static member AwaitTask(task: Task<'T>) : Async<'T> = - MakeAsyncWithCancelCheck(fun ctxt -> - if task.IsCompleted then - // Run synchronously without installing new trampoline - OnTaskCompleted task ctxt - else - // Continue asynchronously, via syncContext if necessary, installing new trampoline - let ctxt = DelimitSyncContext ctxt - ctxt.ProtectCode(fun () -> AttachContinuationToTask task ctxt)) + AwaitTask false task static member AwaitTask(task: Task) : Async = - MakeAsyncWithCancelCheck(fun ctxt -> - if task.IsCompleted then - // Continue synchronously without installing new trampoline - OnUnitTaskCompleted task ctxt - else - // Continue asynchronously, via syncContext if necessary, installing new trampoline - let ctxt = DelimitSyncContext ctxt - ctxt.ProtectCode(fun () -> AttachContinuationToUnitTask task ctxt)) + AwaitUnitTask false task + + static member Await(task: Task<'T>) : Async<'T> = + AwaitTask true task + + static member Await(task: Task) : Async = + AwaitUnitTask true task + +#if NETSTANDARD2_1 + static member Await(task: ValueTask<'T>) : Async<'T> = + if task.IsCompletedSuccessfully then + CreateReturnAsync(task.GetAwaiter().GetResult()) + else + AwaitTask true (task.AsTask()) + + static member Await(task: ValueTask) : Async = + if task.IsCompletedSuccessfully then + CreateReturnAsync(task.GetAwaiter().GetResult()) + else + AwaitUnitTask true (task.AsTask()) +#endif + +module AsyncTaskLikeExtensions = + + type Async with + + [] + static member inline Await< ^TaskLike, ^Awaiter, 'T + when ^TaskLike: (member GetAwaiter: unit -> ^Awaiter) + and ^Awaiter :> ICriticalNotifyCompletion + and ^Awaiter: (member get_IsCompleted: unit -> bool) + and ^Awaiter: (member GetResult: unit -> 'T)> + (task: ^TaskLike) + : Async<'T> = + Async.FromContinuations(fun (cont, econt, _ccont) -> + let mutable awaiter = (^TaskLike: (member GetAwaiter: unit -> ^Awaiter) task) + + if (^Awaiter: (member get_IsCompleted: unit -> bool) awaiter) then + try + cont ((^Awaiter: (member GetResult: unit -> 'T) awaiter)) + with e -> + econt e + else + (awaiter :> ICriticalNotifyCompletion) + .OnCompleted(fun () -> + try + cont ((^Awaiter: (member GetResult: unit -> 'T) awaiter)) + with e -> + econt e)) module CommonExtensions = diff --git a/src/FSharp.Core/async.fsi b/src/FSharp.Core/async.fsi index 1af0fab84db..2171f75164a 100644 --- a/src/FSharp.Core/async.fsi +++ b/src/FSharp.Core/async.fsi @@ -5,9 +5,11 @@ namespace Microsoft.FSharp.Control open System open System.Threading open System.Threading.Tasks + open System.Runtime.CompilerServices open System.Runtime.ExceptionServices open Microsoft.FSharp.Core + open Microsoft.FSharp.Core.CompilerServices open Microsoft.FSharp.Control open Microsoft.FSharp.Collections @@ -776,47 +778,210 @@ namespace Microsoft.FSharp.Control /// static member AwaitIAsyncResult: iar: IAsyncResult * ?millisecondsTimeout:int -> Async - /// Return an asynchronous computation that will wait for the given task to complete and return - /// its result. - /// + /// Creates an asynchronous computation that will wait asynchronously for the given task to complete, returning + /// its result. Note exceptions are wrapped in ; for new + /// code, prefer Async.Await, which surfaces single exceptions directly. /// The task to await. - /// - /// If an exception occurs in the asynchronous computation then an exception is re-raised by this - /// function. - /// - /// If the task is cancelled then is raised. Note + /// If the task is canceled then is raised. Note /// that the task may be governed by a different cancellation token to the overall async computation /// where the AwaitTask occurs. In practice you should normally start the task with the /// cancellation token returned by let! ct = Async.CancellationToken, and catch - /// any at the point where the + /// any at the point where the /// overall async is started. /// - /// /// Awaiting Results - /// - /// + /// + /// + /// let t = Task.Run(fun () -> invalidOp "test"; 42) + /// async { + /// try + /// let! _ = Async.AwaitTask t + /// () + /// with + /// | :? System.InvalidOperationException -> + /// printfn "unreachable" // will not match: exception is wrapped in AggregateException + /// | :? System.AggregateException as e -> + /// printfn $"Caught: {e.InnerException.Message}" + /// } |> Async.RunSynchronously + /// + /// Prints Caught: test. The InvalidOperationException branch is not reached because + /// exceptions from tasks are always wrapped in . Contrast with Async.Await. + /// static member AwaitTask: task: Task<'T> -> Async<'T> - /// Return an asynchronous computation that will wait for the given task to complete and return - /// its result. - /// + /// Creates an asynchronous computation that will wait asynchronously for the given task to complete. + /// Note exceptions are wrapped in ; for new + /// code, prefer Async.Await, which surfaces single exceptions directly. /// The task to await. - /// - /// If an exception occurs in the asynchronous computation then an exception is re-raised by this - /// function. - /// - /// If the task is cancelled then is raised. Note + /// If the task is canceled then is raised. Note /// that the task may be governed by a different cancellation token to the overall async computation /// where the AwaitTask occurs. In practice you should normally start the task with the /// cancellation token returned by let! ct = Async.CancellationToken, and catch - /// any at the point where the + /// any at the point where the /// overall async is started. /// + /// Awaiting Results + /// + /// + /// let t = Task.Run(fun () -> invalidOp "test") + /// async { + /// try + /// do! Async.AwaitTask t + /// with + /// | :? System.InvalidOperationException -> + /// printfn "unreachable" // will not match: exception is wrapped in AggregateException + /// | :? System.AggregateException as e -> + /// printfn $"Caught: {e.InnerException.Message}" + /// } |> Async.RunSynchronously + /// + /// Prints Caught: test. The InvalidOperationException branch is not reached because + /// exceptions from tasks are always wrapped in . Contrast with Async.Await. + /// + static member AwaitTask: task: Task -> Async + + /// Creates an asynchronous computation that will wait for the given task to complete and return + /// its result. + /// + /// The task to await. + /// + /// + ///

Exceptions are surfaced directly: a task faulted with a single exception raises that + /// exception; only s carrying multiple inner exceptions are + /// re-raised as-is. For the legacy behavior of uniformly presenting the raw underlying + /// , use Async.AwaitTask.

+ /// + ///

If the task is canceled then is raised.

+ /// + ///

Note the task may be governed by a different cancellation token than the overall async computation; + /// typically tasks should be wired to the ambient cancellation token obtained via + /// let! ct = Async.CancellationToken, catching + /// where the overall async is started.

+ ///
/// /// Awaiting Results /// - /// - static member AwaitTask: task: Task -> Async + /// + /// + /// let t = Task.Run(fun () -> invalidOp "test"; 42) + /// async { + /// try + /// let! _ = Async.Await t + /// () + /// with + /// | :? System.InvalidOperationException as e -> + /// printfn $"Caught: {e.Message}" + /// | :? System.AggregateException -> + /// printfn "unreachable" // will not match: single exception is unwrapped + /// } |> Async.RunSynchronously + /// + /// Prints Caught: test. The AggregateException branch is not reached because a + /// single-inner exception is unwrapped. Contrast with Async.AwaitTask. + /// + static member Await: task: Task<'T> -> Async<'T> + + /// Creates an asynchronous computation that will wait for the given task to complete. + /// The task to await. + /// + ///

Exceptions are surfaced directly: a task faulted with a single exception raises that + /// exception; only s carrying multiple inner exceptions are + /// re-raised as-is. For the legacy behavior of uniformly presenting the raw underlying + /// , use Async.AwaitTask.

+ /// + ///

If the task is canceled then is raised.

+ /// + ///

Note the task may be governed by a different cancellation token than the overall async computation; + /// typically tasks should be wired to the ambient cancellation token obtained via + /// let! ct = Async.CancellationToken, catching + /// where the overall async is started.

+ ///
+ /// Awaiting Results + /// + /// + /// let t = Task.Run(fun () -> invalidOp "test") + /// async { + /// try + /// do! Async.Await t + /// with + /// | :? System.InvalidOperationException as e -> + /// printfn $"Caught: {e.Message}" + /// | :? System.AggregateException -> + /// printfn "unreachable" // will not match: single exception is unwrapped + /// } |> Async.RunSynchronously + /// + /// Prints Caught: test. The AggregateException branch is not reached because a + /// single-inner exception is unwrapped. Contrast with Async.AwaitTask. + /// + static member Await: task: Task -> Async + +#if NETSTANDARD2_1 + /// Creates an asynchronous computation that will wait for the given ValueTask to complete and return + /// its result. + /// The ValueTask to await. + /// + ///

Exceptions are surfaced directly: a task faulted with a single exception raises that + /// exception; only s carrying multiple inner exceptions are + /// re-raised as-is. For the legacy behavior of uniformly presenting the raw underlying + /// , use Async.AwaitTask.

+ /// + ///

If the task is canceled then is raised.

+ /// + ///

Note the task may be governed by a different cancellation token than the overall async computation; + /// typically tasks should be wired to the ambient cancellation token obtained via + /// let! ct = Async.CancellationToken, catching + /// where the overall async is started.

+ ///
+ /// Awaiting Results + /// + /// + /// let vt = ValueTask<int>(Task.Run(fun () -> invalidOp "test"; 42)) + /// async { + /// try + /// let! _ = Async.Await vt + /// () + /// with + /// | :? System.InvalidOperationException as e -> + /// printfn $"Caught: {e.Message}" + /// | :? System.AggregateException -> + /// printfn "unreachable" // will not match: single exception is unwrapped + /// } |> Async.RunSynchronously + /// + /// Prints Caught: test. + /// + static member Await: task: ValueTask<'T> -> Async<'T> + + /// Creates an asynchronous computation that will wait for the given ValueTask to complete. + /// The ValueTask to await. + /// + ///

Exceptions are surfaced directly: a task faulted with a single exception raises that + /// exception; only s carrying multiple inner exceptions are + /// re-raised as-is. For the legacy behavior of uniformly presenting the raw underlying + /// , use Async.AwaitTask.

+ /// + ///

If the task is canceled then is raised.

+ /// + ///

Note the task may be governed by a different cancellation token than the overall async computation; + /// typically tasks should be wired to the ambient cancellation token obtained via + /// let! ct = Async.CancellationToken, catching + /// where the overall async is started.

+ ///
+ /// Awaiting Results + /// + /// + /// let vt = ValueTask(Task.Run(fun () -> invalidOp "test")) + /// async { + /// try + /// do! Async.Await vt + /// with + /// | :? System.InvalidOperationException as e -> + /// printfn $"Caught: {e.Message}" + /// | :? System.AggregateException -> + /// printfn "unreachable" // will not match: single exception is unwrapped + /// } |> Async.RunSynchronously + /// + /// Prints Caught: test. + /// + static member Await: task: ValueTask -> Async +#endif /// /// Creates an asynchronous computation that will sleep for the given time. This is scheduled @@ -1110,6 +1275,61 @@ namespace Microsoft.FSharp.Control computation:Async<'T> * ?cancellationToken:CancellationToken-> Task<'T> + /// A module of extension members providing support for awaiting any task-like value via the GetAwaiter pattern. + /// + /// Awaiting Results + [] + module AsyncTaskLikeExtensions = + + type Async with + + /// Creates an asynchronous computation that will wait for the given task-like value to complete and return + /// its result. + /// The task-like value to await. + ///

The value must satisfy the GetAwaiter pattern: it must have a GetAwaiter() method + /// returning an awaiter implementing + /// with IsCompleted and GetResult() members.

+ ///

Exceptions thrown by GetResult() are propagated directly.

+ ///

Unlike the +#if NETSTANDARD2_1 + /// and +#endif + /// overloads, an carrying multiple inner exceptions is not preserved: + /// the first inner exception surfaces (standard GetResult() semantics).

+ ///

This overload uses statically resolved type parameters (SRTP) so it can accept any task-like type. +#if NETSTANDARD2_1 + /// The specific overloads for , , + /// and +#else + /// The specific overloads for and +#endif + /// are preferred when the argument type is known.

+ ///
+ /// Awaiting Results + /// + /// + /// // A minimal custom task-like type + /// type MyTask<'T>(task: System.Threading.Tasks.Task<'T>) = + /// member _.GetAwaiter() = task.GetAwaiter() + /// + /// let myTask = MyTask(System.Threading.Tasks.Task.FromResult 42) + /// async { + /// let! result = Async.Await myTask + /// printfn $"Result: {result}" + /// } |> Async.RunSynchronously + /// + /// Prints Result: 42. + /// + // NOTE Aside from being a catch-all to cover the GetAwaiter pattern, + // On netstandard2.0, this overload also covers ValueTask and ValueTask<'T>. + [] + static member inline Await< ^TaskLike, ^Awaiter, 'T> : + task: ^TaskLike -> Async<'T> + when ^TaskLike: (member GetAwaiter: unit -> ^Awaiter) + and ^Awaiter :> ICriticalNotifyCompletion + and ^Awaiter: (member get_IsCompleted: unit -> bool) + and ^Awaiter: (member GetResult: unit -> 'T) + /// The F# compiler emits references to this type to implement F# async expressions. /// /// Async Internals diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/InferenceProcedures/ByrefSafetyAnalysis/ByrefSafetyAnalysis.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/InferenceProcedures/ByrefSafetyAnalysis/ByrefSafetyAnalysis.fs index 40d511a028b..32f18bfb19f 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/InferenceProcedures/ByrefSafetyAnalysis/ByrefSafetyAnalysis.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/InferenceProcedures/ByrefSafetyAnalysis/ByrefSafetyAnalysis.fs @@ -1000,7 +1000,7 @@ type outref<'T> with |> shouldSucceed #endif -#if NETSTANDARD2_1_OR_GREATER +#if NETCOREAPP [] let``E_TopLevelByref_fs`` compilation = compilation diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/Types/TypeConstraints/IWSAMsAndSRTPs/IWSAMsAndSRTPsTests.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/Types/TypeConstraints/IWSAMsAndSRTPs/IWSAMsAndSRTPsTests.fs index fb6c648ca49..3de8d80d947 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/Types/TypeConstraints/IWSAMsAndSRTPs/IWSAMsAndSRTPsTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/Types/TypeConstraints/IWSAMsAndSRTPs/IWSAMsAndSRTPsTests.fs @@ -1970,6 +1970,69 @@ let resultInt: int = call 42 if resultFloat <> 0.0 then failwith $"Expected 0.0 but got {resultFloat}" if resultDecimal <> 0M then failwith $"Expected 0M but got {resultDecimal}" if resultInt <> 0 then failwith $"Expected 0 but got {resultInt}" +""" + |> asExe + |> compileAndRun + |> shouldSucceed + + // Recursive inline SRTP resolution must not be truncated by one currying level (regression from + // the domain-order reversal in nullness PR #15181). + [] + let ``Recursive inline SRTP memoization specializes at every currying depth`` () = + FSharp """ +module Test +open System.Collections.Concurrent + +type Default1 = class end + +[] +type MemoizationKeyWrapper<'a> = MemoizationKeyWrapper of 'a + +type MemoizeN = + inherit Default1 + static member getOrAdd (cd: ConcurrentDictionary,'b>) (f: 'a -> 'b) k = + cd.GetOrAdd (MemoizationKeyWrapper k, (fun (MemoizationKeyWrapper x) -> x) >> f) + +let inline memoizeN (f: ^F) : ^F = + let inline call_2 (a: ^MemoizeN, b: ^b) = ((^MemoizeN or ^b) : (static member MemoizeN : ^MemoizeN * 'b -> _ ) (a, b)) + call_2 (Unchecked.defaultof, Unchecked.defaultof< ^F >) f + +type MemoizeN with + static member MemoizeN (_: Default1, _: 'a -> 'b) = MemoizeN.getOrAdd (ConcurrentDictionary ()) + static member inline MemoizeN (_: MemoizeN, _:'t -> 'a -> 'b) = MemoizeN.getOrAdd (ConcurrentDictionary ()) << (<<) memoizeN + +let effs = ResizeArray () +let sum3 a (b:int) c = effs.Add "sum3"; a + b + c +let msum3 = memoizeN sum3 +msum3 1 2 3 |> ignore +msum3 1 2 3 |> ignore +if effs.Count <> 1 then failwith $"depth-3 memoization ran the function {effs.Count} times, expected 1" + +let effs2 = ResizeArray () +let sum2 (a:int) (b:int) = effs2.Add "sum2"; a + b +let msum2 = memoizeN sum2 +msum2 1 1 |> ignore +msum2 1 1 |> ignore +if effs2.Count <> 1 then failwith $"depth-2 memoization ran the function {effs2.Count} times, expected 1" +""" + |> asExe + |> compileAndRun + |> shouldSucceed + + // Guards the `not csenv.MatchingOnly` gate of the SolveFunTypeEqn SRTP fix (mirrors the same + // guard in SolveTypeEqualsType): an SRTP-constrained argument must not disturb overload + // candidate selection, else the lambda's type is left uninferred (FS0072). + [] + let ``SRTP argument does not disturb overload resolution during MatchingOnly`` () = + FSharp """ +module Test +let inline dbl x = x + x +type K = + static member M(g: int -> int, f: string -> int) = f "a" + static member M(g: System.DateTime -> System.DateTime, f: System.DateTime -> int) = 0 + +let r = K.M(dbl, fun v -> v.Length) +if r <> 1 then failwith $"Expected 1 but got {r}" """ |> asExe |> compileAndRun diff --git a/tests/FSharp.Compiler.Service.Tests/Tooltip/TooltipTests.Types.fs b/tests/FSharp.Compiler.Service.Tests/Tooltip/TooltipTests.Types.fs index e875a276073..1a669b7980b 100644 --- a/tests/FSharp.Compiler.Service.Tests/Tooltip/TooltipTests.Types.fs +++ b/tests/FSharp.Compiler.Service.Tests/Tooltip/TooltipTests.Types.fs @@ -45,8 +45,12 @@ y.M() [] let ``QuickInfoForTypesWithHiddenRepresentation`` () = let signatureListing = - "type Async =\n static member AsBeginEnd: computation: ('Arg -> Async<'T>) -> ('Arg * AsyncCallback * objnull -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit)\n static member AwaitEvent: event: IEvent<'Del,'T> * ?cancelAction: (unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate and 'Del: not null)\n static member AwaitIAsyncResult: iar: IAsyncResult * ?millisecondsTimeout: int -> Async\n static member AwaitTask: task: Task<'T> -> Async<'T> + 1 overload\n static member AwaitWaitHandle: waitHandle: WaitHandle * ?millisecondsTimeout: int -> Async\n static member CancelDefaultToken: unit -> unit\n static member Catch: computation: Async<'T> -> Async>\n static member Choice: computations: Async<'T option> seq -> Async<'T option>\n static member FromBeginEnd: beginAction: (AsyncCallback * objnull -> IAsyncResult) * endAction: (IAsyncResult -> 'T) * ?cancelAction: (unit -> unit) -> Async<'T> + 3 overloads\n static member FromContinuations: callback: (('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T>\n ..." - + "type Async =\n static member AsBeginEnd: computation: ('Arg -> Async<'T>) -> ('Arg * AsyncCallback * objnull -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit)\n static member Await: task: Task<'T> -> Async<'T> + 3 overloads\n static member AwaitEvent: event: IEvent<'Del,'T> * ?cancelAction: (unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate and 'Del: not null)\n static member AwaitIAsyncResult: iar: IAsyncResult * ?millisecondsTimeout: int -> Async\n static member AwaitTask: task: Task<'T> -> Async<'T> + 1 overload\n static member AwaitWaitHandle: waitHandle: WaitHandle * ?millisecondsTimeout: int -> Async\n static member CancelDefaultToken: unit -> unit\n static member Catch: computation: Async<'T> -> Async>\n static member Choice: computations: Async<'T option> seq -> Async<'T option>\n static member FromBeginEnd: beginAction: (AsyncCallback * objnull -> IAsyncResult) * endAction: (IAsyncResult -> 'T) * ?cancelAction: (unit -> unit) -> Async<'T> + 3 overloads\n ..." +#if !NETCOREAPP + // on netstandard2.0, Await doesn't have ValueTask overloads + |> _.Replace("static member Await: task: Task<'T> -> Async<'T> + 3 overloads", + "static member Await: task: Task<'T> -> Async<'T> + 1 overload") +#endif assertTooltipContainsInOrder [ signatureListing; "Full name: Microsoft.FSharp.Control.Async" ] (markAtEndOfMarker "let x = Async.AsBeginEnd\n1" "Asyn") diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.debug.bsl b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.debug.bsl index 175102ee368..1f6241a57e3 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.debug.bsl +++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.debug.bsl @@ -631,6 +631,8 @@ Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.AsyncReturn I Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.AsyncReturn TryFinally[T](Microsoft.FSharp.Control.AsyncActivation`1[T], Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]) Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.AsyncReturn TryWith[T](Microsoft.FSharp.Control.AsyncActivation`1[T], Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpFunc`2[System.Exception,Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Control.FSharpAsync`1[T]]]) Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.FSharpAsync`1[T] MakeAsync[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Control.AsyncActivation`1[T],Microsoft.FSharp.Control.AsyncReturn]) +Microsoft.FSharp.Control.AsyncTaskLikeExtensions: Microsoft.FSharp.Control.FSharpAsync`1[T] Async.Await.Static$W[TTaskLike,TAwaiter,T](Microsoft.FSharp.Core.FSharpFunc`2[TTaskLike,TAwaiter], Microsoft.FSharp.Core.FSharpFunc`2[TAwaiter,T], Microsoft.FSharp.Core.FSharpFunc`2[TAwaiter,System.Boolean], TTaskLike) +Microsoft.FSharp.Control.AsyncTaskLikeExtensions: Microsoft.FSharp.Control.FSharpAsync`1[T] Async.Await.Static[TTaskLike,TAwaiter,T](TTaskLike) Microsoft.FSharp.Control.BackgroundTaskBuilder: System.Threading.Tasks.Task`1[T] RunDynamic[T](Microsoft.FSharp.Core.CompilerServices.ResumableCode`2[Microsoft.FSharp.Control.TaskStateMachineData`1[T],T]) Microsoft.FSharp.Control.BackgroundTaskBuilder: System.Threading.Tasks.Task`1[T] Run[T](Microsoft.FSharp.Core.CompilerServices.ResumableCode`2[Microsoft.FSharp.Control.TaskStateMachineData`1[T],T]) Microsoft.FSharp.Control.CommonExtensions: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] AsyncWrite(System.IO.Stream, Byte[], Microsoft.FSharp.Core.FSharpOption`1[System.Int32], Microsoft.FSharp.Core.FSharpOption`1[System.Int32]) @@ -650,6 +652,7 @@ Microsoft.FSharp.Control.EventModule: Void Add[T,TDel](Microsoft.FSharp.Core.FSh Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Control.FSharpAsync`1[T]] StartChild[T](Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpOption`1[System.Int32]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpChoice`2[T,System.Exception]] Catch[T](Microsoft.FSharp.Control.FSharpAsync`1[T]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpOption`1[T]] Choice[T](System.Collections.Generic.IEnumerable`1[Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpOption`1[T]]]) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] Await(System.Threading.Tasks.Task) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] AwaitTask(System.Threading.Tasks.Task) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] Ignore[T](Microsoft.FSharp.Control.FSharpAsync`1[T]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] Sleep(Int32) @@ -667,6 +670,7 @@ Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T[] Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T[]] Parallel[T](System.Collections.Generic.IEnumerable`1[Microsoft.FSharp.Control.FSharpAsync`1[T]], Microsoft.FSharp.Core.FSharpOption`1[System.Int32]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T[]] Sequential[T](System.Collections.Generic.IEnumerable`1[Microsoft.FSharp.Control.FSharpAsync`1[T]]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] AwaitEvent[TDel,T](Microsoft.FSharp.Control.IEvent`2[TDel,T], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]]) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] Await[T](System.Threading.Tasks.Task`1[T]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] AwaitTask[T](System.Threading.Tasks.Task`1[T]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] FromBeginEnd[TArg1,TArg2,TArg3,T](TArg1, TArg2, TArg3, Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`5[TArg1,TArg2,TArg3,System.AsyncCallback,System.Object],System.IAsyncResult], Microsoft.FSharp.Core.FSharpFunc`2[System.IAsyncResult,T], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] FromBeginEnd[TArg1,TArg2,T](TArg1, TArg2, Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`4[TArg1,TArg2,System.AsyncCallback,System.Object],System.IAsyncResult], Microsoft.FSharp.Core.FSharpFunc`2[System.IAsyncResult,T], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]]) diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.release.bsl b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.release.bsl index cdf68c2d1ef..0b025a942de 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.release.bsl +++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard20.release.bsl @@ -631,6 +631,8 @@ Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.AsyncReturn I Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.AsyncReturn TryFinally[T](Microsoft.FSharp.Control.AsyncActivation`1[T], Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]) Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.AsyncReturn TryWith[T](Microsoft.FSharp.Control.AsyncActivation`1[T], Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpFunc`2[System.Exception,Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Control.FSharpAsync`1[T]]]) Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.FSharpAsync`1[T] MakeAsync[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Control.AsyncActivation`1[T],Microsoft.FSharp.Control.AsyncReturn]) +Microsoft.FSharp.Control.AsyncTaskLikeExtensions: Microsoft.FSharp.Control.FSharpAsync`1[T] Async.Await.Static$W[TTaskLike,TAwaiter,T](Microsoft.FSharp.Core.FSharpFunc`2[TTaskLike,TAwaiter], Microsoft.FSharp.Core.FSharpFunc`2[TAwaiter,T], Microsoft.FSharp.Core.FSharpFunc`2[TAwaiter,System.Boolean], TTaskLike) +Microsoft.FSharp.Control.AsyncTaskLikeExtensions: Microsoft.FSharp.Control.FSharpAsync`1[T] Async.Await.Static[TTaskLike,TAwaiter,T](TTaskLike) Microsoft.FSharp.Control.BackgroundTaskBuilder: System.Threading.Tasks.Task`1[T] RunDynamic[T](Microsoft.FSharp.Core.CompilerServices.ResumableCode`2[Microsoft.FSharp.Control.TaskStateMachineData`1[T],T]) Microsoft.FSharp.Control.BackgroundTaskBuilder: System.Threading.Tasks.Task`1[T] Run[T](Microsoft.FSharp.Core.CompilerServices.ResumableCode`2[Microsoft.FSharp.Control.TaskStateMachineData`1[T],T]) Microsoft.FSharp.Control.CommonExtensions: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] AsyncWrite(System.IO.Stream, Byte[], Microsoft.FSharp.Core.FSharpOption`1[System.Int32], Microsoft.FSharp.Core.FSharpOption`1[System.Int32]) @@ -650,6 +652,7 @@ Microsoft.FSharp.Control.EventModule: Void Add[T,TDel](Microsoft.FSharp.Core.FSh Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Control.FSharpAsync`1[T]] StartChild[T](Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpOption`1[System.Int32]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpChoice`2[T,System.Exception]] Catch[T](Microsoft.FSharp.Control.FSharpAsync`1[T]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpOption`1[T]] Choice[T](System.Collections.Generic.IEnumerable`1[Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpOption`1[T]]]) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] Await(System.Threading.Tasks.Task) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] AwaitTask(System.Threading.Tasks.Task) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] Ignore[T](Microsoft.FSharp.Control.FSharpAsync`1[T]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] Sleep(Int32) @@ -667,6 +670,7 @@ Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T[] Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T[]] Parallel[T](System.Collections.Generic.IEnumerable`1[Microsoft.FSharp.Control.FSharpAsync`1[T]], Microsoft.FSharp.Core.FSharpOption`1[System.Int32]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T[]] Sequential[T](System.Collections.Generic.IEnumerable`1[Microsoft.FSharp.Control.FSharpAsync`1[T]]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] AwaitEvent[TDel,T](Microsoft.FSharp.Control.IEvent`2[TDel,T], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]]) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] Await[T](System.Threading.Tasks.Task`1[T]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] AwaitTask[T](System.Threading.Tasks.Task`1[T]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] FromBeginEnd[TArg1,TArg2,TArg3,T](TArg1, TArg2, TArg3, Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`5[TArg1,TArg2,TArg3,System.AsyncCallback,System.Object],System.IAsyncResult], Microsoft.FSharp.Core.FSharpFunc`2[System.IAsyncResult,T], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] FromBeginEnd[TArg1,TArg2,T](TArg1, TArg2, Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`4[TArg1,TArg2,System.AsyncCallback,System.Object],System.IAsyncResult], Microsoft.FSharp.Core.FSharpFunc`2[System.IAsyncResult,T], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]]) diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.debug.bsl b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.debug.bsl index cac5fe9d0ef..9496d1dfe2b 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.debug.bsl +++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.debug.bsl @@ -633,6 +633,8 @@ Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.AsyncReturn I Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.AsyncReturn TryFinally[T](Microsoft.FSharp.Control.AsyncActivation`1[T], Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]) Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.AsyncReturn TryWith[T](Microsoft.FSharp.Control.AsyncActivation`1[T], Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpFunc`2[System.Exception,Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Control.FSharpAsync`1[T]]]) Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.FSharpAsync`1[T] MakeAsync[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Control.AsyncActivation`1[T],Microsoft.FSharp.Control.AsyncReturn]) +Microsoft.FSharp.Control.AsyncTaskLikeExtensions: Microsoft.FSharp.Control.FSharpAsync`1[T] Async.Await.Static$W[TTaskLike,TAwaiter,T](Microsoft.FSharp.Core.FSharpFunc`2[TTaskLike,TAwaiter], Microsoft.FSharp.Core.FSharpFunc`2[TAwaiter,T], Microsoft.FSharp.Core.FSharpFunc`2[TAwaiter,System.Boolean], TTaskLike) +Microsoft.FSharp.Control.AsyncTaskLikeExtensions: Microsoft.FSharp.Control.FSharpAsync`1[T] Async.Await.Static[TTaskLike,TAwaiter,T](TTaskLike) Microsoft.FSharp.Control.BackgroundTaskBuilder: System.Threading.Tasks.Task`1[T] RunDynamic[T](Microsoft.FSharp.Core.CompilerServices.ResumableCode`2[Microsoft.FSharp.Control.TaskStateMachineData`1[T],T]) Microsoft.FSharp.Control.BackgroundTaskBuilder: System.Threading.Tasks.Task`1[T] Run[T](Microsoft.FSharp.Core.CompilerServices.ResumableCode`2[Microsoft.FSharp.Control.TaskStateMachineData`1[T],T]) Microsoft.FSharp.Control.CommonExtensions: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] AsyncWrite(System.IO.Stream, Byte[], Microsoft.FSharp.Core.FSharpOption`1[System.Int32], Microsoft.FSharp.Core.FSharpOption`1[System.Int32]) @@ -652,6 +654,8 @@ Microsoft.FSharp.Control.EventModule: Void Add[T,TDel](Microsoft.FSharp.Core.FSh Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Control.FSharpAsync`1[T]] StartChild[T](Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpOption`1[System.Int32]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpChoice`2[T,System.Exception]] Catch[T](Microsoft.FSharp.Control.FSharpAsync`1[T]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpOption`1[T]] Choice[T](System.Collections.Generic.IEnumerable`1[Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpOption`1[T]]]) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] Await(System.Threading.Tasks.Task) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] Await(System.Threading.Tasks.ValueTask) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] AwaitTask(System.Threading.Tasks.Task) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] Ignore[T](Microsoft.FSharp.Control.FSharpAsync`1[T]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] Sleep(Int32) @@ -669,6 +673,8 @@ Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T[] Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T[]] Parallel[T](System.Collections.Generic.IEnumerable`1[Microsoft.FSharp.Control.FSharpAsync`1[T]], Microsoft.FSharp.Core.FSharpOption`1[System.Int32]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T[]] Sequential[T](System.Collections.Generic.IEnumerable`1[Microsoft.FSharp.Control.FSharpAsync`1[T]]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] AwaitEvent[TDel,T](Microsoft.FSharp.Control.IEvent`2[TDel,T], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]]) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] Await[T](System.Threading.Tasks.Task`1[T]) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] Await[T](System.Threading.Tasks.ValueTask`1[T]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] AwaitTask[T](System.Threading.Tasks.Task`1[T]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] FromBeginEnd[TArg1,TArg2,TArg3,T](TArg1, TArg2, TArg3, Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`5[TArg1,TArg2,TArg3,System.AsyncCallback,System.Object],System.IAsyncResult], Microsoft.FSharp.Core.FSharpFunc`2[System.IAsyncResult,T], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] FromBeginEnd[TArg1,TArg2,T](TArg1, TArg2, Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`4[TArg1,TArg2,System.AsyncCallback,System.Object],System.IAsyncResult], Microsoft.FSharp.Core.FSharpFunc`2[System.IAsyncResult,T], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]]) diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.release.bsl b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.release.bsl index 537095d9e10..b1a377ea33b 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.release.bsl +++ b/tests/FSharp.Core.UnitTests/FSharp.Core.SurfaceArea.netstandard21.release.bsl @@ -633,6 +633,8 @@ Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.AsyncReturn I Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.AsyncReturn TryFinally[T](Microsoft.FSharp.Control.AsyncActivation`1[T], Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]) Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.AsyncReturn TryWith[T](Microsoft.FSharp.Control.AsyncActivation`1[T], Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpFunc`2[System.Exception,Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Control.FSharpAsync`1[T]]]) Microsoft.FSharp.Control.AsyncPrimitives: Microsoft.FSharp.Control.FSharpAsync`1[T] MakeAsync[T](Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Control.AsyncActivation`1[T],Microsoft.FSharp.Control.AsyncReturn]) +Microsoft.FSharp.Control.AsyncTaskLikeExtensions: Microsoft.FSharp.Control.FSharpAsync`1[T] Async.Await.Static$W[TTaskLike,TAwaiter,T](Microsoft.FSharp.Core.FSharpFunc`2[TTaskLike,TAwaiter], Microsoft.FSharp.Core.FSharpFunc`2[TAwaiter,T], Microsoft.FSharp.Core.FSharpFunc`2[TAwaiter,System.Boolean], TTaskLike) +Microsoft.FSharp.Control.AsyncTaskLikeExtensions: Microsoft.FSharp.Control.FSharpAsync`1[T] Async.Await.Static[TTaskLike,TAwaiter,T](TTaskLike) Microsoft.FSharp.Control.BackgroundTaskBuilder: System.Threading.Tasks.Task`1[T] RunDynamic[T](Microsoft.FSharp.Core.CompilerServices.ResumableCode`2[Microsoft.FSharp.Control.TaskStateMachineData`1[T],T]) Microsoft.FSharp.Control.BackgroundTaskBuilder: System.Threading.Tasks.Task`1[T] Run[T](Microsoft.FSharp.Core.CompilerServices.ResumableCode`2[Microsoft.FSharp.Control.TaskStateMachineData`1[T],T]) Microsoft.FSharp.Control.CommonExtensions: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] AsyncWrite(System.IO.Stream, Byte[], Microsoft.FSharp.Core.FSharpOption`1[System.Int32], Microsoft.FSharp.Core.FSharpOption`1[System.Int32]) @@ -652,6 +654,8 @@ Microsoft.FSharp.Control.EventModule: Void Add[T,TDel](Microsoft.FSharp.Core.FSh Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Control.FSharpAsync`1[T]] StartChild[T](Microsoft.FSharp.Control.FSharpAsync`1[T], Microsoft.FSharp.Core.FSharpOption`1[System.Int32]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpChoice`2[T,System.Exception]] Catch[T](Microsoft.FSharp.Control.FSharpAsync`1[T]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpOption`1[T]] Choice[T](System.Collections.Generic.IEnumerable`1[Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.FSharpOption`1[T]]]) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] Await(System.Threading.Tasks.Task) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] Await(System.Threading.Tasks.ValueTask) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] AwaitTask(System.Threading.Tasks.Task) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] Ignore[T](Microsoft.FSharp.Control.FSharpAsync`1[T]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[Microsoft.FSharp.Core.Unit] Sleep(Int32) @@ -669,6 +673,8 @@ Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T[] Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T[]] Parallel[T](System.Collections.Generic.IEnumerable`1[Microsoft.FSharp.Control.FSharpAsync`1[T]], Microsoft.FSharp.Core.FSharpOption`1[System.Int32]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T[]] Sequential[T](System.Collections.Generic.IEnumerable`1[Microsoft.FSharp.Control.FSharpAsync`1[T]]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] AwaitEvent[TDel,T](Microsoft.FSharp.Control.IEvent`2[TDel,T], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]]) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] Await[T](System.Threading.Tasks.Task`1[T]) +Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] Await[T](System.Threading.Tasks.ValueTask`1[T]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] AwaitTask[T](System.Threading.Tasks.Task`1[T]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] FromBeginEnd[TArg1,TArg2,TArg3,T](TArg1, TArg2, TArg3, Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`5[TArg1,TArg2,TArg3,System.AsyncCallback,System.Object],System.IAsyncResult], Microsoft.FSharp.Core.FSharpFunc`2[System.IAsyncResult,T], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]]) Microsoft.FSharp.Control.FSharpAsync: Microsoft.FSharp.Control.FSharpAsync`1[T] FromBeginEnd[TArg1,TArg2,T](TArg1, TArg2, Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`4[TArg1,TArg2,System.AsyncCallback,System.Object],System.IAsyncResult], Microsoft.FSharp.Core.FSharpFunc`2[System.IAsyncResult,T], Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Core.FSharpFunc`2[Microsoft.FSharp.Core.Unit,Microsoft.FSharp.Core.Unit]]) diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Control/AsyncType.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Control/AsyncType.fs index 571a4250175..2ce61e65968 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Control/AsyncType.fs +++ b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Control/AsyncType.fs @@ -220,14 +220,14 @@ type AsyncType() = | _ -> reraise() Assert.True (tcs.Task.IsCompleted, "Task is not completed") - [] - member _.RunSynchronouslyCancellationWithDelayedResult () = + [] + member _.RunSynchronouslyCancellationWithDelayedResult(newAwait: bool) = let cts = new CancellationTokenSource() let tcs = TaskCompletionSource() let _ = cts.Token.Register(fun () -> tcs.SetResult 42) let a = async { - cts.CancelAfter (100) - let! result = tcs.Task |> Async.AwaitTask + cts.CancelAfter(100) + let! result = tcs.Task |> if newAwait then Async.Await else Async.AwaitTask return result } let cancelled = @@ -367,127 +367,127 @@ type AsyncType() = Assert.True(t.IsCanceled) Assert.True(cancelled) - [] - member _.TaskAsyncValue () = + [] + member _.TaskAsyncValue(newAwait: bool) = let s = "Test" use t = Task.Factory.StartNew(Func<_>(fun () -> s)) let a = async { - let! s1 = Async.AwaitTask(t) - return s = s1 - } - Async.RunSynchronously(a) |> Assert.True + let! s1 = t |> if newAwait then Async.Await else Async.AwaitTask + return s = s1 + } + let ok = Async.RunSynchronously a + Assert.True ok - [] - member _.AwaitTaskCancellation () = - let test() = async { - let tcs = new System.Threading.Tasks.TaskCompletionSource() + [] + member _.AwaitTaskCancellation(newAwait: bool) = + let a = async { + let tcs = System.Threading.Tasks.TaskCompletionSource() tcs.SetCanceled() try - do! Async.AwaitTask tcs.Task + do! tcs.Task |> if newAwait then Async.Await else Async.AwaitTask return false - with :? System.OperationCanceledException -> return true + with :? OperationCanceledException -> return true } - - Async.RunSynchronously(test()) |> Assert.True + let ok = Async.RunSynchronously a + Assert.True ok [] member _.AwaitCompletedTask() = - let test() = async { + let a = async { let threadIdBefore = Thread.CurrentThread.ManagedThreadId do! Async.AwaitTask Task.CompletedTask let threadIdAfter = Thread.CurrentThread.ManagedThreadId return threadIdBefore = threadIdAfter } + let ok = Async.RunSynchronously a + Assert.True ok - Async.RunSynchronously(test()) |> Assert.True - - [] - member _.AwaitTaskCancellationUntyped () = - let test() = async { - let tcs = new System.Threading.Tasks.TaskCompletionSource() + [] + member _.AwaitTaskCancellationUntyped(newAwait: bool) = + let a = async { + let tcs = System.Threading.Tasks.TaskCompletionSource() tcs.SetCanceled() try - do! Async.AwaitTask (tcs.Task :> Task) + do! tcs.Task :> Task |> if newAwait then Async.Await else Async.AwaitTask return false - with :? System.OperationCanceledException -> return true + with :? OperationCanceledException -> return true } + let ok = Async.RunSynchronously a + Assert.True ok - Async.RunSynchronously(test()) |> Assert.True - - [] - member _.TaskAsyncValueException () = + [] + member _.TaskAsyncValueException(newAwait: bool) = use t = Task.Factory.StartNew(Func(fun () -> raise <| Exception())) let a = async { - try - let! v = Async.AwaitTask(t) - return false - with e -> return true - } - Async.RunSynchronously(a) |> Assert.True + try let! v = t |> if newAwait then Async.Await else Async.AwaitTask + return false + with e -> return true + } + let ok = Async.RunSynchronously a + Assert.True ok - [] - member _.TaskAsyncValueCancellation () = + [] + member _.TaskAsyncValueCancellation(newAwait: bool) = use ewh = new ManualResetEvent(false) let cts = new CancellationTokenSource() let token = cts.Token use t : Task = Task.Factory.StartNew(Func(fun () -> while not token.IsCancellationRequested do ()), token) let cancelled = ref true - let a = - async { - try - use! _holder = Async.OnCancel(fun _ -> ewh.Set() |> ignore) - let! v = Async.AwaitTask(t) - return v - // AwaitTask raises TaskCanceledException when it is canceled, it is a valid result of this test - with - :? TaskCanceledException -> - ewh.Set() |> ignore // this is ok - } + let a = async { + try + use! _holder = Async.OnCancel(fun _ -> ewh.Set() |> ignore) + let! v = t |> if newAwait then Async.Await else Async.AwaitTask + return v + // A canceled task yields TaskCanceledException via the exception continuation + with + :? TaskCanceledException -> + ewh.Set() |> ignore // this is ok + } let t1 = Async.StartAsTask a cts.Cancel() ewh.WaitOne(10000) |> ignore // Don't leave unobserved background tasks, because they can crash the test run. t1.Wait() - [] - member _.NonGenericTaskAsyncValue () = + [] + member _.NonGenericTaskAsyncValue(newAwait: bool) = let mutable hasBeenCalled = false use t = Task.Factory.StartNew(Action(fun () -> hasBeenCalled <- true)) let a = async { - do! Async.AwaitTask(t) - return true - } - let result = Async.RunSynchronously(a) - (hasBeenCalled && result) |> Assert.True + do! t |> if newAwait then Async.Await else Async.AwaitTask + return true + } + let ok = Async.RunSynchronously a + Assert.True(hasBeenCalled && ok) - [] - member _.NonGenericTaskAsyncValueException () = + [] + member _.NonGenericTaskAsyncValueException(newAwait: bool) = use t = Task.Factory.StartNew(Action(fun () -> raise <| Exception())) let a = async { - try - let! v = Async.AwaitTask(t) - return false - with e -> return true - } - Async.RunSynchronously(a) |> Assert.True + try + let! v = t |> if newAwait then Async.Await else Async.AwaitTask + return false + with e -> return true + } + let ok = Async.RunSynchronously a + Assert.True ok - [] - member _.NonGenericTaskAsyncValueCancellation () = + [] + member _.NonGenericTaskAsyncValueCancellation(newAwait: bool) = use ewh = new ManualResetEvent(false) let cts = new CancellationTokenSource() let token = cts.Token use t = Task.Factory.StartNew(Action(fun () -> while not token.IsCancellationRequested do ()), token) - let a = - async { - try - use! _holder = Async.OnCancel(fun _ -> ewh.Set() |> ignore) - let! v = Async.AwaitTask(t) - return v - // AwaitTask raises TaskCanceledException when it is canceled, it is a valid result of this test - with - :? TaskCanceledException -> - ewh.Set() |> ignore // this is ok - } + let a = async { + try + use! _holder = Async.OnCancel(fun _ -> ewh.Set() |> ignore) + let! v = t |> if newAwait then Async.Await else Async.AwaitTask + return v + // A canceled task yields TaskCanceledException via the exception continuation + with + :? TaskCanceledException -> + ewh.Set() |> ignore // this is ok + } let t1 = Async.StartAsTask a cts.Cancel() ewh.WaitOne(10000) |> ignore @@ -510,19 +510,383 @@ type AsyncType() = ewh.Wait(10000) |> ignore Assert.False hasThrown - [] - member _.NoStackOverflowOnRecursion() = - + [] + member _.NoStackOverflowOnRecursion(newAwait: bool) = let mutable hasThrown = false let rec loop (x: int) = async { - do! Task.CompletedTask |> Async.AwaitTask + do! Task.CompletedTask |> if newAwait then Async.Await else Async.AwaitTask Console.WriteLine (if x = 10000 then failwith "finish" else x) return! loop(x+1) } - try - Async.RunSynchronously (loop 0) - hasThrown <- false + try Async.RunSynchronously (loop 0) + hasThrown <- false with Failure "finish" -> hasThrown <- true Assert.True hasThrown + + // Both AwaitTask and Await ignore the ambient cancellation token while waiting + // (Same goes for the typed variants) + [] + member _.``Both AwaitTask and Await ignore ambient cancellation while waiting``(newAwait) = + let cts = new CancellationTokenSource() + let tcs = TaskCompletionSource() // task that never completes + let res = TaskCompletionSource() + + let a = async { + try do! tcs.Task |> if newAwait then Async.Await else Async.AwaitTask + res.TrySetResult true |> ignore + with _ -> res.TrySetResult false |> ignore + } + + Async.Start(a, cts.Token) + // NOTE we only cancel during the Await/AwaitTask - the initial check would throw if we canceled before the Start() + cts.CancelAfter 100 + + // AwaitTask should NOT honor the ambient CT trigger + let taskCompleted = res.Task.Wait 500 + Assert.False(taskCompleted, "Await/AwaitTask should not have responded to ambient CT cancellation") + tcs.TrySetResult() |> ignore // clean up + res.Task.Wait() + + (* When an AggregateException has multiple inner exceptions, Await and AwaitTask behave identically *) + + [] + member _.``Await and AwaitTask(Task<'T>) valid AggregateException is surfaced``(newAwait) = + let tcs = TaskCompletionSource() + tcs.SetException [ ArgumentException "a" :> exn; InvalidOperationException "b" :> exn ] + let a = async { + try + let! _ = tcs.Task |> if newAwait then Async.Await else Async.AwaitTask + return false + with :? AggregateException as ae -> return ae.InnerExceptions.Count = 2 + } + let ok = Async.RunSynchronously a + Assert.True ok + + [] + member _.``Await and AwaitTask(Task) valid AggregateException is surfaced``(newAwait) = + let tcs = TaskCompletionSource() + tcs.SetException [| ArgumentException "a" :> exn; InvalidOperationException "b" |] + let a = async { + try + do! tcs.Task |> if newAwait then Async.Await else Async.AwaitTask + return false + with :? AggregateException as ae -> return ae.InnerExceptions.Count = 2 + } + let ok = Async.RunSynchronously a + Assert.True ok + + (* Async.Await behavioral differences + + The following tests demonstrate where Async.Await deliberately differs from Async.AwaitTask *) + + // Async.AwaitTask(Task) surfaces the wrapping AggregateException ... + [] + member _.``AwaitTask(Task) egregious AggregateException is unchanged``() = + let tcs = TaskCompletionSource() + tcs.SetException(ArgumentException "original") + let a = async { + try do! Async.AwaitTask tcs.Task + return false + with :? AggregateException -> return true + } + let ok = Async.RunSynchronously a + Assert.True ok + + // ... whereas Async.Await(Task) surfaces the inner exception directly. + [] + member _.``Await(Task) egregious AggregateException is unwrapped``() = + let tcs = TaskCompletionSource() + tcs.SetException(ArgumentException "original") + let a = async { + try do! Async.Await tcs.Task + return false + with :? ArgumentException as ae -> return ae.Message = "original" + } + let ok = Async.RunSynchronously a + Assert.True ok + + // Async.AwaitTask(Task<'T>) surfaces the wrapping AggregateException ... + [] + member _.``AwaitTask(Task<'T>) egregious AggregateException is unchanged``() = + let tcs = TaskCompletionSource() + tcs.SetException(ArgumentException "original") + let a = async { + try let! _ = Async.AwaitTask tcs.Task + return false + with :? AggregateException -> return true + } + let ok = Async.RunSynchronously a + Assert.True ok + + // ... whereas Async.Await(Task<'T>) surfaces the inner exception directly. + [] + member _.``Await(Task<'T>) egregious AggregateException is unwrapped``() = + let tcs = TaskCompletionSource() + tcs.SetException(ArgumentException "original") + let a = async { + try let! _ = Async.Await tcs.Task + return false + with :? ArgumentException as ae -> return ae.Message = "original" + } + let ok = Async.RunSynchronously a + Assert.True ok + + (* Await(Task/Task<'T>) overloads happy path *) + + [] + member _.``Await(Task<'T>) happy path``() = + let a = async { + let! v = Async.Await(System.Threading.Tasks.Task.FromResult(42)) + return v = 42 + } + let ok = Async.RunSynchronously a + Assert.True ok + + [] + member _.``Await(Task) happy path``() = + let a = async { + do! Async.Await(System.Threading.Tasks.Task.CompletedTask) + return true + } + let ok = Async.RunSynchronously a + Assert.True ok + +#if !NETFRAMEWORK + (* Await(ValueTask and ValueTask<'T>) overloads coverage of mainline behaviors *) + + [] + member _.``Await(ValueTask) happy path``() = + let a = async { + do! Async.Await(ValueTask()) + return true + } + let ok = Async.RunSynchronously a + Assert.True ok + + [] + member _.``Await(ValueTask<'T>) happy path``() = + let a = async { + let! v = Async.Await(ValueTask(42)) + return v = 42 + } + let ok = Async.RunSynchronously a + Assert.True ok + + [] + member _.``Await(ValueTask) exception unwraps``() = + let tcs = TaskCompletionSource() + tcs.SetException(ArgumentException "original") + let task = ValueTask(tcs.Task :> Task) + let a = async { + try do! Async.Await task + return false + with :? ArgumentException as ae -> return ae.Message = "original" + } + let ok = Async.RunSynchronously a + Assert.True ok + + [] + member _.``Await(ValueTask<'T>) exception unwraps``() = + let tcs = TaskCompletionSource() + tcs.SetException(ArgumentException "original") + let a = async { + try let! _ = Async.Await(ValueTask(tcs.Task)) + return false + with :? ArgumentException as ae -> return ae.Message = "original" + } + let ok = Async.RunSynchronously a + Assert.True ok +#endif + +[] +module AsyncTaskLikeAwaitTests = + + // Minimal custom task-like type wrapping Task<'T> + type MyTask<'T>(inner: Task<'T>) = + member _.GetAwaiter() = inner.GetAwaiter() + + // Minimal custom unit-returning task-like + type MyUnitTask(inner: Task) = + member _.GetAwaiter() = inner.GetAwaiter() + + [] + let ``Await(task-like) happy path with result``() = + let result = + async { + let! v = Async.Await(MyTask(Task.FromResult 99)) + return v + } + |> Async.RunSynchronously + Assert.Equal(99, result) + + [] + let ``Await(task-like) happy path unit``() = + async { + do! Async.Await(MyUnitTask(Task.CompletedTask)) + } + |> Async.RunSynchronously + + [] + let ``Await(task-like) deferred completion``() = + let tcs = TaskCompletionSource() + let t = + async { + let! v = Async.Await(MyTask(tcs.Task)) + return v + } + |> Async.StartAsTask + Assert.False(t.IsCompleted, "Should not be done before TCS is set") + tcs.SetResult 7 + t.Wait(TimeSpan.FromSeconds 5.0) |> ignore + Assert.Equal(7, t.Result) + + [] + let ``Await(task-like) deferred completion preserves AsyncLocal ExecutionContext``() = + let asyncLocal = AsyncLocal() + let tcs = TaskCompletionSource() + + let t = + Async.StartImmediateAsTask(async { + asyncLocal.Value <- "trace-id" + do! Async.Await(MyUnitTask(tcs.Task)) + return asyncLocal.Value // should yield trace-id, *unless ExecutionContext did not propagate* + }) + Assert.False(t.IsCompleted, "Should not be done before TCS is set") + + // This should not pollute the continuation observed + asyncLocal.Value <- "root-context" + let completion = + Task.Run(fun () -> + asyncLocal.Value <- "completing-context" // if ExecutionContext is not propagated correctly to the continuation, it will see this + tcs.SetResult()) + + Assert.True(completion.Wait(TimeSpan.FromSeconds 5.), "Completion task hung?") + Assert.True(t.Wait(TimeSpan.FromSeconds 5.), "Awaited subject task hung?") + Assert.Equal("trace-id", t.Result) // Validate the chaining worked correctly + Assert.Equal("root-context", asyncLocal.Value) // Root level context should be preserved + + [] + let ``Await(task-like) exception propagation``() = + let tcs = TaskCompletionSource() + let a = + async { + try let! _ = Async.Await(MyTask(tcs.Task)) + return false + with :? InvalidOperationException as e -> + return e.Message = "boom" + } + tcs.SetException(InvalidOperationException "boom") + let ok = Async.RunSynchronously a + Assert.True ok + + [] + let ``Await(YieldAwaitable) yields and resumes``() = + // Task.Yield() returns a YieldAwaitable which is a struct — exercises the struct-awaiter path. + let mutable before, after = false, false + async { + before <- true + do! Async.Await(Task.Yield()) + after <- true + } + |> Async.RunSynchronously + Assert.True(before && after) + + [] + let ``Await(ConfiguredTaskAwaitable) from ConfigureAwait``() = + // task.ConfigureAwait(false) returns a ConfiguredTaskAwaitable — a common real-world task-like. + let result = + async { + let! v = Async.Await(Task.FromResult(42).ConfigureAwait(false)) + return v + } + |> Async.RunSynchronously + Assert.Equal(42, result) + +[] +module AsyncAwaitStackTraceTests = + + open System.Runtime.CompilerServices + + // Minimal wrapper to route through the SRTP overload instead of the specific Task<'T> overload. + // Task<'T>, Task, ValueTask<'T>, and ValueTask all have higher-priority intrinsic overloads. + type TaskWrapper<'T>(inner: Task<'T>) = + member _.GetAwaiter() = inner.GetAwaiter() + + // Plain function — provides a stable named frame at the outermost throw site. + [] + let throwAtLevel1 () : unit = invalidOp "boom" + + // Level-1 task: thin wrapper around the direct throw. + [] + let level1Task () : Task = task { throwAtLevel1 () } + + // Level-2 task: introduces a real async await boundary between levels 1 and 2. + [] + let level2Task () : Task = task { do! level1Task () } + + // Run via StartImmediateAsTask + .Wait() and return the inner exception. + // Using StartImmediateAsTask (not RunSynchronously) ensures that the async-layer + // exception machinery goes through TaskCompletionSource.SetException, which preserves + // the stack trace rather than rethrowing synchronously and potentially truncating it. + let runAndCaptureException (computation: Async) : exn = + // TODO swap in usage of Async.RunSynchronouslyImmediate + let t = Async.StartImmediateAsTask computation + let ae = Assert.Throws(fun () -> t.Wait()) + ae.InnerException + + // Template assertion: levels 1 and 2 must be traceable in the stack trace + // regardless of which Async.Await overload is used. + let checkTrace totalCount (e: exn) = + let trace = e.StackTrace + // stacktrace should be relatively compact and not bloat the logs, so unconditionally print it to save time analyzing regressions + printfn "EDI trace ====" + printfn "%s" trace + printfn "==== EDI trace" + Assert.NotNull(trace) + Assert.Contains("throwAtLevel1", trace) + Assert.Contains("level1Task", trace) + Assert.Contains("level2Task", trace) +#if !NETFRAMEWORK // downlevel has interstitial layers we are not seeking to characterize at this point + Assert.Equal(totalCount, trace.Split('\n').Length) +#endif + + // --- Tests per overload --- + // The common skeleton is: build a 3-level chain (throwAtLevel1 → level1Task → level2Task), + // wrap the outermost level in an async block using Async.Await, run via + // StartImmediateAsTask + .Wait(), and assert on the resulting exception's stack trace. + + [] + let ``Await Task-of-T: all three levels visible in stack trace`` () = + let e = runAndCaptureException (async { do! Async.Await(level2Task()) }) + checkTrace 3 e + + [] + let ``Await Task (non-generic): all three levels visible in stack trace`` () = + let e = runAndCaptureException (async { do! Async.Await(level2Task() :> Task) }) + checkTrace 3 e + // Same behavior as the Task<'T> overload — see comment there. + +#if !NETFRAMEWORK + [] + let ``Await ValueTask-of-T: all three levels visible in stack trace`` () = + // For a faulted ValueTask, IsCompletedSuccessfully is false; the overload falls + // through to AwaitTask, which takes the same path as the specific Task<'T> overload. + let e = runAndCaptureException (async { do! Async.Await(ValueTask(level2Task())) }) + checkTrace 3 e + + [] + let ``Await ValueTask (non-generic): all three levels visible in stack trace`` () = + // Same as ValueTask<'T>: falls through to AwaitUnitTask for the non-successfully-completed case. + let e = runAndCaptureException (async { do! Async.Await(ValueTask(level2Task() :> Task)) }) + + checkTrace 3 e +#endif + + [] + let ``Await task-like via SRTP overload: all three levels visible in stack trace`` () = + let e = runAndCaptureException (async { do! Async.Await(TaskWrapper(level2Task())) }) + + // 4 instead of 3 as current impl has an outer "at FSharp.Core.UnitTests.Control.AsyncAwaitStackTraceTests.e@836-9.Invoke(Tuple`3 tupledArg) + checkTrace 4 e \ No newline at end of file diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/XmlDocumentationValidation.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/XmlDocumentationValidation.fs index 3010ef7e95f..434ecf28c59 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core/XmlDocumentationValidation.fs +++ b/tests/FSharp.Core.UnitTests/FSharp.Core/XmlDocumentationValidation.fs @@ -4,37 +4,55 @@ module FSharp.Core.UnitTests.XmlDocumentationValidation open System open System.IO -open System.Text.RegularExpressions open System.Xml open Xunit +let isConditionalDirectiveLine (trimmedLine: string) = + trimmedLine.StartsWith("#if") + || trimmedLine.StartsWith("#else") + || trimmedLine.StartsWith("#elif") + || trimmedLine.StartsWith("#endif") + /// Extracts XML documentation blocks from F# signature files let extractXmlDocBlocks (content: string) = - // Regex to match XML documentation comments (/// followed by XML content) - let xmlDocPattern = @"^\s*///\s*(.*)$" - let regex = Regex(xmlDocPattern, RegexOptions.Multiline) - - let lines = content.Split([|'\n'; '\r'|], StringSplitOptions.RemoveEmptyEntries) - let mutable xmlBlocks = [] - let mutable currentBlock = [] - let mutable lineNumber = 0 - - for line in lines do - lineNumber <- lineNumber + 1 - let trimmedLine = line.Trim() - if trimmedLine.StartsWith("///") then - let xmlContent = trimmedLine.Substring(3).Trim() - currentBlock <- (xmlContent, lineNumber) :: currentBlock - else - if not (List.isEmpty currentBlock) then - xmlBlocks <- List.rev currentBlock :: xmlBlocks - currentBlock <- [] - - // Don't forget the last block if file ends with XML comments - if not (List.isEmpty currentBlock) then - xmlBlocks <- List.rev currentBlock :: xmlBlocks - - List.rev xmlBlocks + seq { + let currentBlock = ResizeArray<_>() + let tryFlushCurrentBlock () = + if currentBlock.Count > 0 then + let block = currentBlock |> Seq.toList + currentBlock.Clear() + Some block + else + None + + use reader = new StringReader(content) + let mutable lineNumber = 0 + let mutable line = reader.ReadLine() + + while not (isNull line) do + lineNumber <- lineNumber + 1 + let trimmed = line.Trim() + + if trimmed.StartsWith("///") then + let xmlContent = trimmed.Substring(3).Trim() + if not (String.IsNullOrWhiteSpace xmlContent) then + currentBlock.Add((xmlContent, lineNumber)) + elif isConditionalDirectiveLine trimmed || trimmed.Length = 0 then + // Keep the current XML documentation block open across conditional directives and blank lines + // Handles docs that have internal #if/#else/#endif guards within xmldoc blocks to cover TFM variations. + () + else + match tryFlushCurrentBlock () with + | Some block -> yield block + | None -> () + + line <- reader.ReadLine() + + // Don't forget the last block if file ends with XML comments + match tryFlushCurrentBlock () with + | Some block -> yield block + | None -> () + } /// Validates that XML content is well-formed let validateXmlBlock (xmlLines: (string * int) list) =