From 7c7692a2a18388adff9ff7702da3e7e11d865bfc Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Sat, 26 Sep 2026 12:38:40 -0700 Subject: [PATCH 1/8] [release-notes] Draft fsharp notes for .NET 11 RC 2 Add component release guidance and any maintained feature-validation fixtures for owner review. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 637615df-deba-4407-bf00-9a3bd4e569b5 --- release-notes/11.0/preview/rc2/fsharp.md | 48 ++++++++++++++++++ .../11.0/samples/fsharp/FSharp.fsproj | 11 ++++ release-notes/11.0/samples/fsharp/Polyfill.fs | 7 +++ release-notes/11.0/samples/fsharp/Program.fs | 50 +++++++++++++++++++ release-notes/11.0/samples/fsharp/README.md | 3 ++ .../11.0/samples/fsharp/Validate.ps1 | 11 ++++ .../11.0/samples/fsharp/invalid/Invalid.fs | 7 +++ .../samples/fsharp/invalid/Invalid.fsproj | 11 ++++ 8 files changed, 148 insertions(+) create mode 100644 release-notes/11.0/preview/rc2/fsharp.md create mode 100644 release-notes/11.0/samples/fsharp/FSharp.fsproj create mode 100644 release-notes/11.0/samples/fsharp/Polyfill.fs create mode 100644 release-notes/11.0/samples/fsharp/Program.fs create mode 100644 release-notes/11.0/samples/fsharp/README.md create mode 100644 release-notes/11.0/samples/fsharp/Validate.ps1 create mode 100644 release-notes/11.0/samples/fsharp/invalid/Invalid.fs create mode 100644 release-notes/11.0/samples/fsharp/invalid/Invalid.fsproj diff --git a/release-notes/11.0/preview/rc2/fsharp.md b/release-notes/11.0/preview/rc2/fsharp.md new file mode 100644 index 0000000000..4504dc2edc --- /dev/null +++ b/release-notes/11.0/preview/rc2/fsharp.md @@ -0,0 +1,48 @@ +# F# in .NET 11 RC 2 - Release Notes + +- [Bound concurrent asynchronous work](#bound-concurrent-asynchronous-work) +- [Require named arguments for selected APIs](#require-named-arguments-for-selected-apis) +- [Reraise from a computation expression handler](#reraise-from-a-computation-expression-handler) + +## Bound concurrent asynchronous work + +`Async.parallelLimit` runs a sequence of async computations with a specified maximum number in flight and returns their results in input order. This helps control how much work a batch starts at once without writing a concurrency limiter yourself. Related helpers include `Async.parallelDoLimit`, `Task.parallelLimit`, and sequential variants ([dotnet/fsharp #20294](https://github.com/dotnet/fsharp/pull/20294)). + +```fsharp +let results = + [ for i in 1..5 -> async { return i * i } ] + |> Async.parallelLimit 2 + |> Async.RunSynchronously +// [| 1; 4; 9; 16; 25 |] +``` + +## Require named arguments for selected APIs + +With `preview`, F# recognizes `System.Diagnostics.CodeAnalysis.RequireNamedArgumentsAttribute` on a method or constructor and reports `FS3923` when a caller passes the required arguments positionally. This lets API authors make call sites more explicit ([dotnet/fsharp #20340](https://github.com/dotnet/fsharp/pull/20340)). + +The RC 2 BCL does not yet include the attribute. Define the attribute in your project or use a library that provides it before marking a member: + +```fsharp +open System.Diagnostics.CodeAnalysis + +type Calculator() = + [] + member _.Double(value: int) = value * 2 + +let answer = Calculator().Double(value = 21) // 42 +``` + +The [maintained fixture](../../samples/fsharp/README.md) includes the attribute definition and verifies the named call against the RC 2 SDK. + +## Reraise from a computation expression handler + +With the preview language version, `reraise ()` works inside the `with` handler of an `async`, `task`, or custom computation expression. The handler propagates the original exception with its stack trace, avoiding the loss of origin information that can result from `raise e` ([dotnet/fsharp #20405](https://github.com/dotnet/fsharp/pull/20405)). + +```fsharp +let operation = async { + try + failwith "operation failed" + with _ -> + reraise () +} +``` diff --git a/release-notes/11.0/samples/fsharp/FSharp.fsproj b/release-notes/11.0/samples/fsharp/FSharp.fsproj new file mode 100644 index 0000000000..af92b4e4b0 --- /dev/null +++ b/release-notes/11.0/samples/fsharp/FSharp.fsproj @@ -0,0 +1,11 @@ + + + Exe + net11.0 + preview + + + + + + diff --git a/release-notes/11.0/samples/fsharp/Polyfill.fs b/release-notes/11.0/samples/fsharp/Polyfill.fs new file mode 100644 index 0000000000..aebeeddc44 --- /dev/null +++ b/release-notes/11.0/samples/fsharp/Polyfill.fs @@ -0,0 +1,7 @@ +namespace System.Diagnostics.CodeAnalysis + +open System + +[] +type RequireNamedArgumentsAttribute() = + inherit Attribute() diff --git a/release-notes/11.0/samples/fsharp/Program.fs b/release-notes/11.0/samples/fsharp/Program.fs new file mode 100644 index 0000000000..f9ef6d372f --- /dev/null +++ b/release-notes/11.0/samples/fsharp/Program.fs @@ -0,0 +1,50 @@ +open System +open System.Diagnostics.CodeAnalysis +open System.Threading +open System.Runtime.CompilerServices + +let mutable active = 0 +let mutable peak = 0 +let gate = obj() +let computations = + [ for i in 1..5 -> + async { + let running = Interlocked.Increment &active + lock gate (fun () -> peak <- max peak running) + do! Async.Sleep 30 + Interlocked.Decrement &active |> ignore + return i * i + } ] + +let results = computations |> Async.parallelLimit 2 |> Async.RunSynchronously +if results <> [| 1; 4; 9; 16; 25 |] || peak > 2 || peak < 1 then + failwithf "Unexpected bounded-parallel results: %A; peak concurrency: %d" results peak +printfn "Bounded-parallel work passed (peak concurrency: %d)." peak + +type Calculator() = + [] + member _.Double(value: int) = value * 2 + +let actual = Calculator().Double(value = 21) +if actual <> 42 then failwithf "Expected 42, got %d" actual +printfn "Named argument validation passed." + +let original = InvalidOperationException("original exception") + +[] +let throwOriginal () = raise original + +let operation = async { + try + throwOriginal () + with _ -> + reraise () +} + +try + operation |> Async.RunSynchronously + failwith "Expected the original exception." +with ex when obj.ReferenceEquals(ex, original) -> + if not (ex.StackTrace.Contains("throwOriginal")) then + failwith "The original exception stack trace was lost." + printfn "Computation-expression reraise retained the exception and stack trace." diff --git a/release-notes/11.0/samples/fsharp/README.md b/release-notes/11.0/samples/fsharp/README.md new file mode 100644 index 0000000000..8984f38106 --- /dev/null +++ b/release-notes/11.0/samples/fsharp/README.md @@ -0,0 +1,3 @@ +# F# release-note validation + +With SDK `11.0.100-rc.2.26475.137` on `PATH`, run `.\Validate.ps1`. The fixture checks the ordered results and peak concurrency of `Async.parallelLimit 2`, and confirms that `reraise ()` in an async computation-expression handler preserves the original exception and stack trace. It also supplies `RequireNamedArgumentsAttribute` as a polyfill because this build does not ship it in the BCL, runs a marked method using a named argument, and checks that a positional call in the separate `invalid/Invalid.fsproj` reports `FS3923`. diff --git a/release-notes/11.0/samples/fsharp/Validate.ps1 b/release-notes/11.0/samples/fsharp/Validate.ps1 new file mode 100644 index 0000000000..d43ecd519b --- /dev/null +++ b/release-notes/11.0/samples/fsharp/Validate.ps1 @@ -0,0 +1,11 @@ +$ErrorActionPreference = 'Stop' +$dotnet = (Get-Command dotnet -ErrorAction Stop).Source + +& $dotnet run --project (Join-Path $PSScriptRoot 'FSharp.fsproj') +if ($LASTEXITCODE -ne 0) { throw 'The named-argument example did not run.' } + +$diagnostics = & $dotnet build (Join-Path $PSScriptRoot 'invalid\Invalid.fsproj') --nologo 2>&1 | Out-String +if ($LASTEXITCODE -eq 0 -or $diagnostics -notmatch 'error FS3923') { + throw "Expected positional-call error FS3923, got: $diagnostics" +} +Write-Output 'Positional call correctly reported FS3923.' diff --git a/release-notes/11.0/samples/fsharp/invalid/Invalid.fs b/release-notes/11.0/samples/fsharp/invalid/Invalid.fs new file mode 100644 index 0000000000..0c70ff88c9 --- /dev/null +++ b/release-notes/11.0/samples/fsharp/invalid/Invalid.fs @@ -0,0 +1,7 @@ +open System.Diagnostics.CodeAnalysis + +type Calculator() = + [] + member _.Double(value: int) = value * 2 + +Calculator().Double(21) |> ignore diff --git a/release-notes/11.0/samples/fsharp/invalid/Invalid.fsproj b/release-notes/11.0/samples/fsharp/invalid/Invalid.fsproj new file mode 100644 index 0000000000..885fdfcf29 --- /dev/null +++ b/release-notes/11.0/samples/fsharp/invalid/Invalid.fsproj @@ -0,0 +1,11 @@ + + + Exe + net11.0 + preview + + + + + + From b12b2bd1253720b68f4f0a349a75d8358a4742d6 Mon Sep 17 00:00:00 2001 From: Daniel Roth Date: Sat, 26 Sep 2026 14:40:47 -0700 Subject: [PATCH 2/8] [release-notes] Link F# in the RC2 index Add the component link alongside its matching release note. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 637615df-deba-4407-bf00-9a3bd4e569b5 --- release-notes/11.0/preview/rc2/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release-notes/11.0/preview/rc2/README.md b/release-notes/11.0/preview/rc2/README.md index c9d38cad8a..f40b6d2558 100644 --- a/release-notes/11.0/preview/rc2/README.md +++ b/release-notes/11.0/preview/rc2/README.md @@ -11,7 +11,7 @@ Component notes are being reviewed in separate draft pull requests. Links will b ## Languages - C# -- F# +- [F#](./fsharp.md) ## Workloads, Libraries, & More From 6ef8e3c96551767b40f96ba1328febb2a209a9dd Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Mon, 5 Oct 2026 14:11:35 +0200 Subject: [PATCH 3/8] [release-notes] Rewrite F# RC2 coverage and validation Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- release-notes/11.0/preview/rc2/fsharp.md | 189 +++++++++++++++++- .../samples/fsharp/ClosureOptimization.fs | 9 + .../11.0/samples/fsharp/FSharp.fsproj | 9 +- release-notes/11.0/samples/fsharp/Fsi.fsx | 19 ++ release-notes/11.0/samples/fsharp/Main.fs | 12 ++ release-notes/11.0/samples/fsharp/Preview.fs | 63 ++++++ release-notes/11.0/samples/fsharp/Program.fs | 105 +++++----- release-notes/11.0/samples/fsharp/README.md | 35 +++- .../11.0/samples/fsharp/RecordConstructors.fs | 8 + .../11.0/samples/fsharp/RuntimeAsync.fs | 20 ++ release-notes/11.0/samples/fsharp/Srtp.fs | 18 ++ .../11.0/samples/fsharp/Validate.ps1 | 69 ++++++- .../11.0/samples/fsharp/aot/Aot.fsproj | 10 + .../11.0/samples/fsharp/aot/Program.fs | 12 ++ .../11.0/samples/fsharp/invalid/CharEnums.fs | 7 + .../samples/fsharp/invalid/Invalid.fsproj | 11 +- .../11.0/samples/fsharp/invalid/Reraise.fs | 6 + 17 files changed, 538 insertions(+), 64 deletions(-) create mode 100644 release-notes/11.0/samples/fsharp/ClosureOptimization.fs create mode 100644 release-notes/11.0/samples/fsharp/Fsi.fsx create mode 100644 release-notes/11.0/samples/fsharp/Main.fs create mode 100644 release-notes/11.0/samples/fsharp/Preview.fs create mode 100644 release-notes/11.0/samples/fsharp/RecordConstructors.fs create mode 100644 release-notes/11.0/samples/fsharp/RuntimeAsync.fs create mode 100644 release-notes/11.0/samples/fsharp/Srtp.fs create mode 100644 release-notes/11.0/samples/fsharp/aot/Aot.fsproj create mode 100644 release-notes/11.0/samples/fsharp/aot/Program.fs create mode 100644 release-notes/11.0/samples/fsharp/invalid/CharEnums.fs create mode 100644 release-notes/11.0/samples/fsharp/invalid/Reraise.fs diff --git a/release-notes/11.0/preview/rc2/fsharp.md b/release-notes/11.0/preview/rc2/fsharp.md index 4504dc2edc..d43e28ed3e 100644 --- a/release-notes/11.0/preview/rc2/fsharp.md +++ b/release-notes/11.0/preview/rc2/fsharp.md @@ -1,12 +1,67 @@ # F# in .NET 11 RC 2 - Release Notes +.NET 11 RC 2 includes the following F# updates: + +- [Lower allocations in collections and compilation](#lower-allocations-in-collections-and-compilation) - [Bound concurrent asynchronous work](#bound-concurrent-asynchronous-work) - [Require named arguments for selected APIs](#require-named-arguments-for-selected-apis) - [Reraise from a computation expression handler](#reraise-from-a-computation-expression-handler) +- [Trimming and Native AOT improvements](#trimming-and-native-aot-improvements) +- [Preview catch-up: extension members and operators](#preview-catch-up-extension-members-and-operators) +- [Other preview feature status](#other-preview-feature-status) +- [Breaking changes from .NET 10](#breaking-changes-from-net-10) +- [Bug fixes and other improvements](#bug-fixes-and-other-improvements) +- [Community contributors](#community-contributors) + +Features marked **preview** require this setting in your project: + +```xml + + preview + +``` + +For F# Interactive, use `dotnet fsi --langversion:preview`. Other updates use the default F# 11 language version. + +## Lower allocations in collections and compilation + +More `List` and `Array` traversal functions, including `fold`, `exists`, and `tryPick`, can now inline their callback lambdas. +This removes callback closures from common collection operations +([dotnet/fsharp#20422](https://github.com/dotnet/fsharp/pull/20422)). + +```fsharp +let sumWithOffset k xs = + List.fold (fun total value -> total + value + k) 0 xs + +let total = sumWithOffset 1 [ 1; 2; 3 ] // 9 +``` + +The author's capturing-lambda microbenchmarks report 24 bytes per call with FSharp.Core 9.0.100 versus zero with the updated Core. +This applies to the measured `List.fold`, `List.exists`, `Array.fold`, and `Array.fold2` calls, not every collection function. +`OptimizeClosureIfNotInlined` is also enabled by default in F# 11. +It lets library authors adapt non-literal callbacks outside an inlined loop +([dotnet/fsharp#20571](https://github.com/dotnet/fsharp/pull/20571)). + +In the author-reported self-build for [dotnet/fsharp#20422](https://github.com/dotnet/fsharp/pull/20422), compiler closure allocations fell from 231,625,834 to 149,844,308 (35.31%). +That comparison used SDK 10.0.400/FSharp.Core 10.1 versus the PR's compiler/Core 11. +It measures that workload's closure allocations, not a general build-time speedup. +RC 2 also avoids duplicate expression copying during inlining and reduces constraint-solver closures +([dotnet/fsharp#20363](https://github.com/dotnet/fsharp/pull/20363), +[dotnet/fsharp#20367](https://github.com/dotnet/fsharp/pull/20367)). + +FSharp.Compiler.Service shares imported assembly data between projects by default, reducing retained metadata in multi-project tools. +Tool authors can opt out with `shareImportedAssemblies = false` on `FSharpChecker.Create` +([dotnet/fsharp#20296](https://github.com/dotnet/fsharp/pull/20296)). +These compiler-service improvements do not require the preview language version. +Thank you [@auduchinok](https://github.com/auduchinok) for this contribution! ## Bound concurrent asynchronous work -`Async.parallelLimit` runs a sequence of async computations with a specified maximum number in flight and returns their results in input order. This helps control how much work a batch starts at once without writing a concurrency limiter yourself. Related helpers include `Async.parallelDoLimit`, `Task.parallelLimit`, and sequential variants ([dotnet/fsharp #20294](https://github.com/dotnet/fsharp/pull/20294)). +`Async.parallelLimit` runs asynchronous computations with a maximum number in flight and returns results in input order. +Use it to limit concurrent work in a batch without writing a separate concurrency limiter. +Related helpers include `Async.parallelDoLimit`, `Task.parallelLimit`, and sequential variants +([dotnet/fsharp#20294](https://github.com/dotnet/fsharp/pull/20294)). +The task helpers accept functions that start tasks, so the limit applies before the work starts. ```fsharp let results = @@ -16,11 +71,18 @@ let results = // [| 1; 4; 9; 16; 25 |] ``` +Thank you [@bartelink](https://github.com/bartelink) for this contribution! + ## Require named arguments for selected APIs -With `preview`, F# recognizes `System.Diagnostics.CodeAnalysis.RequireNamedArgumentsAttribute` on a method or constructor and reports `FS3923` when a caller passes the required arguments positionally. This lets API authors make call sites more explicit ([dotnet/fsharp #20340](https://github.com/dotnet/fsharp/pull/20340)). +> This feature requires `preview` or `--langversion:preview` in RC 2. -The RC 2 BCL does not yet include the attribute. Define the attribute in your project or use a library that provides it before marking a member: +API authors can apply `System.Diagnostics.CodeAnalysis.RequireNamedArgumentsAttribute` to methods and constructors. +With preview enabled, positional calls report `FS3923`, requiring callers to name the affected arguments +([dotnet/fsharp#20340](https://github.com/dotnet/fsharp/pull/20340)). + +The RC 2 BCL does not include the attribute. +Define it in your project or use a library that provides it before marking a member: ```fsharp open System.Diagnostics.CodeAnalysis @@ -32,11 +94,17 @@ type Calculator() = let answer = Calculator().Double(value = 21) // 42 ``` -The [maintained fixture](../../samples/fsharp/README.md) includes the attribute definition and verifies the named call against the RC 2 SDK. +The [maintained fixture](../../samples/fsharp/README.md) includes the attribute definition. +It verifies that the named call runs and that `Calculator().Double(21)` reports `FS3923` under preview. +The default language version does not enforce this attribute. ## Reraise from a computation expression handler -With the preview language version, `reraise ()` works inside the `with` handler of an `async`, `task`, or custom computation expression. The handler propagates the original exception with its stack trace, avoiding the loss of origin information that can result from `raise e` ([dotnet/fsharp #20405](https://github.com/dotnet/fsharp/pull/20405)). +> This feature requires `preview` or `--langversion:preview` in RC 2. + +`reraise ()` now works inside the `with` handler of an `async`, `task`, or custom computation expression. +It propagates the original exception and preserves its stack trace, unlike rethrowing with `raise e` +([dotnet/fsharp#20405](https://github.com/dotnet/fsharp/pull/20405)). ```fsharp let operation = async { @@ -46,3 +114,114 @@ let operation = async { reraise () } ``` + +## Trimming and Native AOT improvements + +`Array2D.create`, `init`, `rebase`, `map`, `mapi`, and `copy` no longer report `IL3050` when targeting .NET 11. +Non-zero-based `*Based` operations still have Native AOT restrictions, now reported at the call site +([dotnet/fsharp#20338](https://github.com/dotnet/fsharp/pull/20338)). + +```fsharp +let matrix = Array2D.init 2 3 (fun row column -> row * 10 + column) +let value = matrix[1, 2] // 12 +``` + +Compiled F# assemblies now embed their metadata-trimming rules, including when assemblies are linked +([dotnet/fsharp#20527](https://github.com/dotnet/fsharp/pull/20527)). +These changes do not require the preview language version. + +## Preview catch-up: extension members and operators + +> This feature requires `preview` or `--langversion:preview`. It was already available in RC 1. + +Extension members can satisfy statically resolved type parameter (SRTP) constraints, including constraints used by operators. +This feature was omitted from the [RC 1 article](../rc1/fsharp.md), rather than newly implemented in RC 2 +([dotnet/fsharp#19602](https://github.com/dotnet/fsharp/pull/19602)). + +```fsharp +type System.String with + static member (*) (s: string, n: int) = + System.String.Concat(Array.replicate n s) + +let repeated = "ha" * 3 // "hahaha" + +type System.Int32 with + static member (++) (a: int, b: int) = a + b + 1 + +let inline incAdd (x: ^T) (y: ^T) = x ++ y +let result = incAdd 3 4 // 8 +``` + +The first call resolves an extension operator on a concrete type. +The second resolves one through a generic inline function. +The preview requirement applies to extension-based constraint resolution, not to ordinary extension-member calls. +Thank you [@gusty](https://github.com/gusty) for this contribution! + +## Other preview feature status + +> Runtime-async compiler support requires `preview` or `--langversion:preview` in RC 2. + +RC 2 adds compiler support for .NET runtime-async intrinsics for custom computation-expression builder authors +([dotnet/fsharp#20235](https://github.com/dotnet/fsharp/pull/20235)). +This also requires a target/runtime that supports runtime-async. +The stock `task {}` and `async {}` builders do not use this path in RC 2. + +[Record constructors](../rc1/fsharp.md#record-constructors) remain **preview**, unchanged from RC 1. +The other features described in the RC 1 article are not reannounced here. + +## Breaking changes from .NET 10 + +With F# 11, `|||`, `&&&`, and `^^^` on enums with non-integer underlying types, such as `char`, now report `FS0001`. +These expressions previously compiled but threw `NotSupportedException` at runtime. +Use an integer-backed enum for bit flags +([dotnet/fsharp#20322](https://github.com/dotnet/fsharp/pull/20322)). +This check is enabled by default, not restricted to preview. +Thank you [@edgarfgp](https://github.com/edgarfgp) for this contribution! + + + +## Bug fixes and other improvements + +### Compiler + +- [Avoid stack overflow when checking long sequence expressions](https://github.com/dotnet/fsharp/pull/20480) +- [Check active-pattern let bindings like equivalent match expressions](https://github.com/dotnet/fsharp/pull/20383) +- [Resolve generic arguments in constraint-dependency order](https://github.com/dotnet/fsharp/pull/20342) +- [Fix final do! expressions being treated as return! or yield!](https://github.com/dotnet/fsharp/pull/20449) +- [Handle optional indexer setter arguments and caller information](https://github.com/dotnet/fsharp/pull/20570) +- [Avoid false nullness warnings for recursive union types](https://github.com/dotnet/fsharp/pull/20562) +- [Resolve recursive inline members in dependency order](https://github.com/dotnet/fsharp/pull/20111) + +### FSharp.Core + +- [Reuse empty arrays in collection operations and conversions](https://github.com/dotnet/fsharp/pull/20388) +- [Return correctly shaped empty slices for extreme reversed bounds](https://github.com/dotnet/fsharp/pull/20557) + +### Tooling and F# Interactive + +- [Avoid repeated background type checking during concurrent requests](https://github.com/dotnet/fsharp/pull/20481) +- [Preserve tuple-pattern parentheses in generated signatures](https://github.com/dotnet/fsharp/pull/20589) +- [Support IAsyncDisposable use bindings in F# Interactive task expressions](https://github.com/dotnet/fsharp/pull/20555) +- [Avoid unnecessary PDB generation with --multiemit+ --debug-](https://github.com/dotnet/fsharp/pull/20394) + +## Community contributors + +Thank you contributors! + +- [@auduchinok](https://github.com/dotnet/fsharp/pulls?q=is%3Apr+is%3Amerged+author%3Aauduchinok) +- [@bartelink](https://github.com/dotnet/fsharp/pulls?q=is%3Apr+is%3Amerged+author%3Abartelink) +- [@edgarfgp](https://github.com/dotnet/fsharp/pulls?q=is%3Apr+is%3Amerged+author%3Aedgarfgp) +- [@gusty](https://github.com/dotnet/fsharp/pulls?q=is%3Apr+is%3Amerged+author%3Agusty) +- [@Happypig375](https://github.com/dotnet/fsharp/pulls?q=is%3Apr+is%3Amerged+author%3AHappypig375) +- [@majocha](https://github.com/dotnet/fsharp/pulls?q=is%3Apr+is%3Amerged+author%3Amajocha) +- [@nojaf](https://github.com/dotnet/fsharp/pulls?q=is%3Apr+is%3Amerged+author%3Anojaf) +- [@xperiandri](https://github.com/dotnet/fsharp/pulls?q=is%3Apr+is%3Amerged+author%3Axperiandri) + +F# updates: + +- [F# release notes](https://fsharp.github.io/fsharp-compiler-docs/release-notes/About.html) +- [dotnet/fsharp repository](https://github.com/dotnet/fsharp) diff --git a/release-notes/11.0/samples/fsharp/ClosureOptimization.fs b/release-notes/11.0/samples/fsharp/ClosureOptimization.fs new file mode 100644 index 0000000000..ec586cc916 --- /dev/null +++ b/release-notes/11.0/samples/fsharp/ClosureOptimization.fs @@ -0,0 +1,9 @@ +module ClosureOptimizationSamples + +let inline fold2 + ([] folder: 'State -> 'T1 -> 'T2 -> 'State) + (state: 'State) (a: 'T1[]) (b: 'T2[]) = + let mutable s = state + for i in 0 .. a.Length - 1 do + s <- folder s a[i] b[i] + s diff --git a/release-notes/11.0/samples/fsharp/FSharp.fsproj b/release-notes/11.0/samples/fsharp/FSharp.fsproj index af92b4e4b0..64d757f22d 100644 --- a/release-notes/11.0/samples/fsharp/FSharp.fsproj +++ b/release-notes/11.0/samples/fsharp/FSharp.fsproj @@ -2,10 +2,17 @@ Exe net11.0 - preview + default + $(DefineConstants);PREVIEW_SAMPLES + + + + + + diff --git a/release-notes/11.0/samples/fsharp/Fsi.fsx b/release-notes/11.0/samples/fsharp/Fsi.fsx new file mode 100644 index 0000000000..23d78088b1 --- /dev/null +++ b/release-notes/11.0/samples/fsharp/Fsi.fsx @@ -0,0 +1,19 @@ +open System +open System.Threading.Tasks + +let mutable disposed = false + +type Resource() = + interface IAsyncDisposable with + member _.DisposeAsync() = + disposed <- true + ValueTask.CompletedTask + +let operation = task { + use resource = new Resource() + do! Task.Delay 1 +} + +operation.GetAwaiter().GetResult() +if not disposed then failwith "FSI task use did not call DisposeAsync." +printfn "FSI task IAsyncDisposable use passed." diff --git a/release-notes/11.0/samples/fsharp/Main.fs b/release-notes/11.0/samples/fsharp/Main.fs new file mode 100644 index 0000000000..839bdbeaea --- /dev/null +++ b/release-notes/11.0/samples/fsharp/Main.fs @@ -0,0 +1,12 @@ +module Main + +[] +let main _ = + DefaultSamples.validate () +#if PREVIEW_SAMPLES + PreviewSamples.validate () + RuntimeAsyncSamples.validate () + SrtpSamples.validate () + RecordConstructorSamples.validate () +#endif + 0 diff --git a/release-notes/11.0/samples/fsharp/Preview.fs b/release-notes/11.0/samples/fsharp/Preview.fs new file mode 100644 index 0000000000..eb35ae4701 --- /dev/null +++ b/release-notes/11.0/samples/fsharp/Preview.fs @@ -0,0 +1,63 @@ +module PreviewSamples + +open System +open System.Diagnostics.CodeAnalysis +open System.Runtime.CompilerServices +open System.Threading.Tasks + +type Calculator() = + [] + member _.Double(value: int) = value * 2 + +[] +let throwOriginal (original: exn) = raise original + +type IdentityBuilder() = + member _.Return(value) = value + member _.Delay(body) = body + member _.Run(body) = body () + member _.TryWith(body, handler) = + try body () with ex -> handler ex + +let assertReraised label original run = + try + run () + failwithf "Expected the original exception from %s." label + with ex when obj.ReferenceEquals(ex, original) -> + if not (ex.StackTrace.Contains("throwOriginal")) then + failwithf "The original exception stack trace was lost in %s." label + printfn "%s reraise retained the exception and stack trace." label + +let validate () = + let actual = Calculator().Double(value = 21) + if actual <> 42 then failwithf "Expected 42, got %d" actual + printfn "Named argument validation passed." + + let original = InvalidOperationException("original exception") + let operation = async { + try + do! Async.Sleep 1 + throwOriginal original + with _ -> + reraise () + } + + assertReraised "Async" original (fun () -> operation |> Async.RunSynchronously) + + let taskOperation () = task { + try + do! Task.Delay 1 + throwOriginal original + with _ -> + reraise () + } + assertReraised "Task" original (fun () -> (taskOperation ()).GetAwaiter().GetResult()) + + let identity = IdentityBuilder() + assertReraised "Custom builder" original (fun () -> + identity { + try + return throwOriginal original + with _ -> + return reraise () + }) diff --git a/release-notes/11.0/samples/fsharp/Program.fs b/release-notes/11.0/samples/fsharp/Program.fs index f9ef6d372f..fd519970b3 100644 --- a/release-notes/11.0/samples/fsharp/Program.fs +++ b/release-notes/11.0/samples/fsharp/Program.fs @@ -1,50 +1,57 @@ -open System -open System.Diagnostics.CodeAnalysis -open System.Threading -open System.Runtime.CompilerServices - -let mutable active = 0 -let mutable peak = 0 -let gate = obj() -let computations = - [ for i in 1..5 -> - async { - let running = Interlocked.Increment &active - lock gate (fun () -> peak <- max peak running) - do! Async.Sleep 30 - Interlocked.Decrement &active |> ignore - return i * i - } ] - -let results = computations |> Async.parallelLimit 2 |> Async.RunSynchronously -if results <> [| 1; 4; 9; 16; 25 |] || peak > 2 || peak < 1 then - failwithf "Unexpected bounded-parallel results: %A; peak concurrency: %d" results peak -printfn "Bounded-parallel work passed (peak concurrency: %d)." peak - -type Calculator() = - [] - member _.Double(value: int) = value * 2 - -let actual = Calculator().Double(value = 21) -if actual <> 42 then failwithf "Expected 42, got %d" actual -printfn "Named argument validation passed." +module DefaultSamples -let original = InvalidOperationException("original exception") - -[] -let throwOriginal () = raise original - -let operation = async { - try - throwOriginal () - with _ -> - reraise () -} - -try - operation |> Async.RunSynchronously - failwith "Expected the original exception." -with ex when obj.ReferenceEquals(ex, original) -> - if not (ex.StackTrace.Contains("throwOriginal")) then - failwith "The original exception stack trace was lost." - printfn "Computation-expression reraise retained the exception and stack trace." +open System.Threading +open System.Threading.Tasks + +let sumWithOffset k xs = + List.fold (fun total value -> total + value + k) 0 xs + +let validate () = + let resources = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceNames() + if not (Array.contains "ILLink.Substitutions.xml" resources) then + failwith "The compiled assembly is missing its metadata-trimming rules." + printfn "Assembly metadata-trimming rules passed." + + if sumWithOffset 1 [ 1; 2; 3 ] <> 9 then + failwith "Unexpected List.fold result." + let arrayTotal = [| 1; 2; 3 |] |> Array.fold (fun total value -> total + 2 * value) 0 + if arrayTotal <> 12 then failwith "Unexpected Array.fold result." + let fold2Total = ClosureOptimizationSamples.fold2 (fun total x y -> total + x + y) 0 [| 1; 2 |] [| 3; 4 |] + if fold2Total <> 10 then failwith "Unexpected optimized-closure fold2 result." + let matrix = Array2D.init 2 3 (fun row column -> row * 10 + column) + if matrix[1, 2] <> 12 then failwith "Unexpected Array2D.init result." + printfn "Collection and Array2D examples passed." + + let mutable active = 0 + let mutable peak = 0 + let gate = obj() + let computations = + [ for i in 1..5 -> + async { + let running = Interlocked.Increment &active + lock gate (fun () -> peak <- max peak running) + do! Async.Sleep 30 + Interlocked.Decrement &active |> ignore + return i * i + } ] + + let results = computations |> Async.parallelLimit 2 |> Async.RunSynchronously + if results <> [| 1; 4; 9; 16; 25 |] || peak > 2 || peak < 1 then + failwithf "Unexpected bounded-parallel results: %A; peak concurrency: %d" results peak + printfn "Bounded-parallel work passed (peak concurrency: %d)." peak + + active <- 0 + peak <- 0 + let taskFactories = + [ for i in 1..5 -> + fun cancellationToken -> task { + let running = Interlocked.Increment &active + lock gate (fun () -> peak <- max peak running) + do! Task.Delay(30, cancellationToken) + Interlocked.Decrement &active |> ignore + return i * i + } ] + let taskResults = (Task.parallelLimit 2 CancellationToken.None taskFactories).GetAwaiter().GetResult() + if taskResults <> [| 1; 4; 9; 16; 25 |] || peak > 2 || peak < 1 then + failwithf "Unexpected bounded-task results: %A; peak concurrency: %d" taskResults peak + printfn "Bounded task factories passed (peak concurrency: %d)." peak diff --git a/release-notes/11.0/samples/fsharp/README.md b/release-notes/11.0/samples/fsharp/README.md index 8984f38106..815d69e515 100644 --- a/release-notes/11.0/samples/fsharp/README.md +++ b/release-notes/11.0/samples/fsharp/README.md @@ -1,3 +1,36 @@ # F# release-note validation -With SDK `11.0.100-rc.2.26475.137` on `PATH`, run `.\Validate.ps1`. The fixture checks the ordered results and peak concurrency of `Async.parallelLimit 2`, and confirms that `reraise ()` in an async computation-expression handler preserves the original exception and stack trace. It also supplies `RequireNamedArgumentsAttribute` as a polyfill because this build does not ship it in the BCL, runs a marked method using a named argument, and checks that a positional call in the separate `invalid/Invalid.fsproj` reports `FS3923`. +With SDK `11.0.100-rc.2.26475.137` on `PATH`, run: + +```powershell +pwsh -NoProfile -File ./Validate.ps1 +``` + +The script rejects other SDK versions. It builds and runs the fixture with both the default language version and `preview`. + +| Scenario | Expected result | +| -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| Default-language collection operations | Correct `List.fold`, `Array.fold`, `Array2D.init`, and attributed custom `fold2` results | +| `Async.parallelLimit 2` and `Task.parallelLimit 2` | Results in input order, peak active concurrency at most two | +| Preview named arguments | Named call runs; positional call reports `FS3923`. Positional call compiles by default | +| Preview `reraise` | Async, task, and custom-builder handlers preserve the original exception and stack origin. The article's async example reports `FS3350` by default | +| Preview runtime-async compiler support | Source-test-derived intrinsic sample returns 42; default-language build reports `FS3350`. This does not switch stock builders to runtime-async | +| Preview extension operators and SRTP | Source-test-derived examples return `"hahaha"` and 8. Default-language build reports `FS0001`/`FS0043` | +| Carry-forward preview record constructor | Constructor returns the expected record under preview; default-language build reports `FS0800` | +| Default F# 11 closure optimization | Attributed helper runs by default; F# 10 language mode reports `FS3350` | +| Char-backed enum bitwise operations | `FS0001` under default and preview; compiles with F# 10 language mode | +| F# Interactive asynchronous disposal | Default-language `task` calls `IAsyncDisposable.DisposeAsync` | +| Array2D operations | `create`, `init`, `copy`, `map`, `mapi`, and `rebase` return the expected values | +| Assembly metadata trimming | Compiled fixture contains its generated `ILLink.Substitutions.xml` resource | + +The fixture supplies `System.Diagnostics.CodeAnalysis.RequireNamedArgumentsAttribute` as a polyfill because the pinned build does not ship it in the BCL. + +To verify the `Array2D` Native AOT claim, run: + +```powershell +pwsh -NoProfile -File ./Validate.ps1 -PublishAot +``` + +This also publishes and runs the small `aot/` project with warnings treated as errors. It requires the host's [Native AOT prerequisites](https://learn.microsoft.com/dotnet/core/deploying/native-aot/#prerequisites). A normal F# build alone does not prove that Native AOT publishing is warning-free. + +Performance figures in the article come from the linked PR workloads. This fixture verifies example behavior and language gates; it does not reproduce those benchmarks. diff --git a/release-notes/11.0/samples/fsharp/RecordConstructors.fs b/release-notes/11.0/samples/fsharp/RecordConstructors.fs new file mode 100644 index 0000000000..03d838cc84 --- /dev/null +++ b/release-notes/11.0/samples/fsharp/RecordConstructors.fs @@ -0,0 +1,8 @@ +module RecordConstructorSamples + +type Point = { X: int; Y: int } +let origin = Point(0, 0) + +let validate () = + if origin <> { X = 0; Y = 0 } then failwith "Unexpected record constructor result." + printfn "Carry-forward preview record constructor passed." diff --git a/release-notes/11.0/samples/fsharp/RuntimeAsync.fs b/release-notes/11.0/samples/fsharp/RuntimeAsync.fs new file mode 100644 index 0000000000..34d469694f --- /dev/null +++ b/release-notes/11.0/samples/fsharp/RuntimeAsync.fs @@ -0,0 +1,20 @@ +module RuntimeAsyncSamples + +open System.Runtime.CompilerServices +open System.Threading.Tasks +open Microsoft.FSharp.Core.CompilerServices + +let delayed value = task { + do! Task.Delay 1 + return value +} + +let add (x: int) (y: int) : Task = + StateMachineHelpers.__runtimeAsyncReturn ( + let first = AsyncHelpers.Await(delayed x) + first + y) + +let validate () = + let result = (add 40 2).GetAwaiter().GetResult() + if result <> 42 then failwithf "Expected runtime-async result 42, got %d" result + printfn "Builder-author runtime-async intrinsic example passed." diff --git a/release-notes/11.0/samples/fsharp/Srtp.fs b/release-notes/11.0/samples/fsharp/Srtp.fs new file mode 100644 index 0000000000..076cf2a925 --- /dev/null +++ b/release-notes/11.0/samples/fsharp/Srtp.fs @@ -0,0 +1,18 @@ +module SrtpSamples + +type System.String with + static member (*) (s: string, n: int) = + System.String.Concat(Array.replicate n s) + +let repeated = "ha" * 3 + +type System.Int32 with + static member (++) (a: int, b: int) = a + b + 1 + +let inline incAdd (x: ^T) (y: ^T) = x ++ y +let result = incAdd 3 4 + +let validate () = + if repeated <> "hahaha" || result <> 8 then + failwithf "Unexpected extension-operator results: %s, %d" repeated result + printfn "Preview SRTP extension operators passed." diff --git a/release-notes/11.0/samples/fsharp/Validate.ps1 b/release-notes/11.0/samples/fsharp/Validate.ps1 index d43ecd519b..1d3f9c1c1c 100644 --- a/release-notes/11.0/samples/fsharp/Validate.ps1 +++ b/release-notes/11.0/samples/fsharp/Validate.ps1 @@ -1,11 +1,68 @@ +param([switch] $PublishAot) + $ErrorActionPreference = 'Stop' $dotnet = (Get-Command dotnet -ErrorAction Stop).Source +$metadata = Get-Content (Join-Path $PSScriptRoot '../../preview/rc2/build-metadata.json') -Raw | ConvertFrom-Json +$sdkVersion = & $dotnet --version +if ($LASTEXITCODE -ne 0 -or $sdkVersion -ne $metadata.build.sdk_version) { + throw "Expected SDK $($metadata.build.sdk_version), got '$sdkVersion'. Put the exact SDK on PATH." +} + +$project = Join-Path $PSScriptRoot 'FSharp.fsproj' +$invalidProject = Join-Path $PSScriptRoot 'invalid/Invalid.fsproj' +$aotProject = Join-Path $PSScriptRoot 'aot/Aot.fsproj' + +foreach ($language in @('default', 'preview')) { + & $dotnet build $project --nologo --no-incremental "-p:LangVersion=$language" + if ($LASTEXITCODE -ne 0) { throw "The $language-language examples did not build." } + & $dotnet run --project $project --no-build + if ($LASTEXITCODE -ne 0) { throw "The $language-language examples did not run." } +} + +function Assert-BuildFailure([string] $Case, [string] $Language, [string[]] $Expected) { + $diagnostics = & $dotnet build $invalidProject --nologo --no-incremental ` + "-p:LangVersion=$Language" "-p:ValidationCase=$Case" 2>&1 | Out-String + $exitCode = $LASTEXITCODE + $errors = @([regex]::Matches($diagnostics, 'error ([A-Z]+\d+)') | + ForEach-Object { $_.Groups[1].Value } | Sort-Object -Unique) + $expectedErrors = @($Expected | Sort-Object -Unique) + if ($exitCode -eq 0 -or ($errors -join ',') -ne ($expectedErrors -join ',')) { + throw "Expected only $($expectedErrors -join ', ') for $Case with $Language, got: $diagnostics" + } + Write-Output "$Case with $Language correctly reported $($expectedErrors -join ', ')." +} + +& $dotnet build $invalidProject --nologo --no-incremental -p:LangVersion=default +if ($LASTEXITCODE -ne 0) { throw 'A positional call should compile with the default language version.' } +Assert-BuildFailure 'NamedArguments' 'preview' 'FS3923' +Assert-BuildFailure 'Reraise' 'default' 'FS3350' +Assert-BuildFailure 'RuntimeAsync' 'default' 'FS3350' +Assert-BuildFailure 'Srtp' 'default' @('FS0001', 'FS0043') +Assert-BuildFailure 'RecordConstructors' 'default' 'FS0800' +Assert-BuildFailure 'ClosureOptimization' '10.0' 'FS3350' +Assert-BuildFailure 'CharEnums' 'default' 'FS0001' +Assert-BuildFailure 'CharEnums' 'preview' 'FS0001' + +& $dotnet build $invalidProject --nologo --no-incremental -p:LangVersion=10.0 -p:ValidationCase=CharEnums +if ($LASTEXITCODE -ne 0) { throw 'Char-backed enum bitwise operations should compile with F# 10 language rules.' } + +& $dotnet build $invalidProject --nologo --no-incremental -p:LangVersion=preview -p:ValidationCase=Reraise +if ($LASTEXITCODE -ne 0) { throw 'The article reraise example should compile with preview.' } + +& $dotnet fsi --langversion:default --exec (Join-Path $PSScriptRoot 'Fsi.fsx') +if ($LASTEXITCODE -ne 0) { throw 'The FSI asynchronous-disposal example did not run.' } -& $dotnet run --project (Join-Path $PSScriptRoot 'FSharp.fsproj') -if ($LASTEXITCODE -ne 0) { throw 'The named-argument example did not run.' } +& $dotnet build $aotProject --nologo -p:TreatWarningsAsErrors=true +if ($LASTEXITCODE -ne 0) { throw 'The Array2D example did not build.' } +& $dotnet run --project $aotProject --no-build +if ($LASTEXITCODE -ne 0) { throw 'The Array2D example did not run.' } -$diagnostics = & $dotnet build (Join-Path $PSScriptRoot 'invalid\Invalid.fsproj') --nologo 2>&1 | Out-String -if ($LASTEXITCODE -eq 0 -or $diagnostics -notmatch 'error FS3923') { - throw "Expected positional-call error FS3923, got: $diagnostics" +if ($PublishAot) { + $nativeDirectory = Join-Path $PSScriptRoot 'aot/bin/native' + & $dotnet publish $aotProject --nologo -c Release -p:PublishAot=true ` + -p:TreatWarningsAsErrors=true -p:TrimmerSingleWarn=false -p:IlcSingleWarn=false -o $nativeDirectory + if ($LASTEXITCODE -ne 0) { throw 'The Array2D Native AOT publish failed.' } + $executable = if ($IsWindows) { 'Aot.exe' } else { 'Aot' } + & (Join-Path $nativeDirectory $executable) + if ($LASTEXITCODE -ne 0) { throw 'The Array2D Native AOT executable failed.' } } -Write-Output 'Positional call correctly reported FS3923.' diff --git a/release-notes/11.0/samples/fsharp/aot/Aot.fsproj b/release-notes/11.0/samples/fsharp/aot/Aot.fsproj new file mode 100644 index 0000000000..bde82373fa --- /dev/null +++ b/release-notes/11.0/samples/fsharp/aot/Aot.fsproj @@ -0,0 +1,10 @@ + + + Exe + net11.0 + true + + + + + diff --git a/release-notes/11.0/samples/fsharp/aot/Program.fs b/release-notes/11.0/samples/fsharp/aot/Program.fs new file mode 100644 index 0000000000..217f2da98f --- /dev/null +++ b/release-notes/11.0/samples/fsharp/aot/Program.fs @@ -0,0 +1,12 @@ +let matrix = Array2D.init 2 3 (fun row column -> row * 10 + column) +let created = Array2D.create 2 3 12 +let copied = Array2D.copy matrix +let mapped = Array2D.map ((+) 1) matrix +let indexed = Array2D.mapi (fun row column value -> row + column + value) matrix +let rebased = Array2D.rebase matrix + +if matrix[1, 2] <> 12 || created[1, 2] <> 12 || copied[1, 2] <> 12 || + mapped[1, 2] <> 13 || indexed[1, 2] <> 15 || rebased[1, 2] <> 12 then + failwith "Unexpected Array2D result." + +System.Console.WriteLine "Array2D operations passed." diff --git a/release-notes/11.0/samples/fsharp/invalid/CharEnums.fs b/release-notes/11.0/samples/fsharp/invalid/CharEnums.fs new file mode 100644 index 0000000000..9c1433efa1 --- /dev/null +++ b/release-notes/11.0/samples/fsharp/invalid/CharEnums.fs @@ -0,0 +1,7 @@ +type CharEnum = + | A = 'a' + | B = 'b' + +let invalidOr = CharEnum.A ||| CharEnum.B +let invalidAnd = CharEnum.A &&& CharEnum.B +let invalidXor = CharEnum.A ^^^ CharEnum.B diff --git a/release-notes/11.0/samples/fsharp/invalid/Invalid.fsproj b/release-notes/11.0/samples/fsharp/invalid/Invalid.fsproj index 885fdfcf29..02fe280cf8 100644 --- a/release-notes/11.0/samples/fsharp/invalid/Invalid.fsproj +++ b/release-notes/11.0/samples/fsharp/invalid/Invalid.fsproj @@ -2,10 +2,17 @@ Exe net11.0 - preview + preview + NamedArguments - + + + + + + + diff --git a/release-notes/11.0/samples/fsharp/invalid/Reraise.fs b/release-notes/11.0/samples/fsharp/invalid/Reraise.fs new file mode 100644 index 0000000000..a8dde75658 --- /dev/null +++ b/release-notes/11.0/samples/fsharp/invalid/Reraise.fs @@ -0,0 +1,6 @@ +let operation = async { + try + failwith "operation failed" + with _ -> + reraise () +} From 6f970c01d4e7a5ed91b80fe2dea92e453f59d0fa Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Mon, 5 Oct 2026 14:52:55 +0200 Subject: [PATCH 4/8] [release-notes] Prioritize F# language capabilities Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- release-notes/11.0/preview/rc2/fsharp.md | 221 ++++++++++-------- release-notes/11.0/samples/fsharp/README.md | 28 +-- .../11.0/samples/fsharp/RuntimeAsync.fs | 21 ++ release-notes/11.0/samples/fsharp/Srtp.fs | 16 ++ 4 files changed, 180 insertions(+), 106 deletions(-) diff --git a/release-notes/11.0/preview/rc2/fsharp.md b/release-notes/11.0/preview/rc2/fsharp.md index d43e28ed3e..88b16cda9d 100644 --- a/release-notes/11.0/preview/rc2/fsharp.md +++ b/release-notes/11.0/preview/rc2/fsharp.md @@ -2,13 +2,14 @@ .NET 11 RC 2 includes the following F# updates: -- [Lower allocations in collections and compilation](#lower-allocations-in-collections-and-compilation) -- [Bound concurrent asynchronous work](#bound-concurrent-asynchronous-work) -- [Require named arguments for selected APIs](#require-named-arguments-for-selected-apis) +- [Extend existing types for generic code](#extend-existing-types-for-generic-code) +- [Build computation expressions on runtime-async](#build-computation-expressions-on-runtime-async) - [Reraise from a computation expression handler](#reraise-from-a-computation-expression-handler) +- [Require named arguments for selected APIs](#require-named-arguments-for-selected-apis) +- [Lower allocations in FSharp.Core collections](#lower-allocations-in-fsharpcore-collections) +- [Compiler memory usage improvements](#compiler-memory-usage-improvements) +- [Bound concurrent asynchronous work](#bound-concurrent-asynchronous-work) - [Trimming and Native AOT improvements](#trimming-and-native-aot-improvements) -- [Preview catch-up: extension members and operators](#preview-catch-up-extension-members-and-operators) -- [Other preview feature status](#other-preview-feature-status) - [Breaking changes from .NET 10](#breaking-changes-from-net-10) - [Bug fixes and other improvements](#bug-fixes-and-other-improvements) - [Community contributors](#community-contributors) @@ -23,59 +24,93 @@ Features marked **preview** require this setting in your project: For F# Interactive, use `dotnet fsi --langversion:preview`. Other updates use the default F# 11 language version. -## Lower allocations in collections and compilation +## Extend existing types for generic code -More `List` and `Array` traversal functions, including `fold`, `exists`, and `tryPick`, can now inline their callback lambdas. -This removes callback closures from common collection operations -([dotnet/fsharp#20422](https://github.com/dotnet/fsharp/pull/20422)). +> **Preview.** + +Bring existing .NET and third-party types into generic F# algorithms without changing those types or introducing wrappers. +Statically resolved type parameter (SRTP) constraints can select extension members, including operators, supplied by your code or a library. +An algorithm no longer needs every operation to be declared inside the original type. ```fsharp -let sumWithOffset k xs = - List.fold (fun total value -> total + value + k) 0 xs +open System -let total = sumWithOffset 1 [ 1; 2; 3 ] // 9 +type MyOffset = { Hours: float } + +type System.DateTime with + static member (+) (date: DateTime, offset: MyOffset) = + date.AddHours(offset.Hours) + +let inline shift (date: DateTime) offset = date + offset + +let oneHourLater = shift DateTime.MinValue (TimeSpan.FromHours 1.0) +let twoHoursLater = shift DateTime.MinValue { Hours = 2.0 } ``` -The author's capturing-lambda microbenchmarks report 24 bytes per call with FSharp.Core 9.0.100 versus zero with the updated Core. -This applies to the measured `List.fold`, `List.exists`, `Array.fold`, and `Array.fold2` calls, not every collection function. -`OptimizeClosureIfNotInlined` is also enabled by default in F# 11. -It lets library authors adapt non-literal callbacks outside an inlined loop -([dotnet/fsharp#20571](https://github.com/dotnet/fsharp/pull/20571)). +The same `shift` function accepts the built-in `TimeSpan` and a domain-specific offset added through an extension. +Inline overload resolution stays open until the call site instead of fixing the offset type to `TimeSpan` at the function definition. +Libraries can use this mechanism to adapt existing types to generic operations. -In the author-reported self-build for [dotnet/fsharp#20422](https://github.com/dotnet/fsharp/pull/20422), compiler closure allocations fell from 231,625,834 to 149,844,308 (35.31%). -That comparison used SDK 10.0.400/FSharp.Core 10.1 versus the PR's compiler/Core 11. -It measures that workload's closure allocations, not a general build-time speedup. -RC 2 also avoids duplicate expression copying during inlining and reduces constraint-solver closures -([dotnet/fsharp#20363](https://github.com/dotnet/fsharp/pull/20363), -[dotnet/fsharp#20367](https://github.com/dotnet/fsharp/pull/20367)). +Only public extensions can satisfy these constraints. +When using extensions from another assembly, open their defining module at the call site. +Built-in operations still take precedence when they already apply. +Ordinary extension-member calls do not require preview. -FSharp.Compiler.Service shares imported assembly data between projects by default, reducing retained metadata in multi-project tools. -Tool authors can opt out with `shareImportedAssemblies = false` on `FSharpChecker.Create` -([dotnet/fsharp#20296](https://github.com/dotnet/fsharp/pull/20296)). -These compiler-service improvements do not require the preview language version. -Thank you [@auduchinok](https://github.com/auduchinok) for this contribution! +This capability was already available in RC 1 +([dotnet/fsharp#19602](https://github.com/dotnet/fsharp/pull/19602)). +Thank you [@gusty](https://github.com/gusty) for this contribution! -## Bound concurrent asynchronous work +## Build computation expressions on runtime-async -`Async.parallelLimit` runs asynchronous computations with a maximum number in flight and returns results in input order. -Use it to limit concurrent work in a batch without writing a separate concurrency limiter. -Related helpers include `Async.parallelDoLimit`, `Task.parallelLimit`, and sequential variants -([dotnet/fsharp#20294](https://github.com/dotnet/fsharp/pull/20294)). -The task helpers accept functions that start tasks, so the limit applies before the work starts. +> **Preview.** + +Library authors can build custom computation-expression builders on .NET's runtime-async execution model. +RC 2 supplies compiler-recognized return and await intrinsics for generating runtime-async methods +([dotnet/fsharp#20235](https://github.com/dotnet/fsharp/pull/20235)). +The runtime manages asynchronous suspension and continuation instead of requiring a compiler-generated await state machine. + +The [compiler's prototype builder](https://github.com/dotnet/fsharp/blob/9cd6167a7265ce7264b22719503b7dfa9eb8f83c/tests/FSharp.Compiler.ComponentTests/Language/RuntimeAsync/RuntimeTaskBuilder.fs) demonstrates familiar `let!`, `return`, loops, exception handling, and resource management on these mechanics: ```fsharp -let results = - [ for i in 1..5 -> async { return i * i } ] - |> Async.parallelLimit 2 - |> Async.RunSynchronously -// [| 1; 4; 9; 16; 25 |] +let calculation () = + runtimeTask { + let captured = 40 + let! delta = delayed 2 + return captured + delta + } ``` -Thank you [@bartelink](https://github.com/bartelink) for this contribution! +Here `runtimeTask` is the custom builder defined by the tests, not a shipped FSharp.Core symbol. +Its `Run` method passes the delayed body to `StateMachineHelpers.__runtimeAsyncReturn`. +Its `Source` methods use `AsyncHelpers.Await` to await tasks before handing their results to the continuation. +The [maintained fixture](../../samples/fsharp/README.md) includes a small test-derived builder and runs this example across suspension. + +This release gives library authors the building blocks to create their own computation-expression APIs. +FSharp.Core does not yet ship a builder using these mechanics. +The existing `task {}` and `async {}` builders are unchanged. +The example targets `net11.0` and runs on the .NET 11 RC 2 runtime. +The intrinsics come from FSharp.Core's `net10.0` asset, not its `netstandard2.0` asset; the target and executing runtime must support runtime-async. + +## Reraise from a computation expression handler + +> **Preview.** + +`reraise ()` now works inside the `with` handler of an `async`, `task`, or custom computation expression. +It propagates the original exception and preserves its stack trace, unlike rethrowing with `raise e` +([dotnet/fsharp#20405](https://github.com/dotnet/fsharp/pull/20405)). + +```fsharp +let operation = async { + try + failwith "operation failed" + with _ -> + reraise () +} +``` ## Require named arguments for selected APIs -> This feature requires `preview` or `--langversion:preview` in RC 2. +> **Preview.** API authors can apply `System.Diagnostics.CodeAnalysis.RequireNamedArgumentsAttribute` to methods and constructors. With preview enabled, positional calls report `FS3923`, requiring callers to name the affected arguments @@ -98,76 +133,78 @@ The [maintained fixture](../../samples/fsharp/README.md) includes the attribute It verifies that the named call runs and that `Calculator().Double(21)` reports `FS3923` under preview. The default language version does not enforce this attribute. -## Reraise from a computation expression handler - -> This feature requires `preview` or `--langversion:preview` in RC 2. +## Lower allocations in FSharp.Core collections -`reraise ()` now works inside the `with` handler of an `async`, `task`, or custom computation expression. -It propagates the original exception and preserves its stack trace, unlike rethrowing with `raise e` -([dotnet/fsharp#20405](https://github.com/dotnet/fsharp/pull/20405)). +More `List` and `Array` traversal functions, including `fold`, `exists`, and `tryPick`, can now inline their callback lambdas. +This removes callback closures from common collection operations in your applications +([dotnet/fsharp#20422](https://github.com/dotnet/fsharp/pull/20422)). ```fsharp -let operation = async { - try - failwith "operation failed" - with _ -> - reraise () -} -``` - -## Trimming and Native AOT improvements - -`Array2D.create`, `init`, `rebase`, `map`, `mapi`, and `copy` no longer report `IL3050` when targeting .NET 11. -Non-zero-based `*Based` operations still have Native AOT restrictions, now reported at the call site -([dotnet/fsharp#20338](https://github.com/dotnet/fsharp/pull/20338)). +let sumWithOffset k xs = + List.fold (fun total value -> total + value + k) 0 xs -```fsharp -let matrix = Array2D.init 2 3 (fun row column -> row * 10 + column) -let value = matrix[1, 2] // 12 +let total = sumWithOffset 1 [ 1; 2; 3 ] // 9 ``` -Compiled F# assemblies now embed their metadata-trimming rules, including when assemblies are linked -([dotnet/fsharp#20527](https://github.com/dotnet/fsharp/pull/20527)). -These changes do not require the preview language version. +The author's capturing-lambda microbenchmarks report 24 bytes per call with FSharp.Core 9.0.100 versus zero with the updated Core. +This applies to the measured `List.fold`, `List.exists`, `Array.fold`, and `Array.fold2` calls, not every collection function. +`OptimizeClosureIfNotInlined` is also enabled by default in F# 11. +It lets library authors adapt non-literal callbacks outside an inlined loop +([dotnet/fsharp#20571](https://github.com/dotnet/fsharp/pull/20571)). + +## Compiler memory usage improvements -## Preview catch-up: extension members and operators +Compilation and project analysis create fewer temporary objects and retain less duplicated assembly metadata. +RC 2 combines expression copying and type instantiation during inlining, removes constraint-solver closures, and reuses metadata objects +([dotnet/fsharp#20363](https://github.com/dotnet/fsharp/pull/20363), +[dotnet/fsharp#20367](https://github.com/dotnet/fsharp/pull/20367), +[dotnet/fsharp#20255](https://github.com/dotnet/fsharp/pull/20255), +[dotnet/fsharp#20489](https://github.com/dotnet/fsharp/pull/20489)). -> This feature requires `preview` or `--langversion:preview`. It was already available in RC 1. +The PE-reader reuse PR's FSharp.Common build benchmark reports allocated bytes decreasing from 2,912 MB to 2,606 MB (10.5%). +These are cumulative allocations during compilation, separate from the application runtime allocations described above. -Extension members can satisfy statically resolved type parameter (SRTP) constraints, including constraints used by operators. -This feature was omitted from the [RC 1 article](../rc1/fsharp.md), rather than newly implemented in RC 2 -([dotnet/fsharp#19602](https://github.com/dotnet/fsharp/pull/19602)). +FSharp.Compiler.Service also shares imported assembly data between projects by default and creates referenced-type metadata only when needed +([dotnet/fsharp#20296](https://github.com/dotnet/fsharp/pull/20296), +[dotnet/fsharp#20494](https://github.com/dotnet/fsharp/pull/20494)). +The sharing PR's 10-project ReSharper.FSharp benchmark reports retained memory decreasing from 1,547 MB to 1,002 MB (35.2%). +Tool authors can opt out with `shareImportedAssemblies = false` on `FSharpChecker.Create`. -```fsharp -type System.String with - static member (*) (s: string, n: int) = - System.String.Concat(Array.replicate n s) +These measurements describe their specific workloads, not peak process memory or a universal build-time speedup. +The compiler improvements are enabled by default. +Thank you [@auduchinok](https://github.com/auduchinok) for this contribution! -let repeated = "ha" * 3 // "hahaha" +## Bound concurrent asynchronous work -type System.Int32 with - static member (++) (a: int, b: int) = a + b + 1 +`Async.parallelLimit` runs asynchronous computations with a maximum number in flight and returns results in input order. +Use it to limit concurrent work in a batch without writing a separate concurrency limiter. +Related helpers include `Async.parallelDoLimit`, `Task.parallelLimit`, and sequential variants +([dotnet/fsharp#20294](https://github.com/dotnet/fsharp/pull/20294)). +The task helpers accept functions that start tasks, so the limit applies before the work starts. -let inline incAdd (x: ^T) (y: ^T) = x ++ y -let result = incAdd 3 4 // 8 +```fsharp +let results = + [ for i in 1..5 -> async { return i * i } ] + |> Async.parallelLimit 2 + |> Async.RunSynchronously +// [| 1; 4; 9; 16; 25 |] ``` -The first call resolves an extension operator on a concrete type. -The second resolves one through a generic inline function. -The preview requirement applies to extension-based constraint resolution, not to ordinary extension-member calls. -Thank you [@gusty](https://github.com/gusty) for this contribution! +Thank you [@bartelink](https://github.com/bartelink) for this contribution! -## Other preview feature status +## Trimming and Native AOT improvements -> Runtime-async compiler support requires `preview` or `--langversion:preview` in RC 2. +`Array2D.create`, `init`, `rebase`, `map`, `mapi`, and `copy` no longer report `IL3050` when targeting .NET 11. +Non-zero-based `*Based` operations still have Native AOT restrictions, now reported at the call site +([dotnet/fsharp#20338](https://github.com/dotnet/fsharp/pull/20338)). -RC 2 adds compiler support for .NET runtime-async intrinsics for custom computation-expression builder authors -([dotnet/fsharp#20235](https://github.com/dotnet/fsharp/pull/20235)). -This also requires a target/runtime that supports runtime-async. -The stock `task {}` and `async {}` builders do not use this path in RC 2. +```fsharp +let matrix = Array2D.init 2 3 (fun row column -> row * 10 + column) +let value = matrix[1, 2] // 12 +``` -[Record constructors](../rc1/fsharp.md#record-constructors) remain **preview**, unchanged from RC 1. -The other features described in the RC 1 article are not reannounced here. +Compiled F# assemblies now embed their metadata-trimming rules, including when assemblies are linked +([dotnet/fsharp#20527](https://github.com/dotnet/fsharp/pull/20527)). ## Breaking changes from .NET 10 diff --git a/release-notes/11.0/samples/fsharp/README.md b/release-notes/11.0/samples/fsharp/README.md index 815d69e515..b9d6f631db 100644 --- a/release-notes/11.0/samples/fsharp/README.md +++ b/release-notes/11.0/samples/fsharp/README.md @@ -8,20 +8,20 @@ pwsh -NoProfile -File ./Validate.ps1 The script rejects other SDK versions. It builds and runs the fixture with both the default language version and `preview`. -| Scenario | Expected result | -| -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | -| Default-language collection operations | Correct `List.fold`, `Array.fold`, `Array2D.init`, and attributed custom `fold2` results | -| `Async.parallelLimit 2` and `Task.parallelLimit 2` | Results in input order, peak active concurrency at most two | -| Preview named arguments | Named call runs; positional call reports `FS3923`. Positional call compiles by default | -| Preview `reraise` | Async, task, and custom-builder handlers preserve the original exception and stack origin. The article's async example reports `FS3350` by default | -| Preview runtime-async compiler support | Source-test-derived intrinsic sample returns 42; default-language build reports `FS3350`. This does not switch stock builders to runtime-async | -| Preview extension operators and SRTP | Source-test-derived examples return `"hahaha"` and 8. Default-language build reports `FS0001`/`FS0043` | -| Carry-forward preview record constructor | Constructor returns the expected record under preview; default-language build reports `FS0800` | -| Default F# 11 closure optimization | Attributed helper runs by default; F# 10 language mode reports `FS3350` | -| Char-backed enum bitwise operations | `FS0001` under default and preview; compiles with F# 10 language mode | -| F# Interactive asynchronous disposal | Default-language `task` calls `IAsyncDisposable.DisposeAsync` | -| Array2D operations | `create`, `init`, `copy`, `map`, `mapi`, and `rebase` return the expected values | -| Assembly metadata trimming | Compiled fixture contains its generated `ILLink.Substitutions.xml` resource | +| Scenario | Expected result | +| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Default-language collection operations | Correct `List.fold`, `Array.fold`, `Array2D.init`, and attributed custom `fold2` results | +| `Async.parallelLimit 2` and `Task.parallelLimit 2` | Results in input order, peak active concurrency at most two | +| Preview named arguments | Named call runs; positional call reports `FS3923`. Positional call compiles by default | +| Preview `reraise` | Async, task, and custom-builder handlers preserve the original exception and stack origin. The article's async example reports `FS3350` by default | +| Preview runtime-async compiler support | Source-test-derived intrinsic and custom `runtimeTask` builder samples return 42 across suspension; default-language build reports `FS3350`. The builder is fixture-defined, not shipped in FSharp.Core | +| Preview extension operators and SRTP | Generic DateTime shifting accepts both built-in TimeSpan and custom MyOffset. Other source-test examples return `"hahaha"` and 8. Default-language build reports `FS0001`/`FS0043` | +| Carry-forward preview record constructor | Constructor returns the expected record under preview; default-language build reports `FS0800` | +| Default F# 11 closure optimization | Attributed helper runs by default; F# 10 language mode reports `FS3350` | +| Char-backed enum bitwise operations | `FS0001` under default and preview; compiles with F# 10 language mode | +| F# Interactive asynchronous disposal | Default-language `task` calls `IAsyncDisposable.DisposeAsync` | +| Array2D operations | `create`, `init`, `copy`, `map`, `mapi`, and `rebase` return the expected values | +| Assembly metadata trimming | Compiled fixture contains its generated `ILLink.Substitutions.xml` resource | The fixture supplies `System.Diagnostics.CodeAnalysis.RequireNamedArgumentsAttribute` as a polyfill because the pinned build does not ship it in the BCL. diff --git a/release-notes/11.0/samples/fsharp/RuntimeAsync.fs b/release-notes/11.0/samples/fsharp/RuntimeAsync.fs index 34d469694f..a9a164e5b4 100644 --- a/release-notes/11.0/samples/fsharp/RuntimeAsync.fs +++ b/release-notes/11.0/samples/fsharp/RuntimeAsync.fs @@ -14,7 +14,28 @@ let add (x: int) (y: int) : Task = let first = AsyncHelpers.Await(delayed x) first + y) +type RuntimeTaskBuilder() = + member inline _.Return(value: 'T) = value + member inline _.Bind(source, continuation) = continuation source + member inline _.Delay([] generator: unit -> 'T) = generator + member inline _.Run([] code: unit -> 'T) : Task<'T> = + StateMachineHelpers.__runtimeAsyncReturn (code ()) + member inline _.Source(source: Task<'T>) = AsyncHelpers.Await source + member inline _.Source(source: Task) = AsyncHelpers.Await source + +let runtimeTask = RuntimeTaskBuilder() + +let calculation () = + runtimeTask { + let captured = 40 + let! delta = delayed 2 + return captured + delta + } + let validate () = let result = (add 40 2).GetAwaiter().GetResult() if result <> 42 then failwithf "Expected runtime-async result 42, got %d" result printfn "Builder-author runtime-async intrinsic example passed." + let builderResult = (calculation ()).GetAwaiter().GetResult() + if builderResult <> 42 then failwithf "Expected custom runtimeTask result 42, got %d" builderResult + printfn "Custom test-derived runtimeTask builder passed across suspension." diff --git a/release-notes/11.0/samples/fsharp/Srtp.fs b/release-notes/11.0/samples/fsharp/Srtp.fs index 076cf2a925..7b80e5f3e7 100644 --- a/release-notes/11.0/samples/fsharp/Srtp.fs +++ b/release-notes/11.0/samples/fsharp/Srtp.fs @@ -1,5 +1,18 @@ module SrtpSamples +open System + +type MyOffset = { Hours: float } + +type System.DateTime with + static member (+) (date: DateTime, offset: MyOffset) = + date.AddHours(offset.Hours) + +let inline shift (date: DateTime) offset = date + offset + +let oneHourLater = shift DateTime.MinValue (TimeSpan.FromHours 1.0) +let twoHoursLater = shift DateTime.MinValue { Hours = 2.0 } + type System.String with static member (*) (s: string, n: int) = System.String.Concat(Array.replicate n s) @@ -15,4 +28,7 @@ let result = incAdd 3 4 let validate () = if repeated <> "hahaha" || result <> 8 then failwithf "Unexpected extension-operator results: %s, %d" repeated result + if oneHourLater <> DateTime.MinValue.AddHours 1.0 || + twoHoursLater <> DateTime.MinValue.AddHours 2.0 then + failwith "Generic DateTime shifting did not retain both overload choices." printfn "Preview SRTP extension operators passed." From 98211b43920db25630d607498b95cf1a54ae1e3c Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Mon, 5 Oct 2026 15:38:18 +0200 Subject: [PATCH 5/8] [release-notes] Use established F# feature titles Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- release-notes/11.0/preview/rc2/fsharp.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/release-notes/11.0/preview/rc2/fsharp.md b/release-notes/11.0/preview/rc2/fsharp.md index 88b16cda9d..f0a5b5c1ea 100644 --- a/release-notes/11.0/preview/rc2/fsharp.md +++ b/release-notes/11.0/preview/rc2/fsharp.md @@ -2,9 +2,9 @@ .NET 11 RC 2 includes the following F# updates: -- [Extend existing types for generic code](#extend-existing-types-for-generic-code) -- [Build computation expressions on runtime-async](#build-computation-expressions-on-runtime-async) -- [Reraise from a computation expression handler](#reraise-from-a-computation-expression-handler) +- [SRTP for extension members and operators](#srtp-for-extension-members-and-operators) +- [Runtime async support](#runtime-async-support) +- [reraise in computation expressions](#reraise-in-computation-expressions) - [Require named arguments for selected APIs](#require-named-arguments-for-selected-apis) - [Lower allocations in FSharp.Core collections](#lower-allocations-in-fsharpcore-collections) - [Compiler memory usage improvements](#compiler-memory-usage-improvements) @@ -24,7 +24,7 @@ Features marked **preview** require this setting in your project: For F# Interactive, use `dotnet fsi --langversion:preview`. Other updates use the default F# 11 language version. -## Extend existing types for generic code +## SRTP for extension members and operators > **Preview.** @@ -60,7 +60,7 @@ This capability was already available in RC 1 ([dotnet/fsharp#19602](https://github.com/dotnet/fsharp/pull/19602)). Thank you [@gusty](https://github.com/gusty) for this contribution! -## Build computation expressions on runtime-async +## Runtime async support > **Preview.** @@ -91,7 +91,7 @@ The existing `task {}` and `async {}` builders are unchanged. The example targets `net11.0` and runs on the .NET 11 RC 2 runtime. The intrinsics come from FSharp.Core's `net10.0` asset, not its `netstandard2.0` asset; the target and executing runtime must support runtime-async. -## Reraise from a computation expression handler +## reraise in computation expressions > **Preview.** From 33077ff86336f07edbd8ab3f9e8e3d1ec3305fef Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Mon, 5 Oct 2026 16:33:47 +0200 Subject: [PATCH 6/8] [release-notes] Keep F# PR documentation-only Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- release-notes/11.0/preview/rc2/fsharp.md | 4 +- .../samples/fsharp/ClosureOptimization.fs | 9 --- .../11.0/samples/fsharp/FSharp.fsproj | 18 ----- release-notes/11.0/samples/fsharp/Fsi.fsx | 19 ------ release-notes/11.0/samples/fsharp/Main.fs | 12 ---- release-notes/11.0/samples/fsharp/Polyfill.fs | 7 -- release-notes/11.0/samples/fsharp/Preview.fs | 63 ----------------- release-notes/11.0/samples/fsharp/Program.fs | 57 ---------------- release-notes/11.0/samples/fsharp/README.md | 36 ---------- .../11.0/samples/fsharp/RecordConstructors.fs | 8 --- .../11.0/samples/fsharp/RuntimeAsync.fs | 41 ----------- release-notes/11.0/samples/fsharp/Srtp.fs | 34 ---------- .../11.0/samples/fsharp/Validate.ps1 | 68 ------------------- .../11.0/samples/fsharp/aot/Aot.fsproj | 10 --- .../11.0/samples/fsharp/aot/Program.fs | 12 ---- .../11.0/samples/fsharp/invalid/CharEnums.fs | 7 -- .../11.0/samples/fsharp/invalid/Invalid.fs | 7 -- .../samples/fsharp/invalid/Invalid.fsproj | 18 ----- .../11.0/samples/fsharp/invalid/Reraise.fs | 6 -- 19 files changed, 1 insertion(+), 435 deletions(-) delete mode 100644 release-notes/11.0/samples/fsharp/ClosureOptimization.fs delete mode 100644 release-notes/11.0/samples/fsharp/FSharp.fsproj delete mode 100644 release-notes/11.0/samples/fsharp/Fsi.fsx delete mode 100644 release-notes/11.0/samples/fsharp/Main.fs delete mode 100644 release-notes/11.0/samples/fsharp/Polyfill.fs delete mode 100644 release-notes/11.0/samples/fsharp/Preview.fs delete mode 100644 release-notes/11.0/samples/fsharp/Program.fs delete mode 100644 release-notes/11.0/samples/fsharp/README.md delete mode 100644 release-notes/11.0/samples/fsharp/RecordConstructors.fs delete mode 100644 release-notes/11.0/samples/fsharp/RuntimeAsync.fs delete mode 100644 release-notes/11.0/samples/fsharp/Srtp.fs delete mode 100644 release-notes/11.0/samples/fsharp/Validate.ps1 delete mode 100644 release-notes/11.0/samples/fsharp/aot/Aot.fsproj delete mode 100644 release-notes/11.0/samples/fsharp/aot/Program.fs delete mode 100644 release-notes/11.0/samples/fsharp/invalid/CharEnums.fs delete mode 100644 release-notes/11.0/samples/fsharp/invalid/Invalid.fs delete mode 100644 release-notes/11.0/samples/fsharp/invalid/Invalid.fsproj delete mode 100644 release-notes/11.0/samples/fsharp/invalid/Reraise.fs diff --git a/release-notes/11.0/preview/rc2/fsharp.md b/release-notes/11.0/preview/rc2/fsharp.md index f0a5b5c1ea..a6df8f2bca 100644 --- a/release-notes/11.0/preview/rc2/fsharp.md +++ b/release-notes/11.0/preview/rc2/fsharp.md @@ -83,7 +83,6 @@ let calculation () = Here `runtimeTask` is the custom builder defined by the tests, not a shipped FSharp.Core symbol. Its `Run` method passes the delayed body to `StateMachineHelpers.__runtimeAsyncReturn`. Its `Source` methods use `AsyncHelpers.Await` to await tasks before handing their results to the continuation. -The [maintained fixture](../../samples/fsharp/README.md) includes a small test-derived builder and runs this example across suspension. This release gives library authors the building blocks to create their own computation-expression APIs. FSharp.Core does not yet ship a builder using these mechanics. @@ -129,8 +128,7 @@ type Calculator() = let answer = Calculator().Double(value = 21) // 42 ``` -The [maintained fixture](../../samples/fsharp/README.md) includes the attribute definition. -It verifies that the named call runs and that `Calculator().Double(21)` reports `FS3923` under preview. +`Calculator().Double(21)` reports `FS3923` under preview. The default language version does not enforce this attribute. ## Lower allocations in FSharp.Core collections diff --git a/release-notes/11.0/samples/fsharp/ClosureOptimization.fs b/release-notes/11.0/samples/fsharp/ClosureOptimization.fs deleted file mode 100644 index ec586cc916..0000000000 --- a/release-notes/11.0/samples/fsharp/ClosureOptimization.fs +++ /dev/null @@ -1,9 +0,0 @@ -module ClosureOptimizationSamples - -let inline fold2 - ([] folder: 'State -> 'T1 -> 'T2 -> 'State) - (state: 'State) (a: 'T1[]) (b: 'T2[]) = - let mutable s = state - for i in 0 .. a.Length - 1 do - s <- folder s a[i] b[i] - s diff --git a/release-notes/11.0/samples/fsharp/FSharp.fsproj b/release-notes/11.0/samples/fsharp/FSharp.fsproj deleted file mode 100644 index 64d757f22d..0000000000 --- a/release-notes/11.0/samples/fsharp/FSharp.fsproj +++ /dev/null @@ -1,18 +0,0 @@ - - - Exe - net11.0 - default - $(DefineConstants);PREVIEW_SAMPLES - - - - - - - - - - - - diff --git a/release-notes/11.0/samples/fsharp/Fsi.fsx b/release-notes/11.0/samples/fsharp/Fsi.fsx deleted file mode 100644 index 23d78088b1..0000000000 --- a/release-notes/11.0/samples/fsharp/Fsi.fsx +++ /dev/null @@ -1,19 +0,0 @@ -open System -open System.Threading.Tasks - -let mutable disposed = false - -type Resource() = - interface IAsyncDisposable with - member _.DisposeAsync() = - disposed <- true - ValueTask.CompletedTask - -let operation = task { - use resource = new Resource() - do! Task.Delay 1 -} - -operation.GetAwaiter().GetResult() -if not disposed then failwith "FSI task use did not call DisposeAsync." -printfn "FSI task IAsyncDisposable use passed." diff --git a/release-notes/11.0/samples/fsharp/Main.fs b/release-notes/11.0/samples/fsharp/Main.fs deleted file mode 100644 index 839bdbeaea..0000000000 --- a/release-notes/11.0/samples/fsharp/Main.fs +++ /dev/null @@ -1,12 +0,0 @@ -module Main - -[] -let main _ = - DefaultSamples.validate () -#if PREVIEW_SAMPLES - PreviewSamples.validate () - RuntimeAsyncSamples.validate () - SrtpSamples.validate () - RecordConstructorSamples.validate () -#endif - 0 diff --git a/release-notes/11.0/samples/fsharp/Polyfill.fs b/release-notes/11.0/samples/fsharp/Polyfill.fs deleted file mode 100644 index aebeeddc44..0000000000 --- a/release-notes/11.0/samples/fsharp/Polyfill.fs +++ /dev/null @@ -1,7 +0,0 @@ -namespace System.Diagnostics.CodeAnalysis - -open System - -[] -type RequireNamedArgumentsAttribute() = - inherit Attribute() diff --git a/release-notes/11.0/samples/fsharp/Preview.fs b/release-notes/11.0/samples/fsharp/Preview.fs deleted file mode 100644 index eb35ae4701..0000000000 --- a/release-notes/11.0/samples/fsharp/Preview.fs +++ /dev/null @@ -1,63 +0,0 @@ -module PreviewSamples - -open System -open System.Diagnostics.CodeAnalysis -open System.Runtime.CompilerServices -open System.Threading.Tasks - -type Calculator() = - [] - member _.Double(value: int) = value * 2 - -[] -let throwOriginal (original: exn) = raise original - -type IdentityBuilder() = - member _.Return(value) = value - member _.Delay(body) = body - member _.Run(body) = body () - member _.TryWith(body, handler) = - try body () with ex -> handler ex - -let assertReraised label original run = - try - run () - failwithf "Expected the original exception from %s." label - with ex when obj.ReferenceEquals(ex, original) -> - if not (ex.StackTrace.Contains("throwOriginal")) then - failwithf "The original exception stack trace was lost in %s." label - printfn "%s reraise retained the exception and stack trace." label - -let validate () = - let actual = Calculator().Double(value = 21) - if actual <> 42 then failwithf "Expected 42, got %d" actual - printfn "Named argument validation passed." - - let original = InvalidOperationException("original exception") - let operation = async { - try - do! Async.Sleep 1 - throwOriginal original - with _ -> - reraise () - } - - assertReraised "Async" original (fun () -> operation |> Async.RunSynchronously) - - let taskOperation () = task { - try - do! Task.Delay 1 - throwOriginal original - with _ -> - reraise () - } - assertReraised "Task" original (fun () -> (taskOperation ()).GetAwaiter().GetResult()) - - let identity = IdentityBuilder() - assertReraised "Custom builder" original (fun () -> - identity { - try - return throwOriginal original - with _ -> - return reraise () - }) diff --git a/release-notes/11.0/samples/fsharp/Program.fs b/release-notes/11.0/samples/fsharp/Program.fs deleted file mode 100644 index fd519970b3..0000000000 --- a/release-notes/11.0/samples/fsharp/Program.fs +++ /dev/null @@ -1,57 +0,0 @@ -module DefaultSamples - -open System.Threading -open System.Threading.Tasks - -let sumWithOffset k xs = - List.fold (fun total value -> total + value + k) 0 xs - -let validate () = - let resources = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceNames() - if not (Array.contains "ILLink.Substitutions.xml" resources) then - failwith "The compiled assembly is missing its metadata-trimming rules." - printfn "Assembly metadata-trimming rules passed." - - if sumWithOffset 1 [ 1; 2; 3 ] <> 9 then - failwith "Unexpected List.fold result." - let arrayTotal = [| 1; 2; 3 |] |> Array.fold (fun total value -> total + 2 * value) 0 - if arrayTotal <> 12 then failwith "Unexpected Array.fold result." - let fold2Total = ClosureOptimizationSamples.fold2 (fun total x y -> total + x + y) 0 [| 1; 2 |] [| 3; 4 |] - if fold2Total <> 10 then failwith "Unexpected optimized-closure fold2 result." - let matrix = Array2D.init 2 3 (fun row column -> row * 10 + column) - if matrix[1, 2] <> 12 then failwith "Unexpected Array2D.init result." - printfn "Collection and Array2D examples passed." - - let mutable active = 0 - let mutable peak = 0 - let gate = obj() - let computations = - [ for i in 1..5 -> - async { - let running = Interlocked.Increment &active - lock gate (fun () -> peak <- max peak running) - do! Async.Sleep 30 - Interlocked.Decrement &active |> ignore - return i * i - } ] - - let results = computations |> Async.parallelLimit 2 |> Async.RunSynchronously - if results <> [| 1; 4; 9; 16; 25 |] || peak > 2 || peak < 1 then - failwithf "Unexpected bounded-parallel results: %A; peak concurrency: %d" results peak - printfn "Bounded-parallel work passed (peak concurrency: %d)." peak - - active <- 0 - peak <- 0 - let taskFactories = - [ for i in 1..5 -> - fun cancellationToken -> task { - let running = Interlocked.Increment &active - lock gate (fun () -> peak <- max peak running) - do! Task.Delay(30, cancellationToken) - Interlocked.Decrement &active |> ignore - return i * i - } ] - let taskResults = (Task.parallelLimit 2 CancellationToken.None taskFactories).GetAwaiter().GetResult() - if taskResults <> [| 1; 4; 9; 16; 25 |] || peak > 2 || peak < 1 then - failwithf "Unexpected bounded-task results: %A; peak concurrency: %d" taskResults peak - printfn "Bounded task factories passed (peak concurrency: %d)." peak diff --git a/release-notes/11.0/samples/fsharp/README.md b/release-notes/11.0/samples/fsharp/README.md deleted file mode 100644 index b9d6f631db..0000000000 --- a/release-notes/11.0/samples/fsharp/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# F# release-note validation - -With SDK `11.0.100-rc.2.26475.137` on `PATH`, run: - -```powershell -pwsh -NoProfile -File ./Validate.ps1 -``` - -The script rejects other SDK versions. It builds and runs the fixture with both the default language version and `preview`. - -| Scenario | Expected result | -| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Default-language collection operations | Correct `List.fold`, `Array.fold`, `Array2D.init`, and attributed custom `fold2` results | -| `Async.parallelLimit 2` and `Task.parallelLimit 2` | Results in input order, peak active concurrency at most two | -| Preview named arguments | Named call runs; positional call reports `FS3923`. Positional call compiles by default | -| Preview `reraise` | Async, task, and custom-builder handlers preserve the original exception and stack origin. The article's async example reports `FS3350` by default | -| Preview runtime-async compiler support | Source-test-derived intrinsic and custom `runtimeTask` builder samples return 42 across suspension; default-language build reports `FS3350`. The builder is fixture-defined, not shipped in FSharp.Core | -| Preview extension operators and SRTP | Generic DateTime shifting accepts both built-in TimeSpan and custom MyOffset. Other source-test examples return `"hahaha"` and 8. Default-language build reports `FS0001`/`FS0043` | -| Carry-forward preview record constructor | Constructor returns the expected record under preview; default-language build reports `FS0800` | -| Default F# 11 closure optimization | Attributed helper runs by default; F# 10 language mode reports `FS3350` | -| Char-backed enum bitwise operations | `FS0001` under default and preview; compiles with F# 10 language mode | -| F# Interactive asynchronous disposal | Default-language `task` calls `IAsyncDisposable.DisposeAsync` | -| Array2D operations | `create`, `init`, `copy`, `map`, `mapi`, and `rebase` return the expected values | -| Assembly metadata trimming | Compiled fixture contains its generated `ILLink.Substitutions.xml` resource | - -The fixture supplies `System.Diagnostics.CodeAnalysis.RequireNamedArgumentsAttribute` as a polyfill because the pinned build does not ship it in the BCL. - -To verify the `Array2D` Native AOT claim, run: - -```powershell -pwsh -NoProfile -File ./Validate.ps1 -PublishAot -``` - -This also publishes and runs the small `aot/` project with warnings treated as errors. It requires the host's [Native AOT prerequisites](https://learn.microsoft.com/dotnet/core/deploying/native-aot/#prerequisites). A normal F# build alone does not prove that Native AOT publishing is warning-free. - -Performance figures in the article come from the linked PR workloads. This fixture verifies example behavior and language gates; it does not reproduce those benchmarks. diff --git a/release-notes/11.0/samples/fsharp/RecordConstructors.fs b/release-notes/11.0/samples/fsharp/RecordConstructors.fs deleted file mode 100644 index 03d838cc84..0000000000 --- a/release-notes/11.0/samples/fsharp/RecordConstructors.fs +++ /dev/null @@ -1,8 +0,0 @@ -module RecordConstructorSamples - -type Point = { X: int; Y: int } -let origin = Point(0, 0) - -let validate () = - if origin <> { X = 0; Y = 0 } then failwith "Unexpected record constructor result." - printfn "Carry-forward preview record constructor passed." diff --git a/release-notes/11.0/samples/fsharp/RuntimeAsync.fs b/release-notes/11.0/samples/fsharp/RuntimeAsync.fs deleted file mode 100644 index a9a164e5b4..0000000000 --- a/release-notes/11.0/samples/fsharp/RuntimeAsync.fs +++ /dev/null @@ -1,41 +0,0 @@ -module RuntimeAsyncSamples - -open System.Runtime.CompilerServices -open System.Threading.Tasks -open Microsoft.FSharp.Core.CompilerServices - -let delayed value = task { - do! Task.Delay 1 - return value -} - -let add (x: int) (y: int) : Task = - StateMachineHelpers.__runtimeAsyncReturn ( - let first = AsyncHelpers.Await(delayed x) - first + y) - -type RuntimeTaskBuilder() = - member inline _.Return(value: 'T) = value - member inline _.Bind(source, continuation) = continuation source - member inline _.Delay([] generator: unit -> 'T) = generator - member inline _.Run([] code: unit -> 'T) : Task<'T> = - StateMachineHelpers.__runtimeAsyncReturn (code ()) - member inline _.Source(source: Task<'T>) = AsyncHelpers.Await source - member inline _.Source(source: Task) = AsyncHelpers.Await source - -let runtimeTask = RuntimeTaskBuilder() - -let calculation () = - runtimeTask { - let captured = 40 - let! delta = delayed 2 - return captured + delta - } - -let validate () = - let result = (add 40 2).GetAwaiter().GetResult() - if result <> 42 then failwithf "Expected runtime-async result 42, got %d" result - printfn "Builder-author runtime-async intrinsic example passed." - let builderResult = (calculation ()).GetAwaiter().GetResult() - if builderResult <> 42 then failwithf "Expected custom runtimeTask result 42, got %d" builderResult - printfn "Custom test-derived runtimeTask builder passed across suspension." diff --git a/release-notes/11.0/samples/fsharp/Srtp.fs b/release-notes/11.0/samples/fsharp/Srtp.fs deleted file mode 100644 index 7b80e5f3e7..0000000000 --- a/release-notes/11.0/samples/fsharp/Srtp.fs +++ /dev/null @@ -1,34 +0,0 @@ -module SrtpSamples - -open System - -type MyOffset = { Hours: float } - -type System.DateTime with - static member (+) (date: DateTime, offset: MyOffset) = - date.AddHours(offset.Hours) - -let inline shift (date: DateTime) offset = date + offset - -let oneHourLater = shift DateTime.MinValue (TimeSpan.FromHours 1.0) -let twoHoursLater = shift DateTime.MinValue { Hours = 2.0 } - -type System.String with - static member (*) (s: string, n: int) = - System.String.Concat(Array.replicate n s) - -let repeated = "ha" * 3 - -type System.Int32 with - static member (++) (a: int, b: int) = a + b + 1 - -let inline incAdd (x: ^T) (y: ^T) = x ++ y -let result = incAdd 3 4 - -let validate () = - if repeated <> "hahaha" || result <> 8 then - failwithf "Unexpected extension-operator results: %s, %d" repeated result - if oneHourLater <> DateTime.MinValue.AddHours 1.0 || - twoHoursLater <> DateTime.MinValue.AddHours 2.0 then - failwith "Generic DateTime shifting did not retain both overload choices." - printfn "Preview SRTP extension operators passed." diff --git a/release-notes/11.0/samples/fsharp/Validate.ps1 b/release-notes/11.0/samples/fsharp/Validate.ps1 deleted file mode 100644 index 1d3f9c1c1c..0000000000 --- a/release-notes/11.0/samples/fsharp/Validate.ps1 +++ /dev/null @@ -1,68 +0,0 @@ -param([switch] $PublishAot) - -$ErrorActionPreference = 'Stop' -$dotnet = (Get-Command dotnet -ErrorAction Stop).Source -$metadata = Get-Content (Join-Path $PSScriptRoot '../../preview/rc2/build-metadata.json') -Raw | ConvertFrom-Json -$sdkVersion = & $dotnet --version -if ($LASTEXITCODE -ne 0 -or $sdkVersion -ne $metadata.build.sdk_version) { - throw "Expected SDK $($metadata.build.sdk_version), got '$sdkVersion'. Put the exact SDK on PATH." -} - -$project = Join-Path $PSScriptRoot 'FSharp.fsproj' -$invalidProject = Join-Path $PSScriptRoot 'invalid/Invalid.fsproj' -$aotProject = Join-Path $PSScriptRoot 'aot/Aot.fsproj' - -foreach ($language in @('default', 'preview')) { - & $dotnet build $project --nologo --no-incremental "-p:LangVersion=$language" - if ($LASTEXITCODE -ne 0) { throw "The $language-language examples did not build." } - & $dotnet run --project $project --no-build - if ($LASTEXITCODE -ne 0) { throw "The $language-language examples did not run." } -} - -function Assert-BuildFailure([string] $Case, [string] $Language, [string[]] $Expected) { - $diagnostics = & $dotnet build $invalidProject --nologo --no-incremental ` - "-p:LangVersion=$Language" "-p:ValidationCase=$Case" 2>&1 | Out-String - $exitCode = $LASTEXITCODE - $errors = @([regex]::Matches($diagnostics, 'error ([A-Z]+\d+)') | - ForEach-Object { $_.Groups[1].Value } | Sort-Object -Unique) - $expectedErrors = @($Expected | Sort-Object -Unique) - if ($exitCode -eq 0 -or ($errors -join ',') -ne ($expectedErrors -join ',')) { - throw "Expected only $($expectedErrors -join ', ') for $Case with $Language, got: $diagnostics" - } - Write-Output "$Case with $Language correctly reported $($expectedErrors -join ', ')." -} - -& $dotnet build $invalidProject --nologo --no-incremental -p:LangVersion=default -if ($LASTEXITCODE -ne 0) { throw 'A positional call should compile with the default language version.' } -Assert-BuildFailure 'NamedArguments' 'preview' 'FS3923' -Assert-BuildFailure 'Reraise' 'default' 'FS3350' -Assert-BuildFailure 'RuntimeAsync' 'default' 'FS3350' -Assert-BuildFailure 'Srtp' 'default' @('FS0001', 'FS0043') -Assert-BuildFailure 'RecordConstructors' 'default' 'FS0800' -Assert-BuildFailure 'ClosureOptimization' '10.0' 'FS3350' -Assert-BuildFailure 'CharEnums' 'default' 'FS0001' -Assert-BuildFailure 'CharEnums' 'preview' 'FS0001' - -& $dotnet build $invalidProject --nologo --no-incremental -p:LangVersion=10.0 -p:ValidationCase=CharEnums -if ($LASTEXITCODE -ne 0) { throw 'Char-backed enum bitwise operations should compile with F# 10 language rules.' } - -& $dotnet build $invalidProject --nologo --no-incremental -p:LangVersion=preview -p:ValidationCase=Reraise -if ($LASTEXITCODE -ne 0) { throw 'The article reraise example should compile with preview.' } - -& $dotnet fsi --langversion:default --exec (Join-Path $PSScriptRoot 'Fsi.fsx') -if ($LASTEXITCODE -ne 0) { throw 'The FSI asynchronous-disposal example did not run.' } - -& $dotnet build $aotProject --nologo -p:TreatWarningsAsErrors=true -if ($LASTEXITCODE -ne 0) { throw 'The Array2D example did not build.' } -& $dotnet run --project $aotProject --no-build -if ($LASTEXITCODE -ne 0) { throw 'The Array2D example did not run.' } - -if ($PublishAot) { - $nativeDirectory = Join-Path $PSScriptRoot 'aot/bin/native' - & $dotnet publish $aotProject --nologo -c Release -p:PublishAot=true ` - -p:TreatWarningsAsErrors=true -p:TrimmerSingleWarn=false -p:IlcSingleWarn=false -o $nativeDirectory - if ($LASTEXITCODE -ne 0) { throw 'The Array2D Native AOT publish failed.' } - $executable = if ($IsWindows) { 'Aot.exe' } else { 'Aot' } - & (Join-Path $nativeDirectory $executable) - if ($LASTEXITCODE -ne 0) { throw 'The Array2D Native AOT executable failed.' } -} diff --git a/release-notes/11.0/samples/fsharp/aot/Aot.fsproj b/release-notes/11.0/samples/fsharp/aot/Aot.fsproj deleted file mode 100644 index bde82373fa..0000000000 --- a/release-notes/11.0/samples/fsharp/aot/Aot.fsproj +++ /dev/null @@ -1,10 +0,0 @@ - - - Exe - net11.0 - true - - - - - diff --git a/release-notes/11.0/samples/fsharp/aot/Program.fs b/release-notes/11.0/samples/fsharp/aot/Program.fs deleted file mode 100644 index 217f2da98f..0000000000 --- a/release-notes/11.0/samples/fsharp/aot/Program.fs +++ /dev/null @@ -1,12 +0,0 @@ -let matrix = Array2D.init 2 3 (fun row column -> row * 10 + column) -let created = Array2D.create 2 3 12 -let copied = Array2D.copy matrix -let mapped = Array2D.map ((+) 1) matrix -let indexed = Array2D.mapi (fun row column value -> row + column + value) matrix -let rebased = Array2D.rebase matrix - -if matrix[1, 2] <> 12 || created[1, 2] <> 12 || copied[1, 2] <> 12 || - mapped[1, 2] <> 13 || indexed[1, 2] <> 15 || rebased[1, 2] <> 12 then - failwith "Unexpected Array2D result." - -System.Console.WriteLine "Array2D operations passed." diff --git a/release-notes/11.0/samples/fsharp/invalid/CharEnums.fs b/release-notes/11.0/samples/fsharp/invalid/CharEnums.fs deleted file mode 100644 index 9c1433efa1..0000000000 --- a/release-notes/11.0/samples/fsharp/invalid/CharEnums.fs +++ /dev/null @@ -1,7 +0,0 @@ -type CharEnum = - | A = 'a' - | B = 'b' - -let invalidOr = CharEnum.A ||| CharEnum.B -let invalidAnd = CharEnum.A &&& CharEnum.B -let invalidXor = CharEnum.A ^^^ CharEnum.B diff --git a/release-notes/11.0/samples/fsharp/invalid/Invalid.fs b/release-notes/11.0/samples/fsharp/invalid/Invalid.fs deleted file mode 100644 index 0c70ff88c9..0000000000 --- a/release-notes/11.0/samples/fsharp/invalid/Invalid.fs +++ /dev/null @@ -1,7 +0,0 @@ -open System.Diagnostics.CodeAnalysis - -type Calculator() = - [] - member _.Double(value: int) = value * 2 - -Calculator().Double(21) |> ignore diff --git a/release-notes/11.0/samples/fsharp/invalid/Invalid.fsproj b/release-notes/11.0/samples/fsharp/invalid/Invalid.fsproj deleted file mode 100644 index 02fe280cf8..0000000000 --- a/release-notes/11.0/samples/fsharp/invalid/Invalid.fsproj +++ /dev/null @@ -1,18 +0,0 @@ - - - Exe - net11.0 - preview - NamedArguments - - - - - - - - - - - - diff --git a/release-notes/11.0/samples/fsharp/invalid/Reraise.fs b/release-notes/11.0/samples/fsharp/invalid/Reraise.fs deleted file mode 100644 index a8dde75658..0000000000 --- a/release-notes/11.0/samples/fsharp/invalid/Reraise.fs +++ /dev/null @@ -1,6 +0,0 @@ -let operation = async { - try - failwith "operation failed" - with _ -> - reraise () -} From 728c4b98675fbc825633eb67ac7d4920ef8ef8cf Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Mon, 5 Oct 2026 17:46:33 +0200 Subject: [PATCH 7/8] [release-notes] Add F# 11 memory savings evaluation Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../11.0/preview/rc2/fsharp-memory-savings.md | 200 ++++++++++++++++++ release-notes/11.0/preview/rc2/fsharp.md | 2 + ...arp-memory-savings-compiler-allocation.svg | 81 +++++++ ...sharp-memory-savings-ide-retained-heap.svg | 43 ++++ ...harp-memory-savings-runtime-allocation.svg | 63 ++++++ 5 files changed, 389 insertions(+) create mode 100644 release-notes/11.0/preview/rc2/fsharp-memory-savings.md create mode 100644 release-notes/11.0/preview/rc2/media/fsharp-memory-savings-compiler-allocation.svg create mode 100644 release-notes/11.0/preview/rc2/media/fsharp-memory-savings-ide-retained-heap.svg create mode 100644 release-notes/11.0/preview/rc2/media/fsharp-memory-savings-runtime-allocation.svg diff --git a/release-notes/11.0/preview/rc2/fsharp-memory-savings.md b/release-notes/11.0/preview/rc2/fsharp-memory-savings.md new file mode 100644 index 0000000000..e250c0956f --- /dev/null +++ b/release-notes/11.0/preview/rc2/fsharp-memory-savings.md @@ -0,0 +1,200 @@ +# Performance improvements in the F# 11 compiler + + +F# 11 brings **roughly 20-50% less allocation during compilation** across six real-world projects and **about 30% less memory retained after IDE project checks**. **And this is a win for your F# code, too!** Better [inlining of `FSharp.Core` calls](https://github.com/dotnet/fsharp/pull/20422) eliminates closure allocations in everyday functional code. Recompile with the new compiler and `FSharp.Core`, and these optimizations reach your applications and libraries, not just the compiler. + +Behind these gains are **49 performance and supporting PRs**, merged between **Aug 12 and Sep 21, 2026**. **12 are already in RC1**; the remaining **37 are in the RC2 source**, headed for .NET 11 and F# 11 GA. + + +These are author-provided measurements of a source-built compiler, not an official .NET 11 RC2 SDK benchmark. +The **New** payload uses [F# source commit `9cd6167a7`](https://github.com/dotnet/fsharp/commit/9cd6167a7265ce7264b22719503b7dfa9eb8f83c), the production source used by the pinned RC2 build. +The SDK comparison also changes runtime and packaging: Old runs on .NET 10, while RC1 and New run on .NET 11 RC1. +The .NET Framework comparison is separate and keeps the runtime family fixed. + +## Less allocation during compilation + +**Old:** SDK 10.0.100. **RC1:** SDK 11 RC1. **New:** optimized, source-built F# 11 from the RC2/main snapshot. Old runs on .NET 10; RC1 and new run on .NET 11 RC1. + + +| Compilation workload | Old GB | RC1 GB | New GB | RC1 vs old: less allocation | New vs old: less allocation | +| --- | ---: | ---: | ---: | ---: | ---: | +| FSharp.Core | 8.127 | 8.064 | 5.995 | 0.8% | 26.2% | +| FSharp.Compiler.Service | 37.799 | 34.946 | 29.300 | 7.5% | 22.5% | +| FsToolkit.ErrorHandling | 2.378 | 2.100 | 1.847 | 11.7% | 22.3% | +| Oxpecker | 1.230 | 0.759 | 0.634 | 38.3% | 48.5% | +| Nu | 15.853 | 14.871 | 12.276 | 6.2% | 22.6% | +| FsAutoComplete | 6.258 | 5.676 | 4.879 | 9.3% | 22.0% | + + +![Actual compiler allocation in GB for six workloads: the released SDK 10 compiler, RC1, and source-built F# 11. Each project's bar widths use their own zero-based scale.](media/fsharp-memory-savings-compiler-allocation.svg) + + +Peak compilation RAM rose in 4 of six SDK workloads, and FCS used more CPU time. + + +## The allocation gains also reach .NET Framework + +FCS-hosted compilation on **x64 .NET Framework 4.8.1**, comparing released and source-built compiler packages. + + +| Compilation workload | Old GB | New GB | New vs old: less allocation | +| --- | ---: | ---: | ---: | +| FSharp.Core | 8.305 | 6.160 | 25.8% | +| FSharp.Compiler.Service | 40.120 | 30.246 | 24.6% | +| FsToolkit.ErrorHandling | 2.414 | 1.889 | 21.7% | +| Oxpecker | 1.240 | 0.636 | 48.7% | +| Nu | 16.663 | 12.858 | 22.8% | +| FsAutoComplete | 6.429 | 5.002 | 22.2% | + + +![Actual allocation in GB on .NET Framework, comparing the released compiler generation with source-built F# 11 for all six workloads.](media/fsharp-memory-savings-runtime-allocation.svg) + +FCS peak RAM also increased on Framework. + +## Less memory held after IDE project checks + +FCS now [shares imported assemblies](https://github.com/dotnet/fsharp/pull/20296) across projects instead of keeping a separate copy for each one. + + +| Project graph | Old MB | RC1 MB | New MB | New vs old: less retained memory | +| --- | ---: | ---: | ---: | ---: | +| FsAutoComplete | 571.1 | 488.7 | 398.6 | 30.2% | +| Oxpecker | 113.2 | 88.5 | 78.5 | 30.6% | + + +*Managed memory retained after typechecking the project graphs.* + +![Retained managed heap in MB after a full GC: released SDK 10, RC1 and source-built F# 11. Both project graphs retain less memory. All bars share a zero-based MB axis.](media/fsharp-memory-savings-ide-retained-heap.svg) + + +On .NET Framework, retained memory falls by **30.1%-30.6%** too. + + +Editors and language servers pick up these gains by updating FCS. + +## Fewer closure allocations in your F# code + +A **closure** combines a function with values from surrounding code, such as `discount`, which the compiler used to store in a new object for this fold. +[Inlining](https://github.com/dotnet/fsharp/pull/20422) replaces a function call with its body, and `[]` tells the compiler to inline known lambda arguments too. + +```fsharp +let discountedTotal discount prices = + prices |> List.fold (fun total price -> total + price * (100 - discount) / 100) 0 +``` + +For operations such as `List.fold` that otherwise allocate nothing, the closure can be their entire allocation cost. +`List.map` and other collection builders allocate a new collection anyway, so removing a closure usually saves a smaller share. + + + +| F# code (simplified) | Old B/op | New B/op | +| --- | ---: | ---: | +| `List.fold (fun s struct (p, q) -> s + p * q * (100 - d) / 100) 0 items` | 24 | 0 | +| `let any = List.exists (fun x -> x > lo) xs`
`List.forall (fun x -> x < hi) xs && any` | 48 | 0 | +| `Array.fold (fun s x -> s + x * k) 0 xs + Array.fold2 (fun s x w -> s + x * w * k) 0 xs ws` | 48 | 0 | +| `Option.defaultValue 0 (Option.map (addOffset k) opt)` | 0 | 0 | +| `List.fold (fun s xs -> s + List.fold (fun t x -> t + x * k) 0 xs) 0 groups` | 48 | 0 | +| `List.sum (List.map (fun x -> x + k) (List.filter (fun x -> x > lo) xs))` | 146 | 146 | +| `saved <- fun x -> x + offset`
`saved n` | 24 | 24 | +| `List.fold (+) seed xs` | 0 | 0 | + + + +*List and array operations use four-element collections.* + +### Which functions benefit? + +`Option` and `ValueOption` (for `voption`) already inline lambda arguments in functions such as `map`, `bind`, and `fold`; this is not new in F# 11. + + +These existing functions gained explicit lambda inlining in [#20422](https://github.com/dotnet/fsharp/pull/20422) and the earlier [`Array.init` change](https://github.com/dotnet/fsharp/pull/19869): + +| Module | Functions gaining explicit lambda inlining | +| --- | --- | +| `List` (14) | `exists`, `exists2`, `find`, `findIndex`, `fold`, `fold2`, `forall`, `forall2`, `iter2`, `iteri2`, `pick`, `reduce`, `skipWhile`, `tryPick` | +| `Array` (19) | `exists2`, `find`, `findBack`, `findIndex`, `findIndexBack`, `fold`, `fold2`, `foldBack`, `foldBack2`, `forall`, `forall2`, `init`, `iter2`, `iteri`, `iteri2`, `pick`, `reduce`, `reduceBack`, `tryPick` | + + +### Partially applied functions, too + +Previously, `InlineIfLambda` handled explicit lambdas, but some partial applications still left a closure behind. + +```fsharp +type Settings(offset: int) = + member _.Offset = offset + +let add offset x = x + offset + +let adjust (settings: Settings) value = + value |> Option.map (add settings.Offset) +``` + +`add settings.Offset` supplies only the first argument and captures the current offset inside `adjust`, leaving a function waiting for `x`. +The [new compiler](https://github.com/dotnet/fsharp/pull/20487) handles this partial application like a lambda and no longer generates the extra closure object. + +Recompile with the new compiler and FSharp.Core to bring these improvements to your own code. + +## Contributing changes + + +### Already in RC1 + +- **Aug 12** - [#20090](https://github.com/dotnet/fsharp/pull/20090): Import: Don't walk non-F# assemblies when labelling trait constraint sources. +- **Aug 12** - [#20088](https://github.com/dotnet/fsharp/pull/20088): Avoid per-instance lock object in InterruptibleLazy and DelayInitArrayMap. +- **Aug 12** - [#20092](https://github.com/dotnet/fsharp/pull/20092): IL: add ILPreNamespace, make ILPreTypeDef creation lazy. +- **Aug 13** - [#20250](https://github.com/dotnet/fsharp/pull/20250): IL: fix leaking binary view. +- **Aug 13** - [#20249](https://github.com/dotnet/fsharp/pull/20249): IL: use empty tables for members when possible. +- **Aug 19** - [#20254](https://github.com/dotnet/fsharp/pull/20254): IL: share ILCallingConv instances. +- **Aug 19** - [#20256](https://github.com/dotnet/fsharp/pull/20256): IL: cache C# extension methods per CCU. +- **Aug 19** - [#20244](https://github.com/dotnet/fsharp/pull/20244): Fix super-linear compilation of guarded shared-or active-pattern matches. +- **Aug 20** - [#20286](https://github.com/dotnet/fsharp/pull/20286): Make Entity's adhoc members list lazy. +- **Aug 24** - [#20301](https://github.com/dotnet/fsharp/pull/20301): IL: share the pickled references. +- **Aug 24** - [#20298](https://github.com/dotnet/fsharp/pull/20298): Name resolution: group C#-style extension members per 'open' and extended type. +- **Aug 24** - [#20259](https://github.com/dotnet/fsharp/pull/20259): IL: cache the ILTypeRef of a type def. + +--- + +### In the RC2 source, headed for GA + +- **Aug 26** - [#20285](https://github.com/dotnet/fsharp/pull/20285): Calculate Entity.PublicPath instead of storing. +- **Aug 26** - [#20337](https://github.com/dotnet/fsharp/pull/20337): Replace the stringified pattern-match memo key with a typed one. +- **Aug 27** - [#20255](https://github.com/dotnet/fsharp/pull/20255): IL: reuse the cached ILTypeRef in ILTypeInfo.FromType. +- **Aug 27** - [#20364](https://github.com/dotnet/fsharp/pull/20364): Unpickling: share one EntityRef per non-local reference row. +- **Aug 27** - [#20296](https://github.com/dotnet/fsharp/pull/20296): Import: share assembly CCUs between projects. +- **Aug 28** - [#20348](https://github.com/dotnet/fsharp/pull/20348): Four profiler-guided hot-path wins from self-build tracing. +- **Aug 28** - [#20350](https://github.com/dotnet/fsharp/pull/20350): Avoid Choice allocation in fslib entity/val-ref equality. +- **Aug 28** - [#20351](https://github.com/dotnet/fsharp/pull/20351): Reduce Detuple usage-analysis allocation with a mutable Dictionary. +- **Aug 31** - [#20354](https://github.com/dotnet/fsharp/pull/20354): Avoid redundant FreeVars record allocation for local vals. +- **Aug 31** - [#20363](https://github.com/dotnet/fsharp/pull/20363): Fuse the optimizer inlining copy + type-instantiation passes. +- **Aug 31** - [#20367](https://github.com/dotnet/fsharp/pull/20367): Inline TryD to remove constraint-solver closure allocations. +- **Aug 31** - [#20368](https://github.com/dotnet/fsharp/pull/20368): eliminate per-call closure in StackGuard.Guard via InlineIfLambda. +- **Sep 1** - [#20287](https://github.com/dotnet/fsharp/pull/20287): IL: hold custom attributes in fields rather than a union case. +- **Sep 4** - [#20353](https://github.com/dotnet/fsharp/pull/20353): Cache IL method parameter attributes during overload resolution. +- **Sep 4** - [#20384](https://github.com/dotnet/fsharp/pull/20384): Avoid per-call closure allocation in type-hierarchy traversal. +- **Sep 8** - [#20385](https://github.com/dotnet/fsharp/pull/20385): Inline the free-variable typar foldBacks. +- **Sep 9** - [#20447](https://github.com/dotnet/fsharp/pull/20447): Remove per-call closure allocations in post-inference checks. +- **Sep 9** - [#20423](https://github.com/dotnet/fsharp/pull/20423): Eliminate closure allocations in well-known-attribute queries. +- **Sep 9** - [#20415](https://github.com/dotnet/fsharp/pull/20415): Make List.vMapFold inline to drop the nullness-import closure. +- **Sep 9** - [#20372](https://github.com/dotnet/fsharp/pull/20372): Eliminate closure allocations in List.mapq and List.lengthsEqAndForall2. +- **Sep 9** - [#20349](https://github.com/dotnet/fsharp/pull/20349): Reverse instead of sort already-ordered branch fixups in IL writer. +- **Sep 10** - [#20374](https://github.com/dotnet/fsharp/pull/20374): Inline the static-abstract interface-constraint predicate. +- **Sep 10** - [#20437](https://github.com/dotnet/fsharp/pull/20437): Drop the per-call closure in generic type-argument codegen. +- **Sep 10** - [#20421](https://github.com/dotnet/fsharp/pull/20421): Drop accessibility and attribute-scan closures via ListInline. +- **Sep 10** - [#20426](https://github.com/dotnet/fsharp/pull/20426): Avoid per-call FSharpFunc closure in remapVal member-info remap. +- **Sep 11** - [#20487](https://github.com/dotnet/fsharp/pull/20487): Eliminate per-call closure for InlineIfLambda partial applications. +- **Sep 16** - [#20422](https://github.com/dotnet/fsharp/pull/20422): Inline List/Array higher-order functions and adapt function arguments. +- **Sep 16** - [#20388](https://github.com/dotnet/fsharp/pull/20388): Share the empty-array singleton for zero-length Array results. +- **Sep 17** - [#20490](https://github.com/dotnet/fsharp/pull/20490): Name resolution: CheckIWSAM only needs the intrinsic methods. +- **Sep 17** - [#20489](https://github.com/dotnet/fsharp/pull/20489): IL: map the short-lived metadata-only PE reader. +- **Sep 17** - [#20486](https://github.com/dotnet/fsharp/pull/20486): IL: intern the attributes and type references read from metadata. +- **Sep 17** - [#20481](https://github.com/dotnet/fsharp/pull/20481): FCS: fix races that made the background builder repeat work. +- **Sep 17** - [#20494](https://github.com/dotnet/fsharp/pull/20494): Typed tree: create a type's augmentation on first use. +- **Sep 17** - [#20261](https://github.com/dotnet/fsharp/pull/20261): IL: fix the per-reader string cache sizing. +- **Sep 18** - [#20571](https://github.com/dotnet/fsharp/pull/20571): Stabilize OptimizeClosureIfNotInlined for F# 11. +- **Sep 18** - [#20555](https://github.com/dotnet/fsharp/pull/20555): Ship net10.0 FSharp.Core with the SDK tools. +- **Sep 21** - [#20506](https://github.com/dotnet/fsharp/pull/20506): Support multithreaded MSBuild in F# build tasks (concurrency benefit not measured here). + + +Special thanks to [Eugene](https://github.com/auduchinok) for his substantial work reducing allocations during cold compiles and IDE typechecks, and reducing the memory retained per project. + +We hope you enjoy the lower allocations, smaller IDE memory footprint, and more room for your own F# code. Keep enjoying F#! diff --git a/release-notes/11.0/preview/rc2/fsharp.md b/release-notes/11.0/preview/rc2/fsharp.md index a6df8f2bca..3ba754651d 100644 --- a/release-notes/11.0/preview/rc2/fsharp.md +++ b/release-notes/11.0/preview/rc2/fsharp.md @@ -172,6 +172,8 @@ These measurements describe their specific workloads, not peak process memory or The compiler improvements are enabled by default. Thank you [@auduchinok](https://github.com/auduchinok) for this contribution! +For the measured combined impact of F# 11 compiler and FSharp.Core improvements, see [the full performance evaluation](./fsharp-memory-savings.md). + ## Bound concurrent asynchronous work `Async.parallelLimit` runs asynchronous computations with a maximum number in flight and returns results in input order. diff --git a/release-notes/11.0/preview/rc2/media/fsharp-memory-savings-compiler-allocation.svg b/release-notes/11.0/preview/rc2/media/fsharp-memory-savings-compiler-allocation.svg new file mode 100644 index 0000000000..fe88fbab5c --- /dev/null +++ b/release-notes/11.0/preview/rc2/media/fsharp-memory-savings-compiler-allocation.svg @@ -0,0 +1,81 @@ + +Less compiler allocation in every workloadLess compiler allocation in every workload. Actual GB; zero baseline. FSharp.Core: Old (SDK 10) 8.127 GB, SDK 11 RC1 8.064 GB, New (source) 5.995 GB. FSharp.Compiler.Service: Old (SDK 10) 37.799 GB, SDK 11 RC1 34.946 GB, New (source) 29.300 GB. FsToolkit.ErrorHandling: Old (SDK 10) 2.378 GB, SDK 11 RC1 2.100 GB, New (source) 1.847 GB. Oxpecker: Old (SDK 10) 1.230 GB, SDK 11 RC1 0.759 GB, New (source) 0.634 GB. Nu: Old (SDK 10) 15.853 GB, SDK 11 RC1 14.871 GB, New (source) 12.276 GB. FsAutoComplete: Old (SDK 10) 6.258 GB, SDK 11 RC1 5.676 GB, New (source) 4.879 GB. + + +Less compiler allocation in every workload +Actual allocation in GB. Bar widths are scaled separately for each project. +FSharp.Core +0 + +Old (SDK 10) + +8.127 GB +SDK 11 RC1 + +8.064 GB +New (source) + +5.995 GB +FSharp.Compiler.Service +0 + +Old (SDK 10) + +37.799 GB +SDK 11 RC1 + +34.946 GB +New (source) + +29.300 GB +FsToolkit.ErrorHandling +0 + +Old (SDK 10) + +2.378 GB +SDK 11 RC1 + +2.100 GB +New (source) + +1.847 GB +Oxpecker +0 + +Old (SDK 10) + +1.230 GB +SDK 11 RC1 + +0.759 GB +New (source) + +0.634 GB +Nu +0 + +Old (SDK 10) + +15.853 GB +SDK 11 RC1 + +14.871 GB +New (source) + +12.276 GB +FsAutoComplete +0 + +Old (SDK 10) + +6.258 GB +SDK 11 RC1 + +5.676 GB +New (source) + +4.879 GB +Compare versions within each project; different projects use different bar scales. Every scale starts at zero. +Old = released SDK 10 generation. New = source-built F# 11. Medians of 12 measured runs. + diff --git a/release-notes/11.0/preview/rc2/media/fsharp-memory-savings-ide-retained-heap.svg b/release-notes/11.0/preview/rc2/media/fsharp-memory-savings-ide-retained-heap.svg new file mode 100644 index 0000000000..f1ff13312f --- /dev/null +++ b/release-notes/11.0/preview/rc2/media/fsharp-memory-savings-ide-retained-heap.svg @@ -0,0 +1,43 @@ + +Less managed heap retained after checkingLess managed heap retained after checking. Actual MB; zero baseline. FsAutoComplete: Old (SDK 10) 571.1 MB, SDK 11 RC1 488.7 MB, New (source) 398.6 MB. Oxpecker: Old (SDK 10) 113.2 MB, SDK 11 RC1 88.5 MB, New (source) 78.5 MB. + + +Less managed heap retained after checking +Live managed heap after full GC, in MB. All projects share the same scale. + +0 + +100 + +200 + +300 + +400 + +500 + +600 +FsAutoComplete +Old (SDK 10) + +571.1 MB +SDK 11 RC1 + +488.7 MB +New (source) + +398.6 MB +Oxpecker +Old (SDK 10) + +113.2 MB +SDK 11 RC1 + +88.5 MB +New (source) + +78.5 MB +Retained managed heap (MB), measured while the checker, options and results remain alive. +Old = released SDK 10 generation. New = source-built F# 11. Medians of 12 measured runs. + diff --git a/release-notes/11.0/preview/rc2/media/fsharp-memory-savings-runtime-allocation.svg b/release-notes/11.0/preview/rc2/media/fsharp-memory-savings-runtime-allocation.svg new file mode 100644 index 0000000000..f4ce9bc02e --- /dev/null +++ b/release-notes/11.0/preview/rc2/media/fsharp-memory-savings-runtime-allocation.svg @@ -0,0 +1,63 @@ + +The allocation gains also reach .NET FrameworkThe allocation gains also reach .NET Framework. Actual GB; zero baseline. FSharp.Core: Old (released) 8.305 GB, New (source) 6.160 GB. FSharp.Compiler.Service: Old (released) 40.120 GB, New (source) 30.246 GB. FsToolkit.ErrorHandling: Old (released) 2.414 GB, New (source) 1.889 GB. Oxpecker: Old (released) 1.240 GB, New (source) 0.636 GB. Nu: Old (released) 16.663 GB, New (source) 12.858 GB. FsAutoComplete: Old (released) 6.429 GB, New (source) 5.002 GB. + + +The allocation gains also reach .NET Framework +Actual allocation in GB. Bar widths are scaled separately for each project. +FSharp.Core +0 + +Old (released) + +8.305 GB +New (source) + +6.160 GB +FSharp.Compiler.Service +0 + +Old (released) + +40.120 GB +New (source) + +30.246 GB +FsToolkit.ErrorHandling +0 + +Old (released) + +2.414 GB +New (source) + +1.889 GB +Oxpecker +0 + +Old (released) + +1.240 GB +New (source) + +0.636 GB +Nu +0 + +Old (released) + +16.663 GB +New (source) + +12.858 GB +FsAutoComplete +0 + +Old (released) + +6.429 GB +New (source) + +5.002 GB +Compare versions within each project; different projects use different bar scales. Every scale starts at zero. +Old = released SDK 10 generation. New = source-built F# 11. Medians of 12 measured runs. + From f913a05a750c7c3594fdf229f6496726296ea1c6 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Mon, 5 Oct 2026 17:59:00 +0200 Subject: [PATCH 8/8] [release-notes] Surface F# performance evaluation in TOC Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- release-notes/11.0/preview/rc2/fsharp.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release-notes/11.0/preview/rc2/fsharp.md b/release-notes/11.0/preview/rc2/fsharp.md index 3ba754651d..59abf6a7a5 100644 --- a/release-notes/11.0/preview/rc2/fsharp.md +++ b/release-notes/11.0/preview/rc2/fsharp.md @@ -7,7 +7,7 @@ - [reraise in computation expressions](#reraise-in-computation-expressions) - [Require named arguments for selected APIs](#require-named-arguments-for-selected-apis) - [Lower allocations in FSharp.Core collections](#lower-allocations-in-fsharpcore-collections) -- [Compiler memory usage improvements](#compiler-memory-usage-improvements) +- [Compiler memory usage improvements](#compiler-memory-usage-improvements) - [Read the full performance evaluation](./fsharp-memory-savings.md) - [Bound concurrent asynchronous work](#bound-concurrent-asynchronous-work) - [Trimming and Native AOT improvements](#trimming-and-native-aot-improvements) - [Breaking changes from .NET 10](#breaking-changes-from-net-10)