Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
a21b6c9
feat(Async+Task+ValueTask): consistent helper modules
bartelink May 28, 2026
d2e4b46
copilot review fixes
bartelink May 28, 2026
bc83170
Fix netstandard2.0 build
bartelink May 28, 2026
40245a2
Merge remote-tracking branch 'upstream/main' into atvt
bartelink Jul 13, 2026
6708b8f
docs: reword release notes
bartelink Jul 13, 2026
bb57111
fix: Correct doc examples
bartelink Jul 13, 2026
60f399e
fix(Task, ValueTask): Add ModuleSuffix
bartelink Jul 13, 2026
cfec168
fix(Task): Complete test suite, correct Cancellation mishandling
bartelink Jul 14, 2026
ac1ad45
fix: Test cancelation on ns2.0
bartelink Jul 14, 2026
c76dc20
Merge remote-tracking branch 'upstream/main' into atvt
bartelink Jul 25, 2026
67f7cc1
fix: Catch mapper/binder exn as Fault
bartelink Jul 25, 2026
85226b5
chore: Clone impl and tests for ValueTask
bartelink Jul 26, 2026
7e9ef3f
chore: fantomas
bartelink Jul 26, 2026
f6c0473
doc: Correct/sync Task+ ValueTask * catch+catchWith
bartelink Jul 26, 2026
bdaf36c
refactor: Align Async test suite with T/VT
bartelink Aug 6, 2026
ca111e7
Report FS3888 for generic attribute type abbreviations instead of FS0…
T-Gro Jul 26, 2026
3672447
Move to .NET 11 (SDK, Arcade, product TargetFramework) (#20080)
T-Gro Jul 28, 2026
810c0f5
Support NotNullIfNotNullAttribute (#19977)
kerams Jul 28, 2026
3af19dd
Move SDL/TSA validation to 1ES templates after Arcade 11 upgrade (#20…
abonie Jul 29, 2026
5cc1883
Compiled ToStrings under -reflectionfree for DUs and Records (#19976)
charlesroddie Jul 30, 2026
2ea2327
Run ilverify via the tool manifest instead of a hard-coded cache path…
abonie Jul 30, 2026
3c2e2ff
Record spreads (#18927)
brianrourkeboll Aug 1, 2026
a28f3a3
Implement interpolated strings via String.Concat (#19971)
charlesroddie Aug 1, 2026
7d67ac8
[main] Source code updates from dotnet/dotnet (#20058)
dotnet-maestro[bot] Aug 3, 2026
6ab04cb
Secure release-note checks for fork pull requests (#20081)
NatElkins Aug 3, 2026
460ea03
Update test project to net11 (#20104)
abonie Aug 3, 2026
e1711e4
Move to Roslyn's unified ExternalAccess library (#20099)
JoeRobich Aug 4, 2026
f086f03
LexFilter: drop non-strict mode (#20106)
auduchinok Aug 4, 2026
15b19ba
Enable Central Package Management with transitive pinning (#20084)
T-Gro Aug 4, 2026
c60bfc3
Implement direct delegates (#19993)
kerams Aug 4, 2026
76f6d76
Fix check_release_notes 403 by restoring pull-requests: write and mak…
T-Gro Aug 4, 2026
7c6a3fc
Adopt ordered multi-caret markers in FCS tests (no behaviour change) …
T-Gro Aug 4, 2026
80846fb
[main] Source code updates from dotnet/dotnet (#20135)
dotnet-maestro[bot] Aug 5, 2026
9dfbda8
[main] Update dependencies from dnceng/internal/dotnet-optimization (…
dotnet-maestro[bot] Aug 5, 2026
b29f3ec
Stop merging main into net11 scouting (#20201)
T-Gro Aug 5, 2026
ca95c00
Record spreads: off-by-default shadowing warnings (#20206)
brianrourkeboll Aug 5, 2026
36626b9
Switch from Newtonsoft to MessagePack restore in fsi tests (#20205)
abonie Aug 5, 2026
f34f441
Remove always-on PrintfBinaryFormat language feature flag (#20202)
T-Gro Aug 5, 2026
3c73249
Switch to FsCheck for fsi restore tests (#20210)
abonie Aug 6, 2026
e732ab2
Stabilize preview language features into F# 11.0 (#20199)
T-Gro Aug 6, 2026
76ffcb7
Implement `<inheritdoc>` XML documentation support for F# (#19188)
Copilot Aug 6, 2026
a414492
Add support for `<include>` XML documentation tag (#19186)
Copilot Aug 6, 2026
f7e7a99
Fix false prompt-injection alarm on PR Tooling Safety Check bypass la…
T-Gro Aug 6, 2026
1115e1d
feat(Async): RunSynchronouslyImmediate (#19804)
bartelink Aug 6, 2026
2a59b0b
Add ECMA-335 EnC metadata delta writer (#20019)
NatElkins Aug 6, 2026
f8d194e
Remove always-on StructActivePattern language feature flag (#20208)
T-Gro Aug 6, 2026
cf04b9b
Remove always-on OpenTypeDeclaration language feature flag (#20209)
T-Gro Aug 6, 2026
0c6debf
tidy: Use RunSynchronouslyImmediate
bartelink Aug 6, 2026
1b966b3
doc: Reorder release notes to reflect merge order
bartelink Aug 6, 2026
5f59e11
Merge remote-tracking branch 'upstream/main' into atvt
bartelink Aug 6, 2026
d4a75db
fix: correct failing test
bartelink Aug 6, 2026
ea376f4
chore: update FSharp.Core baselines
bartelink Aug 6, 2026
2575889
chore: update netstandard2.0 baselines
bartelink Aug 9, 2026
9ede598
Merge remote-tracking branch 'upstream/main' into atvt
bartelink Aug 9, 2026
a719828
doc: Sync doc to align with Task equivalents
bartelink Aug 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/release-notes/.FSharp.Core/11.0.100.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -8,3 +8,5 @@
### Added

* `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))
42 changes: 42 additions & 0 deletions src/FSharp.Core/async.fs
Original file line numberDiff line numberDiff line change
Expand Up@@ -2354,3 +2354,45 @@ module WebExtensions =
start = (fun userToken -> this.DownloadFileAsync(address, fileName, userToken)),
result = (fun _ -> ())
)

[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
Comment thread
bartelink marked this conversation as resolved.
module Async =

[<CompiledName("Result")>]
let inline result (value: 'T) : Async<'T> =
async.Return value

[<CompiledName("Map")>]
let inline map ([<InlineIfLambda>] mapping: 'T -> 'U) (computation: Async<'T>) : Async<'U> =
async.Bind(computation, mapping >> async.Return)

[<CompiledName("Bind")>]
let inline bind ([<InlineIfLambda>] binder: 'T -> Async<'U>) (computation: Async<'T>) : Async<'U> =
async.Bind(computation, binder)

[<CompiledName("Ignore")>]
[<RequiresExplicitTypeArguments>]
let inline ignore<'T> (computation: Async<'T>) : Async<unit> =
Async.Ignore computation

[<CompiledName("CatchWith")>]
let catchWith (handler: exn -> 'T) (computation: Async<'T>) : Async<'T> =
async {
try
return! computation
with e ->
return handler e
}

[<CompiledName("Catch")>]
let catch (computation: Async<'T>) : Async<Result<'T, exn>> =
async {
try
let! v = computation
return Result.Ok v
with e ->
return Result.Error e
}

[<CompiledName("Empty")>]
let empty: Async<unit> = async.Zero()
130 changes: 129 additions & 1 deletion src/FSharp.Core/async.fsi
Original file line numberDiff line numberDiff line change
Expand Up@@ -1006,7 +1006,7 @@ namespace Microsoft.FSharp.Control
/// use file = System.IO.File.OpenRead(filename)
/// printfn "Reading from file %s." filename
/// // Throw away the data being read.
/// do! file.AsyncRead(numBytes) |> Async.Ignore
/// do! file.AsyncRead(numBytes) |> Async.ignore&lt;byte[]&gt;
/// }
/// readFile "example.txt" 42 |> Async.Start
/// </code>
Expand DownExpand Up@@ -1584,3 +1584,131 @@ namespace Microsoft.FSharp.Control
module internal AsyncBuilderImpl =
val async : AsyncBuilder

/// <summary>Contains camelCase module-level functions for <see cref="T:Microsoft.FSharp.Control.FSharpAsync`1"/> computations.</summary>
///
/// <category index="1">Async Programming</category>
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module Async =

/// <summary>Creates an asynchronous computation that returns the given value.</summary>
///
/// <param name="value">The value to return.</param>
///
/// <returns>An asynchronous computation that returns <c>value</c> when executed.</returns>
///
/// <example id="async-result-1">
/// <code lang="fsharp">
/// let computation = Async.result 42
/// computation |> Async.RunSynchronouslyImmediate // evaluates to 42
/// </code>
/// </example>
[<CompiledName("Result")>]
val inline result: value: 'T -> Async<'T>

/// <summary>Creates an asynchronous computation that applies the mapping function to the result of the given computation.</summary>
///
/// <param name="mapping">The function to apply to the result.</param>
/// <param name="computation">The input computation.</param>
///
/// <returns>An asynchronous computation that applies <c>mapping</c> to the result of <c>computation</c>.</returns>
///
/// <example id="async-map-1">
/// <code lang="fsharp">
/// let computation = Async.result 21 |> Async.map (fun x -> x * 2)
/// computation |> Async.RunSynchronouslyImmediate // evaluates to 42
/// </code>
/// </example>
[<CompiledName("Map")>]
val inline map: mapping: ('T -> 'U) -> computation: Async<'T> -> Async<'U>

/// <summary>Creates an asynchronous computation that passes the result of the given computation to the binder function.</summary>
///
/// <param name="binder">A function that takes the result of the computation and returns a new asynchronous computation.</param>
/// <param name="computation">The input computation.</param>
///
/// <returns>An asynchronous computation that performs a monadic bind on the result of <c>computation</c>.</returns>
///
/// <example id="async-bind-1">
/// <code lang="fsharp">
/// let computation = Async.result 21 |> Async.bind (fun x -> Async.result (x * 2))
/// computation |> Async.RunSynchronouslyImmediate // evaluates to 42
/// </code>
/// </example>
[<CompiledName("Bind")>]
val inline bind: binder: ('T -> Async<'U>) -> computation: Async<'T> -> Async<'U>

/// <summary>Creates an asynchronous computation that runs the given computation and ignores its result.</summary>
///
/// <param name="computation">The input computation.</param>
///
/// <returns>A computation that is equivalent to the input computation, but disregards the result.</returns>
///
/// <example id="async-ignore-1">
/// <code lang="fsharp">
/// let readFile filename numBytes: Async&lt;unit&gt; =
/// async {
/// use file = System.IO.File.OpenRead(filename)
/// do! file.AsyncRead(numBytes) |> Async.ignore&lt;byte[]&gt;
/// }
/// </code>
/// </example>
/// <example id="async-ignore-2">
/// <code lang="fsharp">
/// let computation : Async&lt;unit&gt; = Async.result 42 |> Async.ignore&lt;int&gt;
/// computation |> Async.RunSynchronously // evaluates to ()
/// </code>
/// </example>
[<CompiledName("Ignore")>]
[<RequiresExplicitTypeArguments>]
val inline ignore<'T> : computation: Async<'T> -> Async<unit>

/// <summary>Creates an asynchronous computation that yields the original result on success, or the result of
/// <c>handler exn</c> for non-cancellation exceptions.</summary>
/// <remarks><c>OperationCanceledException</c> and derived types such as <c>TaskCanceledException</c> propagate unchanged,
/// and therefore are never passed to <c>handler</c>.
/// </remarks>
/// <param name="handler">A function to handle (non-cancellation) exceptions, yielding a recovery value based on the exception.
/// Any exception thrown by <c>handler</c> will propagate.</param>
/// <param name="computation">The input computation.</param>
/// <returns>An asynchronous computation that yields the result of <c>computation</c> on success,
/// or <c>handler exn</c> on failure.
/// Propagates the underlying cancellation exception where cancellation occurs.</returns>
/// <example id="async-catchwith-1">
/// <code lang="fsharp">
/// let safeDiv x y =
/// async { return x / y }
/// |> Async.catchWith (fun _ -> 0)
/// safeDiv 10 0 |> Async.RunSynchronouslyImmediate // evaluates to 0
/// </code>
/// </example>
[<CompiledName("CatchWith")>]
val catchWith: handler: (exn -> 'T) -> computation: Async<'T> -> Async<'T>

/// <summary>Creates an asynchronous computation that reifies the outcome of the given <c>computation</c> as a <c>Result</c>:
/// <c>Ok</c> on success, <c>Error</c> on failure, so exceptions become values. Cancellation still propagates.</summary>
/// <remarks><c>OperationCanceledException</c> and derived types such as <c>TaskCanceledException</c> propagate unchanged.</remarks>
/// <param name="computation">The input computation.</param>
/// <returns>An asynchronous computation that yields a <c>Result</c>: <c>Ok</c> with the outcome on success,
/// or <c>Error</c> with the exception on failure.
/// Propagates the underlying cancellation exception when cancellation occurs.</returns>
/// <example id="async-catch-1">
/// <code lang="fsharp">
/// let safeDiv x y =
/// async { return x / y } |> Async.catch
/// safeDiv 10 2 |> Async.RunSynchronouslyImmediate // evaluates to Ok 5
/// safeDiv 10 0 |> Async.RunSynchronouslyImmediate // evaluates to Error (DivideByZeroException ...)
/// </code>
/// </example>
[<CompiledName("Catch")>]
val catch: computation: Async<'T> -> Async<Result<'T, exn>>

/// <summary>An asynchronous computation that returns <c>unit</c>. This is equivalent to <c>async.Zero()</c>.</summary>
///
/// <example id="async-empty-1">
/// <code lang="fsharp">
/// Async.empty |> Async.RunSynchronouslyImmediate // evaluates to ()
/// </code>
/// </example>
[<CompiledName("Empty")>]
val empty: Async<unit>

151 changes: 151 additions & 0 deletions src/FSharp.Core/tasks.fs
Original file line numberDiff line numberDiff line change
Expand Up@@ -716,3 +716,154 @@ module LowPlusPriority =
this.Bind(computation, fun (result2: ^TResult2) -> this.Return struct (result1, result2))
)
)

namespace Microsoft.FSharp.Control

open System.Threading.Tasks
open Microsoft.FSharp.Core
open TaskBuilder
open Microsoft.FSharp.Control.TaskBuilderExtensions
open Microsoft.FSharp.Control.TaskBuilderExtensions.LowPriority
open Microsoft.FSharp.Control.TaskBuilderExtensions.HighPriority

[<RequireQualifiedAccess>]
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module Task =

[<CompiledName("Result")>]
let inline result (value: 'T) : Task<'T> =
Task.FromResult value

[<CompiledName("Empty")>]
let empty: Task<unit> = result ()

[<CompiledName("Bind")>]
let inline bind ([<InlineIfLambda>] binder: 'T -> Task<'U>) (task: Task<'T>) : Task<'U> =
if task.Status = TaskStatus.RanToCompletion then
try
binder task.Result
with e ->
Task.FromException<'U>(e)
else
TaskBuilder.task {
let! v = task
return! binder v
}

[<CompiledName("Map")>]
let inline map ([<InlineIfLambda>] mapping: 'T -> 'U) (task: Task<'T>) : Task<'U> =
if task.Status = TaskStatus.RanToCompletion then
try
mapping task.Result |> result
with e ->
Task.FromException<'U>(e)
else
TaskBuilder.task {
let! v = task
return mapping v
}

[<CompiledName("Ignore")>]
[<RequiresExplicitTypeArguments>]
let inline ignore<'T> (task: Task<'T>) : Task<unit> =
Comment thread
bartelink marked this conversation as resolved.
if task.Status = TaskStatus.RanToCompletion then
empty
else
map ignore task

[<CompiledName("CatchWith")>]
let inline catchWith ([<InlineIfLambda>] handler: exn -> 'T) (task: Task<'T>) : Task<'T> =
Comment thread
bartelink marked this conversation as resolved.
if task.Status = TaskStatus.RanToCompletion then
task
else
TaskBuilder.task {
try
return! task
with
| :? System.OperationCanceledException as e -> return! raise e
| e -> return handler e
}

[<CompiledName("Catch")>]
Comment thread
bartelink marked this conversation as resolved.
let catch (task: Task<'T>) : Task<Result<'T, exn>> =
Comment thread
bartelink marked this conversation as resolved.
task |> map Ok |> catchWith Error

#if NETSTANDARD2_1
[<CompiledName("OfValueTask")>]
let inline ofValueTask (valueTask: ValueTask<'T>) : Task<'T> =
valueTask.AsTask()
#endif

#if NETSTANDARD2_1
[<RequireQualifiedAccess>]
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module ValueTask =

[<CompiledName("Result")>]
let inline result (value: 'T) : ValueTask<'T> =
ValueTask<'T>(value)

[<CompiledName("Empty")>]
let empty: ValueTask<unit> = result ()

[<CompiledName("OfTask")>]
let inline ofTask (task: Task<'T>) : ValueTask<'T> =
ValueTask<'T>(task)

[<CompiledName("Bind")>]
let inline bind ([<InlineIfLambda>] binder: 'T -> ValueTask<'U>) (task: ValueTask<'T>) : ValueTask<'U> =
if task.IsCompletedSuccessfully then
try
binder task.Result
with e ->
Task.FromException<'U>(e) |> ofTask
else
let t: Task<'U> =
TaskBuilder.task {
let! v = task
return! binder v
}

ValueTask<'U>(t)

[<CompiledName("Map")>]
let inline map ([<InlineIfLambda>] mapping: 'T -> 'U) (task: ValueTask<'T>) : ValueTask<'U> =
if task.IsCompletedSuccessfully then
try
mapping task.Result |> result
with e ->
Task.FromException<'U>(e) |> ofTask
else
let t: Task<'U> =
TaskBuilder.task {
let! v = task
return mapping v
}

ValueTask<'U>(t)

[<CompiledName("Ignore")>]
[<RequiresExplicitTypeArguments>]
let inline ignore<'T> (task: ValueTask<'T>) : ValueTask<unit> =
Comment thread
bartelink marked this conversation as resolved.
map ignore task

[<CompiledName("CatchWith")>]
let inline catchWith ([<InlineIfLambda>] handler: exn -> 'T) (task: ValueTask<'T>) : ValueTask<'T> =
if task.IsCompletedSuccessfully then
task
else
let t: Task<'T> =
TaskBuilder.task {
try
return! task
with
| :? System.OperationCanceledException as e -> return! raise e
| e -> return handler e
}

ValueTask<'T>(t)

[<CompiledName("Catch")>]
let catch (task: ValueTask<'T>) : ValueTask<Result<'T, exn>> =
task |> map Ok |> catchWith Error
#endif
Loading
Loading